Merge ../wgpu-rs into reunion
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.mtl binary
|
||||||
|
*.obj binary
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Issue or enhancement in wgpu-core
|
||||||
|
url: https://github.com/gfx-rs/wgpu/issues/new/choose
|
||||||
|
about: Issues with or enhancements for the core logic, validation, or the backends should go here.
|
||||||
|
- name: Question about wgpu
|
||||||
|
url: https://github.com/gfx-rs/wgpu-rs/discussions/new
|
||||||
|
about: Any questions about how to use wgpu should go here.
|
3
.github/ISSUE_TEMPLATE/other.md
vendored
@ -4,7 +4,4 @@ about: Strange things you want to tell us
|
|||||||
title: ''
|
title: ''
|
||||||
labels: question
|
labels: question
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
31
.github/workflows/ci.yml
vendored
@ -34,13 +34,18 @@ jobs:
|
|||||||
- name: Additional core features
|
- name: Additional core features
|
||||||
run: cargo check --manifest-path wgpu-core/Cargo.toml --features trace --target ${{ env.TARGET }}
|
run: cargo check --manifest-path wgpu-core/Cargo.toml --features trace --target ${{ env.TARGET }}
|
||||||
|
|
||||||
webgl_build:
|
wasm:
|
||||||
name: Web Assembly
|
name: Web Assembly
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: --cfg=web_sys_unstable_apis
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: rustup target add wasm32-unknown-unknown
|
- run: rustup target add wasm32-unknown-unknown
|
||||||
- run: cargo build --manifest-path wgpu-core/Cargo.toml --target wasm32-unknown-unknown
|
- name: Check WebGPU
|
||||||
|
run: cargo check --all-targets --target=wasm32-unknown-unknown
|
||||||
|
- name: Check WebGL
|
||||||
|
run: cargo check --all-targets --target=wasm32-unknown-unknown --features webgl
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
@ -117,6 +122,20 @@ jobs:
|
|||||||
- if: matrix.channel == 'nightly'
|
- if: matrix.channel == 'nightly'
|
||||||
run: cargo test -- --nocapture
|
run: cargo test -- --nocapture
|
||||||
|
|
||||||
|
docs:
|
||||||
|
runs-on: [ubuntu-18.04]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install latest nightly
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
continue-on-error: true
|
||||||
|
- name: cargo doc
|
||||||
|
run: cargo --version; cargo doc --no-deps
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Clippy
|
name: Clippy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -132,3 +151,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: -- -D warnings
|
args: -- -D warnings
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: -- --check
|
||||||
|
45
.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout the code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install latest nightly
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Add EGL for OpenGL
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -y -qq
|
||||||
|
sudo apt-get install -y -qq libegl1-mesa-dev
|
||||||
|
|
||||||
|
- name: Build the docs (nightly)
|
||||||
|
run: |
|
||||||
|
cargo +nightly doc --lib --all-features
|
||||||
|
|
||||||
|
- name: Build the docs (stable)
|
||||||
|
run: cargo +stable doc --lib --all-features
|
||||||
|
if: ${{ failure() }}
|
||||||
|
|
||||||
|
- name: Deploy the docs
|
||||||
|
uses: JamesIves/github-pages-deploy-action@releases/v3
|
||||||
|
with:
|
||||||
|
ACCESS_TOKEN: ${{ secrets.WEB_DEPLOY }}
|
||||||
|
FOLDER: target/doc
|
||||||
|
REPOSITORY_NAME: gfx-rs/wgpu-rs.github.io
|
||||||
|
BRANCH: master
|
||||||
|
TARGET_FOLDER: doc
|
47
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- gecko
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: --cfg=web_sys_unstable_apis
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout the code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install Rust WASM toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
target: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
- name: Build the examples
|
||||||
|
run: cargo build --release --target wasm32-unknown-unknown --examples
|
||||||
|
|
||||||
|
- name: Install wasm-bindgen-cli
|
||||||
|
run: cargo install wasm-bindgen-cli
|
||||||
|
|
||||||
|
- name: Generate JS bindings for the examples
|
||||||
|
run: |
|
||||||
|
for i in target/wasm32-unknown-unknown/release/examples/*.wasm;
|
||||||
|
do
|
||||||
|
wasm-bindgen --no-typescript --out-dir target/generated --web "$i";
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Deploy the examples
|
||||||
|
uses: JamesIves/github-pages-deploy-action@releases/v3
|
||||||
|
with:
|
||||||
|
ACCESS_TOKEN: ${{ secrets.WEB_DEPLOY }}
|
||||||
|
FOLDER: target/generated
|
||||||
|
REPOSITORY_NAME: gfx-rs/wgpu-rs.github.io
|
||||||
|
BRANCH: master
|
||||||
|
TARGET_FOLDER: examples/wasm
|
15
.gitignore
vendored
@ -1,8 +1,17 @@
|
|||||||
/target
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
#Cargo.lock
|
|
||||||
|
# Other
|
||||||
.fuse_hidden*
|
.fuse_hidden*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# IDE/Editor configuration files
|
||||||
.vscode
|
.vscode
|
||||||
.vs
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Output from capture example
|
||||||
|
wgpu/red.png
|
||||||
|
19
wgpu/CHANGELOG.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
### v0.8 (2021-04-29)
|
||||||
|
- See https://github.com/gfx-rs/wgpu/blob/v0.8/CHANGELOG.md#v08-2021-04-29
|
||||||
|
- Naga is the default shader conversion path on Metal, Vulkan, and OpenGL
|
||||||
|
- SPIRV-Cross is optionally enabled with "cross" feature
|
||||||
|
- All of the examples (except "texture-array") now use WGSL
|
||||||
|
|
||||||
|
### v0.7 (2021-01-31)
|
||||||
|
- See https://github.com/gfx-rs/wgpu/blob/v0.7/CHANGELOG.md#v07-2020-08-30
|
||||||
|
- Features:
|
||||||
|
- (beta) WGSL support
|
||||||
|
- better error messages
|
||||||
|
- API changes:
|
||||||
|
- new `ShaderModuleDescriptor`
|
||||||
|
- new `RenderEncoder`
|
||||||
|
|
||||||
|
### v0.6.2 (2020-11-24)
|
||||||
|
- don't panic in the staging belt if the channel is dropped
|
247
wgpu/Cargo.toml
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
[package]
|
||||||
|
name = "wgpu"
|
||||||
|
version = "0.8.0"
|
||||||
|
authors = ["wgpu developers"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "Rusty WebGPU API wrapper"
|
||||||
|
homepage = "https://github.com/gfx-rs/wgpu-rs"
|
||||||
|
repository = "https://github.com/gfx-rs/wgpu-rs"
|
||||||
|
keywords = ["graphics"]
|
||||||
|
license = "MPL-2.0"
|
||||||
|
exclude = ["etc/**/*", "examples/**/*", "tests/**/*", "Cargo.lock", "target/**/*"]
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
trace = ["serde", "wgc/trace"]
|
||||||
|
replay = ["serde", "wgc/replay"]
|
||||||
|
webgl = ["wgc"]
|
||||||
|
# Enable SPIRV-Cross
|
||||||
|
cross = ["wgc/cross"]
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgc]
|
||||||
|
package = "wgpu-core"
|
||||||
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
|
rev = "eadaa1b7d8f585761e28445904fe619b180aca0d"
|
||||||
|
features = ["raw-window-handle"]
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies.wgc]
|
||||||
|
package = "wgpu-core"
|
||||||
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
|
rev = "eadaa1b7d8f585761e28445904fe619b180aca0d"
|
||||||
|
features = ["raw-window-handle"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.wgt]
|
||||||
|
package = "wgpu-types"
|
||||||
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
|
rev = "eadaa1b7d8f585761e28445904fe619b180aca0d"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
arrayvec = "0.5"
|
||||||
|
log = "0.4"
|
||||||
|
parking_lot = "0.11"
|
||||||
|
raw-window-handle = "0.3"
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
smallvec = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bytemuck = { version = "1.4", features = ["derive"] }
|
||||||
|
cgmath = "0.18"
|
||||||
|
ddsfile = "0.4"
|
||||||
|
log = "0.4"
|
||||||
|
noise = "0.7"
|
||||||
|
obj = "0.10"
|
||||||
|
png = "0.16"
|
||||||
|
rand = { version = "0.7.2", features = ["wasm-bindgen"] }
|
||||||
|
winit = { version = "0.24", features = ["web-sys"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||||
|
async-executor = "1.0"
|
||||||
|
pollster = "0.2"
|
||||||
|
env_logger = "0.8"
|
||||||
|
|
||||||
|
# used to test all the example shaders
|
||||||
|
[dev-dependencies.naga]
|
||||||
|
git = "https://github.com/gfx-rs/naga"
|
||||||
|
tag = "gfx-25"
|
||||||
|
features = ["wgsl-in"]
|
||||||
|
|
||||||
|
# used to generate SPIR-V for the Web target
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies.naga]
|
||||||
|
git = "https://github.com/gfx-rs/naga"
|
||||||
|
tag = "gfx-25"
|
||||||
|
features = ["wgsl-in", "spv-out"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name="hello-compute"
|
||||||
|
path="examples/hello-compute/main.rs"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[patch."https://github.com/gfx-rs/wgpu"]
|
||||||
|
#wgpu-types = { path = "../wgpu/wgpu-types" }
|
||||||
|
#wgpu-core = { path = "../wgpu/wgpu-core" }
|
||||||
|
|
||||||
|
[patch."https://github.com/gfx-rs/subscriber"]
|
||||||
|
#wgpu-subscriber = { version = "0.1", path = "../subscriber" }
|
||||||
|
|
||||||
|
[patch."https://github.com/gfx-rs/naga"]
|
||||||
|
#naga = { path = "../naga" }
|
||||||
|
|
||||||
|
[patch."https://github.com/zakarumych/gpu-descriptor"]
|
||||||
|
#gpu-descriptor = { path = "../gpu-descriptor/gpu-descriptor" }
|
||||||
|
|
||||||
|
[patch."https://github.com/zakarumych/gpu-alloc"]
|
||||||
|
#gpu-alloc = { path = "../gpu-alloc/gpu-alloc" }
|
||||||
|
|
||||||
|
[patch."https://github.com/gfx-rs/gfx"]
|
||||||
|
#gfx-hal = { path = "../gfx/src/hal" }
|
||||||
|
#gfx-backend-empty = { path = "../gfx/src/backend/empty" }
|
||||||
|
#gfx-backend-vulkan = { path = "../gfx/src/backend/vulkan" }
|
||||||
|
#gfx-backend-gl = { path = "../gfx/src/backend/gl" }
|
||||||
|
#gfx-backend-dx12 = { path = "../gfx/src/backend/dx12" }
|
||||||
|
#gfx-backend-dx11 = { path = "../gfx/src/backend/dx11" }
|
||||||
|
#gfx-backend-metal = { path = "../gfx/src/backend/metal" }
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
#web-sys = { path = "../wasm-bindgen/crates/web-sys" }
|
||||||
|
#js-sys = { path = "../wasm-bindgen/crates/js-sys" }
|
||||||
|
#wasm-bindgen = { path = "../wasm-bindgen" }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
wasm-bindgen = "0.2.73" # remember to change version in wiki as well
|
||||||
|
web-sys = { version = "=0.3.50", features = [
|
||||||
|
"Document",
|
||||||
|
"Navigator",
|
||||||
|
"Node",
|
||||||
|
"NodeList",
|
||||||
|
"Gpu",
|
||||||
|
"GpuAdapter",
|
||||||
|
"GpuAdapterFeatures",
|
||||||
|
"GpuAdapterLimits",
|
||||||
|
"GpuAddressMode",
|
||||||
|
"GpuBindGroup",
|
||||||
|
"GpuBindGroupDescriptor",
|
||||||
|
"GpuBindGroupEntry",
|
||||||
|
"GpuBindGroupLayout",
|
||||||
|
"GpuBindGroupLayoutDescriptor",
|
||||||
|
"GpuBindGroupLayoutEntry",
|
||||||
|
"GpuBlendComponent",
|
||||||
|
"GpuBlendFactor",
|
||||||
|
"GpuBlendOperation",
|
||||||
|
"GpuBlendState",
|
||||||
|
"GpuBuffer",
|
||||||
|
"GpuBufferBinding",
|
||||||
|
"GpuBufferBindingLayout",
|
||||||
|
"GpuBufferBindingType",
|
||||||
|
"GpuBufferDescriptor",
|
||||||
|
"GpuBufferUsage",
|
||||||
|
"GpuCanvasContext",
|
||||||
|
"GpuColorDict",
|
||||||
|
"GpuColorTargetState",
|
||||||
|
"GpuColorWrite",
|
||||||
|
"GpuCommandBuffer",
|
||||||
|
"GpuCommandBufferDescriptor",
|
||||||
|
"GpuCommandEncoder",
|
||||||
|
"GpuCommandEncoderDescriptor",
|
||||||
|
"GpuCompareFunction",
|
||||||
|
"GpuCompilationInfo",
|
||||||
|
"GpuCompilationMessage",
|
||||||
|
"GpuCompilationMessageType",
|
||||||
|
"GpuComputePassDescriptor",
|
||||||
|
"GpuComputePassEncoder",
|
||||||
|
"GpuComputePipeline",
|
||||||
|
"GpuComputePipelineDescriptor",
|
||||||
|
"GpuCullMode",
|
||||||
|
"GpuDepthStencilState",
|
||||||
|
"GpuDevice",
|
||||||
|
"GpuDeviceDescriptor",
|
||||||
|
"GpuDeviceLostInfo",
|
||||||
|
"GpuDeviceLostReason",
|
||||||
|
"GpuErrorFilter",
|
||||||
|
"GpuExtent3dDict",
|
||||||
|
"GpuFeatureName",
|
||||||
|
"GpuFilterMode",
|
||||||
|
"GpuFragmentState",
|
||||||
|
"GpuFrontFace",
|
||||||
|
"GpuImageCopyBuffer",
|
||||||
|
"GpuImageCopyImageBitmap",
|
||||||
|
"GpuImageCopyTexture",
|
||||||
|
"GpuImageDataLayout",
|
||||||
|
"GpuIndexFormat",
|
||||||
|
"GpuInputStepMode",
|
||||||
|
"GpuLoadOp",
|
||||||
|
"GpuMapMode",
|
||||||
|
"GpuMultisampleState",
|
||||||
|
"GpuObjectDescriptorBase",
|
||||||
|
"GpuOrigin2dDict",
|
||||||
|
"GpuOrigin3dDict",
|
||||||
|
"GpuOutOfMemoryError",
|
||||||
|
"GpuPipelineDescriptorBase",
|
||||||
|
"GpuPipelineLayout",
|
||||||
|
"GpuPipelineLayoutDescriptor",
|
||||||
|
"GpuPipelineStatisticName",
|
||||||
|
"GpuPowerPreference",
|
||||||
|
"GpuPrimitiveState",
|
||||||
|
"GpuPrimitiveTopology",
|
||||||
|
"GpuProgrammableStage",
|
||||||
|
"GpuQuerySet",
|
||||||
|
"GpuQuerySetDescriptor",
|
||||||
|
"GpuQueryType",
|
||||||
|
"GpuQueue",
|
||||||
|
"GpuRenderBundle",
|
||||||
|
"GpuRenderBundleDescriptor",
|
||||||
|
"GpuRenderBundleEncoder",
|
||||||
|
"GpuRenderBundleEncoderDescriptor",
|
||||||
|
"GpuRenderPassColorAttachment",
|
||||||
|
"GpuRenderPassDepthStencilAttachment",
|
||||||
|
"GpuRenderPassDescriptor",
|
||||||
|
"GpuRenderPassEncoder",
|
||||||
|
"GpuRenderPipeline",
|
||||||
|
"GpuRenderPipelineDescriptor",
|
||||||
|
"GpuRequestAdapterOptions",
|
||||||
|
"GpuSampler",
|
||||||
|
"GpuSamplerBindingLayout",
|
||||||
|
"GpuSamplerBindingType",
|
||||||
|
"GpuSamplerDescriptor",
|
||||||
|
"GpuShaderModule",
|
||||||
|
"GpuShaderModuleDescriptor",
|
||||||
|
"GpuShaderStage",
|
||||||
|
"GpuStencilFaceState",
|
||||||
|
"GpuStencilOperation",
|
||||||
|
"GpuStorageTextureAccess",
|
||||||
|
"GpuStorageTextureBindingLayout",
|
||||||
|
"GpuStoreOp",
|
||||||
|
"GpuSwapChain",
|
||||||
|
"GpuSwapChainDescriptor",
|
||||||
|
"GpuTexture",
|
||||||
|
"GpuTextureAspect",
|
||||||
|
"GpuTextureBindingLayout",
|
||||||
|
"GpuTextureDescriptor",
|
||||||
|
"GpuTextureDimension",
|
||||||
|
"GpuTextureFormat",
|
||||||
|
"GpuTextureSampleType",
|
||||||
|
"GpuTextureUsage",
|
||||||
|
"GpuTextureView",
|
||||||
|
"GpuTextureViewDescriptor",
|
||||||
|
"GpuTextureViewDimension",
|
||||||
|
"GpuUncapturedErrorEvent",
|
||||||
|
"GpuUncapturedErrorEventInit",
|
||||||
|
"GpuValidationError",
|
||||||
|
"GpuVertexAttribute",
|
||||||
|
"GpuVertexBufferLayout",
|
||||||
|
"GpuVertexFormat",
|
||||||
|
"GpuVertexState",
|
||||||
|
"HtmlCanvasElement",
|
||||||
|
"Window",
|
||||||
|
]}
|
||||||
|
js-sys = "0.3.50"
|
||||||
|
wasm-bindgen-futures = "0.4.23"
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
|
console_error_panic_hook = "0.1.6"
|
||||||
|
console_log = "0.1.2"
|
373
wgpu/LICENSE
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
20
wgpu/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# This Makefile generates SPIR-V shaders from GLSL shaders in the examples.
|
||||||
|
|
||||||
|
shader_compiler = glslangValidator
|
||||||
|
|
||||||
|
# All input shaders.
|
||||||
|
glsls = $(wildcard examples/*/*.vert examples/*/*.frag examples/*/*.comp)
|
||||||
|
|
||||||
|
# All SPIR-V targets.
|
||||||
|
spirvs = $(addsuffix .spv,$(glsls))
|
||||||
|
|
||||||
|
.PHONY: default
|
||||||
|
default: $(spirvs)
|
||||||
|
|
||||||
|
# Rule for making a SPIR-V target.
|
||||||
|
$(spirvs): %.spv: %
|
||||||
|
$(shader_compiler) -V $< -o $@
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(spirvs)
|
101
wgpu/README.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<img align="right" width="25%" src="logo.png">
|
||||||
|
|
||||||
|
# wgpu-rs
|
||||||
|
|
||||||
|
[![Build Status](https://github.com/gfx-rs/wgpu-rs/workflows/CI/badge.svg?branch=master)](https://github.com/gfx-rs/wgpu-rs/actions)
|
||||||
|
[![Crates.io](https://img.shields.io/crates/v/wgpu.svg)](https://crates.io/crates/wgpu)
|
||||||
|
[![Docs.rs](https://docs.rs/wgpu/badge.svg)](https://docs.rs/wgpu)
|
||||||
|
|
||||||
|
[![Matrix](https://img.shields.io/badge/Dev_Matrix-%23wgpu%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#wgpu:matrix.org)
|
||||||
|
[![Matrix](https://img.shields.io/badge/User_Matrix-%23wgpu--users%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#wgpu-users:matrix.org)
|
||||||
|
|
||||||
|
wgpu-rs is an idiomatic Rust wrapper over [wgpu-core](https://github.com/gfx-rs/wgpu). It's designed to be suitable for general purpose graphics and computation needs of Rust community.
|
||||||
|
|
||||||
|
wgpu-rs can target both the natively supported backends and WASM directly.
|
||||||
|
|
||||||
|
See our [gallery](https://wgpu.rs/#showcase) and the [wiki page](https://github.com/gfx-rs/wgpu-rs/wiki/Applications-and-Libraries) for the list of libraries and applications using `wgpu-rs`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### How to Run Examples
|
||||||
|
|
||||||
|
All examples are located under the [examples](examples) directory.
|
||||||
|
|
||||||
|
These examples use the default syntax for running examples, as found in the [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) documentation. For example, to run the `cube` example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example cube
|
||||||
|
```
|
||||||
|
|
||||||
|
The `hello*` examples show bare-bones setup without any helper code. For `hello-compute`, pass 4 numbers separated by spaces as arguments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example hello-compute 1 2 3 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The following environment variables can be used to configure how the framework examples run:
|
||||||
|
|
||||||
|
- `WGPU_BACKEND`
|
||||||
|
|
||||||
|
Options: `vulkan`, `metal`, `dx11`, `dx12`, `gl`, `webgpu`
|
||||||
|
|
||||||
|
If unset a default backend is chosen based on what is supported
|
||||||
|
by your system.
|
||||||
|
|
||||||
|
- `WGPU_POWER_PREF`
|
||||||
|
|
||||||
|
Options: `low`, `high`
|
||||||
|
|
||||||
|
If unset a low power adapter is preferred.
|
||||||
|
|
||||||
|
#### Run Examples on the Web (`wasm32-unknown-unknown`)
|
||||||
|
|
||||||
|
See [wiki article](https://github.com/gfx-rs/wgpu-rs/wiki/Running-on-the-Web-with-WebGPU-and-WebGL).
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
`wgpu-core` uses `tracing` for logging and `wgpu-rs` uses `log` for logging.
|
||||||
|
|
||||||
|
### Simple Setup
|
||||||
|
|
||||||
|
If you just want log messages to show up and to use the chrome tracing infrastructure,
|
||||||
|
take a dependency on the `wgpu-subscriber` crate then call `initialize_default_subscriber`. It will
|
||||||
|
set up logging to stdout/stderr based on the `RUST_LOG` environment variable.
|
||||||
|
|
||||||
|
### Manual Conversion
|
||||||
|
|
||||||
|
`tracing` also has tools available to convert all `tracing` events into `log` events and vise versa.
|
||||||
|
|
||||||
|
#### `log` events -> `tracing` events
|
||||||
|
|
||||||
|
The `tracing_log` crate has a `log` logger to translate all events into `tracing` events. Call:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
tracing_log::LogTracer::init().unwrap()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `tracing` events -> `log` events
|
||||||
|
|
||||||
|
The `tracing` crate has a `log` feature which will automatically use `log` if no subscriber is added:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
tracing = { version = "0.1", features = ["log"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want events to be handled both by `tracing` and `log`, enable the `log-always` feature of `tracing`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
tracing = { version = "0.1", features = ["log-always"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
If you need to test local fixes to gfx or other dependencies, the simplest way is to add a Cargo patch. For example, when working on DX12 backend on Windows, you can check out the latest release branch in the [gfx-hal repository](https://github.com/gfx-rs/gfx) (e.g. currently `hal-0.8`) and add this patch to the end of `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[patch."https://github.com/gfx-rs/gfx"]
|
||||||
|
gfx-backend-dx12 = { path = "../gfx/src/backend/dx12" }
|
||||||
|
gfx-hal = { path = "../gfx/src/hal" }
|
||||||
|
```
|
||||||
|
|
||||||
|
If a version needs to be changed, you need to do `cargo update -p gfx-backend-dx12`.
|
7
wgpu/bors.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
status = [
|
||||||
|
"build (macos-10.15)",
|
||||||
|
"build (ubuntu-18.04)",
|
||||||
|
"build (windows-2019)",
|
||||||
|
"wasm",
|
||||||
|
"docs",
|
||||||
|
]
|
56
wgpu/examples/README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
## Structure
|
||||||
|
|
||||||
|
For the simplest examples without using any helping code (see `framework.rs` here), check out:
|
||||||
|
- `hello ` for printing adapter information
|
||||||
|
- `hello-triangle` for graphics and presentation
|
||||||
|
- `hello-compute` for pure computing
|
||||||
|
|
||||||
|
Notably, `capture` example shows rendering without a surface/window. It reads back the contents and saves them to a file.
|
||||||
|
|
||||||
|
All the examples use [WGSL](https://gpuweb.github.io/gpuweb/wgsl.html) shaders unless specified otherwise.
|
||||||
|
|
||||||
|
All framework-based examples render to the window.
|
||||||
|
|
||||||
|
## Feature matrix
|
||||||
|
| Feature | boids | bunnymark | cube | mipmap | msaa-line | shadow | skybox | texture-arrays | water | conservative-raster |
|
||||||
|
| ---------------------------- | ------ | --------- | ------ | ------ | --------- | ------ | ------ | -------------- | ------ | ------------------- |
|
||||||
|
| vertex attributes | :star: | | :star: | | :star: | :star: | :star: | :star: | :star: | |
|
||||||
|
| instancing | :star: | | | | | | | | | |
|
||||||
|
| lines and points | | | | | :star: | | | | | :star: |
|
||||||
|
| dynamic buffer offsets | | :star: | | | | :star: | | | | |
|
||||||
|
| implicit layout | | | | :star: | | | | | | |
|
||||||
|
| sampled color textures | :star: | :star: | :star: | :star: | | | :star: | :star: | :star: | :star: |
|
||||||
|
| storage textures | :star: | | | | | | | | | |
|
||||||
|
| binding array | | | | | | | | :star: | | |
|
||||||
|
| comparison samplers | | | | | | :star: | | | | |
|
||||||
|
| subresource views | | | | :star: | | :star: | | | | |
|
||||||
|
| cubemaps | | | | | | | :star: | | | |
|
||||||
|
| multisampling | | | | | :star: | | | | | |
|
||||||
|
| off-screen rendering | | | | | | :star: | | | :star: | :star: |
|
||||||
|
| stencil testing | | | | | | | | | | |
|
||||||
|
| depth testing | | | | | | :star: | :star: | | :star: | |
|
||||||
|
| depth biasing | | | | | | :star: | | | | |
|
||||||
|
| read-only depth | | | | | | | | | :star: | |
|
||||||
|
| blending | | :star: | :star: | | | | | | :star: | |
|
||||||
|
| render bundles | | | | | :star: | | | | :star: | |
|
||||||
|
| compute passes | :star: | | | | | | | | | |
|
||||||
|
| *optional extensions* | | | | | | | | :star: | | |
|
||||||
|
| - SPIR-V shaders | | | | | | | | :star: | | |
|
||||||
|
| - binding indexing | | | | | | | | :star: | | |
|
||||||
|
| - push constants | | | | | | | | :star: | | |
|
||||||
|
| - depth clamping | | | | | | :star: | | | | |
|
||||||
|
| - compressed textures | | | | | | | :star: | | | |
|
||||||
|
| - polygon mode | | | :star: | | | | | | | |
|
||||||
|
| - queries | | | | :star: | | | | | | |
|
||||||
|
| - conservative rasterization | | | | | | | | | | :star: |
|
||||||
|
| *integrations* | | | | | | | | | | |
|
||||||
|
| - staging belt | | | | | | | | | | |
|
||||||
|
| - typed arena | | | | | | | | | | |
|
||||||
|
| - obj loading | | | | | | | :star: | | | |
|
||||||
|
|
||||||
|
## Hacking
|
||||||
|
|
||||||
|
You can record an API trace any of the framework-based examples by starting them as:
|
||||||
|
```sh
|
||||||
|
mkdir -p trace && WGPU_TRACE=trace cargo run --features trace --example <example-name>
|
||||||
|
```
|
13
wgpu/examples/boids/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# boids
|
||||||
|
|
||||||
|
Flocking boids example with gpu compute update pass
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example boids
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Boids example](./screenshot.png)
|
108
wgpu/examples/boids/compute.wgsl
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
struct Particle {
|
||||||
|
pos : vec2<f32>;
|
||||||
|
vel : vec2<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SimParams {
|
||||||
|
deltaT : f32;
|
||||||
|
rule1Distance : f32;
|
||||||
|
rule2Distance : f32;
|
||||||
|
rule3Distance : f32;
|
||||||
|
rule1Scale : f32;
|
||||||
|
rule2Scale : f32;
|
||||||
|
rule3Scale : f32;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Particles {
|
||||||
|
particles : [[stride(16)]] array<Particle>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<uniform> params : SimParams;
|
||||||
|
[[group(0), binding(1)]] var<storage> particlesSrc : [[access(read)]] Particles;
|
||||||
|
[[group(0), binding(2)]] var<storage> particlesDst : [[access(read_write)]] Particles;
|
||||||
|
|
||||||
|
// https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp
|
||||||
|
[[stage(compute), workgroup_size(64)]]
|
||||||
|
fn main([[builtin(global_invocation_id)]] global_invocation_id: vec3<u32>) {
|
||||||
|
let total = arrayLength(&particlesSrc.particles);
|
||||||
|
let index = global_invocation_id.x;
|
||||||
|
if (index >= total) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var vPos : vec2<f32> = particlesSrc.particles[index].pos;
|
||||||
|
var vVel : vec2<f32> = particlesSrc.particles[index].vel;
|
||||||
|
|
||||||
|
var cMass : vec2<f32> = vec2<f32>(0.0, 0.0);
|
||||||
|
var cVel : vec2<f32> = vec2<f32>(0.0, 0.0);
|
||||||
|
var colVel : vec2<f32> = vec2<f32>(0.0, 0.0);
|
||||||
|
var cMassCount : i32 = 0;
|
||||||
|
var cVelCount : i32 = 0;
|
||||||
|
|
||||||
|
var pos : vec2<f32>;
|
||||||
|
var vel : vec2<f32>;
|
||||||
|
var i : u32 = 0u;
|
||||||
|
loop {
|
||||||
|
if (i >= total) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i == index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = particlesSrc.particles[i].pos;
|
||||||
|
vel = particlesSrc.particles[i].vel;
|
||||||
|
|
||||||
|
if (distance(pos, vPos) < params.rule1Distance) {
|
||||||
|
cMass = cMass + pos;
|
||||||
|
cMassCount = cMassCount + 1;
|
||||||
|
}
|
||||||
|
if (distance(pos, vPos) < params.rule2Distance) {
|
||||||
|
colVel = colVel - (pos - vPos);
|
||||||
|
}
|
||||||
|
if (distance(pos, vPos) < params.rule3Distance) {
|
||||||
|
cVel = cVel + vel;
|
||||||
|
cVelCount = cVelCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
continuing {
|
||||||
|
i = i + 1u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cMassCount > 0) {
|
||||||
|
cMass = cMass * (1.0 / f32(cMassCount)) - vPos;
|
||||||
|
}
|
||||||
|
if (cVelCount > 0) {
|
||||||
|
cVel = cVel * (1.0 / f32(cVelCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
vVel = vVel + (cMass * params.rule1Scale) +
|
||||||
|
(colVel * params.rule2Scale) +
|
||||||
|
(cVel * params.rule3Scale);
|
||||||
|
|
||||||
|
// clamp velocity for a more pleasing simulation
|
||||||
|
vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1);
|
||||||
|
|
||||||
|
// kinematic update
|
||||||
|
vPos = vPos + (vVel * params.deltaT);
|
||||||
|
|
||||||
|
// Wrap around boundary
|
||||||
|
if (vPos.x < -1.0) {
|
||||||
|
vPos.x = 1.0;
|
||||||
|
}
|
||||||
|
if (vPos.x > 1.0) {
|
||||||
|
vPos.x = -1.0;
|
||||||
|
}
|
||||||
|
if (vPos.y < -1.0) {
|
||||||
|
vPos.y = 1.0;
|
||||||
|
}
|
||||||
|
if (vPos.y > 1.0) {
|
||||||
|
vPos.y = -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back
|
||||||
|
particlesDst.particles[index].pos = vPos;
|
||||||
|
particlesDst.particles[index].vel = vVel;
|
||||||
|
}
|
18
wgpu/examples/boids/draw.wgsl
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[[stage(vertex)]]
|
||||||
|
fn main(
|
||||||
|
[[location(0)]] particle_pos: vec2<f32>,
|
||||||
|
[[location(1)]] particle_vel: vec2<f32>,
|
||||||
|
[[location(2)]] position: vec2<f32>,
|
||||||
|
) -> [[builtin(position)]] vec4<f32> {
|
||||||
|
let angle = -atan2(particle_vel.x, particle_vel.y);
|
||||||
|
let pos = vec2<f32>(
|
||||||
|
position.x * cos(angle) - position.y * sin(angle),
|
||||||
|
position.x * sin(angle) + position.y * cos(angle)
|
||||||
|
);
|
||||||
|
return vec4<f32>(pos + particle_pos, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn main() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
326
wgpu/examples/boids/main.rs
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
// Flocking boids example with gpu compute update pass
|
||||||
|
// adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts
|
||||||
|
|
||||||
|
use rand::distributions::{Distribution, Uniform};
|
||||||
|
use std::{borrow::Cow, mem};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
// number of boid particles to simulate
|
||||||
|
|
||||||
|
const NUM_PARTICLES: u32 = 1500;
|
||||||
|
|
||||||
|
// number of single-particle calculations (invocations) in each gpu work group
|
||||||
|
|
||||||
|
const PARTICLES_PER_GROUP: u32 = 64;
|
||||||
|
|
||||||
|
/// Example struct holds references to wgpu resources and frame persistent data
|
||||||
|
struct Example {
|
||||||
|
particle_bind_groups: Vec<wgpu::BindGroup>,
|
||||||
|
particle_buffers: Vec<wgpu::Buffer>,
|
||||||
|
vertices_buffer: wgpu::Buffer,
|
||||||
|
compute_pipeline: wgpu::ComputePipeline,
|
||||||
|
render_pipeline: wgpu::RenderPipeline,
|
||||||
|
work_group_count: u32,
|
||||||
|
frame_num: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
/// constructs initial instance of Example struct
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
// load and compile the shader
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgt::Backend::Vulkan | wgt::Backend::Metal | wgt::Backend::Gl => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION;
|
||||||
|
}
|
||||||
|
_ => {} //TODO
|
||||||
|
}
|
||||||
|
let compute_shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("compute.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
let draw_shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("draw.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
// buffer for simulation parameters uniform
|
||||||
|
|
||||||
|
let sim_param_data = [
|
||||||
|
0.04f32, // deltaT
|
||||||
|
0.1, // rule1Distance
|
||||||
|
0.025, // rule2Distance
|
||||||
|
0.025, // rule3Distance
|
||||||
|
0.02, // rule1Scale
|
||||||
|
0.05, // rule2Scale
|
||||||
|
0.005, // rule3Scale
|
||||||
|
]
|
||||||
|
.to_vec();
|
||||||
|
let sim_param_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Simulation Parameter Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&sim_param_data),
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
// create compute bind layout group and compute pipeline layout
|
||||||
|
|
||||||
|
let compute_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::COMPUTE,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(
|
||||||
|
(sim_param_data.len() * mem::size_of::<f32>()) as _,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::COMPUTE,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::COMPUTE,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let compute_pipeline_layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("compute"),
|
||||||
|
bind_group_layouts: &[&compute_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// create render pipeline with empty bind group layout
|
||||||
|
|
||||||
|
let render_pipeline_layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("render"),
|
||||||
|
bind_group_layouts: &[],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&render_pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &draw_shader,
|
||||||
|
entry_point: "main",
|
||||||
|
buffers: &[
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: 4 * 4,
|
||||||
|
step_mode: wgpu::InputStepMode::Instance,
|
||||||
|
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
|
||||||
|
},
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: 2 * 4,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &wgpu::vertex_attr_array![2 => Float32x2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &draw_shader,
|
||||||
|
entry_point: "main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// create compute pipeline
|
||||||
|
|
||||||
|
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||||
|
label: Some("Compute pipeline"),
|
||||||
|
layout: Some(&compute_pipeline_layout),
|
||||||
|
module: &compute_shader,
|
||||||
|
entry_point: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
// buffer for the three 2d triangle vertices of each instance
|
||||||
|
|
||||||
|
let vertex_buffer_data = [-0.01f32, -0.02, 0.01, -0.02, 0.00, 0.02];
|
||||||
|
let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Vertex Buffer"),
|
||||||
|
contents: bytemuck::bytes_of(&vertex_buffer_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
// buffer for all particles data of type [(posx,posy,velx,vely),...]
|
||||||
|
|
||||||
|
let mut initial_particle_data = vec![0.0f32; (4 * NUM_PARTICLES) as usize];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let unif = Uniform::new_inclusive(-1.0, 1.0);
|
||||||
|
for particle_instance_chunk in initial_particle_data.chunks_mut(4) {
|
||||||
|
particle_instance_chunk[0] = unif.sample(&mut rng); // posx
|
||||||
|
particle_instance_chunk[1] = unif.sample(&mut rng); // posy
|
||||||
|
particle_instance_chunk[2] = unif.sample(&mut rng) * 0.1; // velx
|
||||||
|
particle_instance_chunk[3] = unif.sample(&mut rng) * 0.1; // vely
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates two buffers of particle data each of size NUM_PARTICLES
|
||||||
|
// the two buffers alternate as dst and src for each frame
|
||||||
|
|
||||||
|
let mut particle_buffers = Vec::<wgpu::Buffer>::new();
|
||||||
|
let mut particle_bind_groups = Vec::<wgpu::BindGroup>::new();
|
||||||
|
for i in 0..2 {
|
||||||
|
particle_buffers.push(
|
||||||
|
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some(&format!("Particle Buffer {}", i)),
|
||||||
|
contents: bytemuck::cast_slice(&initial_particle_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX
|
||||||
|
| wgpu::BufferUsage::STORAGE
|
||||||
|
| wgpu::BufferUsage::COPY_DST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create two bind groups, one for each buffer as the src
|
||||||
|
// where the alternate buffer is used as the dst
|
||||||
|
|
||||||
|
for i in 0..2 {
|
||||||
|
particle_bind_groups.push(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &compute_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: sim_param_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: particle_buffers[i].as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: particle_buffers[(i + 1) % 2].as_entire_binding(), // bind to opposite buffer
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculates number of work groups from PARTICLES_PER_GROUP constant
|
||||||
|
let work_group_count =
|
||||||
|
((NUM_PARTICLES as f32) / (PARTICLES_PER_GROUP as f32)).ceil() as u32;
|
||||||
|
|
||||||
|
// returns Example struct and No encoder commands
|
||||||
|
|
||||||
|
Example {
|
||||||
|
particle_bind_groups,
|
||||||
|
particle_buffers,
|
||||||
|
vertices_buffer,
|
||||||
|
compute_pipeline,
|
||||||
|
render_pipeline,
|
||||||
|
work_group_count,
|
||||||
|
frame_num: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update is called for any WindowEvent not handled by the framework
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// resize is called on WindowEvent::Resized events
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
_sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// render is called each frame, dispatching compute groups proportional
|
||||||
|
/// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
// create render pass descriptor and its color attachments
|
||||||
|
let color_attachments = [wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let render_pass_descriptor = wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &color_attachments,
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// get command encoder
|
||||||
|
let mut command_encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
command_encoder.push_debug_group("compute boid movement");
|
||||||
|
{
|
||||||
|
// compute pass
|
||||||
|
let mut cpass =
|
||||||
|
command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None });
|
||||||
|
cpass.set_pipeline(&self.compute_pipeline);
|
||||||
|
cpass.set_bind_group(0, &self.particle_bind_groups[self.frame_num % 2], &[]);
|
||||||
|
cpass.dispatch(self.work_group_count, 1, 1);
|
||||||
|
}
|
||||||
|
command_encoder.pop_debug_group();
|
||||||
|
|
||||||
|
command_encoder.push_debug_group("render boids");
|
||||||
|
{
|
||||||
|
// render pass
|
||||||
|
let mut rpass = command_encoder.begin_render_pass(&render_pass_descriptor);
|
||||||
|
rpass.set_pipeline(&self.render_pipeline);
|
||||||
|
// render dst particles
|
||||||
|
rpass.set_vertex_buffer(0, self.particle_buffers[(self.frame_num + 1) % 2].slice(..));
|
||||||
|
// the three instance-local vertices
|
||||||
|
rpass.set_vertex_buffer(1, self.vertices_buffer.slice(..));
|
||||||
|
rpass.draw(0..3, 0..NUM_PARTICLES);
|
||||||
|
}
|
||||||
|
command_encoder.pop_debug_group();
|
||||||
|
|
||||||
|
// update frame count
|
||||||
|
self.frame_num += 1;
|
||||||
|
|
||||||
|
// done
|
||||||
|
queue.submit(Some(command_encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// run example
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("boids");
|
||||||
|
}
|
BIN
wgpu/examples/boids/screenshot.png
Normal file
After Width: | Height: | Size: 158 KiB |
354
wgpu/examples/bunnymark/main.rs
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::{borrow::Cow, mem};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
const MAX_BUNNIES: usize = 1 << 20;
|
||||||
|
const BUNNY_SIZE: f32 = 0.15 * 256.0;
|
||||||
|
const GRAVITY: f32 = -9.8 * 100.0;
|
||||||
|
const MAX_VELOCITY: f32 = 750.0;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct Globals {
|
||||||
|
mvp: [[f32; 4]; 4],
|
||||||
|
size: [f32; 2],
|
||||||
|
pad: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, align(256))]
|
||||||
|
#[derive(Clone, Copy, Zeroable)]
|
||||||
|
struct Locals {
|
||||||
|
position: [f32; 2],
|
||||||
|
velocity: [f32; 2],
|
||||||
|
color: u32,
|
||||||
|
_pad: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Example struct holds references to wgpu resources and frame persistent data
|
||||||
|
struct Example {
|
||||||
|
global_group: wgpu::BindGroup,
|
||||||
|
local_group: wgpu::BindGroup,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bunnies: Vec<Locals>,
|
||||||
|
local_buffer: wgpu::Buffer,
|
||||||
|
extent: [u32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags: wgpu::ShaderFlags::all(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let global_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(mem::size_of::<Globals>() as _),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler {
|
||||||
|
filtering: true,
|
||||||
|
comparison: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let local_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(mem::size_of::<Locals>() as _),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[&global_bind_group_layout, &local_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: sc_desc.format,
|
||||||
|
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||||
|
write_mask: wgpu::ColorWrite::default(),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||||
|
..wgpu::PrimitiveState::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let texture = {
|
||||||
|
let img_data = include_bytes!("../../logo.png");
|
||||||
|
let decoder = png::Decoder::new(std::io::Cursor::new(img_data));
|
||||||
|
let (info, mut reader) = decoder.read_info().unwrap();
|
||||||
|
let mut buf = vec![0; info.buffer_size()];
|
||||||
|
reader.next_frame(&mut buf).unwrap();
|
||||||
|
|
||||||
|
let size = wgpu::Extent3d {
|
||||||
|
width: info.width,
|
||||||
|
height: info.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: None,
|
||||||
|
size,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED,
|
||||||
|
});
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
&buf,
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: std::num::NonZeroU32::new(info.width * 4),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
size,
|
||||||
|
);
|
||||||
|
texture
|
||||||
|
};
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: None,
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let globals = Globals {
|
||||||
|
mvp: cgmath::ortho(
|
||||||
|
0.0,
|
||||||
|
sc_desc.width as f32,
|
||||||
|
0.0,
|
||||||
|
sc_desc.height as f32,
|
||||||
|
-1.0,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
size: [BUNNY_SIZE; 2],
|
||||||
|
pad: [0.0; 2],
|
||||||
|
};
|
||||||
|
let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("global"),
|
||||||
|
contents: bytemuck::bytes_of(&globals),
|
||||||
|
usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::UNIFORM,
|
||||||
|
});
|
||||||
|
let local_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("local"),
|
||||||
|
size: (MAX_BUNNIES as wgpu::BufferAddress) * wgpu::BIND_BUFFER_ALIGNMENT,
|
||||||
|
usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::UNIFORM,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &global_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: global_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let local_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &local_bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &local_buffer,
|
||||||
|
offset: 0,
|
||||||
|
size: wgpu::BufferSize::new(mem::size_of::<Locals>() as _),
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Example {
|
||||||
|
pipeline,
|
||||||
|
global_group,
|
||||||
|
local_group,
|
||||||
|
bunnies: Vec::new(),
|
||||||
|
local_buffer,
|
||||||
|
extent: [sc_desc.width, sc_desc.height],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, event: winit::event::WindowEvent) {
|
||||||
|
if let winit::event::WindowEvent::KeyboardInput {
|
||||||
|
input:
|
||||||
|
winit::event::KeyboardInput {
|
||||||
|
virtual_keycode: Some(winit::event::VirtualKeyCode::Space),
|
||||||
|
state: winit::event::ElementState::Pressed,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
let spawn_count = 64 + self.bunnies.len() / 2;
|
||||||
|
let color = rand::random::<u32>();
|
||||||
|
println!(
|
||||||
|
"Spawning {} bunnies, total at {}",
|
||||||
|
spawn_count,
|
||||||
|
self.bunnies.len() + spawn_count
|
||||||
|
);
|
||||||
|
for _ in 0..spawn_count {
|
||||||
|
let speed = rand::random::<f32>() * MAX_VELOCITY - (MAX_VELOCITY * 0.5);
|
||||||
|
self.bunnies.push(Locals {
|
||||||
|
position: [0.0, 0.5 * (self.extent[1] as f32)],
|
||||||
|
velocity: [speed, 0.0],
|
||||||
|
color,
|
||||||
|
_pad: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
_sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
let delta = 0.01;
|
||||||
|
for bunny in self.bunnies.iter_mut() {
|
||||||
|
bunny.position[0] += bunny.velocity[0] * delta;
|
||||||
|
bunny.position[1] += bunny.velocity[1] * delta;
|
||||||
|
bunny.velocity[1] += GRAVITY * delta;
|
||||||
|
if (bunny.velocity[0] > 0.0
|
||||||
|
&& bunny.position[0] + 0.5 * BUNNY_SIZE > self.extent[0] as f32)
|
||||||
|
|| (bunny.velocity[0] < 0.0 && bunny.position[0] - 0.5 * BUNNY_SIZE < 0.0)
|
||||||
|
{
|
||||||
|
bunny.velocity[0] *= -1.0;
|
||||||
|
}
|
||||||
|
if bunny.velocity[1] < 0.0 && bunny.position[1] < 0.5 * BUNNY_SIZE {
|
||||||
|
bunny.velocity[1] *= -1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.write_buffer(&self.local_buffer, 0, unsafe {
|
||||||
|
std::slice::from_raw_parts(
|
||||||
|
self.bunnies.as_ptr() as *const u8,
|
||||||
|
self.bunnies.len() * wgpu::BIND_BUFFER_ALIGNMENT as usize,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
|
||||||
|
{
|
||||||
|
let clear_color = wgpu::Color {
|
||||||
|
r: 0.1,
|
||||||
|
g: 0.2,
|
||||||
|
b: 0.3,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(clear_color),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(&self.pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.global_group, &[]);
|
||||||
|
for i in 0..self.bunnies.len() {
|
||||||
|
let offset = (i as wgpu::DynamicOffset)
|
||||||
|
* (wgpu::BIND_BUFFER_ALIGNMENT as wgpu::DynamicOffset);
|
||||||
|
rpass.set_bind_group(1, &self.local_group, &[offset]);
|
||||||
|
rpass.draw(0..4, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("bunnymark");
|
||||||
|
}
|
43
wgpu/examples/bunnymark/shader.wgsl
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
[[block]]
|
||||||
|
struct Globals {
|
||||||
|
mvp: mat4x4<f32>;
|
||||||
|
size: vec2<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Locals {
|
||||||
|
position: vec2<f32>;
|
||||||
|
velocity: vec2<f32>;
|
||||||
|
color: u32;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var<uniform> globals: Globals;
|
||||||
|
|
||||||
|
[[group(1), binding(0)]]
|
||||||
|
var<uniform> locals: Locals;
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] tex_coords: vec2<f32>;
|
||||||
|
[[location(1)]] color: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main([[builtin(vertex_index)]] vi: u32) -> VertexOutput {
|
||||||
|
let tc = vec2<f32>(f32(vi & 1u), 0.5 * f32(vi & 2u));
|
||||||
|
let offset = vec2<f32>(tc.x * globals.size.x, tc.y * globals.size.y);
|
||||||
|
let pos = globals.mvp * vec4<f32>(locals.position + offset, 0.0, 1.0);
|
||||||
|
let color = vec4<f32>((vec4<u32>(locals.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
|
||||||
|
return VertexOutput(pos, tc, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var texture: texture_2d<f32>;
|
||||||
|
[[group(0), binding(2)]]
|
||||||
|
var sampler: sampler;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
return in.color * textureSampleLevel(texture, sampler, in.tex_coords, 0.0);
|
||||||
|
}
|
18
wgpu/examples/capture/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# capture
|
||||||
|
|
||||||
|
This example shows how to capture an image by rendering it to a texture, copying the texture to
|
||||||
|
a buffer, and retrieving it from the buffer.
|
||||||
|
|
||||||
|
This could be used for "taking a screenshot," with the added benefit that this method doesn't
|
||||||
|
require a window to be created.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example capture
|
||||||
|
open examples/capture/red.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Capture example](./screenshot.png)
|
248
wgpu/examples/capture/main.rs
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
use std::env;
|
||||||
|
/// This example shows how to capture an image by rendering it to a texture, copying the texture to
|
||||||
|
/// a buffer, and retrieving it from the buffer. This could be used for "taking a screenshot," with
|
||||||
|
/// the added benefit that this method doesn't require a window to be created.
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::mem::size_of;
|
||||||
|
use wgpu::{Buffer, Device};
|
||||||
|
|
||||||
|
async fn run(png_output_path: &str) {
|
||||||
|
let args: Vec<_> = env::args().collect();
|
||||||
|
let (width, height) = match args.len() {
|
||||||
|
// 0 on wasm, 1 on desktop
|
||||||
|
0 | 1 => (100usize, 200usize),
|
||||||
|
3 => (args[1].parse().unwrap(), args[2].parse().unwrap()),
|
||||||
|
_ => {
|
||||||
|
println!("Incorrect number of arguments, possible usages:");
|
||||||
|
println!("* 0 arguments - uses default width and height of (100, 200)");
|
||||||
|
println!("* 2 arguments - uses specified width and height values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (device, buffer, buffer_dimensions) = create_red_image_with_dimensions(width, height).await;
|
||||||
|
create_png(png_output_path, device, buffer, &buffer_dimensions).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_red_image_with_dimensions(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
) -> (Device, Buffer, BufferDimensions) {
|
||||||
|
let adapter = wgpu::Instance::new(wgpu::BackendBit::PRIMARY)
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: wgpu::Limits::default(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// It is a WebGPU requirement that ImageCopyBuffer.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
|
||||||
|
// So we calculate padded_bytes_per_row by rounding unpadded_bytes_per_row
|
||||||
|
// up to the next multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
|
||||||
|
// https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding
|
||||||
|
let buffer_dimensions = BufferDimensions::new(width, height);
|
||||||
|
// The output buffer lets us retrieve the data as an array
|
||||||
|
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
|
||||||
|
usage: wgpu::BufferUsage::MAP_READ | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let texture_extent = wgpu::Extent3d {
|
||||||
|
width: buffer_dimensions.width as u32,
|
||||||
|
height: buffer_dimensions.height as u32,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The render pipeline renders data into this texture
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: texture_extent,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::COPY_SRC,
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the background to be red
|
||||||
|
let command_buffer = {
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &texture.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::RED),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy the data from the texture to the buffer
|
||||||
|
encoder.copy_texture_to_buffer(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
wgpu::ImageCopyBuffer {
|
||||||
|
buffer: &output_buffer,
|
||||||
|
layout: wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(
|
||||||
|
std::num::NonZeroU32::new(buffer_dimensions.padded_bytes_per_row as u32)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
texture_extent,
|
||||||
|
);
|
||||||
|
|
||||||
|
encoder.finish()
|
||||||
|
};
|
||||||
|
|
||||||
|
queue.submit(Some(command_buffer));
|
||||||
|
(device, output_buffer, buffer_dimensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_png(
|
||||||
|
png_output_path: &str,
|
||||||
|
device: Device,
|
||||||
|
output_buffer: Buffer,
|
||||||
|
buffer_dimensions: &BufferDimensions,
|
||||||
|
) {
|
||||||
|
// Note that we're not calling `.await` here.
|
||||||
|
let buffer_slice = output_buffer.slice(..);
|
||||||
|
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
|
||||||
|
|
||||||
|
// Poll the device in a blocking manner so that our future resolves.
|
||||||
|
// In an actual application, `device.poll(...)` should
|
||||||
|
// be called in an event loop or on another thread.
|
||||||
|
device.poll(wgpu::Maintain::Wait);
|
||||||
|
// If a file system is available, write the buffer as a PNG
|
||||||
|
let has_file_system_available = cfg!(not(target_arch = "wasm32"));
|
||||||
|
if !has_file_system_available {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(()) = buffer_future.await {
|
||||||
|
let padded_buffer = buffer_slice.get_mapped_range();
|
||||||
|
|
||||||
|
let mut png_encoder = png::Encoder::new(
|
||||||
|
File::create(png_output_path).unwrap(),
|
||||||
|
buffer_dimensions.width as u32,
|
||||||
|
buffer_dimensions.height as u32,
|
||||||
|
);
|
||||||
|
png_encoder.set_depth(png::BitDepth::Eight);
|
||||||
|
png_encoder.set_color(png::ColorType::RGBA);
|
||||||
|
let mut png_writer = png_encoder
|
||||||
|
.write_header()
|
||||||
|
.unwrap()
|
||||||
|
.into_stream_writer_with_size(buffer_dimensions.unpadded_bytes_per_row);
|
||||||
|
|
||||||
|
// from the padded_buffer we write just the unpadded bytes into the image
|
||||||
|
for chunk in padded_buffer.chunks(buffer_dimensions.padded_bytes_per_row) {
|
||||||
|
png_writer
|
||||||
|
.write_all(&chunk[..buffer_dimensions.unpadded_bytes_per_row])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
png_writer.finish().unwrap();
|
||||||
|
|
||||||
|
// With the current interface, we have to make sure all mapped views are
|
||||||
|
// dropped before we unmap the buffer.
|
||||||
|
drop(padded_buffer);
|
||||||
|
|
||||||
|
output_buffer.unmap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BufferDimensions {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
unpadded_bytes_per_row: usize,
|
||||||
|
padded_bytes_per_row: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferDimensions {
|
||||||
|
fn new(width: usize, height: usize) -> Self {
|
||||||
|
let bytes_per_pixel = size_of::<u32>();
|
||||||
|
let unpadded_bytes_per_row = width * bytes_per_pixel;
|
||||||
|
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
|
||||||
|
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
|
||||||
|
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
unpadded_bytes_per_row,
|
||||||
|
padded_bytes_per_row,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
env_logger::init();
|
||||||
|
pollster::block_on(run("red.png"));
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
console_log::init().expect("could not initialize logger");
|
||||||
|
wasm_bindgen_futures::spawn_local(run("red.png"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use wgpu::BufferView;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_generated_data_matches_expected() {
|
||||||
|
pollster::block_on(assert_generated_data_matches_expected());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assert_generated_data_matches_expected() {
|
||||||
|
let (device, output_buffer, dimensions) =
|
||||||
|
create_red_image_with_dimensions(100usize, 200usize).await;
|
||||||
|
let buffer_slice = output_buffer.slice(..);
|
||||||
|
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
|
||||||
|
device.poll(wgpu::Maintain::Wait);
|
||||||
|
buffer_future
|
||||||
|
.await
|
||||||
|
.expect("failed to map buffer slice for capture test");
|
||||||
|
let padded_buffer = buffer_slice.get_mapped_range();
|
||||||
|
let expected_buffer_size = dimensions.padded_bytes_per_row * dimensions.height;
|
||||||
|
assert_eq!(padded_buffer.len(), expected_buffer_size);
|
||||||
|
assert_that_content_is_all_red(&dimensions, padded_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_that_content_is_all_red(dimensions: &BufferDimensions, padded_buffer: BufferView) {
|
||||||
|
let red = [0xFFu8, 0, 0, 0xFFu8];
|
||||||
|
let single_rgba = 4;
|
||||||
|
padded_buffer
|
||||||
|
.chunks(dimensions.padded_bytes_per_row)
|
||||||
|
.map(|padded_buffer_row| &padded_buffer_row[..dimensions.unpadded_bytes_per_row])
|
||||||
|
.for_each(|unpadded_row| {
|
||||||
|
unpadded_row
|
||||||
|
.chunks(single_rgba)
|
||||||
|
.for_each(|chunk| assert_eq!(chunk, &red))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
BIN
wgpu/examples/capture/screenshot.png
Normal file
After Width: | Height: | Size: 565 B |
20
wgpu/examples/conservative-raster/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# conservative-raster
|
||||||
|
|
||||||
|
This example shows how to render with conservative rasterization (native extension with limited support).
|
||||||
|
|
||||||
|
When enabled, any pixel touched by a triangle primitive is rasterized.
|
||||||
|
This is useful for various advanced techniques, most prominently for implementing realtime voxelization.
|
||||||
|
|
||||||
|
The demonstration here is implemented by rendering a triangle to a low-resolution target and then upscaling it with nearest-neighbor filtering.
|
||||||
|
The outlines of the triangle are then rendered in the original solution, using the same vertex shader as the triangle.
|
||||||
|
Pixels only drawn with conservative rasterization enabled are depicted red.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example conservative-raster
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Conservative-raster window](./screenshot.png)
|
316
wgpu/examples/conservative-raster/main.rs
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
const RENDER_TARGET_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
low_res_target: wgpu::TextureView,
|
||||||
|
bind_group_upscale: wgpu::BindGroup,
|
||||||
|
|
||||||
|
pipeline_triangle_conservative: wgpu::RenderPipeline,
|
||||||
|
pipeline_triangle_regular: wgpu::RenderPipeline,
|
||||||
|
pipeline_upscale: wgpu::RenderPipeline,
|
||||||
|
pipeline_lines: Option<wgpu::RenderPipeline>,
|
||||||
|
bind_group_layout_upscale: wgpu::BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
fn create_low_res_target(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
bind_group_layout_upscale: &wgpu::BindGroupLayout,
|
||||||
|
) -> (wgpu::TextureView, wgpu::BindGroup) {
|
||||||
|
let texture_view = device
|
||||||
|
.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("Low Resolution Target"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: sc_desc.width / 16,
|
||||||
|
height: sc_desc.width / 16,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: RENDER_TARGET_FORMAT,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
})
|
||||||
|
.create_view(&Default::default());
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("Nearest Neighbor Sampler"),
|
||||||
|
mag_filter: wgpu::FilterMode::Nearest,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("upscale bind group"),
|
||||||
|
layout: &bind_group_layout_upscale,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
(texture_view, bind_group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn required_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::CONSERVATIVE_RASTERIZATION
|
||||||
|
}
|
||||||
|
fn optional_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::NON_FILL_POLYGON_MODE
|
||||||
|
}
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
let pipeline_layout_empty =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let shader_triangle_and_lines =
|
||||||
|
device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
|
||||||
|
"triangle_and_lines.wgsl"
|
||||||
|
))),
|
||||||
|
flags: wgpu::ShaderFlags::all(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_triangle_conservative =
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Conservative Rasterization"),
|
||||||
|
layout: Some(&pipeline_layout_empty),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader_triangle_and_lines,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader_triangle_and_lines,
|
||||||
|
entry_point: "fs_main_red",
|
||||||
|
targets: &[RENDER_TARGET_FORMAT.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
conservative: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_triangle_regular =
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Regular Rasterization"),
|
||||||
|
layout: Some(&pipeline_layout_empty),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader_triangle_and_lines,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader_triangle_and_lines,
|
||||||
|
entry_point: "fs_main_blue",
|
||||||
|
targets: &[RENDER_TARGET_FORMAT.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_lines = if adapter
|
||||||
|
.features()
|
||||||
|
.contains(wgpu::Features::NON_FILL_POLYGON_MODE)
|
||||||
|
{
|
||||||
|
Some(
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Lines"),
|
||||||
|
layout: Some(&pipeline_layout_empty),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader_triangle_and_lines,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader_triangle_and_lines,
|
||||||
|
entry_point: "fs_main_white",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
polygon_mode: wgpu::PolygonMode::Line,
|
||||||
|
topology: wgpu::PrimitiveTopology::LineStrip,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (pipeline_upscale, bind_group_layout_upscale) = {
|
||||||
|
let bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("upscale bindgroup"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler {
|
||||||
|
filtering: false,
|
||||||
|
comparison: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("upscale.wgsl"))),
|
||||||
|
flags: wgpu::ShaderFlags::all(),
|
||||||
|
});
|
||||||
|
(
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Upscale"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
}),
|
||||||
|
bind_group_layout,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (low_res_target, bind_group_upscale) =
|
||||||
|
Self::create_low_res_target(sc_desc, device, &bind_group_layout_upscale);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
low_res_target,
|
||||||
|
bind_group_upscale,
|
||||||
|
|
||||||
|
pipeline_triangle_conservative,
|
||||||
|
pipeline_triangle_regular,
|
||||||
|
pipeline_upscale,
|
||||||
|
pipeline_lines,
|
||||||
|
bind_group_layout_upscale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
let (low_res_target, bind_group_upscale) =
|
||||||
|
Self::create_low_res_target(sc_desc, device, &self.bind_group_layout_upscale);
|
||||||
|
self.low_res_target = low_res_target;
|
||||||
|
self.bind_group_upscale = bind_group_upscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("primary"),
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("low resolution"),
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &self.low_res_target,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.pipeline_triangle_conservative);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
rpass.set_pipeline(&self.pipeline_triangle_regular);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("full resolution"),
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.pipeline_upscale);
|
||||||
|
rpass.set_bind_group(0, &self.bind_group_upscale, &[]);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
|
||||||
|
if let Some(pipeline_lines) = &self.pipeline_lines {
|
||||||
|
rpass.set_pipeline(pipeline_lines);
|
||||||
|
rpass.draw(0..4, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("conservative-raster");
|
||||||
|
}
|
BIN
wgpu/examples/conservative-raster/screenshot.png
Normal file
After Width: | Height: | Size: 19 KiB |
22
wgpu/examples/conservative-raster/triangle_and_lines.wgsl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> [[builtin(position)]] vec4<f32> {
|
||||||
|
let i: i32 = i32(vertex_index % 3u);
|
||||||
|
let x: f32 = f32(i - 1) * 0.75;
|
||||||
|
let y: f32 = f32((i & 1) * 2 - 1) * 0.75 + x * 0.2 + 0.1;
|
||||||
|
return vec4<f32>(x, y, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main_red() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main_blue() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.13, 0.31, 0.85, 1.0); // cornflower blue in linear space
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main_white() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
24
wgpu/examples/conservative-raster/upscale.wgsl
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] tex_coords: vec2<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput {
|
||||||
|
let x: f32 = f32(i32(vertex_index & 1u) << 2u) - 1.0;
|
||||||
|
let y: f32 = f32(i32(vertex_index & 2u) << 1u) - 1.0;
|
||||||
|
var output: VertexOutput;
|
||||||
|
output.position = vec4<f32>(x, -y, 0.0, 1.0);
|
||||||
|
output.tex_coords = vec2<f32>(x + 1.0, y + 1.0) * 0.5;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var r_color: texture_2d<f32>;
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var r_sampler: sampler;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
return textureSample(r_color, r_sampler, in.tex_coords);
|
||||||
|
}
|
13
wgpu/examples/cube/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# cube
|
||||||
|
|
||||||
|
This example renders a textured cube.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example cube
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Cube example](./screenshot.png)
|
393
wgpu/examples/cube/main.rs
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::{borrow::Cow, mem};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct Vertex {
|
||||||
|
_pos: [f32; 4],
|
||||||
|
_tex_coord: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex {
|
||||||
|
Vertex {
|
||||||
|
_pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0],
|
||||||
|
_tex_coord: [tc[0] as f32, tc[1] as f32],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vertices() -> (Vec<Vertex>, Vec<u16>) {
|
||||||
|
let vertex_data = [
|
||||||
|
// top (0, 0, 1)
|
||||||
|
vertex([-1, -1, 1], [0, 0]),
|
||||||
|
vertex([1, -1, 1], [1, 0]),
|
||||||
|
vertex([1, 1, 1], [1, 1]),
|
||||||
|
vertex([-1, 1, 1], [0, 1]),
|
||||||
|
// bottom (0, 0, -1)
|
||||||
|
vertex([-1, 1, -1], [1, 0]),
|
||||||
|
vertex([1, 1, -1], [0, 0]),
|
||||||
|
vertex([1, -1, -1], [0, 1]),
|
||||||
|
vertex([-1, -1, -1], [1, 1]),
|
||||||
|
// right (1, 0, 0)
|
||||||
|
vertex([1, -1, -1], [0, 0]),
|
||||||
|
vertex([1, 1, -1], [1, 0]),
|
||||||
|
vertex([1, 1, 1], [1, 1]),
|
||||||
|
vertex([1, -1, 1], [0, 1]),
|
||||||
|
// left (-1, 0, 0)
|
||||||
|
vertex([-1, -1, 1], [1, 0]),
|
||||||
|
vertex([-1, 1, 1], [0, 0]),
|
||||||
|
vertex([-1, 1, -1], [0, 1]),
|
||||||
|
vertex([-1, -1, -1], [1, 1]),
|
||||||
|
// front (0, 1, 0)
|
||||||
|
vertex([1, 1, -1], [1, 0]),
|
||||||
|
vertex([-1, 1, -1], [0, 0]),
|
||||||
|
vertex([-1, 1, 1], [0, 1]),
|
||||||
|
vertex([1, 1, 1], [1, 1]),
|
||||||
|
// back (0, -1, 0)
|
||||||
|
vertex([1, -1, 1], [0, 0]),
|
||||||
|
vertex([-1, -1, 1], [1, 0]),
|
||||||
|
vertex([-1, -1, -1], [1, 1]),
|
||||||
|
vertex([1, -1, -1], [0, 1]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let index_data: &[u16] = &[
|
||||||
|
0, 1, 2, 2, 3, 0, // top
|
||||||
|
4, 5, 6, 6, 7, 4, // bottom
|
||||||
|
8, 9, 10, 10, 11, 8, // right
|
||||||
|
12, 13, 14, 14, 15, 12, // left
|
||||||
|
16, 17, 18, 18, 19, 16, // front
|
||||||
|
20, 21, 22, 22, 23, 20, // back
|
||||||
|
];
|
||||||
|
|
||||||
|
(vertex_data.to_vec(), index_data.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_texels(size: usize) -> Vec<u8> {
|
||||||
|
(0..size * size)
|
||||||
|
.map(|id| {
|
||||||
|
// get high five for recognizing this ;)
|
||||||
|
let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
|
||||||
|
let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
|
||||||
|
let (mut x, mut y, mut count) = (cx, cy, 0);
|
||||||
|
while count < 0xFF && x * x + y * y < 4.0 {
|
||||||
|
let old_x = x;
|
||||||
|
x = x * x - y * y + cx;
|
||||||
|
y = 2.0 * old_x * y + cy;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
vertex_buf: wgpu::Buffer,
|
||||||
|
index_buf: wgpu::Buffer,
|
||||||
|
index_count: usize,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
uniform_buf: wgpu::Buffer,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
pipeline_wire: Option<wgpu::RenderPipeline>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4<f32> {
|
||||||
|
let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 10.0);
|
||||||
|
let mx_view = cgmath::Matrix4::look_at_rh(
|
||||||
|
cgmath::Point3::new(1.5f32, -5.0, 3.0),
|
||||||
|
cgmath::Point3::new(0f32, 0.0, 0.0),
|
||||||
|
cgmath::Vector3::unit_z(),
|
||||||
|
);
|
||||||
|
let mx_correction = framework::OPENGL_TO_WGPU_MATRIX;
|
||||||
|
mx_correction * mx_projection * mx_view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn optional_features() -> wgt::Features {
|
||||||
|
wgt::Features::NON_FILL_POLYGON_MODE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
// Create the vertex and index buffers
|
||||||
|
let vertex_size = mem::size_of::<Vertex>();
|
||||||
|
let (vertex_data, index_data) = create_vertices();
|
||||||
|
|
||||||
|
let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Vertex Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&vertex_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Index Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&index_data),
|
||||||
|
usage: wgpu::BufferUsage::INDEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create pipeline layout
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(64),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
sample_type: wgpu::TextureSampleType::Uint,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the texture
|
||||||
|
let size = 256u32;
|
||||||
|
let texels = create_texels(size as usize);
|
||||||
|
let texture_extent = wgpu::Extent3d {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: texture_extent,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::R8Uint,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
&texels,
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(std::num::NonZeroU32::new(size).unwrap()),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
texture_extent,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create other resources
|
||||||
|
let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32);
|
||||||
|
let mx_ref: &[f32; 16] = mx_total.as_ref();
|
||||||
|
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Uniform Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(mx_ref),
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Metal | wgpu::Backend::Vulkan | wgpu::Backend::Gl => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION
|
||||||
|
}
|
||||||
|
_ => (), //TODO
|
||||||
|
}
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
let vertex_buffers = [wgpu::VertexBufferLayout {
|
||||||
|
array_stride: vertex_size as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &[
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 0,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
offset: 4 * 4,
|
||||||
|
shader_location: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &vertex_buffers,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_wire = if device
|
||||||
|
.features()
|
||||||
|
.contains(wgt::Features::NON_FILL_POLYGON_MODE)
|
||||||
|
{
|
||||||
|
let pipeline_wire = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &vertex_buffers,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_wire",
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: sc_desc.format,
|
||||||
|
blend: Some(wgpu::BlendState {
|
||||||
|
color: wgpu::BlendComponent {
|
||||||
|
operation: wgpu::BlendOperation::Add,
|
||||||
|
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||||
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||||
|
},
|
||||||
|
alpha: wgpu::BlendComponent::REPLACE,
|
||||||
|
}),
|
||||||
|
write_mask: wgpu::ColorWrite::ALL,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
polygon_mode: wgpu::PolygonMode::Line,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
Some(pipeline_wire)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Done
|
||||||
|
Example {
|
||||||
|
vertex_buf,
|
||||||
|
index_buf,
|
||||||
|
index_count: index_data.len(),
|
||||||
|
bind_group,
|
||||||
|
uniform_buf,
|
||||||
|
pipeline,
|
||||||
|
pipeline_wire,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32);
|
||||||
|
let mx_ref: &[f32; 16] = mx_total.as_ref();
|
||||||
|
queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
r: 0.1,
|
||||||
|
g: 0.2,
|
||||||
|
b: 0.3,
|
||||||
|
a: 1.0,
|
||||||
|
}),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
rpass.push_debug_group("Prepare data for draw.");
|
||||||
|
rpass.set_pipeline(&self.pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16);
|
||||||
|
rpass.set_vertex_buffer(0, self.vertex_buf.slice(..));
|
||||||
|
rpass.pop_debug_group();
|
||||||
|
rpass.insert_debug_marker("Draw!");
|
||||||
|
rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
|
||||||
|
if let Some(ref pipe) = self.pipeline_wire {
|
||||||
|
rpass.set_pipeline(pipe);
|
||||||
|
rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("cube");
|
||||||
|
}
|
BIN
wgpu/examples/cube/screenshot.png
Normal file
After Width: | Height: | Size: 393 KiB |
37
wgpu/examples/cube/shader.wgsl
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
[[location(0)]] tex_coord: vec2<f32>;
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Locals {
|
||||||
|
transform: mat4x4<f32>;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var r_locals: Locals;
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main(
|
||||||
|
[[location(0)]] position: vec4<f32>,
|
||||||
|
[[location(1)]] tex_coord: vec2<f32>,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.tex_coord = tex_coord;
|
||||||
|
out.position = r_locals.transform * position;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var r_color: texture_2d<u32>;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
let tex = textureLoad(r_color, vec2<i32>(in.tex_coord * 256.0), 0);
|
||||||
|
let v = f32(tex.x) / 255.0;
|
||||||
|
return vec4<f32>(1.0 - (v * 5.0), 1.0 - (v * 15.0), 1.0 - (v * 50.0), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_wire() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 0.5, 0.0, 0.5);
|
||||||
|
}
|
370
wgpu/examples/framework.rs
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
use std::future::Future;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use winit::{
|
||||||
|
event::{self, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
||||||
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.5, 0.0,
|
||||||
|
0.0, 0.0, 0.5, 1.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn cast_slice<T>(data: &[T]) -> &[u8] {
|
||||||
|
use std::{mem::size_of, slice::from_raw_parts};
|
||||||
|
|
||||||
|
unsafe { from_raw_parts(data.as_ptr() as *const u8, data.len() * size_of::<T>()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum ShaderStage {
|
||||||
|
Vertex,
|
||||||
|
Fragment,
|
||||||
|
Compute,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Example: 'static + Sized {
|
||||||
|
fn optional_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::empty()
|
||||||
|
}
|
||||||
|
fn required_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::empty()
|
||||||
|
}
|
||||||
|
fn required_limits() -> wgpu::Limits {
|
||||||
|
wgpu::Limits::default()
|
||||||
|
}
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self;
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
);
|
||||||
|
fn update(&mut self, event: WindowEvent);
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
spawner: &Spawner,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Setup {
|
||||||
|
window: winit::window::Window,
|
||||||
|
event_loop: EventLoop<()>,
|
||||||
|
instance: wgpu::Instance,
|
||||||
|
size: winit::dpi::PhysicalSize<u32>,
|
||||||
|
surface: wgpu::Surface,
|
||||||
|
adapter: wgpu::Adapter,
|
||||||
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup<E: Example>(title: &str) -> Setup {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
env_logger::init();
|
||||||
|
};
|
||||||
|
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let mut builder = winit::window::WindowBuilder::new();
|
||||||
|
builder = builder.with_title(title);
|
||||||
|
#[cfg(windows_OFF)] // TODO
|
||||||
|
{
|
||||||
|
use winit::platform::windows::WindowBuilderExtWindows;
|
||||||
|
builder = builder.with_no_redirection_bitmap(true);
|
||||||
|
}
|
||||||
|
let window = builder.build(&event_loop).unwrap();
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
console_log::init().expect("could not initialize logger");
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
// On wasm, append the canvas to the document body
|
||||||
|
web_sys::window()
|
||||||
|
.and_then(|win| win.document())
|
||||||
|
.and_then(|doc| doc.body())
|
||||||
|
.and_then(|body| {
|
||||||
|
body.append_child(&web_sys::Element::from(window.canvas()))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.expect("couldn't append canvas to document body");
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Initializing the surface...");
|
||||||
|
|
||||||
|
let backend = if let Ok(backend) = std::env::var("WGPU_BACKEND") {
|
||||||
|
match backend.to_lowercase().as_str() {
|
||||||
|
"vulkan" => wgpu::BackendBit::VULKAN,
|
||||||
|
"metal" => wgpu::BackendBit::METAL,
|
||||||
|
"dx12" => wgpu::BackendBit::DX12,
|
||||||
|
"dx11" => wgpu::BackendBit::DX11,
|
||||||
|
"gl" => wgpu::BackendBit::GL,
|
||||||
|
"webgpu" => wgpu::BackendBit::BROWSER_WEBGPU,
|
||||||
|
other => panic!("Unknown backend: {}", other),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wgpu::BackendBit::PRIMARY
|
||||||
|
};
|
||||||
|
let power_preference = if let Ok(power_preference) = std::env::var("WGPU_POWER_PREF") {
|
||||||
|
match power_preference.to_lowercase().as_str() {
|
||||||
|
"low" => wgpu::PowerPreference::LowPower,
|
||||||
|
"high" => wgpu::PowerPreference::HighPerformance,
|
||||||
|
other => panic!("Unknown power preference: {}", other),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wgpu::PowerPreference::default()
|
||||||
|
};
|
||||||
|
let instance = wgpu::Instance::new(backend);
|
||||||
|
let (size, surface) = unsafe {
|
||||||
|
let size = window.inner_size();
|
||||||
|
let surface = instance.create_surface(&window);
|
||||||
|
(size, surface)
|
||||||
|
};
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference,
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("No suitable GPU adapters found on the system!");
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
let adapter_info = adapter.get_info();
|
||||||
|
println!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
|
||||||
|
}
|
||||||
|
|
||||||
|
let optional_features = E::optional_features();
|
||||||
|
let required_features = E::required_features();
|
||||||
|
let adapter_features = adapter.features();
|
||||||
|
assert!(
|
||||||
|
adapter_features.contains(required_features),
|
||||||
|
"Adapter does not support required features for this example: {:?}",
|
||||||
|
required_features - adapter_features
|
||||||
|
);
|
||||||
|
|
||||||
|
let needed_limits = E::required_limits();
|
||||||
|
|
||||||
|
let trace_dir = std::env::var("WGPU_TRACE");
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: (optional_features & adapter_features) | required_features,
|
||||||
|
limits: needed_limits,
|
||||||
|
},
|
||||||
|
trace_dir.ok().as_ref().map(std::path::Path::new),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Unable to find a suitable GPU adapter!");
|
||||||
|
|
||||||
|
Setup {
|
||||||
|
window,
|
||||||
|
event_loop,
|
||||||
|
instance,
|
||||||
|
size,
|
||||||
|
surface,
|
||||||
|
adapter,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start<E: Example>(
|
||||||
|
Setup {
|
||||||
|
window,
|
||||||
|
event_loop,
|
||||||
|
instance,
|
||||||
|
size,
|
||||||
|
surface,
|
||||||
|
adapter,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
}: Setup,
|
||||||
|
) {
|
||||||
|
let spawner = Spawner::new();
|
||||||
|
let mut sc_desc = wgpu::SwapChainDescriptor {
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
format: adapter.get_swap_chain_preferred_format(&surface).unwrap(),
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: wgpu::PresentMode::Mailbox,
|
||||||
|
};
|
||||||
|
let mut swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||||
|
|
||||||
|
log::info!("Initializing the example...");
|
||||||
|
let mut example = E::init(&sc_desc, &adapter, &device, &queue);
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let mut last_update_inst = Instant::now();
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let mut last_frame_inst = Instant::now();
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let (mut frame_count, mut accum_time) = (0, 0.0);
|
||||||
|
|
||||||
|
log::info!("Entering render loop...");
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
let _ = (&instance, &adapter); // force ownership by the closure
|
||||||
|
*control_flow = if cfg!(feature = "metal-auto-capture") {
|
||||||
|
ControlFlow::Exit
|
||||||
|
} else {
|
||||||
|
ControlFlow::Poll
|
||||||
|
};
|
||||||
|
match event {
|
||||||
|
event::Event::RedrawEventsCleared => {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
// Clamp to some max framerate to avoid busy-looping too much
|
||||||
|
// (we might be in wgpu::PresentMode::Mailbox, thus discarding superfluous frames)
|
||||||
|
//
|
||||||
|
// winit has window.current_monitor().video_modes() but that is a list of all full screen video modes.
|
||||||
|
// So without extra dependencies it's a bit tricky to get the max refresh rate we can run the window on.
|
||||||
|
// Therefore we just go with 60fps - sorry 120hz+ folks!
|
||||||
|
let target_frametime = Duration::from_secs_f64(1.0 / 60.0);
|
||||||
|
let time_since_last_frame = last_update_inst.elapsed();
|
||||||
|
if time_since_last_frame >= target_frametime {
|
||||||
|
window.request_redraw();
|
||||||
|
last_update_inst = Instant::now();
|
||||||
|
} else {
|
||||||
|
*control_flow = ControlFlow::WaitUntil(
|
||||||
|
Instant::now() + target_frametime - time_since_last_frame,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
spawner.run_until_stalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
event::Event::WindowEvent {
|
||||||
|
event: WindowEvent::Resized(size),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
log::info!("Resizing to {:?}", size);
|
||||||
|
sc_desc.width = size.width.max(1);
|
||||||
|
sc_desc.height = size.height.max(1);
|
||||||
|
example.resize(&sc_desc, &device, &queue);
|
||||||
|
swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||||
|
}
|
||||||
|
event::Event::WindowEvent { event, .. } => match event {
|
||||||
|
WindowEvent::KeyboardInput {
|
||||||
|
input:
|
||||||
|
event::KeyboardInput {
|
||||||
|
virtual_keycode: Some(event::VirtualKeyCode::Escape),
|
||||||
|
state: event::ElementState::Pressed,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| WindowEvent::CloseRequested => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
example.update(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event::Event::RedrawRequested(_) => {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
accum_time += last_frame_inst.elapsed().as_secs_f32();
|
||||||
|
last_frame_inst = Instant::now();
|
||||||
|
frame_count += 1;
|
||||||
|
if frame_count == 100 {
|
||||||
|
println!(
|
||||||
|
"Avg frame time {}ms",
|
||||||
|
accum_time * 1000.0 / frame_count as f32
|
||||||
|
);
|
||||||
|
accum_time = 0.0;
|
||||||
|
frame_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = match swap_chain.get_current_frame() {
|
||||||
|
Ok(frame) => frame,
|
||||||
|
Err(_) => {
|
||||||
|
swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||||
|
swap_chain
|
||||||
|
.get_current_frame()
|
||||||
|
.expect("Failed to acquire next swap chain texture!")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
example.render(&frame.output, &device, &queue, &spawner);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub struct Spawner<'a> {
|
||||||
|
executor: async_executor::LocalExecutor<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<'a> Spawner<'a> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
executor: async_executor::LocalExecutor::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn spawn_local(&self, future: impl Future<Output = ()> + 'a) {
|
||||||
|
self.executor.spawn(future).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_until_stalled(&self) {
|
||||||
|
while self.executor.try_tick() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub struct Spawner {}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
impl Spawner {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn spawn_local(&self, future: impl Future<Output = ()> + 'static) {
|
||||||
|
wasm_bindgen_futures::spawn_local(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn run<E: Example>(title: &str) {
|
||||||
|
let setup = pollster::block_on(setup::<E>(title));
|
||||||
|
start::<E>(setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub fn run<E: Example>(title: &str) {
|
||||||
|
let title = title.to_owned();
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
let setup = setup::<E>(&title).await;
|
||||||
|
start::<E>(setup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This allows treating the framework as a standalone example,
|
||||||
|
// thus avoiding listing the example names in `Cargo.toml`.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn main() {}
|
22
wgpu/examples/hello-compute/README.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# hello-compute
|
||||||
|
|
||||||
|
Runs a compute shader to determine the number of iterations of the rules from
|
||||||
|
Collatz Conjecture
|
||||||
|
|
||||||
|
- If n is even, n = n/2
|
||||||
|
- If n is odd, n = 3n+1
|
||||||
|
|
||||||
|
that it will take to finish and reach the number `1`.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
# Pass in any 4 numbers as arguments
|
||||||
|
RUST_LOG=hello_compute cargo run --example hello-compute 1 4 3 295
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
```
|
||||||
|
[2020-04-25T11:15:33Z INFO hello_compute] Times: [0, 2, 7, 55]
|
||||||
|
```
|
243
wgpu/examples/hello-compute/main.rs
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
use std::{borrow::Cow, convert::TryInto, str::FromStr};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
// Indicates a u32 overflow in an intermediate Collatz value
|
||||||
|
const OVERFLOW: u32 = 0xffffffff;
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
let numbers = if std::env::args().len() <= 1 {
|
||||||
|
let default = vec![1, 2, 3, 4];
|
||||||
|
println!("No numbers were provided, defaulting to {:?}", default);
|
||||||
|
default
|
||||||
|
} else {
|
||||||
|
std::env::args()
|
||||||
|
.skip(1)
|
||||||
|
.map(|s| u32::from_str(&s).expect("You must pass a list of positive integers!"))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let steps = execute_gpu(numbers).await;
|
||||||
|
|
||||||
|
let disp_steps: Vec<String> = steps
|
||||||
|
.iter()
|
||||||
|
.map(|&n| match n {
|
||||||
|
OVERFLOW => "OVERFLOW".to_string(),
|
||||||
|
_ => n.to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
println!("Steps: [{}]", disp_steps.join(", "));
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
log::info!("Steps: [{}]", disp_steps.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_gpu(numbers: Vec<u32>) -> Vec<u32> {
|
||||||
|
// Instantiates instance of WebGPU
|
||||||
|
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
|
||||||
|
|
||||||
|
// `request_adapter` instantiates the general connection to the GPU
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// `request_device` instantiates the feature specific connection to the GPU, defining some parameters,
|
||||||
|
// `features` being the available features.
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: wgpu::Limits::default(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Loads the shader from the SPIR-V file.arrayvec
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Vulkan | wgpu::Backend::Metal | wgpu::Backend::Gl => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let cs_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gets the size in bytes of the buffer.
|
||||||
|
let slice_size = numbers.len() * std::mem::size_of::<u32>();
|
||||||
|
let size = slice_size as wgpu::BufferAddress;
|
||||||
|
|
||||||
|
// Instantiates buffer without data.
|
||||||
|
// `usage` of buffer specifies how it can be used:
|
||||||
|
// `BufferUsage::MAP_READ` allows it to be read (outside the shader).
|
||||||
|
// `BufferUsage::COPY_DST` allows it to be the destination of the copy.
|
||||||
|
let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size,
|
||||||
|
usage: wgpu::BufferUsage::MAP_READ | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instantiates buffer with data (`numbers`).
|
||||||
|
// Usage allowing the buffer to be:
|
||||||
|
// A storage buffer (can be bound within a bind group and thus available to a shader).
|
||||||
|
// The destination of a copy.
|
||||||
|
// The source of a copy.
|
||||||
|
let storage_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Storage Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&numbers),
|
||||||
|
usage: wgpu::BufferUsage::STORAGE
|
||||||
|
| wgpu::BufferUsage::COPY_DST
|
||||||
|
| wgpu::BufferUsage::COPY_SRC,
|
||||||
|
});
|
||||||
|
|
||||||
|
// A bind group defines how buffers are accessed by shaders.
|
||||||
|
// It is to WebGPU what a descriptor set is to Vulkan.
|
||||||
|
// `binding` here refers to the `binding` of a buffer in the shader (`layout(set = 0, binding = 0) buffer`).
|
||||||
|
|
||||||
|
// A pipeline specifies the operation of a shader
|
||||||
|
|
||||||
|
// Instantiates the pipeline.
|
||||||
|
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: None,
|
||||||
|
module: &cs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instantiates the bind group, once again specifying the binding of buffers.
|
||||||
|
let bind_group_layout = compute_pipeline.get_bind_group_layout(0);
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: storage_buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
// A command encoder executes one or many pipelines.
|
||||||
|
// It is to WebGPU what a command buffer is to Vulkan.
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None });
|
||||||
|
cpass.set_pipeline(&compute_pipeline);
|
||||||
|
cpass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
cpass.insert_debug_marker("compute collatz iterations");
|
||||||
|
cpass.dispatch(numbers.len() as u32, 1, 1); // Number of cells to run, the (x,y,z) size of item being processed
|
||||||
|
}
|
||||||
|
// Sets adds copy operation to command encoder.
|
||||||
|
// Will copy data from storage buffer on GPU to staging buffer on CPU.
|
||||||
|
encoder.copy_buffer_to_buffer(&storage_buffer, 0, &staging_buffer, 0, size);
|
||||||
|
|
||||||
|
// Submits command encoder for processing
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
|
||||||
|
// Note that we're not calling `.await` here.
|
||||||
|
let buffer_slice = staging_buffer.slice(..);
|
||||||
|
// Gets the future representing when `staging_buffer` can be read from
|
||||||
|
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
|
||||||
|
|
||||||
|
// Poll the device in a blocking manner so that our future resolves.
|
||||||
|
// In an actual application, `device.poll(...)` should
|
||||||
|
// be called in an event loop or on another thread.
|
||||||
|
device.poll(wgpu::Maintain::Wait);
|
||||||
|
|
||||||
|
// Awaits until `buffer_future` can be read from
|
||||||
|
if let Ok(()) = buffer_future.await {
|
||||||
|
// Gets contents of buffer
|
||||||
|
let data = buffer_slice.get_mapped_range();
|
||||||
|
// Since contents are got in bytes, this converts these bytes back to u32
|
||||||
|
let result = data
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|b| u32::from_ne_bytes(b.try_into().unwrap()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// With the current interface, we have to make sure all mapped views are
|
||||||
|
// dropped before we unmap the buffer.
|
||||||
|
drop(data);
|
||||||
|
staging_buffer.unmap(); // Unmaps buffer from memory
|
||||||
|
// If you are familiar with C++ these 2 lines can be thought of similarly to:
|
||||||
|
// delete myPointer;
|
||||||
|
// myPointer = NULL;
|
||||||
|
// It effectively frees the memory
|
||||||
|
|
||||||
|
// Returns data from buffer
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
panic!("failed to run compute on gpu!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
env_logger::init();
|
||||||
|
pollster::block_on(run());
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
console_log::init().expect("could not initialize logger");
|
||||||
|
wasm_bindgen_futures::spawn_local(run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compute_1() {
|
||||||
|
let input = vec![1, 2, 3, 4];
|
||||||
|
pollster::block_on(assert_execute_gpu(input, vec![0, 1, 7, 2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compute_2() {
|
||||||
|
let input = vec![5, 23, 10, 9];
|
||||||
|
pollster::block_on(assert_execute_gpu(input, vec![5, 15, 6, 19]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compute_overflow() {
|
||||||
|
let input = vec![77031, 837799, 8400511, 63728127];
|
||||||
|
pollster::block_on(assert_execute_gpu(
|
||||||
|
input,
|
||||||
|
vec![350, 524, OVERFLOW, OVERFLOW],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multithreaded_compute() {
|
||||||
|
use std::{sync::mpsc, thread, time::Duration};
|
||||||
|
|
||||||
|
let thread_count = 8;
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
for _ in 0..thread_count {
|
||||||
|
let tx = tx.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let input = vec![100, 100, 100];
|
||||||
|
pollster::block_on(assert_execute_gpu(input, vec![25, 25, 25]));
|
||||||
|
tx.send(true).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..thread_count {
|
||||||
|
rx.recv_timeout(Duration::from_secs(10))
|
||||||
|
.expect("A thread never completed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assert_execute_gpu(input: Vec<u32>, expected: Vec<u32>) {
|
||||||
|
assert_eq!(execute_gpu(input).await, expected);
|
||||||
|
}
|
||||||
|
}
|
41
wgpu/examples/hello-compute/shader.wgsl
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
[[block]]
|
||||||
|
struct PrimeIndices {
|
||||||
|
data: [[stride(4)]] array<u32>;
|
||||||
|
}; // this is used as both input and output for convenience
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var<storage> v_indices: [[access(read_write)]] PrimeIndices;
|
||||||
|
|
||||||
|
// The Collatz Conjecture states that for any integer n:
|
||||||
|
// If n is even, n = n/2
|
||||||
|
// If n is odd, n = 3n+1
|
||||||
|
// And repeat this process for each new n, you will always eventually reach 1.
|
||||||
|
// Though the conjecture has not been proven, no counterexample has ever been found.
|
||||||
|
// This function returns how many times this recurrence needs to be applied to reach 1.
|
||||||
|
fn collatz_iterations(n_base: u32) -> u32{
|
||||||
|
var n: u32 = n_base;
|
||||||
|
var i: u32 = 0u;
|
||||||
|
loop {
|
||||||
|
if (n <= 1u) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (n % 2u == 0u) {
|
||||||
|
n = n / 2u;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Overflow? (i.e. 3*n + 1 > 0xffffffffu?)
|
||||||
|
if (n >= 1431655765u) { // 0x55555555u
|
||||||
|
return 4294967295u; // 0xffffffffu
|
||||||
|
}
|
||||||
|
|
||||||
|
n = 3u * n + 1u;
|
||||||
|
}
|
||||||
|
i = i + 1u;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(compute), workgroup_size(1)]]
|
||||||
|
fn main([[builtin(global_invocation_id)]] global_id: vec3<u32>) {
|
||||||
|
v_indices.data[global_id.x] = collatz_iterations(v_indices.data[global_id.x]);
|
||||||
|
}
|
13
wgpu/examples/hello-triangle/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# hello-triangle
|
||||||
|
|
||||||
|
This example renders a triangle to a window.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example hello-triangle
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Triangle window](./screenshot.png)
|
154
wgpu/examples/hello-triangle/main.rs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use winit::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn run(event_loop: EventLoop<()>, window: Window) {
|
||||||
|
let size = window.inner_size();
|
||||||
|
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
|
||||||
|
let surface = unsafe { instance.create_surface(&window) };
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::default(),
|
||||||
|
// Request an adapter which can render to our surface
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to find an appropriate adapter");
|
||||||
|
|
||||||
|
// Create the logical device and command queue
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: wgpu::Limits::default(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create device");
|
||||||
|
|
||||||
|
// Load the shaders from disk
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags: wgpu::ShaderFlags::all(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let swapchain_format = adapter.get_swap_chain_preferred_format(&surface).unwrap();
|
||||||
|
|
||||||
|
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[swapchain_format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut sc_desc = wgpu::SwapChainDescriptor {
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
format: swapchain_format,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: wgpu::PresentMode::Mailbox,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
// Have the closure take ownership of the resources.
|
||||||
|
// `event_loop.run` never returns, therefore we must do this to ensure
|
||||||
|
// the resources are properly cleaned up.
|
||||||
|
let _ = (&instance, &adapter, &shader, &pipeline_layout);
|
||||||
|
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::Resized(size),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Recreate the swap chain with the new size
|
||||||
|
sc_desc.width = size.width;
|
||||||
|
sc_desc.height = size.height;
|
||||||
|
swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||||
|
}
|
||||||
|
Event::RedrawRequested(_) => {
|
||||||
|
let frame = swap_chain
|
||||||
|
.get_current_frame()
|
||||||
|
.expect("Failed to acquire next swap chain texture")
|
||||||
|
.output;
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(&render_pipeline);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => *control_flow = ControlFlow::Exit,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let window = winit::window::Window::new(&event_loop).unwrap();
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
env_logger::init();
|
||||||
|
// Temporarily avoid srgb formats for the swapchain on the web
|
||||||
|
pollster::block_on(run(event_loop, window));
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
console_log::init().expect("could not initialize logger");
|
||||||
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
// On wasm, append the canvas to the document body
|
||||||
|
web_sys::window()
|
||||||
|
.and_then(|win| win.document())
|
||||||
|
.and_then(|doc| doc.body())
|
||||||
|
.and_then(|body| {
|
||||||
|
body.append_child(&web_sys::Element::from(window.canvas()))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.expect("couldn't append canvas to document body");
|
||||||
|
wasm_bindgen_futures::spawn_local(run(event_loop, window));
|
||||||
|
}
|
||||||
|
}
|
BIN
wgpu/examples/hello-triangle/screenshot.png
Normal file
After Width: | Height: | Size: 25 KiB |
11
wgpu/examples/hello-triangle/shader.wgsl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> {
|
||||||
|
let x = f32(i32(in_vertex_index) - 1);
|
||||||
|
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
||||||
|
return vec4<f32>(x, y, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
13
wgpu/examples/hello-windows/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# hello-windows
|
||||||
|
|
||||||
|
This example renders a set of 16 windows, with a differently colored background
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example hello-windows
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![16 windows](./screenshot.png)
|
203
wgpu/examples/hello-windows/main.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use winit::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::{Window, WindowId},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ViewportDesc {
|
||||||
|
window: Window,
|
||||||
|
background: wgpu::Color,
|
||||||
|
surface: wgpu::Surface,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Viewport {
|
||||||
|
desc: ViewportDesc,
|
||||||
|
sc_desc: wgpu::SwapChainDescriptor,
|
||||||
|
swap_chain: wgpu::SwapChain,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewportDesc {
|
||||||
|
fn new(window: Window, background: wgpu::Color, instance: &wgpu::Instance) -> Self {
|
||||||
|
let surface = unsafe { instance.create_surface(&window) };
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
background,
|
||||||
|
surface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(self, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Viewport {
|
||||||
|
let size = self.window.inner_size();
|
||||||
|
|
||||||
|
let sc_desc = wgpu::SwapChainDescriptor {
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
format: adapter
|
||||||
|
.get_swap_chain_preferred_format(&self.surface)
|
||||||
|
.unwrap(),
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: wgpu::PresentMode::Fifo,
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_chain = device.create_swap_chain(&self.surface, &sc_desc);
|
||||||
|
|
||||||
|
Viewport {
|
||||||
|
desc: self,
|
||||||
|
sc_desc,
|
||||||
|
swap_chain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Viewport {
|
||||||
|
fn resize(&mut self, device: &wgpu::Device, size: winit::dpi::PhysicalSize<u32>) {
|
||||||
|
self.sc_desc.width = size.width;
|
||||||
|
self.sc_desc.height = size.height;
|
||||||
|
self.swap_chain = device.create_swap_chain(&self.desc.surface, &self.sc_desc);
|
||||||
|
}
|
||||||
|
fn get_current_frame(&mut self) -> wgpu::SwapChainTexture {
|
||||||
|
self.swap_chain
|
||||||
|
.get_current_frame()
|
||||||
|
.expect("Failed to acquire next swap chain texture")
|
||||||
|
.output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(event_loop: EventLoop<()>, viewports: Vec<(Window, wgpu::Color)>) {
|
||||||
|
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
|
||||||
|
let viewports: Vec<_> = viewports
|
||||||
|
.into_iter()
|
||||||
|
.map(|(window, color)| ViewportDesc::new(window, color, &instance))
|
||||||
|
.collect();
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::default(),
|
||||||
|
// Request an adapter which can render to our surface
|
||||||
|
compatible_surface: viewports.first().map(|desc| &desc.surface),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to find an appropriate adapter");
|
||||||
|
|
||||||
|
// Create the logical device and command queue
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: wgpu::Limits::default(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create device");
|
||||||
|
|
||||||
|
let mut viewports: HashMap<WindowId, Viewport> = viewports
|
||||||
|
.into_iter()
|
||||||
|
.map(|desc| (desc.window.id(), desc.build(&adapter, &device)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
// Have the closure take ownership of the resources.
|
||||||
|
// `event_loop.run` never returns, therefore we must do this to ensure
|
||||||
|
// the resources are properly cleaned up.
|
||||||
|
let _ = (&instance, &adapter);
|
||||||
|
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
window_id,
|
||||||
|
event: WindowEvent::Resized(size),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Recreate the swap chain with the new size
|
||||||
|
if let Some(viewport) = viewports.get_mut(&window_id) {
|
||||||
|
viewport.resize(&device, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::RedrawRequested(window_id) => {
|
||||||
|
if let Some(viewport) = viewports.get_mut(&window_id) {
|
||||||
|
let frame = viewport.get_current_frame();
|
||||||
|
let mut encoder = device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let _rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(viewport.desc.background),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::WindowEvent {
|
||||||
|
window_id,
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
viewports.remove(&window_id);
|
||||||
|
if viewports.is_empty() {
|
||||||
|
*control_flow = ControlFlow::Exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
const WINDOW_SIZE: u32 = 128;
|
||||||
|
const WINDOW_PADDING: u32 = 16;
|
||||||
|
const WINDOW_TITLEBAR: u32 = 32;
|
||||||
|
const WINDOW_OFFSET: u32 = WINDOW_SIZE + WINDOW_PADDING;
|
||||||
|
const ROWS: u32 = 4;
|
||||||
|
const COLUMNS: u32 = 4;
|
||||||
|
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let mut viewports = Vec::with_capacity((ROWS * COLUMNS) as usize);
|
||||||
|
for row in 0..ROWS {
|
||||||
|
for column in 0..COLUMNS {
|
||||||
|
let window = winit::window::WindowBuilder::new()
|
||||||
|
.with_title(format!("x{}y{}", column, row))
|
||||||
|
.with_inner_size(winit::dpi::PhysicalSize::new(WINDOW_SIZE, WINDOW_SIZE))
|
||||||
|
.build(&event_loop)
|
||||||
|
.unwrap();
|
||||||
|
window.set_outer_position(winit::dpi::PhysicalPosition::new(
|
||||||
|
WINDOW_PADDING + column * WINDOW_OFFSET,
|
||||||
|
WINDOW_PADDING + row * (WINDOW_OFFSET + WINDOW_TITLEBAR),
|
||||||
|
));
|
||||||
|
fn frac(index: u32, max: u32) -> f64 {
|
||||||
|
index as f64 / max as f64
|
||||||
|
}
|
||||||
|
viewports.push((
|
||||||
|
window,
|
||||||
|
wgpu::Color {
|
||||||
|
r: frac(row, ROWS),
|
||||||
|
g: 0.5 - frac(row * column, ROWS * COLUMNS) * 0.5,
|
||||||
|
b: frac(column, COLUMNS),
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env_logger::init();
|
||||||
|
// Temporarily avoid srgb formats for the swapchain on the web
|
||||||
|
pollster::block_on(run(event_loop, viewports));
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
panic!("wasm32 is not supported")
|
||||||
|
}
|
||||||
|
}
|
BIN
wgpu/examples/hello-windows/screenshot.png
Normal file
After Width: | Height: | Size: 14 KiB |
16
wgpu/examples/hello/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# hello
|
||||||
|
|
||||||
|
This example prints output describing the adapter in use.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example output
|
||||||
|
|
||||||
|
```
|
||||||
|
# You might see different output as it depends on your graphics card
|
||||||
|
AdapterInfo { name: "Intel(R) UHD Graphics 630", vendor: 0, device: 0, device_type: IntegratedGpu, backend: Metal }
|
||||||
|
```
|
25
wgpu/examples/hello/main.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/// This example shows how to describe the adapter in use.
|
||||||
|
async fn run() {
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
|
||||||
|
let adapter = wgpu::Instance::new(wgpu::BackendBit::PRIMARY)
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
println!("{:?}", adapter.get_info())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
env_logger::init();
|
||||||
|
pollster::block_on(run());
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
console_log::init().expect("could not initialize logger");
|
||||||
|
wasm_bindgen_futures::spawn_local(run());
|
||||||
|
}
|
||||||
|
}
|
13
wgpu/examples/mipmap/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# mipmap
|
||||||
|
|
||||||
|
This example shows how to generate and make use of mipmaps.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example mipmap
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Mip maps](./screenshot.png)
|
32
wgpu/examples/mipmap/blit.wgsl
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] tex_coords: vec2<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
let x = i32(vertex_index) / 2;
|
||||||
|
let y = i32(vertex_index) & 1;
|
||||||
|
let tc = vec2<f32>(
|
||||||
|
f32(x) * 2.0,
|
||||||
|
f32(y) * 2.0
|
||||||
|
);
|
||||||
|
out.position = vec4<f32>(
|
||||||
|
tc.x * 2.0 - 1.0,
|
||||||
|
1.0 - tc.y * 2.0,
|
||||||
|
0.0, 1.0
|
||||||
|
);
|
||||||
|
out.tex_coords = tc;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var r_color: texture_2d<f32>;
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var r_sampler: sampler;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
return textureSample(r_color, r_sampler, in.tex_coords);
|
||||||
|
}
|
33
wgpu/examples/mipmap/draw.wgsl
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] tex_coords: vec2<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Locals {
|
||||||
|
transform: mat4x4<f32>;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var r_data: Locals;
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput {
|
||||||
|
let pos = vec2<f32>(
|
||||||
|
100.0 * (1.0 - f32(vertex_index & 2u)),
|
||||||
|
1000.0 * f32(vertex_index & 1u)
|
||||||
|
);
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.tex_coords = 0.05 * pos + vec2<f32>(0.5, 0.5);
|
||||||
|
out.position = r_data.transform * vec4<f32>(pos, 0.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var r_color: texture_2d<f32>;
|
||||||
|
[[group(0), binding(2)]]
|
||||||
|
var r_sampler: sampler;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
return textureSample(r_color, r_sampler, in.tex_coords);
|
||||||
|
}
|
489
wgpu/examples/mipmap/main.rs
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::{borrow::Cow, mem, num::NonZeroU32};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||||
|
const MIP_LEVEL_COUNT: u32 = 9;
|
||||||
|
const MIP_PASS_COUNT: u32 = MIP_LEVEL_COUNT - 1;
|
||||||
|
|
||||||
|
fn create_texels(size: usize, cx: f32, cy: f32) -> Vec<u8> {
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
(0..size * size)
|
||||||
|
.flat_map(|id| {
|
||||||
|
// get high five for recognizing this ;)
|
||||||
|
let mut x = 4.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
|
||||||
|
let mut y = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
|
||||||
|
let mut count = 0;
|
||||||
|
while count < 0xFF && x * x + y * y < 4.0 {
|
||||||
|
let old_x = x;
|
||||||
|
x = x * x - y * y + cx;
|
||||||
|
y = 2.0 * old_x * y + cy;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
iter::once(0xFF - (count * 2) as u8)
|
||||||
|
.chain(iter::once(0xFF - (count * 5) as u8))
|
||||||
|
.chain(iter::once(0xFF - (count * 13) as u8))
|
||||||
|
.chain(iter::once(std::u8::MAX))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QuerySets {
|
||||||
|
timestamp: wgpu::QuerySet,
|
||||||
|
timestamp_period: f32,
|
||||||
|
pipeline_statistics: wgpu::QuerySet,
|
||||||
|
data_buffer: wgpu::Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct TimestampData {
|
||||||
|
start: u64,
|
||||||
|
end: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct QueryData {
|
||||||
|
timestamps: [TimestampData; MIP_PASS_COUNT as usize],
|
||||||
|
pipeline_queries: [u64; MIP_PASS_COUNT as usize],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
uniform_buf: wgpu::Buffer,
|
||||||
|
draw_pipeline: wgpu::RenderPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4<f32> {
|
||||||
|
let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 1000.0);
|
||||||
|
let mx_view = cgmath::Matrix4::look_at_rh(
|
||||||
|
cgmath::Point3::new(0f32, 0.0, 10.0),
|
||||||
|
cgmath::Point3::new(0f32, 50.0, 0.0),
|
||||||
|
cgmath::Vector3::unit_z(),
|
||||||
|
);
|
||||||
|
let mx_correction = framework::OPENGL_TO_WGPU_MATRIX;
|
||||||
|
mx_correction * mx_projection * mx_view
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_mipmaps(
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
texture: &wgpu::Texture,
|
||||||
|
query_sets: &Option<QuerySets>,
|
||||||
|
mip_count: u32,
|
||||||
|
shader_flags: wgpu::ShaderFlags,
|
||||||
|
) {
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("blit.wgsl"))),
|
||||||
|
flags: shader_flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("blit"),
|
||||||
|
layout: None,
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[TEXTURE_FORMAT.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout = pipeline.get_bind_group_layout(0);
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("mip"),
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let views = (0..mip_count)
|
||||||
|
.map(|mip| {
|
||||||
|
texture.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: Some("mip"),
|
||||||
|
format: None,
|
||||||
|
dimension: None,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
base_mip_level: mip,
|
||||||
|
mip_level_count: NonZeroU32::new(1),
|
||||||
|
base_array_layer: 0,
|
||||||
|
array_layer_count: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for target_mip in 1..mip_count as usize {
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_query_index_base = target_mip as u32 - 1;
|
||||||
|
let timestamp_query_index_base = (target_mip as u32 - 1) * 2;
|
||||||
|
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &views[target_mip],
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
if let Some(ref query_sets) = query_sets {
|
||||||
|
rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base);
|
||||||
|
rpass.begin_pipeline_statistics_query(
|
||||||
|
&query_sets.pipeline_statistics,
|
||||||
|
pipeline_query_index_base,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rpass.set_pipeline(&pipeline);
|
||||||
|
rpass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
rpass.draw(0..4, 0..1);
|
||||||
|
if let Some(ref query_sets) = query_sets {
|
||||||
|
rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base + 1);
|
||||||
|
rpass.end_pipeline_statistics_query();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref query_sets) = query_sets {
|
||||||
|
let timestamp_query_count = MIP_PASS_COUNT * 2;
|
||||||
|
encoder.resolve_query_set(
|
||||||
|
&query_sets.timestamp,
|
||||||
|
0..timestamp_query_count,
|
||||||
|
&query_sets.data_buffer,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
encoder.resolve_query_set(
|
||||||
|
&query_sets.pipeline_statistics,
|
||||||
|
0..MIP_PASS_COUNT,
|
||||||
|
&query_sets.data_buffer,
|
||||||
|
(timestamp_query_count * mem::size_of::<u64>() as u32) as wgpu::BufferAddress,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn optional_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::PIPELINE_STATISTICS_QUERY
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
let mut init_encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
// Create the texture
|
||||||
|
let size = 1 << MIP_LEVEL_COUNT;
|
||||||
|
let texels = create_texels(size as usize, -0.8, 0.156);
|
||||||
|
let texture_extent = wgpu::Extent3d {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: texture_extent,
|
||||||
|
mip_level_count: MIP_LEVEL_COUNT,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: TEXTURE_FORMAT,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED
|
||||||
|
| wgpu::TextureUsage::RENDER_ATTACHMENT
|
||||||
|
| wgpu::TextureUsage::COPY_DST,
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
//Note: we could use queue.write_texture instead, and this is what other
|
||||||
|
// examples do, but here we want to show another way to do this.
|
||||||
|
let temp_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Temporary Buffer"),
|
||||||
|
contents: texels.as_slice(),
|
||||||
|
usage: wgpu::BufferUsage::COPY_SRC,
|
||||||
|
});
|
||||||
|
init_encoder.copy_buffer_to_texture(
|
||||||
|
wgpu::ImageCopyBuffer {
|
||||||
|
buffer: &temp_buf,
|
||||||
|
layout: wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(NonZeroU32::new(4 * size).unwrap()),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
texture_extent,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create other resources
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: None,
|
||||||
|
address_mode_u: wgpu::AddressMode::Repeat,
|
||||||
|
address_mode_v: wgpu::AddressMode::Repeat,
|
||||||
|
address_mode_w: wgpu::AddressMode::Repeat,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Linear,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32);
|
||||||
|
let mx_ref: &[f32; 16] = mx_total.as_ref();
|
||||||
|
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Uniform Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(mx_ref),
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipeline
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Metal | wgpu::Backend::Vulkan => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION
|
||||||
|
}
|
||||||
|
_ => (), //TODO
|
||||||
|
}
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("draw.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
let draw_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("draw"),
|
||||||
|
layout: None,
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group
|
||||||
|
let bind_group_layout = draw_pipeline.get_bind_group_layout(0);
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If both kinds of query are supported, use queries
|
||||||
|
let query_sets = if device
|
||||||
|
.features()
|
||||||
|
.contains(wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::PIPELINE_STATISTICS_QUERY)
|
||||||
|
{
|
||||||
|
// For N total mips, it takes N - 1 passes to generate them, and we're measuring those.
|
||||||
|
let mip_passes = MIP_LEVEL_COUNT - 1;
|
||||||
|
|
||||||
|
// Create the timestamp query set. We need twice as many queries as we have passes,
|
||||||
|
// as we need a query at the beginning and at the end of the operation.
|
||||||
|
let timestamp = device.create_query_set(&wgpu::QuerySetDescriptor {
|
||||||
|
count: mip_passes * 2,
|
||||||
|
ty: wgpu::QueryType::Timestamp,
|
||||||
|
});
|
||||||
|
// Timestamp queries use an device-specific timestamp unit. We need to figure out how many
|
||||||
|
// nanoseconds go by for the timestamp to be incremented by one. The period is this value.
|
||||||
|
let timestamp_period = queue.get_timestamp_period();
|
||||||
|
|
||||||
|
// We only need one pipeline statistics query per pass.
|
||||||
|
let pipeline_statistics = device.create_query_set(&wgpu::QuerySetDescriptor {
|
||||||
|
count: mip_passes,
|
||||||
|
ty: wgpu::QueryType::PipelineStatistics(
|
||||||
|
wgpu::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// This databuffer has to store all of the query results, 2 * passes timestamp queries
|
||||||
|
// and 1 * passes statistics queries. Each query returns a u64 value.
|
||||||
|
let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("query buffer"),
|
||||||
|
size: mip_passes as wgpu::BufferAddress
|
||||||
|
* 3
|
||||||
|
* mem::size_of::<u64>() as wgpu::BufferAddress,
|
||||||
|
usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(QuerySets {
|
||||||
|
timestamp,
|
||||||
|
timestamp_period,
|
||||||
|
pipeline_statistics,
|
||||||
|
data_buffer,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::generate_mipmaps(
|
||||||
|
&mut init_encoder,
|
||||||
|
&device,
|
||||||
|
&texture,
|
||||||
|
&query_sets,
|
||||||
|
MIP_LEVEL_COUNT,
|
||||||
|
flags,
|
||||||
|
);
|
||||||
|
|
||||||
|
queue.submit(Some(init_encoder.finish()));
|
||||||
|
if let Some(ref query_sets) = query_sets {
|
||||||
|
// We can ignore the future as we're about to wait for the device.
|
||||||
|
let _ = query_sets
|
||||||
|
.data_buffer
|
||||||
|
.slice(..)
|
||||||
|
.map_async(wgpu::MapMode::Read);
|
||||||
|
// Wait for device to be done rendering mipmaps
|
||||||
|
device.poll(wgpu::Maintain::Wait);
|
||||||
|
// This is guaranteed to be ready.
|
||||||
|
let view = query_sets.data_buffer.slice(..).get_mapped_range();
|
||||||
|
// Convert the raw data into a useful structure
|
||||||
|
let data: &QueryData = bytemuck::from_bytes(&*view);
|
||||||
|
// Iterate over the data
|
||||||
|
for (idx, (timestamp, pipeline)) in data
|
||||||
|
.timestamps
|
||||||
|
.iter()
|
||||||
|
.zip(data.pipeline_queries.iter())
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
// Figure out the timestamp differences and multiply by the period to get nanoseconds
|
||||||
|
let nanoseconds =
|
||||||
|
(timestamp.end - timestamp.start) as f32 * query_sets.timestamp_period;
|
||||||
|
// Nanoseconds is a bit small, so lets use microseconds.
|
||||||
|
let microseconds = nanoseconds / 1000.0;
|
||||||
|
// Print the data!
|
||||||
|
println!(
|
||||||
|
"Generating mip level {} took {:.3} μs and called the fragment shader {} times",
|
||||||
|
idx + 1,
|
||||||
|
microseconds,
|
||||||
|
pipeline
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Example {
|
||||||
|
bind_group,
|
||||||
|
uniform_buf,
|
||||||
|
draw_pipeline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32);
|
||||||
|
let mx_ref: &[f32; 16] = mx_total.as_ref();
|
||||||
|
queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let clear_color = wgpu::Color {
|
||||||
|
r: 0.1,
|
||||||
|
g: 0.2,
|
||||||
|
b: 0.3,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(clear_color),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(&self.draw_pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
rpass.draw(0..4, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("mipmap");
|
||||||
|
}
|
BIN
wgpu/examples/mipmap/screenshot.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
13
wgpu/examples/msaa-line/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# msaa-line
|
||||||
|
|
||||||
|
This example shows how to render lines using MSAA.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example msaa-line
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![MSAA line](./screenshot.png)
|
292
wgpu/examples/msaa-line/main.rs
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
//! The parts of this example enabling MSAA are:
|
||||||
|
//! * The render pipeline is created with a sample_count > 1.
|
||||||
|
//! * A new texture with a sample_count > 1 is created and set as the color_attachment instead of the swapchain.
|
||||||
|
//! * The swapchain is now specified as a resolve_target.
|
||||||
|
//!
|
||||||
|
//! The parts of this example enabling LineList are:
|
||||||
|
//! * Set the primitive_topology to PrimitiveTopology::LineList.
|
||||||
|
//! * Vertices and Indices describe the two points that make up a line.
|
||||||
|
|
||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use std::{borrow::Cow, iter};
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct Vertex {
|
||||||
|
_pos: [f32; 2],
|
||||||
|
_color: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
bundle: wgpu::RenderBundle,
|
||||||
|
shader: wgpu::ShaderModule,
|
||||||
|
pipeline_layout: wgpu::PipelineLayout,
|
||||||
|
multisampled_framebuffer: wgpu::TextureView,
|
||||||
|
vertex_buffer: wgpu::Buffer,
|
||||||
|
vertex_count: u32,
|
||||||
|
sample_count: u32,
|
||||||
|
rebuild_bundle: bool,
|
||||||
|
sc_desc: wgpu::SwapChainDescriptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
fn create_bundle(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
shader: &wgpu::ShaderModule,
|
||||||
|
pipeline_layout: &wgpu::PipelineLayout,
|
||||||
|
sample_count: u32,
|
||||||
|
vertex_buffer: &wgpu::Buffer,
|
||||||
|
vertex_count: u32,
|
||||||
|
) -> wgpu::RenderBundle {
|
||||||
|
log::info!("sample_count: {}", sample_count);
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
|
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::LineList,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let mut encoder =
|
||||||
|
device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_formats: &[sc_desc.format],
|
||||||
|
depth_stencil_format: None,
|
||||||
|
sample_count,
|
||||||
|
});
|
||||||
|
encoder.set_pipeline(&pipeline);
|
||||||
|
encoder.set_vertex_buffer(0, vertex_buffer.slice(..));
|
||||||
|
encoder.draw(0..vertex_count, 0..1);
|
||||||
|
encoder.finish(&wgpu::RenderBundleDescriptor {
|
||||||
|
label: Some("main"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_multisampled_framebuffer(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
sample_count: u32,
|
||||||
|
) -> wgpu::TextureView {
|
||||||
|
let multisampled_texture_extent = wgpu::Extent3d {
|
||||||
|
width: sc_desc.width,
|
||||||
|
height: sc_desc.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let multisampled_frame_descriptor = &wgpu::TextureDescriptor {
|
||||||
|
size: multisampled_texture_extent,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: sc_desc.format,
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
label: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
device
|
||||||
|
.create_texture(multisampled_frame_descriptor)
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
log::info!("Press left/right arrow keys to change sample_count.");
|
||||||
|
let sample_count = 4;
|
||||||
|
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Metal | wgpu::Backend::Vulkan => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION
|
||||||
|
}
|
||||||
|
_ => (), //TODO
|
||||||
|
}
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let multisampled_framebuffer =
|
||||||
|
Example::create_multisampled_framebuffer(device, sc_desc, sample_count);
|
||||||
|
|
||||||
|
let mut vertex_data = vec![];
|
||||||
|
|
||||||
|
let max = 50;
|
||||||
|
for i in 0..max {
|
||||||
|
let percent = i as f32 / max as f32;
|
||||||
|
let (sin, cos) = (percent * 2.0 * std::f32::consts::PI).sin_cos();
|
||||||
|
vertex_data.push(Vertex {
|
||||||
|
_pos: [0.0, 0.0],
|
||||||
|
_color: [1.0, -sin, cos, 1.0],
|
||||||
|
});
|
||||||
|
vertex_data.push(Vertex {
|
||||||
|
_pos: [1.0 * cos, 1.0 * sin],
|
||||||
|
_color: [sin, -cos, 1.0, 1.0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Vertex Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&vertex_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
let vertex_count = vertex_data.len() as u32;
|
||||||
|
|
||||||
|
let bundle = Example::create_bundle(
|
||||||
|
device,
|
||||||
|
&sc_desc,
|
||||||
|
&shader,
|
||||||
|
&pipeline_layout,
|
||||||
|
sample_count,
|
||||||
|
&vertex_buffer,
|
||||||
|
vertex_count,
|
||||||
|
);
|
||||||
|
|
||||||
|
Example {
|
||||||
|
bundle,
|
||||||
|
shader,
|
||||||
|
pipeline_layout,
|
||||||
|
multisampled_framebuffer,
|
||||||
|
vertex_buffer,
|
||||||
|
vertex_count,
|
||||||
|
sample_count,
|
||||||
|
rebuild_bundle: false,
|
||||||
|
sc_desc: sc_desc.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
fn update(&mut self, event: winit::event::WindowEvent) {
|
||||||
|
match event {
|
||||||
|
winit::event::WindowEvent::KeyboardInput { input, .. } => {
|
||||||
|
if let winit::event::ElementState::Pressed = input.state {
|
||||||
|
match input.virtual_keycode {
|
||||||
|
// TODO: Switch back to full scans of possible options when we expose
|
||||||
|
// supported sample counts to the user.
|
||||||
|
Some(winit::event::VirtualKeyCode::Left) => {
|
||||||
|
if self.sample_count == 4 {
|
||||||
|
self.sample_count = 1;
|
||||||
|
self.rebuild_bundle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(winit::event::VirtualKeyCode::Right) => {
|
||||||
|
if self.sample_count == 1 {
|
||||||
|
self.sample_count = 4;
|
||||||
|
self.rebuild_bundle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
self.sc_desc = sc_desc.clone();
|
||||||
|
self.multisampled_framebuffer =
|
||||||
|
Example::create_multisampled_framebuffer(device, sc_desc, self.sample_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
if self.rebuild_bundle {
|
||||||
|
self.bundle = Example::create_bundle(
|
||||||
|
device,
|
||||||
|
&self.sc_desc,
|
||||||
|
&self.shader,
|
||||||
|
&self.pipeline_layout,
|
||||||
|
self.sample_count,
|
||||||
|
&self.vertex_buffer,
|
||||||
|
self.vertex_count,
|
||||||
|
);
|
||||||
|
self.multisampled_framebuffer =
|
||||||
|
Example::create_multisampled_framebuffer(device, &self.sc_desc, self.sample_count);
|
||||||
|
self.rebuild_bundle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let ops = wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: true,
|
||||||
|
};
|
||||||
|
let rpass_color_attachment = if self.sample_count == 1 {
|
||||||
|
wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wgpu::RenderPassColorAttachment {
|
||||||
|
view: &self.multisampled_framebuffer,
|
||||||
|
resolve_target: Some(&frame.view),
|
||||||
|
ops,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
encoder
|
||||||
|
.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[rpass_color_attachment],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
})
|
||||||
|
.execute_bundles(iter::once(&self.bundle));
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(iter::once(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("msaa-line");
|
||||||
|
}
|
BIN
wgpu/examples/msaa-line/screenshot.png
Normal file
After Width: | Height: | Size: 179 KiB |
20
wgpu/examples/msaa-line/shader.wgsl
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
[[location(0)]] color: vec4<f32>;
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main(
|
||||||
|
[[location(0)]] position: vec2<f32>,
|
||||||
|
[[location(1)]] color: vec4<f32>,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.position = vec4<f32>(position, 0.0, 1.0);
|
||||||
|
out.color = color;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
return in.color;
|
||||||
|
}
|
13
wgpu/examples/shadow/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# shadow
|
||||||
|
|
||||||
|
This animated example demonstrates shadow mapping.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example shadow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Shadow mapping](./screenshot.png)
|
831
wgpu/examples/shadow/main.rs
Normal file
@ -0,0 +1,831 @@
|
|||||||
|
use std::{borrow::Cow, iter, mem, num::NonZeroU32, ops::Range, rc::Rc};
|
||||||
|
|
||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct Vertex {
|
||||||
|
_pos: [i8; 4],
|
||||||
|
_normal: [i8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex(pos: [i8; 3], nor: [i8; 3]) -> Vertex {
|
||||||
|
Vertex {
|
||||||
|
_pos: [pos[0], pos[1], pos[2], 1],
|
||||||
|
_normal: [nor[0], nor[1], nor[2], 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_cube() -> (Vec<Vertex>, Vec<u16>) {
|
||||||
|
let vertex_data = [
|
||||||
|
// top (0, 0, 1)
|
||||||
|
vertex([-1, -1, 1], [0, 0, 1]),
|
||||||
|
vertex([1, -1, 1], [0, 0, 1]),
|
||||||
|
vertex([1, 1, 1], [0, 0, 1]),
|
||||||
|
vertex([-1, 1, 1], [0, 0, 1]),
|
||||||
|
// bottom (0, 0, -1)
|
||||||
|
vertex([-1, 1, -1], [0, 0, -1]),
|
||||||
|
vertex([1, 1, -1], [0, 0, -1]),
|
||||||
|
vertex([1, -1, -1], [0, 0, -1]),
|
||||||
|
vertex([-1, -1, -1], [0, 0, -1]),
|
||||||
|
// right (1, 0, 0)
|
||||||
|
vertex([1, -1, -1], [1, 0, 0]),
|
||||||
|
vertex([1, 1, -1], [1, 0, 0]),
|
||||||
|
vertex([1, 1, 1], [1, 0, 0]),
|
||||||
|
vertex([1, -1, 1], [1, 0, 0]),
|
||||||
|
// left (-1, 0, 0)
|
||||||
|
vertex([-1, -1, 1], [-1, 0, 0]),
|
||||||
|
vertex([-1, 1, 1], [-1, 0, 0]),
|
||||||
|
vertex([-1, 1, -1], [-1, 0, 0]),
|
||||||
|
vertex([-1, -1, -1], [-1, 0, 0]),
|
||||||
|
// front (0, 1, 0)
|
||||||
|
vertex([1, 1, -1], [0, 1, 0]),
|
||||||
|
vertex([-1, 1, -1], [0, 1, 0]),
|
||||||
|
vertex([-1, 1, 1], [0, 1, 0]),
|
||||||
|
vertex([1, 1, 1], [0, 1, 0]),
|
||||||
|
// back (0, -1, 0)
|
||||||
|
vertex([1, -1, 1], [0, -1, 0]),
|
||||||
|
vertex([-1, -1, 1], [0, -1, 0]),
|
||||||
|
vertex([-1, -1, -1], [0, -1, 0]),
|
||||||
|
vertex([1, -1, -1], [0, -1, 0]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let index_data: &[u16] = &[
|
||||||
|
0, 1, 2, 2, 3, 0, // top
|
||||||
|
4, 5, 6, 6, 7, 4, // bottom
|
||||||
|
8, 9, 10, 10, 11, 8, // right
|
||||||
|
12, 13, 14, 14, 15, 12, // left
|
||||||
|
16, 17, 18, 18, 19, 16, // front
|
||||||
|
20, 21, 22, 22, 23, 20, // back
|
||||||
|
];
|
||||||
|
|
||||||
|
(vertex_data.to_vec(), index_data.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_plane(size: i8) -> (Vec<Vertex>, Vec<u16>) {
|
||||||
|
let vertex_data = [
|
||||||
|
vertex([size, -size, 0], [0, 0, 1]),
|
||||||
|
vertex([size, size, 0], [0, 0, 1]),
|
||||||
|
vertex([-size, -size, 0], [0, 0, 1]),
|
||||||
|
vertex([-size, size, 0], [0, 0, 1]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let index_data: &[u16] = &[0, 1, 2, 2, 1, 3];
|
||||||
|
|
||||||
|
(vertex_data.to_vec(), index_data.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Entity {
|
||||||
|
mx_world: cgmath::Matrix4<f32>,
|
||||||
|
rotation_speed: f32,
|
||||||
|
color: wgpu::Color,
|
||||||
|
vertex_buf: Rc<wgpu::Buffer>,
|
||||||
|
index_buf: Rc<wgpu::Buffer>,
|
||||||
|
index_format: wgpu::IndexFormat,
|
||||||
|
index_count: usize,
|
||||||
|
uniform_offset: wgpu::DynamicOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Light {
|
||||||
|
pos: cgmath::Point3<f32>,
|
||||||
|
color: wgpu::Color,
|
||||||
|
fov: f32,
|
||||||
|
depth: Range<f32>,
|
||||||
|
target_view: wgpu::TextureView,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct LightRaw {
|
||||||
|
proj: [[f32; 4]; 4],
|
||||||
|
pos: [f32; 4],
|
||||||
|
color: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Light {
|
||||||
|
fn to_raw(&self) -> LightRaw {
|
||||||
|
use cgmath::{Deg, EuclideanSpace, Matrix4, PerspectiveFov, Point3, Vector3};
|
||||||
|
|
||||||
|
let mx_view = Matrix4::look_at_rh(self.pos, Point3::origin(), Vector3::unit_z());
|
||||||
|
let projection = PerspectiveFov {
|
||||||
|
fovy: Deg(self.fov).into(),
|
||||||
|
aspect: 1.0,
|
||||||
|
near: self.depth.start,
|
||||||
|
far: self.depth.end,
|
||||||
|
};
|
||||||
|
let mx_correction = framework::OPENGL_TO_WGPU_MATRIX;
|
||||||
|
let mx_view_proj =
|
||||||
|
mx_correction * cgmath::Matrix4::from(projection.to_perspective()) * mx_view;
|
||||||
|
LightRaw {
|
||||||
|
proj: *mx_view_proj.as_ref(),
|
||||||
|
pos: [self.pos.x, self.pos.y, self.pos.z, 1.0],
|
||||||
|
color: [
|
||||||
|
self.color.r as f32,
|
||||||
|
self.color.g as f32,
|
||||||
|
self.color.b as f32,
|
||||||
|
1.0,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct GlobalUniforms {
|
||||||
|
proj: [[f32; 4]; 4],
|
||||||
|
num_lights: [u32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct EntityUniforms {
|
||||||
|
model: [[f32; 4]; 4],
|
||||||
|
color: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pass {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
uniform_buf: wgpu::Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
entities: Vec<Entity>,
|
||||||
|
lights: Vec<Light>,
|
||||||
|
lights_are_dirty: bool,
|
||||||
|
shadow_pass: Pass,
|
||||||
|
forward_pass: Pass,
|
||||||
|
forward_depth: wgpu::TextureView,
|
||||||
|
entity_bind_group: wgpu::BindGroup,
|
||||||
|
light_storage_buf: wgpu::Buffer,
|
||||||
|
entity_uniform_buf: wgpu::Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
const MAX_LIGHTS: usize = 10;
|
||||||
|
const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||||
|
const SHADOW_SIZE: wgpu::Extent3d = wgpu::Extent3d {
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
depth_or_array_layers: Self::MAX_LIGHTS as u32,
|
||||||
|
};
|
||||||
|
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||||
|
|
||||||
|
fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4<f32> {
|
||||||
|
let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 20.0);
|
||||||
|
let mx_view = cgmath::Matrix4::look_at_rh(
|
||||||
|
cgmath::Point3::new(3.0f32, -10.0, 6.0),
|
||||||
|
cgmath::Point3::new(0f32, 0.0, 0.0),
|
||||||
|
cgmath::Vector3::unit_z(),
|
||||||
|
);
|
||||||
|
let mx_correction = framework::OPENGL_TO_WGPU_MATRIX;
|
||||||
|
mx_correction * mx_projection * mx_view
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_depth_texture(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
) -> wgpu::TextureView {
|
||||||
|
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: sc_desc.width,
|
||||||
|
height: sc_desc.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: Self::DEPTH_FORMAT,
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn optional_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::DEPTH_CLAMPING
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
// Create the vertex and index buffers
|
||||||
|
let vertex_size = mem::size_of::<Vertex>();
|
||||||
|
let (cube_vertex_data, cube_index_data) = create_cube();
|
||||||
|
let cube_vertex_buf = Rc::new(device.create_buffer_init(
|
||||||
|
&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Cubes Vertex Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&cube_vertex_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let cube_index_buf = Rc::new(device.create_buffer_init(
|
||||||
|
&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Cubes Index Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&cube_index_data),
|
||||||
|
usage: wgpu::BufferUsage::INDEX,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let (plane_vertex_data, plane_index_data) = create_plane(7);
|
||||||
|
let plane_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Plane Vertex Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&plane_vertex_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
let plane_index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Plane Index Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&plane_index_data),
|
||||||
|
usage: wgpu::BufferUsage::INDEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
struct CubeDesc {
|
||||||
|
offset: cgmath::Vector3<f32>,
|
||||||
|
angle: f32,
|
||||||
|
scale: f32,
|
||||||
|
rotation: f32,
|
||||||
|
}
|
||||||
|
let cube_descs = [
|
||||||
|
CubeDesc {
|
||||||
|
offset: cgmath::vec3(-2.0, -2.0, 2.0),
|
||||||
|
angle: 10.0,
|
||||||
|
scale: 0.7,
|
||||||
|
rotation: 0.1,
|
||||||
|
},
|
||||||
|
CubeDesc {
|
||||||
|
offset: cgmath::vec3(2.0, -2.0, 2.0),
|
||||||
|
angle: 50.0,
|
||||||
|
scale: 1.3,
|
||||||
|
rotation: 0.2,
|
||||||
|
},
|
||||||
|
CubeDesc {
|
||||||
|
offset: cgmath::vec3(-2.0, 2.0, 2.0),
|
||||||
|
angle: 140.0,
|
||||||
|
scale: 1.1,
|
||||||
|
rotation: 0.3,
|
||||||
|
},
|
||||||
|
CubeDesc {
|
||||||
|
offset: cgmath::vec3(2.0, 2.0, 2.0),
|
||||||
|
angle: 210.0,
|
||||||
|
scale: 0.9,
|
||||||
|
rotation: 0.4,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let entity_uniform_size = mem::size_of::<EntityUniforms>() as wgpu::BufferAddress;
|
||||||
|
let num_entities = 1 + cube_descs.len() as wgpu::BufferAddress;
|
||||||
|
assert!(entity_uniform_size <= wgpu::BIND_BUFFER_ALIGNMENT);
|
||||||
|
//Note: dynamic offsets also have to be aligned to `BIND_BUFFER_ALIGNMENT`.
|
||||||
|
let entity_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: num_entities * wgpu::BIND_BUFFER_ALIGNMENT,
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let index_format = wgpu::IndexFormat::Uint16;
|
||||||
|
|
||||||
|
let mut entities = vec![{
|
||||||
|
use cgmath::SquareMatrix;
|
||||||
|
Entity {
|
||||||
|
mx_world: cgmath::Matrix4::identity(),
|
||||||
|
rotation_speed: 0.0,
|
||||||
|
color: wgpu::Color::WHITE,
|
||||||
|
vertex_buf: Rc::new(plane_vertex_buf),
|
||||||
|
index_buf: Rc::new(plane_index_buf),
|
||||||
|
index_format,
|
||||||
|
index_count: plane_index_data.len(),
|
||||||
|
uniform_offset: 0,
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
for (i, cube) in cube_descs.iter().enumerate() {
|
||||||
|
use cgmath::{Decomposed, Deg, InnerSpace, Quaternion, Rotation3};
|
||||||
|
|
||||||
|
let transform = Decomposed {
|
||||||
|
disp: cube.offset,
|
||||||
|
rot: Quaternion::from_axis_angle(cube.offset.normalize(), Deg(cube.angle)),
|
||||||
|
scale: cube.scale,
|
||||||
|
};
|
||||||
|
entities.push(Entity {
|
||||||
|
mx_world: cgmath::Matrix4::from(transform),
|
||||||
|
rotation_speed: cube.rotation,
|
||||||
|
color: wgpu::Color::GREEN,
|
||||||
|
vertex_buf: Rc::clone(&cube_vertex_buf),
|
||||||
|
index_buf: Rc::clone(&cube_index_buf),
|
||||||
|
index_format,
|
||||||
|
index_count: cube_index_data.len(),
|
||||||
|
uniform_offset: ((i + 1) * wgpu::BIND_BUFFER_ALIGNMENT as usize) as _,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(entity_uniform_size),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let entity_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &local_bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &entity_uniform_buf,
|
||||||
|
offset: 0,
|
||||||
|
size: wgpu::BufferSize::new(entity_uniform_size),
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create other resources
|
||||||
|
let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("shadow"),
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let shadow_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: Self::SHADOW_SIZE,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: Self::SHADOW_FORMAT,
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::SAMPLED,
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let shadow_view = shadow_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let mut shadow_target_views = (0..2)
|
||||||
|
.map(|i| {
|
||||||
|
Some(shadow_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: Some("shadow"),
|
||||||
|
format: None,
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
base_mip_level: 0,
|
||||||
|
mip_level_count: None,
|
||||||
|
base_array_layer: i as u32,
|
||||||
|
array_layer_count: NonZeroU32::new(1),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let lights = vec![
|
||||||
|
Light {
|
||||||
|
pos: cgmath::Point3::new(7.0, -5.0, 10.0),
|
||||||
|
color: wgpu::Color {
|
||||||
|
r: 0.5,
|
||||||
|
g: 1.0,
|
||||||
|
b: 0.5,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
fov: 60.0,
|
||||||
|
depth: 1.0..20.0,
|
||||||
|
target_view: shadow_target_views[0].take().unwrap(),
|
||||||
|
},
|
||||||
|
Light {
|
||||||
|
pos: cgmath::Point3::new(-5.0, 7.0, 10.0),
|
||||||
|
color: wgpu::Color {
|
||||||
|
r: 1.0,
|
||||||
|
g: 0.5,
|
||||||
|
b: 0.5,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
fov: 45.0,
|
||||||
|
depth: 1.0..20.0,
|
||||||
|
target_view: shadow_target_views[1].take().unwrap(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let light_uniform_size =
|
||||||
|
(Self::MAX_LIGHTS * mem::size_of::<LightRaw>()) as wgpu::BufferAddress;
|
||||||
|
let light_storage_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: light_uniform_size,
|
||||||
|
usage: wgpu::BufferUsage::STORAGE
|
||||||
|
| wgpu::BufferUsage::COPY_SRC
|
||||||
|
| wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let vertex_attr = wgpu::vertex_attr_array![0 => Sint8x4, 1 => Sint8x4];
|
||||||
|
let vb_desc = wgpu::VertexBufferLayout {
|
||||||
|
array_stride: vertex_size as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &vertex_attr,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Metal | wgpu::Backend::Vulkan => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION;
|
||||||
|
}
|
||||||
|
_ => (), //TODO
|
||||||
|
}
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
let shadow_pass = {
|
||||||
|
let uniform_size = mem::size_of::<GlobalUniforms>() as wgpu::BufferAddress;
|
||||||
|
// Create pipeline layout
|
||||||
|
let bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0, // global
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(uniform_size),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("shadow"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: uniform_size,
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buf.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipeline
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("shadow"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_bake",
|
||||||
|
buffers: &[vb_desc.clone()],
|
||||||
|
},
|
||||||
|
fragment: None,
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
clamp_depth: device.features().contains(wgpu::Features::DEPTH_CLAMPING),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: Self::SHADOW_FORMAT,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState {
|
||||||
|
constant: 2, // corresponds to bilinear filtering
|
||||||
|
slope_scale: 2.0,
|
||||||
|
clamp: 0.0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Pass {
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
uniform_buf,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let forward_pass = {
|
||||||
|
// Create pipeline layout
|
||||||
|
let bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0, // global
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(
|
||||||
|
mem::size_of::<GlobalUniforms>() as _,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1, // lights
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(light_uniform_size),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
sample_type: wgpu::TextureSampleType::Depth,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler {
|
||||||
|
comparison: true,
|
||||||
|
filtering: true,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("main"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout, &local_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32);
|
||||||
|
let forward_uniforms = GlobalUniforms {
|
||||||
|
proj: *mx_total.as_ref(),
|
||||||
|
num_lights: [lights.len() as u32, 0, 0, 0],
|
||||||
|
};
|
||||||
|
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Uniform Buffer"),
|
||||||
|
contents: bytemuck::bytes_of(&forward_uniforms),
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: light_storage_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&shadow_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&shadow_sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipeline
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("main"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[vb_desc],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: Self::DEPTH_FORMAT,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::Less,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Pass {
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
uniform_buf,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let forward_depth = Self::create_depth_texture(sc_desc, device);
|
||||||
|
|
||||||
|
Example {
|
||||||
|
entities,
|
||||||
|
lights,
|
||||||
|
lights_are_dirty: true,
|
||||||
|
shadow_pass,
|
||||||
|
forward_pass,
|
||||||
|
forward_depth,
|
||||||
|
light_storage_buf,
|
||||||
|
entity_uniform_buf,
|
||||||
|
entity_bind_group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
// update view-projection matrix
|
||||||
|
let mx_total = Self::generate_matrix(sc_desc.width as f32 / sc_desc.height as f32);
|
||||||
|
let mx_ref: &[f32; 16] = mx_total.as_ref();
|
||||||
|
queue.write_buffer(
|
||||||
|
&self.forward_pass.uniform_buf,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(mx_ref),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.forward_depth = Self::create_depth_texture(sc_desc, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
// update uniforms
|
||||||
|
for entity in self.entities.iter_mut() {
|
||||||
|
if entity.rotation_speed != 0.0 {
|
||||||
|
let rotation = cgmath::Matrix4::from_angle_x(cgmath::Deg(entity.rotation_speed));
|
||||||
|
entity.mx_world = entity.mx_world * rotation;
|
||||||
|
}
|
||||||
|
let data = EntityUniforms {
|
||||||
|
model: entity.mx_world.into(),
|
||||||
|
color: [
|
||||||
|
entity.color.r as f32,
|
||||||
|
entity.color.g as f32,
|
||||||
|
entity.color.b as f32,
|
||||||
|
entity.color.a as f32,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
queue.write_buffer(
|
||||||
|
&self.entity_uniform_buf,
|
||||||
|
entity.uniform_offset as wgpu::BufferAddress,
|
||||||
|
bytemuck::bytes_of(&data),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.lights_are_dirty {
|
||||||
|
self.lights_are_dirty = false;
|
||||||
|
for (i, light) in self.lights.iter().enumerate() {
|
||||||
|
queue.write_buffer(
|
||||||
|
&self.light_storage_buf,
|
||||||
|
(i * mem::size_of::<LightRaw>()) as wgpu::BufferAddress,
|
||||||
|
bytemuck::bytes_of(&light.to_raw()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
encoder.push_debug_group("shadow passes");
|
||||||
|
for (i, light) in self.lights.iter().enumerate() {
|
||||||
|
encoder.push_debug_group(&format!(
|
||||||
|
"shadow pass {} (light at position {:?})",
|
||||||
|
i, light.pos
|
||||||
|
));
|
||||||
|
|
||||||
|
// The light uniform buffer already has the projection,
|
||||||
|
// let's just copy it over to the shadow uniform buffer.
|
||||||
|
encoder.copy_buffer_to_buffer(
|
||||||
|
&self.light_storage_buf,
|
||||||
|
(i * mem::size_of::<LightRaw>()) as wgpu::BufferAddress,
|
||||||
|
&self.shadow_pass.uniform_buf,
|
||||||
|
0,
|
||||||
|
64,
|
||||||
|
);
|
||||||
|
|
||||||
|
encoder.insert_debug_marker("render entities");
|
||||||
|
{
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[],
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &light.target_view,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(1.0),
|
||||||
|
store: true,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
pass.set_pipeline(&self.shadow_pass.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]);
|
||||||
|
|
||||||
|
for entity in &self.entities {
|
||||||
|
pass.set_bind_group(1, &self.entity_bind_group, &[entity.uniform_offset]);
|
||||||
|
pass.set_index_buffer(entity.index_buf.slice(..), entity.index_format);
|
||||||
|
pass.set_vertex_buffer(0, entity.vertex_buf.slice(..));
|
||||||
|
pass.draw_indexed(0..entity.index_count as u32, 0, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.pop_debug_group();
|
||||||
|
}
|
||||||
|
encoder.pop_debug_group();
|
||||||
|
|
||||||
|
// forward pass
|
||||||
|
encoder.push_debug_group("forward rendering pass");
|
||||||
|
{
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
r: 0.1,
|
||||||
|
g: 0.2,
|
||||||
|
b: 0.3,
|
||||||
|
a: 1.0,
|
||||||
|
}),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.forward_depth,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(1.0),
|
||||||
|
store: false,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
pass.set_pipeline(&self.forward_pass.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.forward_pass.bind_group, &[]);
|
||||||
|
|
||||||
|
for entity in &self.entities {
|
||||||
|
pass.set_bind_group(1, &self.entity_bind_group, &[entity.uniform_offset]);
|
||||||
|
pass.set_index_buffer(entity.index_buf.slice(..), entity.index_format);
|
||||||
|
pass.set_vertex_buffer(0, entity.vertex_buf.slice(..));
|
||||||
|
pass.draw_indexed(0..entity.index_count as u32, 0, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.pop_debug_group();
|
||||||
|
|
||||||
|
queue.submit(iter::once(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("shadow");
|
||||||
|
}
|
BIN
wgpu/examples/shadow/screenshot.png
Normal file
After Width: | Height: | Size: 277 KiB |
104
wgpu/examples/shadow/shader.wgsl
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
[[block]]
|
||||||
|
struct Globals {
|
||||||
|
view_proj: mat4x4<f32>;
|
||||||
|
num_lights: vec4<u32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var<uniform> u_globals: Globals;
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Entity {
|
||||||
|
world: mat4x4<f32>;
|
||||||
|
color: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(1), binding(0)]]
|
||||||
|
var<uniform> u_entity: Entity;
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_bake([[location(0)]] position: vec4<i32>) -> [[builtin(position)]] vec4<f32> {
|
||||||
|
return u_globals.view_proj * u_entity.world * vec4<f32>(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] proj_position: vec4<f32>;
|
||||||
|
[[location(0)]] world_normal: vec3<f32>;
|
||||||
|
[[location(1)]] world_position: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main(
|
||||||
|
[[location(0)]] position: vec4<i32>,
|
||||||
|
[[location(1)]] normal: vec4<i32>,
|
||||||
|
) -> VertexOutput {
|
||||||
|
let w = u_entity.world;
|
||||||
|
let world_pos = u_entity.world * vec4<f32>(position);
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.world_normal = mat3x3<f32>(w.x.xyz, w.y.xyz, w.z.xyz) * vec3<f32>(normal.xyz);
|
||||||
|
out.world_position = world_pos;
|
||||||
|
out.proj_position = u_globals.view_proj * world_pos;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fragment shader
|
||||||
|
|
||||||
|
struct Light {
|
||||||
|
proj: mat4x4<f32>;
|
||||||
|
pos: vec4<f32>;
|
||||||
|
color: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Lights {
|
||||||
|
data: [[stride(96)]] array<Light>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var<storage> s_lights: [[access(read)]] Lights;
|
||||||
|
[[group(0), binding(2)]]
|
||||||
|
var t_shadow: texture_depth_2d_array;
|
||||||
|
[[group(0), binding(3)]]
|
||||||
|
var sampler_shadow: sampler_comparison;
|
||||||
|
|
||||||
|
fn fetch_shadow(light_id: u32, homogeneous_coords: vec4<f32>) -> f32 {
|
||||||
|
if (homogeneous_coords.w <= 0.0) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
// compensate for the Y-flip difference between the NDC and texture coordinates
|
||||||
|
let flip_correction = vec2<f32>(0.5, -0.5);
|
||||||
|
// compute texture coordinates for shadow lookup
|
||||||
|
let proj_correction = 1.0 / homogeneous_coords.w;
|
||||||
|
let light_local = homogeneous_coords.xy * flip_correction * proj_correction + vec2<f32>(0.5, 0.5);
|
||||||
|
// do the lookup, using HW PCF and comparison
|
||||||
|
return textureSampleCompare(t_shadow, sampler_shadow, light_local, i32(light_id), homogeneous_coords.z * proj_correction);
|
||||||
|
}
|
||||||
|
|
||||||
|
let c_ambient: vec3<f32> = vec3<f32>(0.05, 0.05, 0.05);
|
||||||
|
let c_max_lights: u32 = 10u;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
let normal = normalize(in.world_normal);
|
||||||
|
// accumulate color
|
||||||
|
var color: vec3<f32> = c_ambient;
|
||||||
|
var i: u32 = 0u;
|
||||||
|
loop {
|
||||||
|
if (i >= min(u_globals.num_lights.x, c_max_lights)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let light = s_lights.data[i];
|
||||||
|
// project into the light space
|
||||||
|
let shadow = fetch_shadow(i, light.proj * in.world_position);
|
||||||
|
// compute Lambertian diffuse term
|
||||||
|
let light_dir = normalize(light.pos.xyz - in.world_position.xyz);
|
||||||
|
let diffuse = max(0.0, dot(normal, light_dir));
|
||||||
|
// add light contribution
|
||||||
|
color = color + shadow * diffuse * light.color.xyz;
|
||||||
|
continuing {
|
||||||
|
i = i + 1u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// multiply the light by material color
|
||||||
|
return vec4<f32>(color, 1.0) * u_entity.color;
|
||||||
|
}
|
14
wgpu/examples/skybox/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# skybox
|
||||||
|
|
||||||
|
This animated example demonstrates loading a Wavefront OBJ model, and rendering it with skybox and simple reflections.
|
||||||
|
It hooks up `winit` mouse controls for camera rotation around the model at the center.
|
||||||
|
|
||||||
|
## To Run
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example skybox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Skybox](./screenshot.png)
|
BIN
wgpu/examples/skybox/images/astc.dds
Normal file
BIN
wgpu/examples/skybox/images/bc1.dds
Normal file
BIN
wgpu/examples/skybox/images/bgra.dds
Normal file
BIN
wgpu/examples/skybox/images/etc2.dds
Normal file
469
wgpu/examples/skybox/main.rs
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use cgmath::SquareMatrix;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
const IMAGE_SIZE: u32 = 128;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex {
|
||||||
|
pos: [f32; 3],
|
||||||
|
normal: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Entity {
|
||||||
|
vertex_count: u32,
|
||||||
|
vertex_buf: wgpu::Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we use the Y=up coordinate space in this example.
|
||||||
|
struct Camera {
|
||||||
|
screen_size: (u32, u32),
|
||||||
|
angle_y: f32,
|
||||||
|
angle_xz: f32,
|
||||||
|
dist: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MODEL_CENTER_Y: f32 = 2.0;
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
fn to_uniform_data(&self) -> [f32; 16 * 3 + 4] {
|
||||||
|
let aspect = self.screen_size.0 as f32 / self.screen_size.1 as f32;
|
||||||
|
let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect, 1.0, 50.0);
|
||||||
|
let cam_pos = cgmath::Point3::new(
|
||||||
|
self.angle_xz.cos() * self.angle_y.sin() * self.dist,
|
||||||
|
self.angle_xz.sin() * self.dist + MODEL_CENTER_Y,
|
||||||
|
self.angle_xz.cos() * self.angle_y.cos() * self.dist,
|
||||||
|
);
|
||||||
|
let mx_view = cgmath::Matrix4::look_at_rh(
|
||||||
|
cam_pos,
|
||||||
|
cgmath::Point3::new(0f32, MODEL_CENTER_Y, 0.0),
|
||||||
|
cgmath::Vector3::unit_y(),
|
||||||
|
);
|
||||||
|
let proj = framework::OPENGL_TO_WGPU_MATRIX * mx_projection;
|
||||||
|
let proj_inv = proj.invert().unwrap();
|
||||||
|
let view = framework::OPENGL_TO_WGPU_MATRIX * mx_view;
|
||||||
|
|
||||||
|
let mut raw = [0f32; 16 * 3 + 4];
|
||||||
|
raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]);
|
||||||
|
raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]);
|
||||||
|
raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]);
|
||||||
|
raw[48..51].copy_from_slice(AsRef::<[f32; 3]>::as_ref(&cam_pos));
|
||||||
|
raw[51] = 1.0;
|
||||||
|
raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Skybox {
|
||||||
|
camera: Camera,
|
||||||
|
sky_pipeline: wgpu::RenderPipeline,
|
||||||
|
entity_pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
uniform_buf: wgpu::Buffer,
|
||||||
|
entities: Vec<Entity>,
|
||||||
|
depth_view: wgpu::TextureView,
|
||||||
|
staging_belt: wgpu::util::StagingBelt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Skybox {
|
||||||
|
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus;
|
||||||
|
|
||||||
|
fn create_depth_texture(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
) -> wgpu::TextureView {
|
||||||
|
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: sc_desc.width,
|
||||||
|
height: sc_desc.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: Self::DEPTH_FORMAT,
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Skybox {
|
||||||
|
fn optional_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR
|
||||||
|
| wgpu::Features::TEXTURE_COMPRESSION_ETC2
|
||||||
|
| wgpu::Features::TEXTURE_COMPRESSION_BC
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
let mut entities = Vec::new();
|
||||||
|
{
|
||||||
|
let source = include_bytes!("models/teslacyberv3.0.obj");
|
||||||
|
let data = obj::ObjData::load_buf(&source[..]).unwrap();
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
for object in data.objects {
|
||||||
|
for group in object.groups {
|
||||||
|
vertices.clear();
|
||||||
|
for poly in group.polys {
|
||||||
|
for end_index in 2..poly.0.len() {
|
||||||
|
for &index in &[0, end_index - 1, end_index] {
|
||||||
|
let obj::IndexTuple(position_id, _texture_id, normal_id) =
|
||||||
|
poly.0[index];
|
||||||
|
vertices.push(Vertex {
|
||||||
|
pos: data.position[position_id],
|
||||||
|
normal: data.normal[normal_id.unwrap()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Vertex"),
|
||||||
|
contents: bytemuck::cast_slice(&vertices),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
entities.push(Entity {
|
||||||
|
vertex_count: vertices.len() as u32,
|
||||||
|
vertex_buf,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::Cube,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler {
|
||||||
|
comparison: false,
|
||||||
|
filtering: true,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipeline
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Metal | wgpu::Backend::Vulkan => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION
|
||||||
|
}
|
||||||
|
_ => (), //TODO
|
||||||
|
}
|
||||||
|
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
let camera = Camera {
|
||||||
|
screen_size: (sc_desc.width, sc_desc.height),
|
||||||
|
angle_xz: 0.2,
|
||||||
|
angle_y: 0.2,
|
||||||
|
dist: 30.0,
|
||||||
|
};
|
||||||
|
let raw_uniforms = camera.to_uniform_data();
|
||||||
|
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&raw_uniforms),
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipelines
|
||||||
|
let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Sky"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_sky",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_sky",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: wgpu::FrontFace::Cw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: Self::DEPTH_FORMAT,
|
||||||
|
depth_write_enabled: false,
|
||||||
|
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
let entity_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Entity"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_entity",
|
||||||
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
|
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_entity",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: wgpu::FrontFace::Cw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: Self::DEPTH_FORMAT,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: None,
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Linear,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let device_features = device.features();
|
||||||
|
|
||||||
|
let skybox_format =
|
||||||
|
if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR) {
|
||||||
|
wgpu::TextureFormat::Astc4x4RgbaUnormSrgb
|
||||||
|
} else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) {
|
||||||
|
wgpu::TextureFormat::Etc2RgbUnormSrgb
|
||||||
|
} else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
|
||||||
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb
|
||||||
|
} else {
|
||||||
|
wgpu::TextureFormat::Bgra8UnormSrgb
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = wgpu::Extent3d {
|
||||||
|
width: IMAGE_SIZE,
|
||||||
|
height: IMAGE_SIZE,
|
||||||
|
depth_or_array_layers: 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer_size = wgpu::Extent3d {
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
..size
|
||||||
|
};
|
||||||
|
let max_mips = layer_size.max_mips();
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"Copying {:?} skybox images of size {}, {}, 6 with {} mips to gpu",
|
||||||
|
skybox_format,
|
||||||
|
IMAGE_SIZE,
|
||||||
|
IMAGE_SIZE,
|
||||||
|
max_mips,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bytes = match skybox_format {
|
||||||
|
wgpu::TextureFormat::Astc4x4RgbaUnormSrgb => &include_bytes!("images/astc.dds")[..],
|
||||||
|
wgpu::TextureFormat::Etc2RgbUnormSrgb => &include_bytes!("images/etc2.dds")[..],
|
||||||
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb => &include_bytes!("images/bc1.dds")[..],
|
||||||
|
wgpu::TextureFormat::Bgra8UnormSrgb => &include_bytes!("images/bgra.dds")[..],
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = ddsfile::Dds::read(&mut std::io::Cursor::new(&bytes)).unwrap();
|
||||||
|
|
||||||
|
let texture = device.create_texture_with_data(
|
||||||
|
&queue,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
size,
|
||||||
|
mip_level_count: max_mips as u32,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: skybox_format,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||||
|
label: None,
|
||||||
|
},
|
||||||
|
&image.data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: None,
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::Cube),
|
||||||
|
..wgpu::TextureViewDescriptor::default()
|
||||||
|
});
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let depth_view = Self::create_depth_texture(sc_desc, device);
|
||||||
|
|
||||||
|
Skybox {
|
||||||
|
camera,
|
||||||
|
sky_pipeline,
|
||||||
|
entity_pipeline,
|
||||||
|
bind_group,
|
||||||
|
uniform_buf,
|
||||||
|
entities,
|
||||||
|
depth_view,
|
||||||
|
staging_belt: wgpu::util::StagingBelt::new(0x100),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
fn update(&mut self, event: winit::event::WindowEvent) {
|
||||||
|
match event {
|
||||||
|
winit::event::WindowEvent::CursorMoved { position, .. } => {
|
||||||
|
let norm_x = position.x as f32 / self.camera.screen_size.0 as f32 - 0.5;
|
||||||
|
let norm_y = position.y as f32 / self.camera.screen_size.1 as f32 - 0.5;
|
||||||
|
self.camera.angle_y = norm_x * 5.0;
|
||||||
|
self.camera.angle_xz = norm_y;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
self.depth_view = Self::create_depth_texture(sc_desc, device);
|
||||||
|
self.camera.screen_size = (sc_desc.width, sc_desc.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
// update rotation
|
||||||
|
let raw_uniforms = self.camera.to_uniform_data();
|
||||||
|
self.staging_belt
|
||||||
|
.write_buffer(
|
||||||
|
&mut encoder,
|
||||||
|
&self.uniform_buf,
|
||||||
|
0,
|
||||||
|
wgpu::BufferSize::new((raw_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
|
||||||
|
device,
|
||||||
|
)
|
||||||
|
.copy_from_slice(bytemuck::cast_slice(&raw_uniforms));
|
||||||
|
|
||||||
|
self.staging_belt.finish();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
r: 0.1,
|
||||||
|
g: 0.2,
|
||||||
|
b: 0.3,
|
||||||
|
a: 1.0,
|
||||||
|
}),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_view,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(1.0),
|
||||||
|
store: false,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
rpass.set_pipeline(&self.entity_pipeline);
|
||||||
|
|
||||||
|
for entity in self.entities.iter() {
|
||||||
|
rpass.set_vertex_buffer(0, entity.vertex_buf.slice(..));
|
||||||
|
rpass.draw(0..entity.vertex_count, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.sky_pipeline);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
|
||||||
|
let belt_future = self.staging_belt.recall();
|
||||||
|
spawner.spawn_local(belt_future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Skybox>("skybox");
|
||||||
|
}
|
62
wgpu/examples/skybox/models/teslacyberv3.0.mtl
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Blender MTL File: 'teslacyberv3.0.blend'
|
||||||
|
# Material Count: 6
|
||||||
|
|
||||||
|
newmtl Material
|
||||||
|
Ns 65.476285
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.411568 0.411568 0.411568
|
||||||
|
Ks 0.614679 0.614679 0.614679
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 36.750000
|
||||||
|
d 1.000000
|
||||||
|
illum 3
|
||||||
|
|
||||||
|
newmtl Материал
|
||||||
|
Ns 323.999994
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.800000 0.800000 0.800000
|
||||||
|
Ks 0.500000 0.500000 0.500000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 2
|
||||||
|
|
||||||
|
newmtl Материал.001
|
||||||
|
Ns 900.000000
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.026240 0.026240 0.026240
|
||||||
|
Ks 0.000000 0.000000 0.000000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.450000
|
||||||
|
d 1.000000
|
||||||
|
illum 1
|
||||||
|
|
||||||
|
newmtl Материал.002
|
||||||
|
Ns 0.000000
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.031837 0.032429 0.029425
|
||||||
|
Ks 0.169725 0.169725 0.169725
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 0.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 2
|
||||||
|
|
||||||
|
newmtl Материал.003
|
||||||
|
Ns 900.000000
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.023585 0.083235 0.095923
|
||||||
|
Ks 1.000000 1.000000 1.000000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 45.049999
|
||||||
|
d 1.000000
|
||||||
|
illum 3
|
||||||
|
|
||||||
|
newmtl Материал.004
|
||||||
|
Ns 323.999994
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.800000 0.800000 0.800000
|
||||||
|
Ks 0.500000 0.500000 0.500000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.000000
|
||||||
|
d 1.000000
|
||||||
|
illum 2
|
6617
wgpu/examples/skybox/models/teslacyberv3.0.obj
Normal file
BIN
wgpu/examples/skybox/screenshot.png
Normal file
After Width: | Height: | Size: 484 KiB |
78
wgpu/examples/skybox/shader.wgsl
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
struct SkyOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] uv: vec3<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct Data {
|
||||||
|
// from camera to screen
|
||||||
|
proj: mat4x4<f32>;
|
||||||
|
// from screen to camera
|
||||||
|
proj_inv: mat4x4<f32>;
|
||||||
|
// from world to camera
|
||||||
|
view: mat4x4<f32>;
|
||||||
|
// camera position
|
||||||
|
cam_pos: vec4<f32>;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var r_data: Data;
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_sky([[builtin(vertex_index)]] vertex_index: u32) -> SkyOutput {
|
||||||
|
// hacky way to draw a large triangle
|
||||||
|
let tmp1 = i32(vertex_index) / 2;
|
||||||
|
let tmp2 = i32(vertex_index) & 1;
|
||||||
|
let pos = vec4<f32>(
|
||||||
|
f32(tmp1) * 4.0 - 1.0,
|
||||||
|
f32(tmp2) * 4.0 - 1.0,
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
// transposition = inversion for this orthonormal matrix
|
||||||
|
let inv_model_view = transpose(mat3x3<f32>(r_data.view.x.xyz, r_data.view.y.xyz, r_data.view.z.xyz));
|
||||||
|
let unprojected = r_data.proj_inv * pos;
|
||||||
|
|
||||||
|
var out: SkyOutput;
|
||||||
|
out.uv = inv_model_view * unprojected.xyz;
|
||||||
|
out.position = pos;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EntityOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(1)]] normal: vec3<f32>;
|
||||||
|
[[location(3)]] view: vec3<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_entity(
|
||||||
|
[[location(0)]] pos: vec3<f32>,
|
||||||
|
[[location(1)]] normal: vec3<f32>,
|
||||||
|
) -> EntityOutput {
|
||||||
|
var out: EntityOutput;
|
||||||
|
out.normal = normal;
|
||||||
|
out.view = pos - r_data.cam_pos.xyz;
|
||||||
|
out.position = r_data.proj * r_data.view * vec4<f32>(pos, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[group(0), binding(1)]]
|
||||||
|
var r_texture: texture_cube<f32>;
|
||||||
|
[[group(0), binding(2)]]
|
||||||
|
var r_sampler: sampler;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_sky(in: SkyOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
return textureSample(r_texture, r_sampler, in.uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_entity(in: EntityOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
let incident = normalize(in.view);
|
||||||
|
let normal = normalize(in.normal);
|
||||||
|
let reflected = incident - 2.0 * dot(normal, incident) * normal;
|
||||||
|
|
||||||
|
let reflected_color = textureSample(r_texture, r_sampler, reflected);
|
||||||
|
return vec4<f32>(0.1, 0.1, 0.1, 0.1) + 0.5 * reflected_color;
|
||||||
|
}
|
19
wgpu/examples/texture-arrays/constant.frag
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_TexCoord;
|
||||||
|
layout(location = 1) flat in int v_Index; // dynamically non-uniform
|
||||||
|
layout(location = 0) out vec4 o_Color;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform texture2D u_Textures[2];
|
||||||
|
layout(set = 0, binding = 1) uniform sampler u_Sampler;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (v_Index == 0) {
|
||||||
|
o_Color = vec4(texture(sampler2D(u_Textures[0], u_Sampler), v_TexCoord).rgb, 1.0);
|
||||||
|
} else if (v_Index == 1) {
|
||||||
|
o_Color = vec4(texture(sampler2D(u_Textures[1], u_Sampler), v_TexCoord).rgb, 1.0);
|
||||||
|
} else {
|
||||||
|
// We need to write something to output color
|
||||||
|
o_Color = vec4(0.0, 0.0, 1.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
BIN
wgpu/examples/texture-arrays/constant.frag.spv
Normal file
332
wgpu/examples/texture-arrays/main.rs
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
struct Vertex {
|
||||||
|
_pos: [f32; 2],
|
||||||
|
_tex_coord: [f32; 2],
|
||||||
|
_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex(pos: [i8; 2], tc: [i8; 2], index: i8) -> Vertex {
|
||||||
|
Vertex {
|
||||||
|
_pos: [pos[0] as f32, pos[1] as f32],
|
||||||
|
_tex_coord: [tc[0] as f32, tc[1] as f32],
|
||||||
|
_index: index as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vertices() -> Vec<Vertex> {
|
||||||
|
vec![
|
||||||
|
// left rectangle
|
||||||
|
vertex([-1, -1], [0, 1], 0),
|
||||||
|
vertex([-1, 1], [0, 0], 0),
|
||||||
|
vertex([0, 1], [1, 0], 0),
|
||||||
|
vertex([0, -1], [1, 1], 0),
|
||||||
|
// right rectangle
|
||||||
|
vertex([0, -1], [0, 1], 1),
|
||||||
|
vertex([0, 1], [0, 0], 1),
|
||||||
|
vertex([1, 1], [1, 0], 1),
|
||||||
|
vertex([1, -1], [1, 1], 1),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_indices() -> Vec<u16> {
|
||||||
|
vec![
|
||||||
|
// Left rectangle
|
||||||
|
0, 1, 2, // 1st
|
||||||
|
2, 0, 3, // 2nd
|
||||||
|
// Right rectangle
|
||||||
|
4, 5, 6, // 1st
|
||||||
|
6, 4, 7, // 2nd
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum Color {
|
||||||
|
RED,
|
||||||
|
GREEN,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_texture_data(color: Color) -> [u8; 4] {
|
||||||
|
match color {
|
||||||
|
Color::RED => [255, 0, 0, 255],
|
||||||
|
Color::GREEN => [0, 255, 0, 255],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
vertex_buffer: wgpu::Buffer,
|
||||||
|
index_buffer: wgpu::Buffer,
|
||||||
|
index_format: wgpu::IndexFormat,
|
||||||
|
uniform_workaround: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn optional_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::UNSIZED_BINDING_ARRAY
|
||||||
|
| wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING
|
||||||
|
| wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING
|
||||||
|
| wgpu::Features::PUSH_CONSTANTS
|
||||||
|
}
|
||||||
|
fn required_features() -> wgpu::Features {
|
||||||
|
wgpu::Features::SAMPLED_TEXTURE_BINDING_ARRAY
|
||||||
|
}
|
||||||
|
fn required_limits() -> wgpu::Limits {
|
||||||
|
wgpu::Limits {
|
||||||
|
max_push_constant_size: 4,
|
||||||
|
..wgpu::Limits::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
let mut uniform_workaround = false;
|
||||||
|
let vs_module = device.create_shader_module(&wgpu::include_spirv!("shader.vert.spv"));
|
||||||
|
let fs_source = match device.features() {
|
||||||
|
f if f.contains(wgpu::Features::UNSIZED_BINDING_ARRAY) => {
|
||||||
|
wgpu::include_spirv!("unsized-non-uniform.frag.spv")
|
||||||
|
}
|
||||||
|
f if f.contains(wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING) => {
|
||||||
|
wgpu::include_spirv!("non-uniform.frag.spv")
|
||||||
|
}
|
||||||
|
f if f.contains(wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING) => {
|
||||||
|
uniform_workaround = true;
|
||||||
|
wgpu::include_spirv!("uniform.frag.spv")
|
||||||
|
}
|
||||||
|
f if f.contains(wgpu::Features::SAMPLED_TEXTURE_BINDING_ARRAY) => {
|
||||||
|
wgpu::include_spirv!("constant.frag.spv")
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let fs_module = device.create_shader_module(&fs_source);
|
||||||
|
|
||||||
|
let vertex_size = std::mem::size_of::<Vertex>();
|
||||||
|
let vertex_data = create_vertices();
|
||||||
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Vertex Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&vertex_data),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
let index_data = create_indices();
|
||||||
|
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Index Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&index_data),
|
||||||
|
usage: wgpu::BufferUsage::INDEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
let red_texture_data = create_texture_data(Color::RED);
|
||||||
|
let green_texture_data = create_texture_data(Color::GREEN);
|
||||||
|
|
||||||
|
let texture_descriptor = wgpu::TextureDescriptor {
|
||||||
|
size: wgpu::Extent3d::default(),
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||||
|
label: None,
|
||||||
|
};
|
||||||
|
let red_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("red"),
|
||||||
|
..texture_descriptor
|
||||||
|
});
|
||||||
|
let green_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("green"),
|
||||||
|
..texture_descriptor
|
||||||
|
});
|
||||||
|
|
||||||
|
let red_texture_view = red_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let green_texture_view = green_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
texture: &red_texture,
|
||||||
|
},
|
||||||
|
&red_texture_data,
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(NonZeroU32::new(4).unwrap()),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
wgpu::Extent3d::default(),
|
||||||
|
);
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
texture: &green_texture,
|
||||||
|
},
|
||||||
|
&green_texture_data,
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(NonZeroU32::new(4).unwrap()),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
wgpu::Extent3d::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("bind group layout"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: NonZeroU32::new(2),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler {
|
||||||
|
comparison: false,
|
||||||
|
filtering: true,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureViewArray(&[
|
||||||
|
&red_texture_view,
|
||||||
|
&green_texture_view,
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
label: Some("bind group"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("main"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: if uniform_workaround {
|
||||||
|
&[wgpu::PushConstantRange {
|
||||||
|
stages: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
range: 0..4,
|
||||||
|
}]
|
||||||
|
} else {
|
||||||
|
&[]
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let index_format = wgpu::IndexFormat::Uint16;
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &vs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
|
array_stride: vertex_size as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Sint32],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &fs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
vertex_buffer,
|
||||||
|
index_buffer,
|
||||||
|
index_format,
|
||||||
|
bind_group,
|
||||||
|
pipeline,
|
||||||
|
uniform_workaround,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
_sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
_device: &wgpu::Device,
|
||||||
|
_queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("primary"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||||
|
rpass.set_index_buffer(self.index_buffer.slice(..), self.index_format);
|
||||||
|
if self.uniform_workaround {
|
||||||
|
rpass.set_push_constants(wgpu::ShaderStage::FRAGMENT, 0, bytemuck::cast_slice(&[0]));
|
||||||
|
rpass.draw_indexed(0..6, 0, 0..1);
|
||||||
|
rpass.set_push_constants(wgpu::ShaderStage::FRAGMENT, 0, bytemuck::cast_slice(&[1]));
|
||||||
|
rpass.draw_indexed(6..12, 0, 0..1);
|
||||||
|
} else {
|
||||||
|
rpass.draw_indexed(0..12, 0, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(rpass);
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("texture-arrays");
|
||||||
|
}
|
14
wgpu/examples/texture-arrays/non-uniform.frag
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
#extension GL_EXT_nonuniform_qualifier : require
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_TexCoord;
|
||||||
|
layout(location = 1) nonuniformEXT flat in int v_Index; // dynamically non-uniform
|
||||||
|
layout(location = 0) out vec4 o_Color;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform texture2D u_Textures[2];
|
||||||
|
layout(set = 0, binding = 1) uniform sampler u_Sampler;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_Color = vec4(texture(sampler2D(u_Textures[v_Index], u_Sampler), v_TexCoord).rgb, 1.0);
|
||||||
|
}
|
BIN
wgpu/examples/texture-arrays/non-uniform.frag.spv
Normal file
13
wgpu/examples/texture-arrays/shader.vert
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 a_Pos;
|
||||||
|
layout(location = 1) in vec2 a_TexCoord;
|
||||||
|
layout(location = 2) in int a_Index;
|
||||||
|
layout(location = 0) out vec2 v_TexCoord;
|
||||||
|
layout(location = 1) flat out int v_Index;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
v_TexCoord = a_TexCoord;
|
||||||
|
v_Index = a_Index;
|
||||||
|
gl_Position = vec4(a_Pos, 0.0, 1.0);
|
||||||
|
}
|
BIN
wgpu/examples/texture-arrays/shader.vert.spv
Normal file
15
wgpu/examples/texture-arrays/uniform.frag
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_TexCoord;
|
||||||
|
layout(location = 1) flat in int v_Index; // dynamically non-uniform
|
||||||
|
layout(location = 0) out vec4 o_Color;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform texture2D u_Textures[2];
|
||||||
|
layout(set = 0, binding = 1) uniform sampler u_Sampler;
|
||||||
|
layout(push_constant) uniform Uniforms {
|
||||||
|
int u_Index; // dynamically uniform
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_Color = vec4(texture(sampler2D(u_Textures[u_Index], u_Sampler), v_TexCoord).rgb, 1.0);
|
||||||
|
}
|
BIN
wgpu/examples/texture-arrays/uniform.frag.spv
Normal file
14
wgpu/examples/texture-arrays/unsized-non-uniform.frag
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
#extension GL_EXT_nonuniform_qualifier : require
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_TexCoord;
|
||||||
|
layout(location = 1) nonuniformEXT flat in int v_Index; // dynamically non-uniform
|
||||||
|
layout(location = 0) out vec4 o_Color;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform texture2D u_Textures[];
|
||||||
|
layout(set = 0, binding = 1) uniform sampler u_Sampler;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_Color = vec4(texture(sampler2D(u_Textures[v_Index], u_Sampler), v_TexCoord).rgb, 1.0);
|
||||||
|
}
|
BIN
wgpu/examples/texture-arrays/unsized-non-uniform.frag.spv
Normal file
24
wgpu/examples/water/README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Water example
|
||||||
|
|
||||||
|
This example renders animated water.
|
||||||
|
|
||||||
|
It demonstrates Read only Depth/Stencil (abbreviated RODS), where a depth/stencil buffer is used as an attachment which is read-only. In this case it's used in the shaders to calculate reflections and depth.
|
||||||
|
|
||||||
|
## Files:
|
||||||
|
```
|
||||||
|
water
|
||||||
|
├── main.rs ------------------ Main program
|
||||||
|
├── point_gen.rs ------------- Hexagon point generation
|
||||||
|
├── README.md ---------------- This readme
|
||||||
|
├── screenshot.png ----------- Screenshot
|
||||||
|
├── terrain.wgsl ------------- WGSL Shader for terrain
|
||||||
|
└── water.wgsl --------------- WGSL Shader for water
|
||||||
|
```
|
||||||
|
|
||||||
|
## To run
|
||||||
|
```
|
||||||
|
cargo run --example water
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
![Water example](./screenshot.png)
|
799
wgpu/examples/water/main.rs
Normal file
@ -0,0 +1,799 @@
|
|||||||
|
#[path = "../framework.rs"]
|
||||||
|
mod framework;
|
||||||
|
|
||||||
|
mod point_gen;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use cgmath::Point3;
|
||||||
|
use std::{borrow::Cow, iter, mem};
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Radius of the terrain.
|
||||||
|
///
|
||||||
|
/// Changing this value will change the size of the
|
||||||
|
/// water and terrain. Note however, that changes to
|
||||||
|
/// this value will require modification of the time
|
||||||
|
/// scale in the `render` method below.
|
||||||
|
///
|
||||||
|
const SIZE: f32 = 10.0;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Location of the camera.
|
||||||
|
/// Location of light is in terrain/water shaders.
|
||||||
|
///
|
||||||
|
const CAMERA: Point3<f32> = Point3 {
|
||||||
|
x: -100.0,
|
||||||
|
y: 50.0,
|
||||||
|
z: 100.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Matrices {
|
||||||
|
view: cgmath::Matrix4<f32>,
|
||||||
|
flipped_view: cgmath::Matrix4<f32>,
|
||||||
|
projection: cgmath::Matrix4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
|
||||||
|
struct TerrainUniforms {
|
||||||
|
view_projection: [f32; 16],
|
||||||
|
clipping_plane: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
|
||||||
|
struct WaterUniforms {
|
||||||
|
view: [f32; 16],
|
||||||
|
projection: [f32; 16],
|
||||||
|
time_size_width: [f32; 4],
|
||||||
|
height: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Uniforms {
|
||||||
|
terrain_normal: TerrainUniforms,
|
||||||
|
terrain_flipped: TerrainUniforms,
|
||||||
|
water: WaterUniforms,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
water_vertex_buf: wgpu::Buffer,
|
||||||
|
water_vertex_count: usize,
|
||||||
|
water_bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
water_bind_group: wgpu::BindGroup,
|
||||||
|
water_uniform_buf: wgpu::Buffer,
|
||||||
|
water_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
|
terrain_vertex_buf: wgpu::Buffer,
|
||||||
|
terrain_vertex_count: usize,
|
||||||
|
terrain_normal_bind_group: wgpu::BindGroup,
|
||||||
|
///
|
||||||
|
/// Binds to the uniform buffer where the
|
||||||
|
/// camera has been placed underwater.
|
||||||
|
///
|
||||||
|
terrain_flipped_bind_group: wgpu::BindGroup,
|
||||||
|
terrain_normal_uniform_buf: wgpu::Buffer,
|
||||||
|
///
|
||||||
|
/// Contains uniform variables where the camera
|
||||||
|
/// has been placed underwater.
|
||||||
|
///
|
||||||
|
terrain_flipped_uniform_buf: wgpu::Buffer,
|
||||||
|
terrain_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
|
reflect_view: wgpu::TextureView,
|
||||||
|
|
||||||
|
depth_buffer: wgpu::TextureView,
|
||||||
|
|
||||||
|
current_frame: usize,
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Used to prevent issues when rendering after
|
||||||
|
/// minimizing the window.
|
||||||
|
///
|
||||||
|
active: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
///
|
||||||
|
/// Creates the view matrices, and the corrected projection matrix.
|
||||||
|
///
|
||||||
|
fn generate_matrices(aspect_ratio: f32) -> Matrices {
|
||||||
|
let projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 10.0, 400.0);
|
||||||
|
let reg_view = cgmath::Matrix4::look_at_rh(
|
||||||
|
CAMERA,
|
||||||
|
cgmath::Point3::new(0f32, 0.0, 0.0),
|
||||||
|
cgmath::Vector3::unit_y(), //Note that y is up. Differs from other examples.
|
||||||
|
);
|
||||||
|
|
||||||
|
let scale = cgmath::Matrix4::from_nonuniform_scale(8.0, 1.5, 8.0);
|
||||||
|
|
||||||
|
let reg_view = reg_view * scale;
|
||||||
|
|
||||||
|
let flipped_view = cgmath::Matrix4::look_at_rh(
|
||||||
|
cgmath::Point3::new(CAMERA.x, -CAMERA.y, CAMERA.z),
|
||||||
|
cgmath::Point3::new(0f32, 0.0, 0.0),
|
||||||
|
cgmath::Vector3::unit_y(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let correction = framework::OPENGL_TO_WGPU_MATRIX;
|
||||||
|
|
||||||
|
let flipped_view = flipped_view * scale;
|
||||||
|
|
||||||
|
Matrices {
|
||||||
|
view: reg_view,
|
||||||
|
flipped_view,
|
||||||
|
projection: correction * projection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_uniforms(width: u32, height: u32) -> Uniforms {
|
||||||
|
let Matrices {
|
||||||
|
view,
|
||||||
|
flipped_view,
|
||||||
|
projection,
|
||||||
|
} = Self::generate_matrices(width as f32 / height as f32);
|
||||||
|
|
||||||
|
Uniforms {
|
||||||
|
terrain_normal: TerrainUniforms {
|
||||||
|
view_projection: *(projection * view).as_ref(),
|
||||||
|
clipping_plane: [0.0; 4],
|
||||||
|
},
|
||||||
|
terrain_flipped: TerrainUniforms {
|
||||||
|
view_projection: *(projection * flipped_view).as_ref(),
|
||||||
|
clipping_plane: [0., 1., 0., 0.],
|
||||||
|
},
|
||||||
|
water: WaterUniforms {
|
||||||
|
view: *view.as_ref(),
|
||||||
|
projection: *projection.as_ref(),
|
||||||
|
time_size_width: [0.0, 1.0, SIZE * 2.0, width as f32],
|
||||||
|
height: [height as f32, 0.0, 0.0, 0.0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Initializes Uniforms and textures.
|
||||||
|
///
|
||||||
|
fn initialize_resources(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
water_uniforms: &wgpu::Buffer,
|
||||||
|
terrain_normal_uniforms: &wgpu::Buffer,
|
||||||
|
terrain_flipped_uniforms: &wgpu::Buffer,
|
||||||
|
water_bind_group_layout: &wgpu::BindGroupLayout,
|
||||||
|
) -> (wgpu::TextureView, wgpu::TextureView, wgpu::BindGroup) {
|
||||||
|
// Matrices for our projection and view.
|
||||||
|
// flipped_view is the view from under the water.
|
||||||
|
let Uniforms {
|
||||||
|
terrain_normal,
|
||||||
|
terrain_flipped,
|
||||||
|
water,
|
||||||
|
} = Self::generate_uniforms(sc_desc.width, sc_desc.height);
|
||||||
|
|
||||||
|
// Put the uniforms into buffers on the GPU
|
||||||
|
queue.write_buffer(
|
||||||
|
terrain_normal_uniforms,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[terrain_normal]),
|
||||||
|
);
|
||||||
|
queue.write_buffer(
|
||||||
|
terrain_flipped_uniforms,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[terrain_flipped]),
|
||||||
|
);
|
||||||
|
queue.write_buffer(water_uniforms, 0, bytemuck::cast_slice(&[water]));
|
||||||
|
|
||||||
|
let texture_extent = wgpu::Extent3d {
|
||||||
|
width: sc_desc.width,
|
||||||
|
height: sc_desc.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let reflection_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("Reflection Render Texture"),
|
||||||
|
size: texture_extent,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: sc_desc.format,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED
|
||||||
|
| wgpu::TextureUsage::COPY_DST
|
||||||
|
| wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
});
|
||||||
|
|
||||||
|
let draw_depth_buffer = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("Depth Buffer"),
|
||||||
|
size: texture_extent,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
usage: wgpu::TextureUsage::SAMPLED
|
||||||
|
| wgpu::TextureUsage::COPY_DST
|
||||||
|
| wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
});
|
||||||
|
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("Texture Sampler"),
|
||||||
|
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::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let depth_view = draw_depth_buffer.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let water_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: water_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: water_uniforms.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(
|
||||||
|
&reflection_texture.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&depth_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("Water Bind Group"),
|
||||||
|
});
|
||||||
|
|
||||||
|
(
|
||||||
|
reflection_texture.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
depth_view,
|
||||||
|
water_bind_group,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl framework::Example for Example {
|
||||||
|
fn init(
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) -> Self {
|
||||||
|
// Size of one water vertex
|
||||||
|
let water_vertex_size = mem::size_of::<point_gen::WaterVertexAttributes>();
|
||||||
|
|
||||||
|
let water_vertices = point_gen::HexWaterMesh::generate(SIZE).generate_points();
|
||||||
|
|
||||||
|
// Size of one terrain vertex
|
||||||
|
let terrain_vertex_size = mem::size_of::<point_gen::TerrainVertexAttributes>();
|
||||||
|
|
||||||
|
// Noise generation
|
||||||
|
let terrain_noise = noise::OpenSimplex::new();
|
||||||
|
|
||||||
|
// Random colouration
|
||||||
|
let mut terrain_random = rand::thread_rng();
|
||||||
|
|
||||||
|
// Generate terrain. The closure determines what each hexagon will look like.
|
||||||
|
let terrain =
|
||||||
|
point_gen::HexTerrainMesh::generate(SIZE, |point| -> point_gen::TerrainVertex {
|
||||||
|
use noise::NoiseFn;
|
||||||
|
use rand::Rng;
|
||||||
|
let noise = terrain_noise.get([point[0] as f64 / 5.0, point[1] as f64 / 5.0]) + 0.1;
|
||||||
|
|
||||||
|
let y = noise as f32 * 8.0;
|
||||||
|
|
||||||
|
// Multiplies a colour by some random amount.
|
||||||
|
fn mul_arr(mut arr: [u8; 4], by: f32) -> [u8; 4] {
|
||||||
|
arr[0] = (arr[0] as f32 * by).min(255.0) as u8;
|
||||||
|
arr[1] = (arr[1] as f32 * by).min(255.0) as u8;
|
||||||
|
arr[2] = (arr[2] as f32 * by).min(255.0) as u8;
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Under water
|
||||||
|
const DARK_SAND: [u8; 4] = [235, 175, 71, 255];
|
||||||
|
// Coast
|
||||||
|
const SAND: [u8; 4] = [217, 191, 76, 255];
|
||||||
|
// Normal
|
||||||
|
const GRASS: [u8; 4] = [122, 170, 19, 255];
|
||||||
|
// Mountain
|
||||||
|
const SNOW: [u8; 4] = [175, 224, 237, 255];
|
||||||
|
|
||||||
|
// Random colouration.
|
||||||
|
let random = terrain_random.gen::<f32>() * 0.2 + 0.9;
|
||||||
|
|
||||||
|
// Choose colour.
|
||||||
|
let colour = if y <= 0.0 {
|
||||||
|
DARK_SAND
|
||||||
|
} else if y <= 0.8 {
|
||||||
|
SAND
|
||||||
|
} else if y <= 3.0 {
|
||||||
|
GRASS
|
||||||
|
} else {
|
||||||
|
SNOW
|
||||||
|
};
|
||||||
|
point_gen::TerrainVertex {
|
||||||
|
position: Point3 {
|
||||||
|
x: point[0],
|
||||||
|
y,
|
||||||
|
z: point[1],
|
||||||
|
},
|
||||||
|
colour: mul_arr(colour, random),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate the buffer data.
|
||||||
|
let terrain_vertices = terrain.make_buffer_data();
|
||||||
|
|
||||||
|
// Create the buffers on the GPU to hold the data.
|
||||||
|
let water_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Water vertices"),
|
||||||
|
contents: bytemuck::cast_slice(&water_vertices),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
let terrain_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Terrain vertices"),
|
||||||
|
contents: bytemuck::cast_slice(&terrain_vertices),
|
||||||
|
usage: wgpu::BufferUsage::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the bind group layout. This is what our uniforms will look like.
|
||||||
|
let water_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("Water Bind Group Layout"),
|
||||||
|
entries: &[
|
||||||
|
// Uniform variables such as projection/view.
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(
|
||||||
|
mem::size_of::<WaterUniforms>() as _,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Reflection texture.
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Depth texture for terrain.
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Sampler to be able to sample the textures.
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler {
|
||||||
|
comparison: false,
|
||||||
|
filtering: true,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let terrain_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("Terrain Bind Group Layout"),
|
||||||
|
entries: &[
|
||||||
|
// Regular uniform variables like view/projection.
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStage::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(
|
||||||
|
mem::size_of::<TerrainUniforms>() as _,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create our pipeline layouts.
|
||||||
|
let water_pipeline_layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("water"),
|
||||||
|
bind_group_layouts: &[&water_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let terrain_pipeline_layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("terrain"),
|
||||||
|
bind_group_layouts: &[&terrain_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let water_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Water Uniforms"),
|
||||||
|
size: mem::size_of::<WaterUniforms>() as _,
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let terrain_normal_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Normal Terrain Uniforms"),
|
||||||
|
size: mem::size_of::<TerrainUniforms>() as _,
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let terrain_flipped_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Flipped Terrain Uniforms"),
|
||||||
|
size: mem::size_of::<TerrainUniforms>() as _,
|
||||||
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group.
|
||||||
|
// This puts values behind what was laid out in the bind group layout.
|
||||||
|
|
||||||
|
let (reflect_view, depth_buffer, water_bind_group) = Self::initialize_resources(
|
||||||
|
sc_desc,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
&water_uniform_buf,
|
||||||
|
&terrain_normal_uniform_buf,
|
||||||
|
&terrain_flipped_uniform_buf,
|
||||||
|
&water_bind_group_layout,
|
||||||
|
);
|
||||||
|
|
||||||
|
let terrain_normal_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &terrain_bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: terrain_normal_uniform_buf.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: Some("Terrain Normal Bind Group"),
|
||||||
|
});
|
||||||
|
let terrain_flipped_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &terrain_bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: terrain_flipped_uniform_buf.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: Some("Terrain Flipped Bind Group"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upload/compile them to GPU code.
|
||||||
|
let mut flags = wgpu::ShaderFlags::VALIDATION;
|
||||||
|
match adapter.get_info().backend {
|
||||||
|
wgpu::Backend::Metal | wgpu::Backend::Vulkan => {
|
||||||
|
flags |= wgpu::ShaderFlags::EXPERIMENTAL_TRANSLATION
|
||||||
|
}
|
||||||
|
_ => (), //TODO
|
||||||
|
}
|
||||||
|
let terrain_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("terrain"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("terrain.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
let water_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("water"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("water.wgsl"))),
|
||||||
|
flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipelines. These describe how the data will flow through the GPU, and what
|
||||||
|
// constraints and modifiers it will have.
|
||||||
|
let water_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("water"),
|
||||||
|
// The "layout" is what uniforms will be needed.
|
||||||
|
layout: Some(&water_pipeline_layout),
|
||||||
|
// Vertex shader and input buffers
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &water_module,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
// Layout of our vertices. This should match the structs
|
||||||
|
// which are uploaded to the GPU. This should also be
|
||||||
|
// ensured by tagging on either a `#[repr(C)]` onto a
|
||||||
|
// struct, or a `#[repr(transparent)]` if it only contains
|
||||||
|
// one item, which is itself `repr(C)`.
|
||||||
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
|
array_stride: water_vertex_size as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &wgpu::vertex_attr_array![0 => Sint16x2, 1 => Sint8x4],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
// Fragment shader and output targets
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &water_module,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
// Describes how the colour will be interpolated
|
||||||
|
// and assigned to the output attachment.
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: sc_desc.format,
|
||||||
|
blend: Some(wgpu::BlendState {
|
||||||
|
color: wgpu::BlendComponent {
|
||||||
|
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||||
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||||
|
operation: wgpu::BlendOperation::Add,
|
||||||
|
},
|
||||||
|
alpha: wgpu::BlendComponent {
|
||||||
|
src_factor: wgpu::BlendFactor::One,
|
||||||
|
dst_factor: wgpu::BlendFactor::One,
|
||||||
|
operation: wgpu::BlendOperation::Max,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
write_mask: wgpu::ColorWrite::ALL,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
// How the triangles will be rasterized. This is more important
|
||||||
|
// for the terrain because of the beneath-the water shot.
|
||||||
|
// This is also dependent on how the triangles are being generated.
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
// What kind of data are we passing in?
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
front_face: wgpu::FrontFace::Cw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// Describes how us writing to the depth/stencil buffer
|
||||||
|
// will work. Since this is water, we need to read from the
|
||||||
|
// depth buffer both as a texture in the shader, and as an
|
||||||
|
// input attachment to do depth-testing. We don't write, so
|
||||||
|
// depth_write_enabled is set to false. This is called
|
||||||
|
// RODS or read-only depth stencil.
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
// We don't use stencil.
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
depth_write_enabled: false,
|
||||||
|
depth_compare: wgpu::CompareFunction::Less,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
// No multisampling is used.
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Same idea as the water pipeline.
|
||||||
|
let terrain_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("terrain"),
|
||||||
|
layout: Some(&terrain_pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &terrain_module,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
|
array_stride: terrain_vertex_size as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Unorm8x4],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &terrain_module,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[sc_desc.format.into()],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Front),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::Less,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Done
|
||||||
|
Example {
|
||||||
|
water_vertex_buf,
|
||||||
|
water_vertex_count: water_vertices.len(),
|
||||||
|
water_bind_group_layout,
|
||||||
|
water_bind_group,
|
||||||
|
water_uniform_buf,
|
||||||
|
water_pipeline,
|
||||||
|
|
||||||
|
terrain_vertex_buf,
|
||||||
|
terrain_vertex_count: terrain_vertices.len(),
|
||||||
|
terrain_normal_bind_group,
|
||||||
|
terrain_flipped_bind_group,
|
||||||
|
terrain_normal_uniform_buf,
|
||||||
|
terrain_flipped_uniform_buf,
|
||||||
|
terrain_pipeline,
|
||||||
|
|
||||||
|
reflect_view,
|
||||||
|
|
||||||
|
depth_buffer,
|
||||||
|
|
||||||
|
current_frame: 0,
|
||||||
|
|
||||||
|
active: Some(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _event: winit::event::WindowEvent) {
|
||||||
|
//empty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
&mut self,
|
||||||
|
sc_desc: &wgpu::SwapChainDescriptor,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
) {
|
||||||
|
if sc_desc.width == 0 && sc_desc.height == 0 {
|
||||||
|
// Stop rendering altogether.
|
||||||
|
self.active = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.active = Some(self.current_frame);
|
||||||
|
|
||||||
|
// Regenerate all of the buffers and textures.
|
||||||
|
|
||||||
|
let (reflect_view, depth_buffer, water_bind_group) = Self::initialize_resources(
|
||||||
|
sc_desc,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
&self.water_uniform_buf,
|
||||||
|
&self.terrain_normal_uniform_buf,
|
||||||
|
&self.terrain_flipped_uniform_buf,
|
||||||
|
&self.water_bind_group_layout,
|
||||||
|
);
|
||||||
|
self.water_bind_group = water_bind_group;
|
||||||
|
|
||||||
|
self.depth_buffer = depth_buffer;
|
||||||
|
self.reflect_view = reflect_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::eq_op)]
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &wgpu::SwapChainTexture,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_spawner: &framework::Spawner,
|
||||||
|
) {
|
||||||
|
// Increment frame count regardless of if we draw.
|
||||||
|
self.current_frame += 1;
|
||||||
|
let back_color = wgpu::Color {
|
||||||
|
r: 161.0 / 255.0,
|
||||||
|
g: 246.0 / 255.0,
|
||||||
|
b: 255.0 / 255.0,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write the sin/cos values to the uniform buffer for the water.
|
||||||
|
let (water_sin, water_cos) = ((self.current_frame as f32) / 600.0).sin_cos();
|
||||||
|
queue.write_buffer(
|
||||||
|
&self.water_uniform_buf,
|
||||||
|
mem::size_of::<[f32; 16]>() as wgpu::BufferAddress * 2,
|
||||||
|
bytemuck::cast_slice(&[water_sin, water_cos]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only render valid frames. See resize method.
|
||||||
|
if let Some(active) = self.active {
|
||||||
|
if active >= self.current_frame {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The encoder provides a way to turn our instructions here, into
|
||||||
|
// a command buffer the GPU can understand.
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("Main Command Encoder"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// First pass: render the reflection.
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &self.reflect_view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(back_color),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
// We still need to use the depth buffer here
|
||||||
|
// since the pipeline requires it.
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_buffer,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(1.0),
|
||||||
|
store: true,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(&self.terrain_pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.terrain_flipped_bind_group, &[]);
|
||||||
|
rpass.set_vertex_buffer(0, self.terrain_vertex_buf.slice(..));
|
||||||
|
rpass.draw(0..self.terrain_vertex_count as u32, 0..1);
|
||||||
|
}
|
||||||
|
// Terrain right side up. This time we need to use the
|
||||||
|
// depth values, so we must use StoreOp::Store.
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(back_color),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_buffer,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(1.0),
|
||||||
|
store: true,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(&self.terrain_pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.terrain_normal_bind_group, &[]);
|
||||||
|
rpass.set_vertex_buffer(0, self.terrain_vertex_buf.slice(..));
|
||||||
|
rpass.draw(0..self.terrain_vertex_count as u32, 0..1);
|
||||||
|
}
|
||||||
|
// Render the water. This reads from the depth buffer, but does not write
|
||||||
|
// to it, so it cannot be in the same render pass.
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: &frame.view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_buffer,
|
||||||
|
depth_ops: None,
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.water_pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.water_bind_group, &[]);
|
||||||
|
rpass.set_vertex_buffer(0, self.water_vertex_buf.slice(..));
|
||||||
|
rpass.draw(0..self.water_vertex_count as u32, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(iter::once(encoder.finish()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
framework::run::<Example>("water");
|
||||||
|
}
|
286
wgpu/examples/water/point_gen.rs
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
//!
|
||||||
|
//! This module covers generating points in a hexagonal fashion.
|
||||||
|
//!
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use cgmath::{InnerSpace, Point3, Vector3};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// The following constants are used in calculations.
|
||||||
|
// A and B are multiplication factors for x and y.
|
||||||
|
|
||||||
|
///
|
||||||
|
/// X multiplication factor.
|
||||||
|
/// 1.0 / sqrt(2)
|
||||||
|
///
|
||||||
|
const A: f32 = std::f32::consts::FRAC_1_SQRT_2;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Y multiplication factor.
|
||||||
|
/// sqrt(3) / sqrt(2) == sqrt(1.5)
|
||||||
|
///
|
||||||
|
const B: f32 = SQRT_3 * A;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// `sin(45deg)` is used to rotate the points.
|
||||||
|
///
|
||||||
|
const S45: f32 = std::f32::consts::FRAC_1_SQRT_2;
|
||||||
|
///
|
||||||
|
/// `cos(45deg)` is used to rotate the points.
|
||||||
|
///
|
||||||
|
const C45: f32 = S45;
|
||||||
|
|
||||||
|
const SQRT_3: f32 = 1.7320508;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
|
||||||
|
pub struct TerrainVertexAttributes {
|
||||||
|
position: [f32; 3],
|
||||||
|
normal: [f32; 3],
|
||||||
|
colour: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
|
||||||
|
pub struct WaterVertexAttributes {
|
||||||
|
position: [i16; 2],
|
||||||
|
offsets: [i8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Represents the center of a single hexagon.
|
||||||
|
///
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct TerrainVertex {
|
||||||
|
pub position: Point3<f32>,
|
||||||
|
pub colour: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Gets the surrounding hexagonal points from a point.
|
||||||
|
///
|
||||||
|
/// +---0---1
|
||||||
|
/// | / | |
|
||||||
|
/// 5---p---2
|
||||||
|
/// | | / |
|
||||||
|
/// 4---3---+
|
||||||
|
///
|
||||||
|
fn surrounding_hexagonal_points(x: isize, y: isize) -> [(isize, isize); 6] {
|
||||||
|
[
|
||||||
|
(x, y - 1),
|
||||||
|
(x + 1, y - 1),
|
||||||
|
(x + 1, y),
|
||||||
|
(x, y + 1),
|
||||||
|
(x - 1, y + 1),
|
||||||
|
(x - 1, y),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surrounding_point_values_iter<T>(
|
||||||
|
hashmap: &HashMap<(isize, isize), T>,
|
||||||
|
x: isize,
|
||||||
|
y: isize,
|
||||||
|
for_each: impl FnMut((&T, &T)),
|
||||||
|
) {
|
||||||
|
let points = surrounding_hexagonal_points(x, y);
|
||||||
|
let points = [
|
||||||
|
points[0], points[1], points[2], points[3], points[4], points[5], points[0],
|
||||||
|
];
|
||||||
|
points
|
||||||
|
.windows(2)
|
||||||
|
.map(|x| (hashmap.get(&x[0]), hashmap.get(&x[1])))
|
||||||
|
.flat_map(|(a, b)| a.and_then(|x| b.map(|y| (x, y))))
|
||||||
|
.for_each(for_each);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Used in calculating terrain normals.
|
||||||
|
///
|
||||||
|
pub fn calculate_normal(a: Point3<f32>, b: Point3<f32>, c: Point3<f32>) -> Vector3<f32> {
|
||||||
|
(b - a).normalize().cross((c - a).normalize()).normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Given the radius, how large of a square do we need to make a unit hexagon grid?
|
||||||
|
///
|
||||||
|
fn q_given_r(radius: f32) -> usize {
|
||||||
|
((((((4.0 * radius) / SQRT_3) + 1.0).floor() / 2.0).floor() * 2.0) + 1.0) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Represents terrain, however it contains the vertices only once.
|
||||||
|
///
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HexTerrainMesh {
|
||||||
|
pub vertices: HashMap<(isize, isize), TerrainVertex>,
|
||||||
|
half_size: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HexTerrainMesh {
|
||||||
|
///
|
||||||
|
/// Generates the vertices (or the centers of the hexagons). The colour and height is determined by
|
||||||
|
/// a function passed in by the user.
|
||||||
|
///
|
||||||
|
pub fn generate(radius: f32, mut gen_vertex: impl FnMut([f32; 2]) -> TerrainVertex) -> Self {
|
||||||
|
let width = q_given_r(radius);
|
||||||
|
let half_width = (width / 2) as isize;
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
let mut max = std::f32::NEG_INFINITY;
|
||||||
|
for i in -half_width..=half_width {
|
||||||
|
let x_o = i as f32;
|
||||||
|
for j in -half_width..=half_width {
|
||||||
|
let y_o = j as f32;
|
||||||
|
let x = A * (x_o * C45 - y_o * S45);
|
||||||
|
let z = B * (x_o * S45 + y_o * C45);
|
||||||
|
if x.hypot(z) < radius {
|
||||||
|
let vertex = gen_vertex([x, z]);
|
||||||
|
if vertex.position.y > max {
|
||||||
|
max = vertex.position.y;
|
||||||
|
}
|
||||||
|
map.insert((i, j), vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
vertices: map,
|
||||||
|
half_size: width as isize / 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Creates the points required to render the mesh.
|
||||||
|
///
|
||||||
|
pub fn make_buffer_data(&self) -> Vec<TerrainVertexAttributes> {
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
fn middle(p1: &TerrainVertex, p2: &TerrainVertex, p: &TerrainVertex) -> Point3<f32> {
|
||||||
|
Point3 {
|
||||||
|
x: (p1.position.x + p2.position.x + p.position.x) / 3.0,
|
||||||
|
y: (p1.position.y + p2.position.y + p.position.y) / 3.0,
|
||||||
|
z: (p1.position.z + p2.position.z + p.position.z) / 3.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn half(p1: &TerrainVertex, p2: &TerrainVertex) -> Point3<f32> {
|
||||||
|
Point3 {
|
||||||
|
x: (p1.position.x + p2.position.x) / 2.0,
|
||||||
|
y: (p1.position.y + p2.position.y) / 2.0,
|
||||||
|
z: (p1.position.z + p2.position.z) / 2.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut push_triangle = |p1: &TerrainVertex,
|
||||||
|
p2: &TerrainVertex,
|
||||||
|
p: &TerrainVertex,
|
||||||
|
c: [u8; 4]| {
|
||||||
|
let m = middle(p1, p2, p);
|
||||||
|
let ap = half(p1, p);
|
||||||
|
let bp = half(p2, p);
|
||||||
|
let p = p.position;
|
||||||
|
let n1 = calculate_normal(ap, m, p);
|
||||||
|
let n2 = calculate_normal(m, bp, p);
|
||||||
|
|
||||||
|
vertices.extend(
|
||||||
|
[ap, m, p, m, bp, p]
|
||||||
|
.iter()
|
||||||
|
.zip(
|
||||||
|
std::iter::repeat::<[f32; 3]>(n1.into())
|
||||||
|
.chain(std::iter::repeat::<[f32; 3]>(n2.into())),
|
||||||
|
)
|
||||||
|
.zip(std::iter::repeat(c))
|
||||||
|
.map(|((pos, normal), colour)| TerrainVertexAttributes {
|
||||||
|
position: *pos.as_ref(),
|
||||||
|
normal,
|
||||||
|
colour,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
for i in -self.half_size..=self.half_size {
|
||||||
|
for j in -self.half_size..=self.half_size {
|
||||||
|
if let Some(p) = self.vertices.get(&(i, j)) {
|
||||||
|
surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| {
|
||||||
|
push_triangle(a, b, p, p.colour)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vertices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Water mesh which contains vertex data for the water mesh.
|
||||||
|
///
|
||||||
|
/// It stores the values multiplied and rounded to the
|
||||||
|
/// nearest whole number to be more efficient with space when
|
||||||
|
/// sending large meshes to the GPU.
|
||||||
|
///
|
||||||
|
pub struct HexWaterMesh {
|
||||||
|
pub vertices: HashMap<(isize, isize), [i16; 2]>,
|
||||||
|
half_size: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HexWaterMesh {
|
||||||
|
pub fn generate(radius: f32) -> Self {
|
||||||
|
let width = q_given_r(radius);
|
||||||
|
let half_width = (width / 2) as isize;
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for i in -half_width..=half_width {
|
||||||
|
let x_o = i as f32;
|
||||||
|
for j in -half_width..=half_width {
|
||||||
|
let y_o = j as f32;
|
||||||
|
let x = A * (x_o * C45 - y_o * S45);
|
||||||
|
let z = B * (x_o * S45 + y_o * C45);
|
||||||
|
if x.hypot(z) < radius {
|
||||||
|
let x = (x * 2.0).round() as i16;
|
||||||
|
let z = ((z / B) * std::f32::consts::SQRT_2).round() as i16;
|
||||||
|
map.insert((i, j), [x, z]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
vertices: map,
|
||||||
|
half_size: half_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///
|
||||||
|
/// Generates the points required to render the mesh.
|
||||||
|
///
|
||||||
|
pub fn generate_points(&self) -> Vec<WaterVertexAttributes> {
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
|
||||||
|
fn calculate_differences(a: [i16; 2], b: [i16; 2], c: [i16; 2]) -> [i8; 4] {
|
||||||
|
[
|
||||||
|
(b[0] - a[0]) as i8,
|
||||||
|
(b[1] - a[1]) as i8,
|
||||||
|
(c[0] - a[0]) as i8,
|
||||||
|
(c[1] - a[1]) as i8,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut push_triangle = |a: [i16; 2], b: [i16; 2], c: [i16; 2]| {
|
||||||
|
let bc = calculate_differences(a, b, c);
|
||||||
|
let ca = calculate_differences(b, c, a);
|
||||||
|
let ab = calculate_differences(c, a, b);
|
||||||
|
|
||||||
|
vertices.extend(
|
||||||
|
[a, b, c]
|
||||||
|
.iter()
|
||||||
|
.zip([bc, ca, ab].iter())
|
||||||
|
.map(|(&position, &offsets)| WaterVertexAttributes { position, offsets }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in -self.half_size..=self.half_size {
|
||||||
|
for j in -self.half_size..=self.half_size {
|
||||||
|
if (i - j) % 3 == 0 {
|
||||||
|
if let Some(&p) = self.vertices.get(&(i, j)) {
|
||||||
|
surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| {
|
||||||
|
push_triangle(*a, *b, p)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices
|
||||||
|
}
|
||||||
|
}
|
BIN
wgpu/examples/water/screenshot.png
Normal file
After Width: | Height: | Size: 196 KiB |
49
wgpu/examples/water/terrain.wgsl
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
[[block]]
|
||||||
|
struct Uniforms {
|
||||||
|
projection_view: mat4x4<f32>;
|
||||||
|
clipping_plane: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
let light: vec3<f32> = vec3<f32>(150.0, 70.0, 0.0);
|
||||||
|
let light_colour: vec3<f32> = vec3<f32>(1.0, 0.98, 0.82);
|
||||||
|
let ambient: f32 = 0.2;
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] colour: vec4<f32>;
|
||||||
|
// Comment this out if using user-clipping planes:
|
||||||
|
[[location(1)]] clip_dist: f32;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main(
|
||||||
|
[[location(0)]] position: vec3<f32>,
|
||||||
|
[[location(1)]] normal: vec3<f32>,
|
||||||
|
[[location(2)]] colour: vec4<f32>,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.position = uniforms.projection_view * vec4<f32>(position, 1.0);
|
||||||
|
|
||||||
|
// https://www.desmos.com/calculator/nqgyaf8uvo
|
||||||
|
let normalized_light_direction = normalize(position - light);
|
||||||
|
let brightness_diffuse = clamp(dot(normalized_light_direction, normal), 0.2, 1.0);
|
||||||
|
|
||||||
|
out.colour = vec4<f32>(max((brightness_diffuse + ambient) * light_colour * colour.rgb, vec3<f32>(0.0, 0.0, 0.0)), colour.a);
|
||||||
|
out.clip_dist = dot(vec4<f32>(position, 1.0), uniforms.clipping_plane);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment), early_depth_test]]
|
||||||
|
fn fs_main(
|
||||||
|
in: VertexOutput,
|
||||||
|
) -> [[location(0)]] vec4<f32> {
|
||||||
|
// Comment this out if using user-clipping planes:
|
||||||
|
if(in.clip_dist < 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec4<f32>(in.colour.xyz, 1.0);
|
||||||
|
}
|
252
wgpu/examples/water/water.wgsl
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
[[block]]
|
||||||
|
struct Uniforms {
|
||||||
|
view: mat4x4<f32>;
|
||||||
|
projection: mat4x4<f32>;
|
||||||
|
time_size_width: vec4<f32>;
|
||||||
|
viewport_height: f32;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]] var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
let light_point: vec3<f32> = vec3<f32>(150.0, 70.0, 0.0);
|
||||||
|
let light_colour: vec3<f32> = vec3<f32>(1.0, 0.98, 0.82);
|
||||||
|
let one: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
let Y_SCL: f32 = 0.86602540378443864676372317075294;
|
||||||
|
let CURVE_BIAS: f32 = -0.1;
|
||||||
|
let INV_1_CURVE_BIAS: f32 = 1.11111111111; //1.0 / (1.0 + CURVE_BIAS);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The following code to calculate simplex 3D
|
||||||
|
// is from https://github.com/ashima/webgl-noise
|
||||||
|
//
|
||||||
|
// Simplex 3D Noise
|
||||||
|
// by Ian McEwan, Ashima Arts.
|
||||||
|
//
|
||||||
|
fn permute(x: vec4<f32>) -> vec4<f32> {
|
||||||
|
var temp: vec4<f32> = 289.0 * one;
|
||||||
|
return modf(((x*34.0) + one) * x, &temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn taylorInvSqrt(r: vec4<f32>) -> vec4<f32> {
|
||||||
|
return 1.79284291400159 * one - 0.85373472095314 * r;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snoise(v: vec3<f32>) -> f32 {
|
||||||
|
let C = vec2<f32>(1.0/6.0, 1.0/3.0);
|
||||||
|
let D = vec4<f32>(0.0, 0.5, 1.0, 2.0);
|
||||||
|
|
||||||
|
// First corner
|
||||||
|
//TODO: use the splat operations when available
|
||||||
|
let vCy = dot(v, C.yyy);
|
||||||
|
var i: vec3<f32> = floor(v + vec3<f32>(vCy, vCy, vCy));
|
||||||
|
let iCx = dot(i, C.xxx);
|
||||||
|
let x0 = v - i + vec3<f32>(iCx, iCx, iCx);
|
||||||
|
|
||||||
|
// Other corners
|
||||||
|
let g = step(x0.yzx, x0.xyz);
|
||||||
|
let l = (vec3<f32>(1.0, 1.0, 1.0) - g).zxy;
|
||||||
|
let i1 = min(g, l);
|
||||||
|
let i2 = max(g, l);
|
||||||
|
|
||||||
|
// x0 = x0 - 0.0 + 0.0 * C.xxx;
|
||||||
|
// x1 = x0 - i1 + 1.0 * C.xxx;
|
||||||
|
// x2 = x0 - i2 + 2.0 * C.xxx;
|
||||||
|
// x3 = x0 - 1.0 + 3.0 * C.xxx;
|
||||||
|
let x1 = x0 - i1 + C.xxx;
|
||||||
|
let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
|
||||||
|
let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
|
||||||
|
|
||||||
|
// Permutations
|
||||||
|
var temp: vec3<f32> = 289.0 * one.xyz;
|
||||||
|
i = modf(i, &temp);
|
||||||
|
let p = permute(
|
||||||
|
permute(
|
||||||
|
permute(i.zzzz + vec4<f32>(0.0, i1.z, i2.z, 1.0))
|
||||||
|
+ i.yyyy + vec4<f32>(0.0, i1.y, i2.y, 1.0))
|
||||||
|
+ i.xxxx + vec4<f32>(0.0, i1.x, i2.x, 1.0));
|
||||||
|
|
||||||
|
// Gradients: 7x7 points over a square, mapped onto an octahedron.
|
||||||
|
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
|
||||||
|
let n_ = 0.142857142857;// 1.0/7.0
|
||||||
|
let ns = n_ * D.wyz - D.xzx;
|
||||||
|
|
||||||
|
let j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7)
|
||||||
|
|
||||||
|
let x_ = floor(j * ns.z);
|
||||||
|
let y_ = floor(j - 7.0 * x_);// mod(j,N)
|
||||||
|
|
||||||
|
var x: vec4<f32> = x_ *ns.x + ns.yyyy;
|
||||||
|
var y: vec4<f32> = y_ *ns.x + ns.yyyy;
|
||||||
|
let h = one - abs(x) - abs(y);
|
||||||
|
|
||||||
|
let b0 = vec4<f32>(x.xy, y.xy);
|
||||||
|
let b1 = vec4<f32>(x.zw, y.zw);
|
||||||
|
|
||||||
|
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - one;
|
||||||
|
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - one;
|
||||||
|
let s0 = floor(b0)*2.0 + one;
|
||||||
|
let s1 = floor(b1)*2.0 + one;
|
||||||
|
let sh = -step(h, 0.0 * one);
|
||||||
|
|
||||||
|
let a0 = b0.xzyw + s0.xzyw*sh.xxyy;
|
||||||
|
let a1 = b1.xzyw + s1.xzyw*sh.zzww;
|
||||||
|
|
||||||
|
var p0: vec3<f32> = vec3<f32>(a0.xy, h.x);
|
||||||
|
var p1: vec3<f32> = vec3<f32>(a0.zw, h.y);
|
||||||
|
var p2: vec3<f32> = vec3<f32>(a1.xy, h.z);
|
||||||
|
var p3: vec3<f32> = vec3<f32>(a1.zw, h.w);
|
||||||
|
|
||||||
|
//Normalise gradients
|
||||||
|
let norm = taylorInvSqrt(vec4<f32>(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
|
||||||
|
p0 = p0 * norm.x;
|
||||||
|
p1 = p1 * norm.y;
|
||||||
|
p2 = p2 * norm.z;
|
||||||
|
p3 = p3 * norm.w;
|
||||||
|
|
||||||
|
// Mix final noise value
|
||||||
|
var m: vec4<f32> = max(0.6 * one - vec4<f32>(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0 * one);
|
||||||
|
m = m * m;
|
||||||
|
return 9.0 * dot(m*m, vec4<f32>(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of 3D simplex code.
|
||||||
|
|
||||||
|
fn apply_distortion(pos: vec3<f32>) -> vec3<f32> {
|
||||||
|
var perlin_pos: vec3<f32> = pos;
|
||||||
|
|
||||||
|
//Do noise transformation to permit for smooth,
|
||||||
|
//continuous movement.
|
||||||
|
|
||||||
|
//TODO: we should be able to name them `sin` and `cos`.
|
||||||
|
let sn = uniforms.time_size_width.x;
|
||||||
|
let cs = uniforms.time_size_width.y;
|
||||||
|
let size = uniforms.time_size_width.z;
|
||||||
|
|
||||||
|
// Rotate 90 Z, Move Left Size / 2
|
||||||
|
perlin_pos = vec3<f32>(perlin_pos.y - perlin_pos.x - size, perlin_pos.x, perlin_pos.z);
|
||||||
|
|
||||||
|
let xcos = perlin_pos.x * cs;
|
||||||
|
let xsin = perlin_pos.x * sn;
|
||||||
|
let ycos = perlin_pos.y * cs;
|
||||||
|
let ysin = perlin_pos.y * sn;
|
||||||
|
let zcos = perlin_pos.z * cs;
|
||||||
|
let zsin = perlin_pos.z * sn;
|
||||||
|
|
||||||
|
// Rotate Time Y
|
||||||
|
let perlin_pos_y = vec3<f32>(xcos + zsin, perlin_pos.y, -xsin + xcos);
|
||||||
|
|
||||||
|
// Rotate Time Z
|
||||||
|
let perlin_pos_z = vec3<f32>(xcos - ysin, xsin + ycos, perlin_pos.x);
|
||||||
|
|
||||||
|
// Rotate 90 Y
|
||||||
|
perlin_pos = vec3<f32>(perlin_pos.z - perlin_pos.x, perlin_pos.y, perlin_pos.x);
|
||||||
|
|
||||||
|
// Rotate Time X
|
||||||
|
let perlin_pos_x = vec3<f32>(perlin_pos.x, ycos - zsin, ysin + zcos);
|
||||||
|
|
||||||
|
// Sample at different places for x/y/z to get random-looking water.
|
||||||
|
return vec3<f32>(
|
||||||
|
//TODO: use splats
|
||||||
|
pos.x + snoise(perlin_pos_x + 2.0*one.xxx) * 0.4,
|
||||||
|
pos.y + snoise(perlin_pos_y - 2.0*one.xxx) * 1.8,
|
||||||
|
pos.z + snoise(perlin_pos_z) * 0.4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply the input by the scale values.
|
||||||
|
fn make_position(original: vec2<f32>) -> vec4<f32> {
|
||||||
|
let interpreted = vec3<f32>(original.x * 0.5, 0.0, original.y * Y_SCL);
|
||||||
|
return vec4<f32>(apply_distortion(interpreted), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the normal, and apply the curve. Change the Curve Bias above.
|
||||||
|
fn make_normal(a: vec3<f32>, b: vec3<f32>, c: vec3<f32>) -> vec3<f32> {
|
||||||
|
let norm = normalize(cross(b - c, a - c));
|
||||||
|
let center = (a + b + c) * (1.0 / 3.0); //TODO: use splat
|
||||||
|
return (normalize(a - center) * CURVE_BIAS + norm) * INV_1_CURVE_BIAS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the fresnel effect.
|
||||||
|
fn calc_fresnel(view: vec3<f32>, normal: vec3<f32>) -> f32 {
|
||||||
|
var refractive: f32 = abs(dot(view, normal));
|
||||||
|
refractive = pow(refractive, 1.33333333333);
|
||||||
|
return refractive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the specular lighting.
|
||||||
|
fn calc_specular(eye: vec3<f32>, normal: vec3<f32>, light: vec3<f32>) -> f32 {
|
||||||
|
let light_reflected = reflect(light, normal);
|
||||||
|
var specular: f32 = max(dot(eye, light_reflected), 0.0);
|
||||||
|
specular = pow(specular, 10.0);
|
||||||
|
return specular;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
[[location(0)]] f_WaterScreenPos: vec2<f32>;
|
||||||
|
[[location(1)]] f_Fresnel: f32;
|
||||||
|
[[location(2)]] f_Light: vec3<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vs_main(
|
||||||
|
[[location(0)]] position: vec2<i32>,
|
||||||
|
[[location(1)]] offsets: vec4<i32>,
|
||||||
|
) -> VertexOutput {
|
||||||
|
let p_pos = vec2<f32>(position);
|
||||||
|
let b_pos = make_position(p_pos + vec2<f32>(offsets.xy));
|
||||||
|
let c_pos = make_position(p_pos + vec2<f32>(offsets.zw));
|
||||||
|
let a_pos = make_position(p_pos);
|
||||||
|
let original_pos = vec4<f32>(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0);
|
||||||
|
|
||||||
|
let vm = uniforms.view;
|
||||||
|
let transformed_pos = vm * a_pos;
|
||||||
|
//TODO: use vector splats for division
|
||||||
|
let water_pos = transformed_pos.xyz * (1.0 / transformed_pos.w);
|
||||||
|
let normal = make_normal((vm * a_pos).xyz, (vm * b_pos).xyz, (vm * c_pos).xyz);
|
||||||
|
let eye = normalize(-water_pos);
|
||||||
|
let transformed_light = vm * vec4<f32>(light_point, 1.0);
|
||||||
|
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.f_Light = light_colour * calc_specular(eye, normal, normalize(water_pos.xyz - (transformed_light.xyz * (1.0 / transformed_light.w))));
|
||||||
|
out.f_Fresnel = calc_fresnel(eye, normal);
|
||||||
|
|
||||||
|
let gridpos = uniforms.projection * vm * original_pos;
|
||||||
|
out.f_WaterScreenPos = (0.5 * gridpos.xy * (1.0 / gridpos.w)) + vec2<f32>(0.5, 0.5);
|
||||||
|
|
||||||
|
out.position = uniforms.projection * transformed_pos;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let water_colour: vec3<f32> = vec3<f32>(0.0, 0.46, 0.95);
|
||||||
|
let zNear: f32 = 10.0;
|
||||||
|
let zFar: f32 = 400.0;
|
||||||
|
|
||||||
|
[[group(0), binding(1)]] var reflection: texture_2d<f32>;
|
||||||
|
[[group(0), binding(2)]] var terrain_depth_tex: texture_2d<f32>;
|
||||||
|
[[group(0), binding(3)]] var colour_sampler: sampler;
|
||||||
|
|
||||||
|
fn to_linear_depth(depth: f32) -> f32 {
|
||||||
|
let z_n: f32 = 2.0 * depth - 1.0;
|
||||||
|
let z_e: f32 = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
|
||||||
|
return z_e;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
|
let reflection_colour = textureSample(reflection, colour_sampler, in.f_WaterScreenPos.xy).xyz;
|
||||||
|
|
||||||
|
let pixel_depth = to_linear_depth(in.position.z);
|
||||||
|
let normalized_coords = in.position.xy / vec2<f32>(uniforms.time_size_width.w, uniforms.viewport_height);
|
||||||
|
let terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, colour_sampler, normalized_coords).r);
|
||||||
|
|
||||||
|
let dist = terrain_depth - pixel_depth;
|
||||||
|
let clamped = pow(smoothStep(0.0, 1.5, dist), 4.8);
|
||||||
|
|
||||||
|
let final_colour = in.f_Light + reflection_colour;
|
||||||
|
let t = smoothStep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()?
|
||||||
|
let depth_colour = mix(final_colour, water_colour, vec3<f32>(t, t, t));
|
||||||
|
|
||||||
|
return vec4<f32>(depth_colour, clamped * (1.0 - in.f_Fresnel));
|
||||||
|
}
|
BIN
wgpu/logo.png
Normal file
After Width: | Height: | Size: 37 KiB |
0
wgpu/rustfmt.toml
Normal file
1972
wgpu/src/backend/direct.rs
Normal file
333
wgpu/src/backend/error.rs
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct ContextError {
|
||||||
|
pub string: &'static str,
|
||||||
|
pub cause: Box<dyn Error + Send + Sync + 'static>,
|
||||||
|
pub label_key: &'static str,
|
||||||
|
pub label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ContextError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "In {}", self.string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ContextError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
Some(self.cause.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Context {
|
||||||
|
pub(super) fn format_error(&self, err: &(impl Error + 'static)) -> String {
|
||||||
|
let mut err_descs = vec![self.format_pretty_any(err)];
|
||||||
|
|
||||||
|
let mut source_opt = err.source();
|
||||||
|
while let Some(source) = source_opt {
|
||||||
|
err_descs.push(self.format_pretty_any(source));
|
||||||
|
source_opt = source.source();
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("Validation Error\n\nCaused by:\n{}", err_descs.join(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_pretty_any(&self, error: &(dyn Error + 'static)) -> String {
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<ContextError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::RenderCommandError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::binding_model::CreateBindGroupError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) =
|
||||||
|
error.downcast_ref::<wgc::binding_model::CreatePipelineLayoutError>()
|
||||||
|
{
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::ExecutionError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::RenderPassErrorInner>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::RenderPassError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::ComputePassErrorInner>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::ComputePassError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::RenderBundleError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
if let Some(pretty_err) = error.downcast_ref::<wgc::command::TransferError>() {
|
||||||
|
return pretty_err.fmt_pretty(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
format_error_line(error.as_display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn format_error_line(err: &dyn fmt::Display) -> String {
|
||||||
|
format!(" {}\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn format_note_line(note: &dyn fmt::Display) -> String {
|
||||||
|
format!(" note: {}\n", note)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn format_label_line(label_key: &str, label_value: &str) -> String {
|
||||||
|
if label_key.is_empty() || label_value.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format_note_line(&format!("{} = `{}`", label_key, label_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait AsDisplay {
|
||||||
|
fn as_display(&self) -> &dyn fmt::Display;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: fmt::Display> AsDisplay for T {
|
||||||
|
fn as_display(&self) -> &dyn fmt::Display {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PrettyError: Error {
|
||||||
|
fn fmt_pretty(&self, _context: &super::Context) -> String {
|
||||||
|
format_error_line(self.as_display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for ContextError {
|
||||||
|
fn fmt_pretty(&self, _context: &super::Context) -> String {
|
||||||
|
format_error_line(self.as_display()) + &format_label_line(self.label_key, &self.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::RenderCommandError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
match *self {
|
||||||
|
Self::InvalidBindGroup(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.bind_group_label(id));
|
||||||
|
ret.push_str(&format_label_line("bind group", &name));
|
||||||
|
}
|
||||||
|
Self::InvalidPipeline(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.render_pipeline_label(id));
|
||||||
|
ret.push_str(&format_label_line("render pipeline", &name));
|
||||||
|
}
|
||||||
|
Self::Buffer(id, ..) | Self::DestroyedBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
ret.push_str(&format_label_line("buffer", &name));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PrettyError for wgc::binding_model::CreateBindGroupError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
match *self {
|
||||||
|
Self::InvalidBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
ret.push_str(&format_label_line("buffer", &name));
|
||||||
|
}
|
||||||
|
Self::InvalidTextureView(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.texture_view_label(id));
|
||||||
|
ret.push_str(&format_label_line("texture view", &name));
|
||||||
|
}
|
||||||
|
Self::InvalidSampler(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.sampler_label(id));
|
||||||
|
ret.push_str(&format_label_line("sampler", &name));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::binding_model::CreatePipelineLayoutError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
if let Self::InvalidBindGroupLayout(id) = *self {
|
||||||
|
let name = wgc::gfx_select!(id => global.bind_group_layout_label(id));
|
||||||
|
ret.push_str(&format_label_line("bind group layout", &name));
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::ExecutionError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
match *self {
|
||||||
|
Self::DestroyedBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
ret.push_str(&format_label_line("buffer", &name));
|
||||||
|
}
|
||||||
|
Self::Unimplemented(_reason) => {}
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::RenderPassErrorInner {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
if let Self::InvalidAttachment(id) = *self {
|
||||||
|
let name = wgc::gfx_select!(id => global.texture_view_label(id));
|
||||||
|
ret.push_str(&format_label_line("attachment", &name));
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::RenderPassError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
// This error is wrapper for the inner error,
|
||||||
|
// but the scope has useful labels
|
||||||
|
format_error_line(self) + &self.scope.fmt_pretty(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::ComputePassError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
// This error is wrapper for the inner error,
|
||||||
|
// but the scope has useful labels
|
||||||
|
format_error_line(self) + &self.scope.fmt_pretty(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PrettyError for wgc::command::RenderBundleError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
// This error is wrapper for the inner error,
|
||||||
|
// but the scope has useful labels
|
||||||
|
format_error_line(self) + &self.scope.fmt_pretty(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::ComputePassErrorInner {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
match *self {
|
||||||
|
Self::InvalidBindGroup(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.bind_group_label(id));
|
||||||
|
ret.push_str(&format_label_line("bind group", &name));
|
||||||
|
}
|
||||||
|
Self::InvalidPipeline(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.compute_pipeline_label(id));
|
||||||
|
ret.push_str(&format_label_line("pipeline", &name));
|
||||||
|
}
|
||||||
|
Self::InvalidIndirectBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
ret.push_str(&format_label_line("indirect buffer", &name));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::TransferError {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
let global = context.global();
|
||||||
|
let mut ret = format_error_line(self);
|
||||||
|
match *self {
|
||||||
|
Self::InvalidBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
ret.push_str(&format_label_line("label", &name));
|
||||||
|
}
|
||||||
|
Self::InvalidTexture(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.texture_label(id));
|
||||||
|
ret.push_str(&format_label_line("texture", &name));
|
||||||
|
}
|
||||||
|
// Self::MissingCopySrcUsageFlag(buf_opt, tex_opt) => {
|
||||||
|
// if let Some(buf) = buf_opt {
|
||||||
|
// let name = wgc::gfx_select!(buf => global.buffer_label(buf));
|
||||||
|
// ret.push_str(&format_label_line("source", &name));
|
||||||
|
// }
|
||||||
|
// if let Some(tex) = tex_opt {
|
||||||
|
// let name = wgc::gfx_select!(tex => global.texture_label(tex));
|
||||||
|
// ret.push_str(&format_label_line("source", &name));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Self::MissingCopyDstUsageFlag(buf_opt, tex_opt) => {
|
||||||
|
if let Some(buf) = buf_opt {
|
||||||
|
let name = wgc::gfx_select!(buf => global.buffer_label(buf));
|
||||||
|
ret.push_str(&format_label_line("destination", &name));
|
||||||
|
}
|
||||||
|
if let Some(tex) = tex_opt {
|
||||||
|
let name = wgc::gfx_select!(tex => global.texture_label(tex));
|
||||||
|
ret.push_str(&format_label_line("destination", &name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyError for wgc::command::PassErrorScope {
|
||||||
|
fn fmt_pretty(&self, context: &super::Context) -> String {
|
||||||
|
// This error is not in the error chain, only notes are needed
|
||||||
|
let global = context.global();
|
||||||
|
match *self {
|
||||||
|
Self::Pass(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.command_buffer_label(id));
|
||||||
|
format_label_line("command buffer", &name)
|
||||||
|
}
|
||||||
|
Self::SetBindGroup(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.bind_group_label(id));
|
||||||
|
format_label_line("bind group", &name)
|
||||||
|
}
|
||||||
|
Self::SetPipelineRender(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.render_pipeline_label(id));
|
||||||
|
format_label_line("render pipeline", &name)
|
||||||
|
}
|
||||||
|
Self::SetPipelineCompute(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.compute_pipeline_label(id));
|
||||||
|
format_label_line("compute pipeline", &name)
|
||||||
|
}
|
||||||
|
Self::SetVertexBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
format_label_line("buffer", &name)
|
||||||
|
}
|
||||||
|
Self::SetIndexBuffer(id) => {
|
||||||
|
let name = wgc::gfx_select!(id => global.buffer_label(id));
|
||||||
|
format_label_line("buffer", &name)
|
||||||
|
}
|
||||||
|
Self::Draw { pipeline, .. } => {
|
||||||
|
if let Some(id) = pipeline {
|
||||||
|
let name = wgc::gfx_select!(id => global.render_pipeline_label(id));
|
||||||
|
format_label_line("render pipeline", &name)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Dispatch { pipeline, .. } => {
|
||||||
|
if let Some(id) = pipeline {
|
||||||
|
let name = wgc::gfx_select!(id => global.compute_pipeline_label(id));
|
||||||
|
format_label_line("compute pipeline", &name)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
wgpu/src/backend/mod.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#[cfg(all(target_arch = "wasm32", not(feature = "webgl")))]
|
||||||
|
mod web;
|
||||||
|
#[cfg(all(target_arch = "wasm32", not(feature = "webgl")))]
|
||||||
|
pub(crate) use web::{BufferMappedRange, Context};
|
||||||
|
|
||||||
|
#[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))]
|
||||||
|
mod direct;
|
||||||
|
#[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))]
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
#[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))]
|
||||||
|
pub(crate) use direct::{BufferMappedRange, Context};
|
||||||
|
|
||||||
|
#[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))]
|
||||||
|
mod native_gpu_future;
|
77
wgpu/src/backend/native_gpu_future.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll, Waker};
|
||||||
|
|
||||||
|
enum WakerOrResult<T> {
|
||||||
|
Waker(Waker),
|
||||||
|
Result(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
type GpuFutureData<T> = Mutex<Option<WakerOrResult<T>>>;
|
||||||
|
|
||||||
|
/// A Future that can poll the wgpu::Device
|
||||||
|
pub struct GpuFuture<T> {
|
||||||
|
data: Arc<GpuFutureData<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum OpaqueData {}
|
||||||
|
|
||||||
|
//TODO: merge this with `GpuFuture` and avoid `Arc` on the data.
|
||||||
|
/// A completion handle to set the result on a GpuFuture
|
||||||
|
pub struct GpuFutureCompletion<T> {
|
||||||
|
data: Arc<GpuFutureData<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Future for GpuFuture<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, context: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let mut waker_or_result = self.into_ref().get_ref().data.lock();
|
||||||
|
|
||||||
|
match waker_or_result.take() {
|
||||||
|
Some(WakerOrResult::Result(res)) => Poll::Ready(res),
|
||||||
|
_ => {
|
||||||
|
*waker_or_result = Some(WakerOrResult::Waker(context.waker().clone()));
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> GpuFutureCompletion<T> {
|
||||||
|
pub fn complete(self, value: T) {
|
||||||
|
let mut waker_or_result = self.data.lock();
|
||||||
|
|
||||||
|
match waker_or_result.replace(WakerOrResult::Result(value)) {
|
||||||
|
Some(WakerOrResult::Waker(waker)) => waker.wake(),
|
||||||
|
None => {}
|
||||||
|
Some(WakerOrResult::Result(_)) => {
|
||||||
|
// Drop before panicking. Not sure if this is necessary, but it makes me feel better.
|
||||||
|
drop(waker_or_result);
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_raw(self) -> *mut OpaqueData {
|
||||||
|
Arc::into_raw(self.data) as _
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn from_raw(this: *mut OpaqueData) -> Self {
|
||||||
|
Self {
|
||||||
|
data: Arc::from_raw(this as _),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_gpu_future<T>() -> (GpuFuture<T>, GpuFutureCompletion<T>) {
|
||||||
|
let data = Arc::new(Mutex::new(None));
|
||||||
|
(
|
||||||
|
GpuFuture {
|
||||||
|
data: Arc::clone(&data),
|
||||||
|
},
|
||||||
|
GpuFutureCompletion { data },
|
||||||
|
)
|
||||||
|
}
|
2063
wgpu/src/backend/web.rs
Normal file
2971
wgpu/src/lib.rs
Normal file
74
wgpu/src/macros.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
//! Convenience macros
|
||||||
|
|
||||||
|
/// Macro to produce an array of [VertexAttribute](crate::VertexAttribute).
|
||||||
|
///
|
||||||
|
/// Output has type: `[VertexAttribute; _]`. Usage is as follows:
|
||||||
|
/// ```
|
||||||
|
/// # use wgpu::vertex_attr_array;
|
||||||
|
/// let attrs = vertex_attr_array![0 => Float32x2, 1 => Float32, 2 => Uint16x4];
|
||||||
|
/// ```
|
||||||
|
/// This example specifies a list of three [VertexAttribute](crate::VertexAttribute),
|
||||||
|
/// each with the given `shader_location` and `format`.
|
||||||
|
/// Offsets are calculated automatically.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! vertex_attr_array {
|
||||||
|
($($loc:expr => $fmt:ident),* $(,)?) => {
|
||||||
|
$crate::vertex_attr_array!([] ; 0; $($loc => $fmt ,)*)
|
||||||
|
};
|
||||||
|
([$($t:expr,)*] ; $off:expr ;) => { [$($t,)*] };
|
||||||
|
([$($t:expr,)*] ; $off:expr ; $loc:expr => $item:ident, $($ll:expr => $ii:ident ,)*) => {
|
||||||
|
$crate::vertex_attr_array!(
|
||||||
|
[$($t,)*
|
||||||
|
$crate::VertexAttribute {
|
||||||
|
format: $crate::VertexFormat :: $item,
|
||||||
|
offset: $off,
|
||||||
|
shader_location: $loc,
|
||||||
|
},];
|
||||||
|
$off + $crate::VertexFormat :: $item.size();
|
||||||
|
$($ll => $ii ,)*
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vertex_attr_array() {
|
||||||
|
let attrs = vertex_attr_array![0 => Float32x2, 3 => Uint16x4];
|
||||||
|
// VertexAttribute does not support PartialEq, so we cannot test directly
|
||||||
|
assert_eq!(attrs.len(), 2);
|
||||||
|
assert_eq!(attrs[0].offset, 0);
|
||||||
|
assert_eq!(attrs[0].shader_location, 0);
|
||||||
|
assert_eq!(attrs[1].offset, std::mem::size_of::<(f32, f32)>() as u64);
|
||||||
|
assert_eq!(attrs[1].shader_location, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to load a SPIR-V module statically.
|
||||||
|
///
|
||||||
|
/// It ensures the word alignment as well as the magic number.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! include_spirv {
|
||||||
|
($($token:tt)*) => {
|
||||||
|
{
|
||||||
|
//log::info!("including '{}'", $($token)*);
|
||||||
|
$crate::ShaderModuleDescriptor {
|
||||||
|
label: Some($($token)*),
|
||||||
|
source: $crate::util::make_spirv(include_bytes!($($token)*)),
|
||||||
|
flags: $crate::ShaderFlags::VALIDATION,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to load a WGSL module statically.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! include_wgsl {
|
||||||
|
($($token:tt)*) => {
|
||||||
|
{
|
||||||
|
//log::info!("including '{}'", $($token)*);
|
||||||
|
$crate::ShaderModuleDescriptor {
|
||||||
|
label: Some($($token)*),
|
||||||
|
source: $crate::ShaderSource::Wgsl(include_str!($($token)*).into()),
|
||||||
|
flags: $crate::ShaderFlags::all(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
184
wgpu/src/util/belt.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
use crate::{
|
||||||
|
Buffer, BufferAddress, BufferDescriptor, BufferSize, BufferUsage, BufferViewMut,
|
||||||
|
CommandEncoder, Device, MapMode,
|
||||||
|
};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
use std::{future::Future, sync::mpsc};
|
||||||
|
|
||||||
|
// Given a vector of futures, poll each in parallel until all are ready.
|
||||||
|
struct Join<F> {
|
||||||
|
futures: Vec<Option<F>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Future<Output = ()>> Future for Join<F> {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Self::Output> {
|
||||||
|
// This is safe because we have no Drop implementation to violate the Pin requirements and
|
||||||
|
// do not provide any means of moving the inner futures.
|
||||||
|
let all_ready = unsafe {
|
||||||
|
// Poll all remaining futures, removing all that are ready
|
||||||
|
self.get_unchecked_mut().futures.iter_mut().all(|opt| {
|
||||||
|
if let Some(future) = opt {
|
||||||
|
if Pin::new_unchecked(future).poll(cx) == Poll::Ready(()) {
|
||||||
|
*opt = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.is_none()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if all_ready {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Chunk {
|
||||||
|
buffer: Buffer,
|
||||||
|
size: BufferAddress,
|
||||||
|
offset: BufferAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Staging belt is a machine that uploads data.
|
||||||
|
///
|
||||||
|
/// Internally it uses a ring-buffer of staging buffers that are sub-allocated.
|
||||||
|
/// It has an advantage over `Queue.write_buffer` in a way that it returns a mutable slice,
|
||||||
|
/// which you can fill to avoid an extra data copy.
|
||||||
|
///
|
||||||
|
/// Using a staging belt is slightly complicated, and generally goes as follows:
|
||||||
|
/// - Write to buffers that need writing to using `write_buffer`.
|
||||||
|
/// - Call `finish`.
|
||||||
|
/// - Submit all command encoders used with `write_buffer`.
|
||||||
|
/// - Call `recall`
|
||||||
|
pub struct StagingBelt {
|
||||||
|
chunk_size: BufferAddress,
|
||||||
|
/// Chunks that we are actively using for pending transfers at this moment.
|
||||||
|
active_chunks: Vec<Chunk>,
|
||||||
|
/// Chunks that have scheduled transfers already.
|
||||||
|
closed_chunks: Vec<Chunk>,
|
||||||
|
/// Chunks that are back from the GPU and ready to be used.
|
||||||
|
free_chunks: Vec<Chunk>,
|
||||||
|
sender: mpsc::Sender<Chunk>,
|
||||||
|
receiver: mpsc::Receiver<Chunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StagingBelt {
|
||||||
|
/// Create a new staging belt.
|
||||||
|
///
|
||||||
|
/// The `chunk_size` is the unit of internal buffer allocation.
|
||||||
|
/// It's better when it's big, but ideally still 1-4 times less than
|
||||||
|
/// the total amount of data uploaded per submission.
|
||||||
|
pub fn new(chunk_size: BufferAddress) -> Self {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
StagingBelt {
|
||||||
|
chunk_size,
|
||||||
|
active_chunks: Vec::new(),
|
||||||
|
closed_chunks: Vec::new(),
|
||||||
|
free_chunks: Vec::new(),
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate the staging belt slice of `size` to be uploaded into the `target` buffer
|
||||||
|
/// at the specified offset.
|
||||||
|
///
|
||||||
|
/// The upload will be placed into the provided command encoder. This encoder
|
||||||
|
/// must be submitted after `finish` is called and before `recall` is called.
|
||||||
|
pub fn write_buffer(
|
||||||
|
&mut self,
|
||||||
|
encoder: &mut CommandEncoder,
|
||||||
|
target: &Buffer,
|
||||||
|
offset: BufferAddress,
|
||||||
|
size: BufferSize,
|
||||||
|
device: &Device,
|
||||||
|
) -> BufferViewMut {
|
||||||
|
let mut chunk = if let Some(index) = self
|
||||||
|
.active_chunks
|
||||||
|
.iter()
|
||||||
|
.position(|chunk| chunk.offset + size.get() <= chunk.size)
|
||||||
|
{
|
||||||
|
self.active_chunks.swap_remove(index)
|
||||||
|
} else if let Some(index) = self
|
||||||
|
.free_chunks
|
||||||
|
.iter()
|
||||||
|
.position(|chunk| size.get() <= chunk.size)
|
||||||
|
{
|
||||||
|
self.free_chunks.swap_remove(index)
|
||||||
|
} else {
|
||||||
|
let size = self.chunk_size.max(size.get());
|
||||||
|
Chunk {
|
||||||
|
buffer: device.create_buffer(&BufferDescriptor {
|
||||||
|
label: Some("staging"),
|
||||||
|
size,
|
||||||
|
usage: BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC,
|
||||||
|
mapped_at_creation: true,
|
||||||
|
}),
|
||||||
|
size,
|
||||||
|
offset: 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
encoder.copy_buffer_to_buffer(&chunk.buffer, chunk.offset, target, offset, size.get());
|
||||||
|
let old_offset = chunk.offset;
|
||||||
|
chunk.offset += size.get();
|
||||||
|
let remainder = chunk.offset % crate::MAP_ALIGNMENT;
|
||||||
|
if remainder != 0 {
|
||||||
|
chunk.offset += crate::MAP_ALIGNMENT - remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_chunks.push(chunk);
|
||||||
|
self.active_chunks
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.buffer
|
||||||
|
.slice(old_offset..old_offset + size.get())
|
||||||
|
.get_mapped_range_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare currently mapped buffers for use in a submission.
|
||||||
|
///
|
||||||
|
/// At this point, all the partially used staging buffers are closed until
|
||||||
|
/// the GPU is done copying the data from them.
|
||||||
|
pub fn finish(&mut self) {
|
||||||
|
for chunk in self.active_chunks.drain(..) {
|
||||||
|
chunk.buffer.unmap();
|
||||||
|
self.closed_chunks.push(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recall all of the closed buffers back to be reused.
|
||||||
|
///
|
||||||
|
/// This has to be called after the command encoders written to `write_buffer` are submitted!
|
||||||
|
pub fn recall(&mut self) -> impl Future<Output = ()> + Send {
|
||||||
|
while let Ok(mut chunk) = self.receiver.try_recv() {
|
||||||
|
chunk.offset = 0;
|
||||||
|
self.free_chunks.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sender = &self.sender;
|
||||||
|
let futures = self
|
||||||
|
.closed_chunks
|
||||||
|
.drain(..)
|
||||||
|
.map(|chunk| {
|
||||||
|
let sender = sender.clone();
|
||||||
|
let async_buffer = chunk.buffer.slice(..).map_async(MapMode::Write);
|
||||||
|
|
||||||
|
Some(async move {
|
||||||
|
// The result is ignored
|
||||||
|
async_buffer.await.ok();
|
||||||
|
|
||||||
|
// The only possible error is the other side disconnecting, which is fine
|
||||||
|
let _ = sender.send(chunk);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Join { futures }
|
||||||
|
}
|
||||||
|
}
|
150
wgpu/src/util/device.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
use std::{convert::TryFrom, num::NonZeroU32};
|
||||||
|
|
||||||
|
/// Describes a [Buffer](crate::Buffer) when allocating.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct BufferInitDescriptor<'a> {
|
||||||
|
/// Debug label of a buffer. This will show up in graphics debuggers for easy identification.
|
||||||
|
pub label: crate::Label<'a>,
|
||||||
|
/// Contents of a buffer on creation.
|
||||||
|
pub contents: &'a [u8],
|
||||||
|
/// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
|
||||||
|
/// will panic.
|
||||||
|
pub usage: crate::BufferUsage,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility methods not meant to be in the main API.
|
||||||
|
pub trait DeviceExt {
|
||||||
|
/// Creates a [Buffer](crate::Buffer) with data to initialize it.
|
||||||
|
fn create_buffer_init(&self, desc: &BufferInitDescriptor) -> crate::Buffer;
|
||||||
|
|
||||||
|
/// Upload an entire texture and its mipmaps from a source buffer.
|
||||||
|
///
|
||||||
|
/// Expects all mipmaps to be tightly packed in the data buffer.
|
||||||
|
///
|
||||||
|
/// If the texture is a 2DArray texture, uploads each layer in order, expecting
|
||||||
|
/// each layer and its mips to be tightly packed.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// Layer0Mip0 Layer0Mip1 Layer0Mip2 ... Layer1Mip0 Layer1Mip1 Layer1Mip2 ...
|
||||||
|
fn create_texture_with_data(
|
||||||
|
&self,
|
||||||
|
queue: &crate::Queue,
|
||||||
|
desc: &crate::TextureDescriptor,
|
||||||
|
data: &[u8],
|
||||||
|
) -> crate::Texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceExt for crate::Device {
|
||||||
|
fn create_buffer_init(&self, descriptor: &BufferInitDescriptor<'_>) -> crate::Buffer {
|
||||||
|
// Skip mapping if the buffer is zero sized
|
||||||
|
if descriptor.contents.is_empty() {
|
||||||
|
let wgt_descriptor = crate::BufferDescriptor {
|
||||||
|
label: descriptor.label,
|
||||||
|
size: 0,
|
||||||
|
usage: descriptor.usage,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.create_buffer(&wgt_descriptor)
|
||||||
|
} else {
|
||||||
|
let unpadded_size = descriptor.contents.len() as crate::BufferAddress;
|
||||||
|
// Valid vulkan usage is
|
||||||
|
// 1. buffer size must be a multiple of COPY_BUFFER_ALIGNMENT.
|
||||||
|
// 2. buffer size must be greater than 0.
|
||||||
|
// Therefore we round the value up to the nearest multiple, and ensure it's at least COPY_BUFFER_ALIGNMENT.
|
||||||
|
let align_mask = crate::COPY_BUFFER_ALIGNMENT - 1;
|
||||||
|
let padded_size =
|
||||||
|
((unpadded_size + align_mask) & !align_mask).max(crate::COPY_BUFFER_ALIGNMENT);
|
||||||
|
|
||||||
|
let wgt_descriptor = crate::BufferDescriptor {
|
||||||
|
label: descriptor.label,
|
||||||
|
size: padded_size,
|
||||||
|
usage: descriptor.usage,
|
||||||
|
mapped_at_creation: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffer = self.create_buffer(&wgt_descriptor);
|
||||||
|
|
||||||
|
buffer.slice(..).get_mapped_range_mut()[..unpadded_size as usize]
|
||||||
|
.copy_from_slice(descriptor.contents);
|
||||||
|
buffer.unmap();
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_texture_with_data(
|
||||||
|
&self,
|
||||||
|
queue: &crate::Queue,
|
||||||
|
desc: &crate::TextureDescriptor,
|
||||||
|
data: &[u8],
|
||||||
|
) -> crate::Texture {
|
||||||
|
let texture = self.create_texture(desc);
|
||||||
|
|
||||||
|
let format_info = desc.format.describe();
|
||||||
|
|
||||||
|
let (layer_iterations, mip_extent) = if desc.dimension == crate::TextureDimension::D3 {
|
||||||
|
(1, desc.size)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
desc.size.depth_or_array_layers,
|
||||||
|
crate::Extent3d {
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
..desc.size
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mip_level_count =
|
||||||
|
u8::try_from(desc.mip_level_count).expect("mip level count overflows a u8");
|
||||||
|
|
||||||
|
let mut binary_offset = 0;
|
||||||
|
for layer in 0..layer_iterations {
|
||||||
|
for mip in 0..mip_level_count {
|
||||||
|
let mip_size = mip_extent.at_mip_level(mip).unwrap();
|
||||||
|
|
||||||
|
// When uploading mips of compressed textures and the mip is supposed to be
|
||||||
|
// a size that isn't a multiple of the block size, the mip needs to be uploaded
|
||||||
|
// as its "physical size" which is the size rounded up to the nearest block size.
|
||||||
|
let mip_physical = mip_size.physical_size(desc.format);
|
||||||
|
|
||||||
|
// All these calculations are performed on the physical size as that's the
|
||||||
|
// data that exists in the buffer.
|
||||||
|
let width_blocks = mip_physical.width / format_info.block_dimensions.0 as u32;
|
||||||
|
let height_blocks = mip_physical.height / format_info.block_dimensions.1 as u32;
|
||||||
|
|
||||||
|
let bytes_per_row = width_blocks * format_info.block_size as u32;
|
||||||
|
let data_size = bytes_per_row * height_blocks * mip_extent.depth_or_array_layers;
|
||||||
|
|
||||||
|
let end_offset = binary_offset + data_size as usize;
|
||||||
|
|
||||||
|
queue.write_texture(
|
||||||
|
crate::ImageCopyTexture {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: mip as u32,
|
||||||
|
origin: crate::Origin3d {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: layer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&data[binary_offset..end_offset],
|
||||||
|
crate::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(
|
||||||
|
NonZeroU32::new(bytes_per_row).expect("invalid bytes per row"),
|
||||||
|
),
|
||||||
|
rows_per_image: Some(
|
||||||
|
NonZeroU32::new(mip_physical.height).expect("invalid height"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
mip_physical,
|
||||||
|
);
|
||||||
|
|
||||||
|
binary_offset = end_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture
|
||||||
|
}
|
||||||
|
}
|
223
wgpu/src/util/encoder.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use wgt::{BufferAddress, DynamicOffset, IndexFormat};
|
||||||
|
|
||||||
|
use crate::{BindGroup, Buffer, BufferSlice, RenderBundleEncoder, RenderPass, RenderPipeline};
|
||||||
|
|
||||||
|
/// Methods shared by `RenderPass` and `RenderBundleEncoder`
|
||||||
|
pub trait RenderEncoder<'a> {
|
||||||
|
/// Sets the active bind group for a given bind group index. The bind group layout
|
||||||
|
/// in the active pipeline when any `draw()` function is called must match the layout of this bind group.
|
||||||
|
///
|
||||||
|
/// If the bind group have dynamic offsets, provide them in order of their declaration.
|
||||||
|
fn set_bind_group(&mut self, index: u32, bind_group: &'a BindGroup, offsets: &[DynamicOffset]);
|
||||||
|
|
||||||
|
/// Sets the active render pipeline.
|
||||||
|
///
|
||||||
|
/// Subsequent draw calls will exhibit the behavior defined by `pipeline`.
|
||||||
|
fn set_pipeline(&mut self, pipeline: &'a RenderPipeline);
|
||||||
|
|
||||||
|
/// Sets the active index buffer.
|
||||||
|
///
|
||||||
|
/// Subsequent calls to [`draw_indexed`](RenderBundleEncoder::draw_indexed) on this [`RenderBundleEncoder`] will
|
||||||
|
/// use `buffer` as the source index buffer.
|
||||||
|
fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat);
|
||||||
|
|
||||||
|
/// Assign a vertex buffer to a slot.
|
||||||
|
///
|
||||||
|
/// Subsequent calls to [`draw`] and [`draw_indexed`] on this
|
||||||
|
/// [`RenderBundleEncoder`] will use `buffer` as one of the source vertex buffers.
|
||||||
|
///
|
||||||
|
/// The `slot` refers to the index of the matching descriptor in
|
||||||
|
/// [VertexStateDescriptor::vertex_buffers](crate::VertexStateDescriptor::vertex_buffers).
|
||||||
|
///
|
||||||
|
/// [`draw`]: RenderBundleEncoder::draw
|
||||||
|
/// [`draw_indexed`]: RenderBundleEncoder::draw_indexed
|
||||||
|
fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>);
|
||||||
|
|
||||||
|
/// Draws primitives from the active vertex buffer(s).
|
||||||
|
///
|
||||||
|
/// The active vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`].
|
||||||
|
fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>);
|
||||||
|
|
||||||
|
/// Draws indexed primitives using the active index buffer and the active vertex buffers.
|
||||||
|
///
|
||||||
|
/// The active index buffer can be set with [`RenderBundleEncoder::set_index_buffer`], while the active
|
||||||
|
/// vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`].
|
||||||
|
fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>);
|
||||||
|
|
||||||
|
/// Draws primitives from the active vertex buffer(s) based on the contents of the `indirect_buffer`.
|
||||||
|
///
|
||||||
|
/// The active vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`].
|
||||||
|
///
|
||||||
|
/// The structure expected in `indirect_buffer` is the following:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// #[repr(C)]
|
||||||
|
/// struct DrawIndirect {
|
||||||
|
/// vertex_count: u32, // The number of vertices to draw.
|
||||||
|
/// instance_count: u32, // The number of instances to draw.
|
||||||
|
/// base_vertex: u32, // The Index of the first vertex to draw.
|
||||||
|
/// base_instance: u32, // The instance ID of the first instance to draw.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress);
|
||||||
|
|
||||||
|
/// Draws indexed primitives using the active index buffer and the active vertex buffers,
|
||||||
|
/// based on the contents of the `indirect_buffer`.
|
||||||
|
///
|
||||||
|
/// The active index buffer can be set with [`RenderBundleEncoder::set_index_buffer`], while the active
|
||||||
|
/// vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`].
|
||||||
|
///
|
||||||
|
/// The structure expected in `indirect_buffer` is the following:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// #[repr(C)]
|
||||||
|
/// struct DrawIndexedIndirect {
|
||||||
|
/// vertex_count: u32, // The number of vertices to draw.
|
||||||
|
/// instance_count: u32, // The number of instances to draw.
|
||||||
|
/// base_index: u32, // The base index within the index buffer.
|
||||||
|
/// vertex_offset: i32, // The value added to the vertex index before indexing into the vertex buffer.
|
||||||
|
/// base_instance: u32, // The instance ID of the first instance to draw.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn draw_indexed_indirect(
|
||||||
|
&mut self,
|
||||||
|
indirect_buffer: &'a Buffer,
|
||||||
|
indirect_offset: BufferAddress,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// [`wgt::Features::PUSH_CONSTANTS`] must be enabled on the device in order to call this function.
|
||||||
|
///
|
||||||
|
/// Set push constant data.
|
||||||
|
///
|
||||||
|
/// Offset is measured in bytes, but must be a multiple of [`wgt::PUSH_CONSTANT_ALIGNMENT`].
|
||||||
|
///
|
||||||
|
/// Data size must be a multiple of 4 and must be aligned to the 4s, so we take an array of u32.
|
||||||
|
/// For example, with an offset of 4 and an array of `[u32; 3]`, that will write to the range
|
||||||
|
/// of 4..16.
|
||||||
|
///
|
||||||
|
/// For each byte in the range of push constant data written, the union of the stages of all push constant
|
||||||
|
/// ranges that covers that byte must be exactly `stages`. There's no good way of explaining this simply,
|
||||||
|
/// so here are some examples:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// For the given ranges:
|
||||||
|
/// - 0..4 Vertex
|
||||||
|
/// - 4..8 Fragment
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You would need to upload this in two set_push_constants calls. First for the `Vertex` range, second for the `Fragment` range.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// For the given ranges:
|
||||||
|
/// - 0..8 Vertex
|
||||||
|
/// - 4..12 Fragment
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You would need to upload this in three set_push_constants calls. First for the `Vertex` only range 0..4, second
|
||||||
|
/// for the `Vertex | Fragment` range 4..8, third for the `Fragment` range 8..12.
|
||||||
|
fn set_push_constants(&mut self, stages: wgt::ShaderStage, offset: u32, data: &[u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RenderEncoder<'a> for RenderPass<'a> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_bind_group(&mut self, index: u32, bind_group: &'a BindGroup, offsets: &[DynamicOffset]) {
|
||||||
|
Self::set_bind_group(self, index, bind_group, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) {
|
||||||
|
Self::set_pipeline(self, pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat) {
|
||||||
|
Self::set_index_buffer(self, buffer_slice, index_format);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>) {
|
||||||
|
Self::set_vertex_buffer(self, slot, buffer_slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
|
||||||
|
Self::draw(self, vertices, instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
|
||||||
|
Self::draw_indexed(self, indices, base_vertex, instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) {
|
||||||
|
Self::draw_indirect(self, indirect_buffer, indirect_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw_indexed_indirect(
|
||||||
|
&mut self,
|
||||||
|
indirect_buffer: &'a Buffer,
|
||||||
|
indirect_offset: BufferAddress,
|
||||||
|
) {
|
||||||
|
Self::draw_indexed_indirect(self, indirect_buffer, indirect_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_push_constants(&mut self, stages: wgt::ShaderStage, offset: u32, data: &[u8]) {
|
||||||
|
Self::set_push_constants(self, stages, offset, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RenderEncoder<'a> for RenderBundleEncoder<'a> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_bind_group(&mut self, index: u32, bind_group: &'a BindGroup, offsets: &[DynamicOffset]) {
|
||||||
|
Self::set_bind_group(self, index, bind_group, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) {
|
||||||
|
Self::set_pipeline(self, pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat) {
|
||||||
|
Self::set_index_buffer(self, buffer_slice, index_format);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>) {
|
||||||
|
Self::set_vertex_buffer(self, slot, buffer_slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
|
||||||
|
Self::draw(self, vertices, instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
|
||||||
|
Self::draw_indexed(self, indices, base_vertex, instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) {
|
||||||
|
Self::draw_indirect(self, indirect_buffer, indirect_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn draw_indexed_indirect(
|
||||||
|
&mut self,
|
||||||
|
indirect_buffer: &'a Buffer,
|
||||||
|
indirect_offset: BufferAddress,
|
||||||
|
) {
|
||||||
|
Self::draw_indexed_indirect(self, indirect_buffer, indirect_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_push_constants(&mut self, stages: wgt::ShaderStage, offset: u32, data: &[u8]) {
|
||||||
|
Self::set_push_constants(self, stages, offset, data);
|
||||||
|
}
|
||||||
|
}
|
102
wgpu/src/util/mod.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
//! Utility structures and functions.
|
||||||
|
|
||||||
|
mod belt;
|
||||||
|
mod device;
|
||||||
|
mod encoder;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
future::Future,
|
||||||
|
mem::{align_of, size_of},
|
||||||
|
ptr::copy_nonoverlapping,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use belt::StagingBelt;
|
||||||
|
pub use device::{BufferInitDescriptor, DeviceExt};
|
||||||
|
pub use encoder::RenderEncoder;
|
||||||
|
|
||||||
|
/// Treat the given byte slice as a SPIR-V module.
|
||||||
|
///
|
||||||
|
/// # Panic
|
||||||
|
///
|
||||||
|
/// This function panics if:
|
||||||
|
///
|
||||||
|
/// - Input length isn't multiple of 4
|
||||||
|
/// - Input is longer than [`usize::max_value`]
|
||||||
|
/// - SPIR-V magic number is missing from beginning of stream
|
||||||
|
pub fn make_spirv(data: &[u8]) -> super::ShaderSource {
|
||||||
|
const MAGIC_NUMBER: u32 = 0x0723_0203;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
data.len() % size_of::<u32>(),
|
||||||
|
0,
|
||||||
|
"data size is not a multiple of 4"
|
||||||
|
);
|
||||||
|
|
||||||
|
//If the data happens to be aligned, directly use the byte array,
|
||||||
|
// otherwise copy the byte array in an owned vector and use that instead.
|
||||||
|
let words = if data.as_ptr().align_offset(align_of::<u32>()) == 0 {
|
||||||
|
let (pre, words, post) = unsafe { data.align_to::<u32>() };
|
||||||
|
debug_assert!(pre.is_empty());
|
||||||
|
debug_assert!(post.is_empty());
|
||||||
|
Cow::from(words)
|
||||||
|
} else {
|
||||||
|
let mut words = vec![0u32; data.len() / size_of::<u32>()];
|
||||||
|
unsafe {
|
||||||
|
copy_nonoverlapping(data.as_ptr(), words.as_mut_ptr() as *mut u8, data.len());
|
||||||
|
}
|
||||||
|
Cow::from(words)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
words[0], MAGIC_NUMBER,
|
||||||
|
"wrong magic word {:x}. Make sure you are using a binary SPIRV file.",
|
||||||
|
words[0]
|
||||||
|
);
|
||||||
|
super::ShaderSource::SpirV(words)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CPU accessible buffer used to download data back from the GPU.
|
||||||
|
pub struct DownloadBuffer(super::Buffer, super::BufferMappedRange);
|
||||||
|
|
||||||
|
impl DownloadBuffer {
|
||||||
|
/// Asynchronously read the contents of a buffer.
|
||||||
|
pub fn read_buffer(
|
||||||
|
device: &super::Device,
|
||||||
|
queue: &super::Queue,
|
||||||
|
buffer: &super::BufferSlice,
|
||||||
|
) -> impl Future<Output = Result<Self, super::BufferAsyncError>> + Send {
|
||||||
|
let size = match buffer.size {
|
||||||
|
Some(size) => size.into(),
|
||||||
|
None => buffer.buffer.map_context.lock().total_size - buffer.offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
let download = device.create_buffer(&super::BufferDescriptor {
|
||||||
|
size,
|
||||||
|
usage: super::BufferUsage::COPY_DST | super::BufferUsage::MAP_READ,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&super::CommandEncoderDescriptor { label: None });
|
||||||
|
encoder.copy_buffer_to_buffer(buffer.buffer, buffer.offset, &download, 0, size);
|
||||||
|
let command_buffer: super::CommandBuffer = encoder.finish();
|
||||||
|
queue.submit(Some(command_buffer));
|
||||||
|
|
||||||
|
let fut = download.slice(..).map_async(super::MapMode::Read);
|
||||||
|
async move {
|
||||||
|
fut.await?;
|
||||||
|
let mapped_range =
|
||||||
|
super::Context::buffer_get_mapped_range(&*download.context, &download.id, 0..size);
|
||||||
|
Ok(Self(download, mapped_range))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for DownloadBuffer {
|
||||||
|
type Target = [u8];
|
||||||
|
fn deref(&self) -> &[u8] {
|
||||||
|
super::BufferMappedRangeSlice::slice(&self.1)
|
||||||
|
}
|
||||||
|
}
|
52
wgpu/tests/example-wgsl.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use naga::{front::wgsl, valid::Validator};
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_example_wgsl() {
|
||||||
|
let read_dir = match PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("examples")
|
||||||
|
.read_dir()
|
||||||
|
{
|
||||||
|
Ok(iter) => iter,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Unable to open the examples folder: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for example_entry in read_dir {
|
||||||
|
let read_files = match example_entry {
|
||||||
|
Ok(dir_entry) => match dir_entry.path().read_dir() {
|
||||||
|
Ok(iter) => iter,
|
||||||
|
Err(_) => continue,
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Skipping example: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for file_entry in read_files {
|
||||||
|
let shader = match file_entry {
|
||||||
|
Ok(entry) => match entry.path().extension() {
|
||||||
|
Some(ostr) if &*ostr == "wgsl" => {
|
||||||
|
println!("Validating {:?}", entry.path());
|
||||||
|
fs::read_to_string(entry.path()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Skipping file: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = wgsl::parse_str(&shader).unwrap();
|
||||||
|
//TODO: re-use the validator
|
||||||
|
Validator::new(
|
||||||
|
naga::valid::ValidationFlags::all(),
|
||||||
|
naga::valid::Capabilities::all(),
|
||||||
|
)
|
||||||
|
.validate(&module)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|