mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-25 00:04:11 +00:00
👀 hot shader reloading (#655)
* Update builder to use a shared validation method * Add the error for using print_metadata and watching We cannot use print_metadata with watching because print_metadata only makes sense in build scripts, but watching does not and would instead stall the script * Add the initial implementation of watching * Make hot reloading work in the wgpu example * Attempt to address CI failures * Add exception for notify CC0-1.0 license * Address review comments Co-authored-by: khyperia <github@khyperia.com>
This commit is contained in:
parent
0410fc53e1
commit
3bbe963998
73
Cargo.lock
generated
73
Cargo.lock
generated
@ -841,6 +841,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c0e564d24da983c053beff1bb7178e237501206840a3e6bf4e267b9e8ae734a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuchsia-zircon"
|
name = "fuchsia-zircon"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -1227,6 +1236,26 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b031475cb1b103ee221afb806a23d35e0570bf7271d7588762ceba8127ed43b3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inplace_it"
|
name = "inplace_it"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -1485,6 +1514,19 @@ dependencies = [
|
|||||||
"winapi 0.2.8",
|
"winapi 0.2.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.7.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow 0.3.7",
|
||||||
|
"ntapi",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio-extras"
|
name = "mio-extras"
|
||||||
version = "2.0.6"
|
version = "2.0.6"
|
||||||
@ -1493,7 +1535,7 @@ checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"lazycell",
|
"lazycell",
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio 0.6.23",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1649,6 +1691,32 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "5.0.0-pre.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51f18203a26893ca1d3526cf58084025d5639f91c44f8b70ab3b724f60e819a0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"filetime",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"libc",
|
||||||
|
"mio 0.7.11",
|
||||||
|
"walkdir",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@ -2338,6 +2406,7 @@ name = "spirv-builder"
|
|||||||
version = "0.4.0-alpha.9"
|
version = "0.4.0-alpha.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"notify",
|
||||||
"raw-string",
|
"raw-string",
|
||||||
"rustc_codegen_spirv",
|
"rustc_codegen_spirv",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3074,7 +3143,7 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio 0.6.23",
|
||||||
"mio-extras",
|
"mio-extras",
|
||||||
"ndk",
|
"ndk",
|
||||||
"ndk-glue",
|
"ndk-glue",
|
||||||
|
@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
default = ["use-compiled-tools"]
|
default = ["use-compiled-tools"]
|
||||||
use-installed-tools = ["rustc_codegen_spirv/use-installed-tools"]
|
use-installed-tools = ["rustc_codegen_spirv/use-installed-tools"]
|
||||||
use-compiled-tools = ["rustc_codegen_spirv/use-compiled-tools"]
|
use-compiled-tools = ["rustc_codegen_spirv/use-compiled-tools"]
|
||||||
|
watch = ["notify"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memchr = "2.3"
|
memchr = "2.3"
|
||||||
@ -18,3 +19,5 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
# See comment in lib.rs invoke_rustc for why this is here
|
# See comment in lib.rs invoke_rustc for why this is here
|
||||||
rustc_codegen_spirv = { path = "../rustc_codegen_spirv", default-features = false }
|
rustc_codegen_spirv = { path = "../rustc_codegen_spirv", default-features = false }
|
||||||
|
|
||||||
|
notify = { version = "5.0.0-pre.10", optional = true }
|
||||||
|
@ -54,6 +54,8 @@
|
|||||||
#![allow()]
|
#![allow()]
|
||||||
|
|
||||||
mod depfile;
|
mod depfile;
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
mod watch;
|
||||||
|
|
||||||
use raw_string::{RawStr, RawString};
|
use raw_string::{RawStr, RawString};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -71,10 +73,12 @@ pub use rustc_codegen_spirv::rspirv::spirv::Capability;
|
|||||||
pub use rustc_codegen_spirv::{CompileResult, ModuleResult};
|
pub use rustc_codegen_spirv::{CompileResult, ModuleResult};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum SpirvBuilderError {
|
pub enum SpirvBuilderError {
|
||||||
CratePathDoesntExist(PathBuf),
|
CratePathDoesntExist(PathBuf),
|
||||||
BuildFailed,
|
BuildFailed,
|
||||||
MultiModuleWithPrintMetadata,
|
MultiModuleWithPrintMetadata,
|
||||||
|
WatchWithPrintMetadata,
|
||||||
MetadataFileMissing(std::io::Error),
|
MetadataFileMissing(std::io::Error),
|
||||||
MetadataFileMalformed(serde_json::Error),
|
MetadataFileMalformed(serde_json::Error),
|
||||||
}
|
}
|
||||||
@ -89,6 +93,9 @@ impl fmt::Display for SpirvBuilderError {
|
|||||||
SpirvBuilderError::MultiModuleWithPrintMetadata => f.write_str(
|
SpirvBuilderError::MultiModuleWithPrintMetadata => f.write_str(
|
||||||
"Multi-module build cannot be used with print_metadata = MetadataPrintout::Full",
|
"Multi-module build cannot be used with print_metadata = MetadataPrintout::Full",
|
||||||
),
|
),
|
||||||
|
SpirvBuilderError::WatchWithPrintMetadata => {
|
||||||
|
f.write_str("Watching within build scripts will prevent build completion")
|
||||||
|
}
|
||||||
SpirvBuilderError::MetadataFileMissing(_) => {
|
SpirvBuilderError::MetadataFileMissing(_) => {
|
||||||
f.write_str("Multi-module metadata file missing")
|
f.write_str("Multi-module metadata file missing")
|
||||||
}
|
}
|
||||||
@ -244,22 +251,47 @@ impl SpirvBuilder {
|
|||||||
|
|
||||||
/// Builds the module. If `print_metadata` is [`MetadataPrintout::Full`], you usually don't have to inspect the path
|
/// Builds the module. If `print_metadata` is [`MetadataPrintout::Full`], you usually don't have to inspect the path
|
||||||
/// in the result, as the environment variable for the path to the module will already be set.
|
/// in the result, as the environment variable for the path to the module will already be set.
|
||||||
pub fn build(self) -> Result<CompileResult, SpirvBuilderError> {
|
pub fn build(mut self) -> Result<CompileResult, SpirvBuilderError> {
|
||||||
|
self.validate_running_conditions()?;
|
||||||
|
let metadata_file = invoke_rustc(&self)?;
|
||||||
|
match self.print_metadata {
|
||||||
|
MetadataPrintout::Full | MetadataPrintout::DependencyOnly => {
|
||||||
|
leaf_deps(&metadata_file, |artifact| {
|
||||||
|
println!("cargo:rerun-if-changed={}", artifact)
|
||||||
|
})
|
||||||
|
// Close enough
|
||||||
|
.map_err(SpirvBuilderError::MetadataFileMissing)?;
|
||||||
|
}
|
||||||
|
MetadataPrintout::None => (),
|
||||||
|
}
|
||||||
|
let metadata = self.parse_metadata_file(&metadata_file)?;
|
||||||
|
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate_running_conditions(&mut self) -> Result<(), SpirvBuilderError> {
|
||||||
if (self.print_metadata == MetadataPrintout::Full) && self.multimodule {
|
if (self.print_metadata == MetadataPrintout::Full) && self.multimodule {
|
||||||
return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
|
return Err(SpirvBuilderError::MultiModuleWithPrintMetadata);
|
||||||
}
|
}
|
||||||
if !self.path_to_crate.is_dir() {
|
if !self.path_to_crate.is_dir() {
|
||||||
return Err(SpirvBuilderError::CratePathDoesntExist(self.path_to_crate));
|
return Err(SpirvBuilderError::CratePathDoesntExist(std::mem::take(
|
||||||
|
&mut self.path_to_crate,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
let metadata_file = invoke_rustc(&self)?;
|
Ok(())
|
||||||
let metadata_contents =
|
}
|
||||||
File::open(&metadata_file).map_err(SpirvBuilderError::MetadataFileMissing)?;
|
|
||||||
|
pub(crate) fn parse_metadata_file(
|
||||||
|
&self,
|
||||||
|
at: &Path,
|
||||||
|
) -> Result<CompileResult, SpirvBuilderError> {
|
||||||
|
let metadata_contents = File::open(&at).map_err(SpirvBuilderError::MetadataFileMissing)?;
|
||||||
let metadata: CompileResult = serde_json::from_reader(BufReader::new(metadata_contents))
|
let metadata: CompileResult = serde_json::from_reader(BufReader::new(metadata_contents))
|
||||||
.map_err(SpirvBuilderError::MetadataFileMalformed)?;
|
.map_err(SpirvBuilderError::MetadataFileMalformed)?;
|
||||||
match &metadata.module {
|
match &metadata.module {
|
||||||
ModuleResult::SingleModule(spirv_module) => {
|
ModuleResult::SingleModule(spirv_module) => {
|
||||||
assert!(!self.multimodule);
|
assert!(!self.multimodule);
|
||||||
let env_var = metadata_file.file_name().unwrap().to_str().unwrap();
|
let env_var = at.file_name().unwrap().to_str().unwrap();
|
||||||
if self.print_metadata == MetadataPrintout::Full {
|
if self.print_metadata == MetadataPrintout::Full {
|
||||||
println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
|
println!("cargo:rustc-env={}={}", env_var, spirv_module.display());
|
||||||
}
|
}
|
||||||
@ -424,13 +456,8 @@ fn invoke_rustc(builder: &SpirvBuilder) -> Result<PathBuf, SpirvBuilderError> {
|
|||||||
// that ended up on stdout instead of stderr.
|
// that ended up on stdout instead of stderr.
|
||||||
let stdout = String::from_utf8(build.stdout).unwrap();
|
let stdout = String::from_utf8(build.stdout).unwrap();
|
||||||
let artifact = get_last_artifact(&stdout);
|
let artifact = get_last_artifact(&stdout);
|
||||||
|
|
||||||
if build.status.success() {
|
if build.status.success() {
|
||||||
match builder.print_metadata {
|
Ok(artifact.expect("Artifact created when compilation succeeded"))
|
||||||
MetadataPrintout::Full | MetadataPrintout::DependencyOnly => print_deps_of(&artifact),
|
|
||||||
MetadataPrintout::None => (),
|
|
||||||
}
|
|
||||||
Ok(artifact)
|
|
||||||
} else {
|
} else {
|
||||||
Err(SpirvBuilderError::BuildFailed)
|
Err(SpirvBuilderError::BuildFailed)
|
||||||
}
|
}
|
||||||
@ -442,7 +469,7 @@ struct RustcOutput {
|
|||||||
filenames: Option<Vec<String>>,
|
filenames: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_artifact(out: &str) -> PathBuf {
|
fn get_last_artifact(out: &str) -> Option<PathBuf> {
|
||||||
let last = out
|
let last = out
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| match serde_json::from_str::<RustcOutput>(line) {
|
.filter_map(|line| match serde_json::from_str::<RustcOutput>(line) {
|
||||||
@ -462,28 +489,33 @@ fn get_last_artifact(out: &str) -> PathBuf {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|v| v.ends_with(".spv"));
|
.filter(|v| v.ends_with(".spv"));
|
||||||
let filename = filenames.next().expect("Crate had no .spv artifacts");
|
let filename = filenames.next()?;
|
||||||
assert_eq!(filenames.next(), None, "Crate had multiple .spv artifacts");
|
assert_eq!(filenames.next(), None, "Crate had multiple .spv artifacts");
|
||||||
filename.into()
|
Some(filename.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_deps_of(artifact: &Path) {
|
/// Internally iterate through the leaf dependencies of the artifact at `artifact`
|
||||||
|
fn leaf_deps(artifact: &Path, mut handle: impl FnMut(&RawStr)) -> std::io::Result<()> {
|
||||||
let deps_file = artifact.with_extension("d");
|
let deps_file = artifact.with_extension("d");
|
||||||
let mut deps_map = HashMap::new();
|
let mut deps_map = HashMap::new();
|
||||||
depfile::read_deps_file(&deps_file, |item, deps| {
|
depfile::read_deps_file(&deps_file, |item, deps| {
|
||||||
deps_map.insert(item, deps);
|
deps_map.insert(item, deps);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})?;
|
||||||
.expect("Could not read dep file");
|
fn recurse(
|
||||||
fn recurse(map: &HashMap<RawString, Vec<RawString>>, artifact: &RawStr) {
|
map: &HashMap<RawString, Vec<RawString>>,
|
||||||
|
artifact: &RawStr,
|
||||||
|
handle: &mut impl FnMut(&RawStr),
|
||||||
|
) {
|
||||||
match map.get(artifact) {
|
match map.get(artifact) {
|
||||||
Some(entries) => {
|
Some(entries) => {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
recurse(map, entry)
|
recurse(map, entry, handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => println!("cargo:rerun-if-changed={}", artifact),
|
None => handle(artifact),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recurse(&deps_map, artifact.to_str().unwrap().into());
|
recurse(&deps_map, artifact.to_str().unwrap().into(), &mut handle);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
104
crates/spirv-builder/src/watch.rs
Normal file
104
crates/spirv-builder/src/watch.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use std::{collections::HashSet, sync::mpsc::sync_channel};
|
||||||
|
|
||||||
|
use notify::{Event, RecursiveMode, Watcher};
|
||||||
|
use rustc_codegen_spirv::CompileResult;
|
||||||
|
|
||||||
|
use crate::{leaf_deps, SpirvBuilder, SpirvBuilderError};
|
||||||
|
|
||||||
|
impl SpirvBuilder {
|
||||||
|
/// Watches the module for changes using [`notify`](https://crates.io/crates/notify).
|
||||||
|
///
|
||||||
|
/// This is a blocking operation, wand should never return in the happy path
|
||||||
|
pub fn watch(
|
||||||
|
mut self,
|
||||||
|
on_compilation_finishes: impl Fn(CompileResult),
|
||||||
|
) -> Result<(), SpirvBuilderError> {
|
||||||
|
self.validate_running_conditions()?;
|
||||||
|
if !matches!(self.print_metadata, crate::MetadataPrintout::None) {
|
||||||
|
return Err(SpirvBuilderError::WatchWithPrintMetadata);
|
||||||
|
}
|
||||||
|
let metadata_result = crate::invoke_rustc(&self);
|
||||||
|
// Load the dependencies of the thing
|
||||||
|
let metadata_file = match metadata_result {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(_) => {
|
||||||
|
let (tx, rx) = sync_channel(0);
|
||||||
|
// Fall back to watching from the crate root if the inital compilation fails
|
||||||
|
let mut watcher =
|
||||||
|
notify::immediate_watcher(move |event: notify::Result<Event>| match event {
|
||||||
|
Ok(e) => match e.kind {
|
||||||
|
notify::EventKind::Access(_) => (),
|
||||||
|
notify::EventKind::Any
|
||||||
|
| notify::EventKind::Create(_)
|
||||||
|
| notify::EventKind::Modify(_)
|
||||||
|
| notify::EventKind::Remove(_)
|
||||||
|
| notify::EventKind::Other => {
|
||||||
|
let _ = tx.try_send(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!("notify error: {:?}", e),
|
||||||
|
})
|
||||||
|
.expect("Could create watcher");
|
||||||
|
// This is likely to notice changes in the `target` dir, however, given that `cargo watch` doesn't seem to handle that,
|
||||||
|
watcher
|
||||||
|
.watch(&self.path_to_crate, RecursiveMode::Recursive)
|
||||||
|
.expect("Could watch crate root");
|
||||||
|
loop {
|
||||||
|
rx.recv().expect("Watcher still alive");
|
||||||
|
let metadata_file = crate::invoke_rustc(&self);
|
||||||
|
if let Ok(f) = metadata_file {
|
||||||
|
break f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let metadata = self.parse_metadata_file(&metadata_file)?;
|
||||||
|
on_compilation_finishes(metadata);
|
||||||
|
let mut watched_paths = HashSet::new();
|
||||||
|
let (tx, rx) = sync_channel(0);
|
||||||
|
let mut watcher =
|
||||||
|
notify::immediate_watcher(move |event: notify::Result<Event>| match event {
|
||||||
|
Ok(e) => match e.kind {
|
||||||
|
notify::EventKind::Access(_) => (),
|
||||||
|
notify::EventKind::Any
|
||||||
|
| notify::EventKind::Create(_)
|
||||||
|
| notify::EventKind::Modify(_)
|
||||||
|
| notify::EventKind::Remove(_)
|
||||||
|
| notify::EventKind::Other => {
|
||||||
|
let _ = tx.try_send(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!("notify error: {:?}", e),
|
||||||
|
})
|
||||||
|
.expect("Could create watcher");
|
||||||
|
leaf_deps(&metadata_file, |it| {
|
||||||
|
let path = it.to_path().unwrap();
|
||||||
|
if watched_paths.insert(path.to_owned()) {
|
||||||
|
watcher
|
||||||
|
.watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
||||||
|
.expect("Cargo dependencies are valid files");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("Could read dependencies file");
|
||||||
|
loop {
|
||||||
|
rx.recv().expect("Watcher still alive");
|
||||||
|
let metadata_result = crate::invoke_rustc(&self);
|
||||||
|
if let Ok(file) = metadata_result {
|
||||||
|
// We can bubble this error up because it's an internal error (e.g. rustc_codegen_spirv's version of CompileResult is somehow out of sync)
|
||||||
|
let metadata = self.parse_metadata_file(&file)?;
|
||||||
|
|
||||||
|
leaf_deps(&file, |it| {
|
||||||
|
let path = it.to_path().unwrap();
|
||||||
|
if watched_paths.insert(path.to_owned()) {
|
||||||
|
watcher
|
||||||
|
.watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
||||||
|
.expect("Cargo dependencies are valid files");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("Could read dependencies file");
|
||||||
|
|
||||||
|
on_compilation_finishes(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -69,6 +69,11 @@ exceptions = [
|
|||||||
# https://tldrlegal.com/license/do-what-the-f*ck-you-want-to-public-license-(wtfpl)
|
# https://tldrlegal.com/license/do-what-the-f*ck-you-want-to-public-license-(wtfpl)
|
||||||
{ allow = ["WTFPL"], name = "xkb" },
|
{ allow = ["WTFPL"], name = "xkb" },
|
||||||
{ allow = ["WTFPL"], name = "xkbcommon-sys" },
|
{ allow = ["WTFPL"], name = "xkbcommon-sys" },
|
||||||
|
|
||||||
|
# CC0 is a permissive license but somewhat unclear status for source code
|
||||||
|
# so we prefer to not have dependencies using it
|
||||||
|
# https://tldrlegal.com/license/creative-commons-cc0-1.0-universal
|
||||||
|
{ allow = ["CC0-1.0"], name = "notify" },
|
||||||
]
|
]
|
||||||
copyleft = "deny"
|
copyleft = "deny"
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ clap = "3.0.0-beta.2"
|
|||||||
strum = { version = "0.20", default_features = false, features = ["derive"] }
|
strum = { version = "0.20", default_features = false, features = ["derive"] }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_arch = "wasm32")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_arch = "wasm32")))'.dependencies]
|
||||||
spirv-builder = { path = "../../../crates/spirv-builder", default-features = false }
|
spirv-builder = { path = "../../../crates/spirv-builder", default-features = false, features = ["watch"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
ndk-glue = "0.2"
|
ndk-glue = "0.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use super::{shader_module, Options};
|
use super::Options;
|
||||||
use futures::future::join;
|
use futures::future::join;
|
||||||
use std::{convert::TryInto, future::Future, num::NonZeroU64, time::Duration};
|
use std::{convert::TryInto, future::Future, num::NonZeroU64, time::Duration};
|
||||||
|
|
||||||
@ -15,7 +15,8 @@ fn block_on<T>(future: impl Future<Output = T>) -> T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(options: &Options) {
|
pub fn start(options: &Options) {
|
||||||
let shader_binary = shader_module(options.shader);
|
let rx = crate::maybe_watch(options.shader, true);
|
||||||
|
let shader_binary = rx.recv().expect("Should send one binary");
|
||||||
|
|
||||||
block_on(start_internal(options, shader_binary))
|
block_on(start_internal(options, shader_binary))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use super::{shader_module, Options};
|
use std::thread::spawn;
|
||||||
|
|
||||||
|
use crate::maybe_watch;
|
||||||
|
|
||||||
|
use super::Options;
|
||||||
use shared::ShaderConstants;
|
use shared::ShaderConstants;
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent},
|
event::{ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent},
|
||||||
@ -35,10 +39,10 @@ fn mouse_button_index(button: MouseButton) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
event_loop: EventLoop<()>,
|
event_loop: EventLoop<wgpu::ShaderModuleDescriptor<'static>>,
|
||||||
window: Window,
|
window: Window,
|
||||||
swapchain_format: wgpu::TextureFormat,
|
swapchain_format: wgpu::TextureFormat,
|
||||||
shader_binary: wgpu::ShaderModuleDescriptor<'_>,
|
shader_binary: wgpu::ShaderModuleDescriptor<'static>,
|
||||||
) {
|
) {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
let instance = wgpu::Instance::new(wgpu::BackendBit::VULKAN | wgpu::BackendBit::METAL);
|
let instance = wgpu::Instance::new(wgpu::BackendBit::VULKAN | wgpu::BackendBit::METAL);
|
||||||
@ -80,7 +84,6 @@ async fn run(
|
|||||||
.expect("Failed to create device");
|
.expect("Failed to create device");
|
||||||
|
|
||||||
// Load the shaders from disk
|
// Load the shaders from disk
|
||||||
let module = device.create_shader_module(&shader_binary);
|
|
||||||
|
|
||||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
@ -91,38 +94,8 @@ async fn run(
|
|||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
let mut render_pipeline =
|
||||||
label: None,
|
create_pipeline(&device, &pipeline_layout, swapchain_format, shader_binary);
|
||||||
layout: Some(&pipeline_layout),
|
|
||||||
vertex: wgpu::VertexState {
|
|
||||||
module: &module,
|
|
||||||
entry_point: shaders::main_vs,
|
|
||||||
buffers: &[],
|
|
||||||
},
|
|
||||||
primitive: wgpu::PrimitiveState {
|
|
||||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
|
||||||
strip_index_format: None,
|
|
||||||
front_face: wgpu::FrontFace::Ccw,
|
|
||||||
cull_mode: wgpu::CullMode::None,
|
|
||||||
polygon_mode: wgpu::PolygonMode::Fill,
|
|
||||||
},
|
|
||||||
depth_stencil: None,
|
|
||||||
multisample: wgpu::MultisampleState {
|
|
||||||
count: 1,
|
|
||||||
mask: !0,
|
|
||||||
alpha_to_coverage_enabled: false,
|
|
||||||
},
|
|
||||||
fragment: Some(wgpu::FragmentState {
|
|
||||||
module: &module,
|
|
||||||
entry_point: shaders::main_fs,
|
|
||||||
targets: &[wgpu::ColorTargetState {
|
|
||||||
format: swapchain_format,
|
|
||||||
alpha_blend: wgpu::BlendState::REPLACE,
|
|
||||||
color_blend: wgpu::BlendState::REPLACE,
|
|
||||||
write_mask: wgpu::ColorWrite::ALL,
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut sc_desc = wgpu::SwapChainDescriptor {
|
let mut sc_desc = wgpu::SwapChainDescriptor {
|
||||||
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
@ -149,7 +122,8 @@ async fn run(
|
|||||||
// Have the closure take ownership of the resources.
|
// Have the closure take ownership of the resources.
|
||||||
// `event_loop.run` never returns, therefore we must do this to ensure
|
// `event_loop.run` never returns, therefore we must do this to ensure
|
||||||
// the resources are properly cleaned up.
|
// the resources are properly cleaned up.
|
||||||
let _ = (&instance, &adapter, &module, &pipeline_layout);
|
let _ = (&instance, &adapter, &pipeline_layout);
|
||||||
|
let render_pipeline = &mut render_pipeline;
|
||||||
|
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
match event {
|
match event {
|
||||||
@ -225,7 +199,7 @@ async fn run(
|
|||||||
mouse_button_press_time,
|
mouse_button_press_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
rpass.set_pipeline(&render_pipeline);
|
rpass.set_pipeline(render_pipeline);
|
||||||
rpass.set_push_constants(wgpu::ShaderStage::all(), 0, unsafe {
|
rpass.set_push_constants(wgpu::ShaderStage::all(), 0, unsafe {
|
||||||
any_as_u8_slice(&push_constants)
|
any_as_u8_slice(&push_constants)
|
||||||
});
|
});
|
||||||
@ -282,16 +256,75 @@ async fn run(
|
|||||||
drag_end_y = cursor_y;
|
drag_end_y = cursor_y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::UserEvent(new_module) => {
|
||||||
|
*render_pipeline =
|
||||||
|
create_pipeline(&device, &pipeline_layout, swapchain_format, new_module);
|
||||||
|
window.request_redraw();
|
||||||
|
*control_flow = ControlFlow::Poll;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_pipeline(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
pipeline_layout: &wgpu::PipelineLayout,
|
||||||
|
swapchain_format: wgpu::TextureFormat,
|
||||||
|
shader_binary: wgpu::ShaderModuleDescriptor<'_>,
|
||||||
|
) -> wgpu::RenderPipeline {
|
||||||
|
let module = device.create_shader_module(&shader_binary);
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &module,
|
||||||
|
entry_point: shaders::main_vs,
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: wgpu::CullMode::None,
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &module,
|
||||||
|
entry_point: shaders::main_fs,
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: swapchain_format,
|
||||||
|
alpha_blend: wgpu::BlendState::REPLACE,
|
||||||
|
color_blend: wgpu::BlendState::REPLACE,
|
||||||
|
write_mask: wgpu::ColorWrite::ALL,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start(options: &Options) {
|
pub fn start(options: &Options) {
|
||||||
// Build the shader before we pop open a window, since it might take a while.
|
// Build the shader before we pop open a window, since it might take a while.
|
||||||
let shader_binary = shader_module(options.shader);
|
let rx = maybe_watch(options.shader, false);
|
||||||
|
let initial_shader = rx.recv().expect("Initial shader is required");
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::with_user_event();
|
||||||
|
let proxy = event_loop.create_proxy();
|
||||||
|
let thread = spawn(move || loop {
|
||||||
|
while let Ok(result) = rx.recv() {
|
||||||
|
match proxy.send_event(result) {
|
||||||
|
Ok(()) => {}
|
||||||
|
// If something goes wrong, close this thread
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
std::mem::forget(thread);
|
||||||
let window = winit::window::WindowBuilder::new()
|
let window = winit::window::WindowBuilder::new()
|
||||||
.with_title("Rust GPU - wgpu")
|
.with_title("Rust GPU - wgpu")
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0))
|
.with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0))
|
||||||
@ -317,6 +350,7 @@ pub fn start(options: &Options) {
|
|||||||
event_loop,
|
event_loop,
|
||||||
window,
|
window,
|
||||||
wgpu::TextureFormat::Bgra8Unorm,
|
wgpu::TextureFormat::Bgra8Unorm,
|
||||||
|
initial_shader,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
wgpu_subscriber::initialize_default_subscriber(None);
|
wgpu_subscriber::initialize_default_subscriber(None);
|
||||||
@ -328,7 +362,8 @@ pub fn start(options: &Options) {
|
|||||||
} else {
|
} else {
|
||||||
wgpu::TextureFormat::Bgra8UnormSrgb
|
wgpu::TextureFormat::Bgra8UnormSrgb
|
||||||
},
|
},
|
||||||
shader_binary,
|
initial_shader,
|
||||||
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,8 @@
|
|||||||
rust_2018_idioms
|
rust_2018_idioms
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use std::sync::mpsc::{self, Receiver};
|
||||||
|
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
@ -56,10 +58,16 @@ pub enum RustGPUShader {
|
|||||||
Mouse,
|
Mouse,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shader_module(shader: RustGPUShader) -> wgpu::ShaderModuleDescriptor<'static> {
|
fn maybe_watch(
|
||||||
|
shader: RustGPUShader,
|
||||||
|
force_no_watch: bool,
|
||||||
|
) -> Receiver<wgpu::ShaderModuleDescriptor<'static>> {
|
||||||
|
// This bound needs to be 1, because in cases where this function is used for direct building (e.g. for the compute example or on android)
|
||||||
|
// we send the value directly in the same thread. This avoids deadlocking in those cases.
|
||||||
|
let (tx, rx) = mpsc::sync_channel(1);
|
||||||
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
|
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
|
||||||
{
|
{
|
||||||
use spirv_builder::{Capability, MetadataPrintout, SpirvBuilder};
|
use spirv_builder::{Capability, CompileResult, MetadataPrintout, SpirvBuilder};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
// Hack: spirv_builder builds into a custom directory if running under cargo, to not
|
// Hack: spirv_builder builds into a custom directory if running under cargo, to not
|
||||||
@ -86,29 +94,53 @@ fn shader_module(shader: RustGPUShader) -> wgpu::ShaderModuleDescriptor<'static>
|
|||||||
for &cap in capabilities {
|
for &cap in capabilities {
|
||||||
builder = builder.capability(cap);
|
builder = builder.capability(cap);
|
||||||
}
|
}
|
||||||
|
if force_no_watch {
|
||||||
let compile_result = builder.build().unwrap();
|
let compile_result = builder.build().unwrap();
|
||||||
|
handle_builder_result(compile_result, &tx);
|
||||||
|
} else {
|
||||||
|
let thread = std::thread::spawn(move || {
|
||||||
|
builder
|
||||||
|
.watch(|compile_result| {
|
||||||
|
handle_builder_result(compile_result, &tx);
|
||||||
|
})
|
||||||
|
.expect("Configuration is correct for watching")
|
||||||
|
});
|
||||||
|
std::mem::forget(thread);
|
||||||
|
}
|
||||||
|
fn handle_builder_result(
|
||||||
|
compile_result: CompileResult,
|
||||||
|
tx: &mpsc::SyncSender<wgpu::ShaderModuleDescriptor<'static>>,
|
||||||
|
) {
|
||||||
let module_path = compile_result.module.unwrap_single();
|
let module_path = compile_result.module.unwrap_single();
|
||||||
let data = std::fs::read(module_path).unwrap();
|
let data = std::fs::read(module_path).unwrap();
|
||||||
let spirv = wgpu::util::make_spirv(&data);
|
let spirv = wgpu::util::make_spirv(&data);
|
||||||
let spirv = match spirv {
|
let spirv = match spirv {
|
||||||
wgpu::ShaderSource::Wgsl(cow) => wgpu::ShaderSource::Wgsl(Cow::Owned(cow.into_owned())),
|
wgpu::ShaderSource::Wgsl(cow) => {
|
||||||
|
wgpu::ShaderSource::Wgsl(Cow::Owned(cow.into_owned()))
|
||||||
|
}
|
||||||
wgpu::ShaderSource::SpirV(cow) => {
|
wgpu::ShaderSource::SpirV(cow) => {
|
||||||
wgpu::ShaderSource::SpirV(Cow::Owned(cow.into_owned()))
|
wgpu::ShaderSource::SpirV(Cow::Owned(cow.into_owned()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
wgpu::ShaderModuleDescriptor {
|
tx.send(wgpu::ShaderModuleDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
source: spirv,
|
source: spirv,
|
||||||
flags: wgpu::ShaderFlags::default(),
|
flags: wgpu::ShaderFlags::default(),
|
||||||
|
})
|
||||||
|
.expect("Rx is still alive");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "android", target_arch = "wasm32"))]
|
#[cfg(any(target_os = "android", target_arch = "wasm32"))]
|
||||||
match shader {
|
{
|
||||||
|
tx.send(match shader {
|
||||||
RustGPUShader::Simplest => wgpu::include_spirv!(env!("simplest_shader.spv")),
|
RustGPUShader::Simplest => wgpu::include_spirv!(env!("simplest_shader.spv")),
|
||||||
RustGPUShader::Sky => wgpu::include_spirv!(env!("sky_shader.spv")),
|
RustGPUShader::Sky => wgpu::include_spirv!(env!("sky_shader.spv")),
|
||||||
RustGPUShader::Compute => wgpu::include_spirv!(env!("compute_shader.spv")),
|
RustGPUShader::Compute => wgpu::include_spirv!(env!("compute_shader.spv")),
|
||||||
RustGPUShader::Mouse => wgpu::include_spirv!(env!("mouse_shader.spv")),
|
RustGPUShader::Mouse => wgpu::include_spirv!(env!("mouse_shader.spv")),
|
||||||
|
})
|
||||||
|
.expect("rx to be alive")
|
||||||
}
|
}
|
||||||
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_compute_shader(shader: RustGPUShader) -> bool {
|
fn is_compute_shader(shader: RustGPUShader) -> bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user