Skip to content

Commit 6b6a663

Browse files
committed
export some of the shader loading functionality
1 parent 287307a commit 6b6a663

File tree

5 files changed

+194
-81
lines changed

5 files changed

+194
-81
lines changed

src/bin/toy.rs

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,37 @@
11
use std::error::Error;
22

33
fn main() -> Result<(), Box<dyn Error>> {
4-
#[cfg(not(feature = "winit"))]
4+
#[cfg(any(target_arch = "wasm32", not(feature = "winit")))]
55
return Err("must be compiled with winit feature to run".into());
66

7-
#[cfg(feature = "winit")]
7+
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
88
return winit::main();
99
}
1010

11-
#[cfg(feature = "winit")]
11+
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
1212
mod winit {
13-
use serde::{Deserialize, Serialize};
1413
use std::error::Error;
1514
use wgputoy::context::init_wgpu;
15+
use wgputoy::shader::{FolderLoader, WebLoader, load_shader};
1616
use wgputoy::WgpuToyRenderer;
1717

18-
#[derive(Serialize, Deserialize, Debug)]
19-
#[serde(rename_all = "camelCase")]
20-
struct ShaderMeta {
21-
uniforms: Vec<Uniform>,
22-
textures: Vec<Texture>,
23-
#[serde(default)]
24-
float32_enabled: bool,
25-
}
26-
27-
#[derive(Serialize, Deserialize, Debug)]
28-
struct Uniform {
29-
name: String,
30-
value: f32,
31-
}
32-
33-
#[derive(Serialize, Deserialize, Debug)]
34-
struct Texture {
35-
img: String,
36-
}
37-
3818
async fn init() -> Result<WgpuToyRenderer, Box<dyn Error>> {
39-
let wgpu = init_wgpu(1280, 720, "").await?;
40-
let mut wgputoy = WgpuToyRenderer::new(wgpu);
41-
42-
let filename = if std::env::args().len() > 1 {
19+
let name = if std::env::args().len() > 1 {
4320
std::env::args().nth(1).unwrap()
4421
} else {
45-
"examples/default.wgsl".to_string()
22+
"default".to_string()
4623
};
47-
let shader = std::fs::read_to_string(&filename)?;
48-
49-
let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
50-
.with(reqwest_middleware_cache::Cache {
51-
mode: reqwest_middleware_cache::CacheMode::Default,
52-
cache_manager: reqwest_middleware_cache::managers::CACacheManager::default(),
53-
})
54-
.build();
5524

56-
if let Ok(json) = std::fs::read_to_string(std::format!("{filename}.json")) {
57-
let metadata: ShaderMeta = serde_json::from_str(&json)?;
58-
println!("{:?}", metadata);
25+
let source_loader = FolderLoader::new("./examples".to_string());
26+
let texture_loader = WebLoader::new();
5927

60-
for (i, texture) in metadata.textures.iter().enumerate() {
61-
let url = if texture.img.starts_with("http") {
62-
texture.img.clone()
63-
} else {
64-
std::format!("https://compute.toys/{}", texture.img)
65-
};
66-
let resp = client.get(&url).send().await?;
67-
let img = resp.bytes().await?.to_vec();
68-
if texture.img.ends_with(".hdr") {
69-
wgputoy.load_channel_hdr(i, &img)?;
70-
} else {
71-
wgputoy.load_channel(i, &img);
72-
}
73-
}
28+
let shader = load_shader(&source_loader, &texture_loader, &name)?;
7429

75-
let uniform_names: Vec<String> =
76-
metadata.uniforms.iter().map(|u| u.name.clone()).collect();
77-
let uniform_values: Vec<f32> = metadata.uniforms.iter().map(|u| u.value).collect();
78-
if !uniform_names.is_empty() {
79-
wgputoy.set_custom_floats(uniform_names, uniform_values);
80-
}
30+
let wgpu = init_wgpu(1280, 720, "").await?;
31+
let mut wgputoy = WgpuToyRenderer::new(wgpu);
8132

82-
wgputoy.set_pass_f32(metadata.float32_enabled);
83-
}
33+
wgputoy.load_shader(shader).await?;
8434

85-
if let Some(source) = wgputoy.preprocess_async(&shader).await {
86-
println!("{}", source.source);
87-
wgputoy.compile(source);
88-
}
8935
Ok(wgputoy)
9036
}
9137

@@ -139,7 +85,5 @@ mod winit {
13985
_ => (),
14086
}
14187
});
142-
143-
Ok(())
14488
}
14589
}

src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ mod bind;
22
mod blit;
33
pub mod context;
44
mod pp;
5+
#[cfg(not(target_arch = "wasm32"))]
6+
pub mod shader;
57
mod utils;
68

79
#[cfg(feature = "winit")]
@@ -10,6 +12,8 @@ use context::WgpuContext;
1012
use lazy_regex::regex;
1113
use num::Integer;
1214
use pp::{SourceMap, WGSLError};
15+
#[cfg(not(target_arch = "wasm32"))]
16+
use shader::Shader;
1317
use std::collections::HashMap;
1418
use std::mem::{size_of, take};
1519
use std::sync::atomic::{AtomicBool, Ordering};
@@ -737,6 +741,32 @@ fn passSampleLevelBilinearRepeat(pass_index: int, uv: float2, lod: float) -> flo
737741
log::info!("Channel {index} loaded in {}s", now.elapsed().as_secs_f32());
738742
Ok(())
739743
}
744+
745+
#[cfg(not(target_arch = "wasm32"))]
746+
pub async fn load_shader(&mut self, shader: Shader) -> Result<(), String> {
747+
for (i, texture) in shader.textures.iter().enumerate() {
748+
if texture.img.ends_with(".hdr") {
749+
self.load_channel_hdr(i, &texture.data)?;
750+
} else {
751+
self.load_channel(i, &texture.data);
752+
}
753+
}
754+
755+
let meta = shader.meta;
756+
let uniform_names: Vec<String> = meta.uniforms.iter().map(|u| u.name.clone()).collect();
757+
let uniform_values: Vec<f32> = meta.uniforms.iter().map(|u| u.value).collect();
758+
if !uniform_names.is_empty() {
759+
self.set_custom_floats(uniform_names, uniform_values);
760+
}
761+
762+
self.set_pass_f32(meta.float32_enabled);
763+
764+
if let Some(source) = self.preprocess_async(&shader.shader).await {
765+
self.compile(source);
766+
}
767+
768+
Ok(())
769+
}
740770
}
741771

742772
fn create_texture_from_image(

src/pp.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crate::{
2-
bind::NUM_ASSERT_COUNTERS,
3-
utils::{fetch_include, parse_u32},
4-
};
1+
use crate::{bind::NUM_ASSERT_COUNTERS, utils::{parse_u32, fetch_include}};
52
use async_recursion::async_recursion;
63
use itertools::Itertools;
74
use lazy_regex::*;
@@ -144,7 +141,7 @@ impl Preprocessor {
144141
if path == "string" {
145142
self.enable_strings = true;
146143
}
147-
fetch_include(format!("std/{path}")).await
144+
fetch_include(path.to_string()).await
148145
}
149146
},
150147
Some(cap) => fetch_include(cap[1].to_string()).await,

src/shader.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use pollster::block_on;
2+
use reqwest_middleware::ClientWithMiddleware;
3+
use serde::{Deserialize, Serialize};
4+
use std::error::Error;
5+
6+
use crate::utils::fetch;
7+
8+
pub struct Shader {
9+
pub shader: String,
10+
pub meta: ShaderMeta,
11+
pub textures: Vec<LoadedTexture>,
12+
}
13+
14+
#[derive(Serialize, Deserialize, Debug, Default)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct ShaderMeta {
17+
pub uniforms: Vec<Uniform>,
18+
pub textures: Vec<Texture>,
19+
#[serde(default)]
20+
pub float32_enabled: bool,
21+
}
22+
23+
#[derive(Serialize, Deserialize, Debug, Clone)]
24+
pub struct Uniform {
25+
pub name: String,
26+
pub value: f32,
27+
}
28+
29+
#[derive(Serialize, Deserialize, Debug)]
30+
pub struct Texture {
31+
pub img: String,
32+
}
33+
34+
pub struct LoadedTexture {
35+
pub img: String,
36+
pub data: Vec<u8>,
37+
}
38+
39+
pub fn load_shader_meta(json: String) -> Result<ShaderMeta, Box<dyn Error>> {
40+
Ok(serde_json::from_str(&json)?)
41+
}
42+
43+
pub fn load_shader<S: Loader, T: Loader>(
44+
source_loader: S,
45+
texture_loader: T,
46+
name: &String,
47+
) -> Result<Shader, String> {
48+
let shader_filename = format!("{name}.wgsl");
49+
let meta_filename = format!("{name}.wgsl.json");
50+
51+
let shader_source = source_loader.load_string(&shader_filename)?;
52+
let meta = if let Ok(meta_json) = source_loader.load_string(&meta_filename) {
53+
load_shader_meta(meta_json)
54+
.map_err(|e| format!("error loading meta for {}: {:?}", name, e))?
55+
} else {
56+
ShaderMeta::default()
57+
};
58+
59+
let textures = meta
60+
.textures
61+
.iter()
62+
.map(|t| {
63+
let data = texture_loader.load_bytes(&t.img)?;
64+
Ok(LoadedTexture {
65+
img: t.img.clone(),
66+
data,
67+
})
68+
})
69+
.collect::<Result<Vec<LoadedTexture>, String>>()?;
70+
71+
Ok(Shader {
72+
shader: shader_source,
73+
meta,
74+
textures,
75+
})
76+
}
77+
78+
pub trait Loader {
79+
fn load_bytes(&self, path: &String) -> Result<Vec<u8>, String>;
80+
81+
fn load_string(&self, path: &String) -> Result<String, String> {
82+
String::from_utf8(self.load_bytes(path)?)
83+
.map_err(|e| format!("error reading {} as utf8: {:?}", path, e))
84+
}
85+
}
86+
87+
pub struct FolderLoader {
88+
base_path: String,
89+
}
90+
91+
impl FolderLoader {
92+
pub fn new(base_path: String) -> Self {
93+
Self { base_path }
94+
}
95+
}
96+
97+
impl Loader for &FolderLoader {
98+
fn load_bytes(&self, path: &String) -> Result<Vec<u8>, String> {
99+
Ok(std::fs::read(format!("{}/{path}", self.base_path))
100+
.map_err(|e| format!("error including file {}: {:?}", path, e))?)
101+
}
102+
}
103+
104+
pub struct WebLoader {
105+
client: ClientWithMiddleware,
106+
}
107+
108+
impl WebLoader {
109+
pub fn new() -> Self {
110+
let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
111+
.with(reqwest_middleware_cache::Cache {
112+
mode: reqwest_middleware_cache::CacheMode::Default,
113+
cache_manager: reqwest_middleware_cache::managers::CACacheManager::default(),
114+
})
115+
.build();
116+
Self { client }
117+
}
118+
}
119+
120+
impl Loader for &WebLoader {
121+
fn load_bytes(&self, path: &String) -> Result<Vec<u8>, String> {
122+
block_on(async {
123+
let url = if path.starts_with("http") {
124+
path.clone()
125+
} else {
126+
std::format!("https://compute.toys/{}", path)
127+
};
128+
let resp = self
129+
.client
130+
.get(&url)
131+
.send()
132+
.await
133+
.map_err(|e| format!("{:?}", e))?;
134+
Ok(resp.bytes().await.map_err(|e| format!("{:?}", e))?.to_vec())
135+
})
136+
}
137+
}

src/utils.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ pub fn parse_u32(value: &str, line: usize) -> Result<u32, WGSLError> {
3030
)))
3131
}
3232

33-
#[cfg(target_arch = "wasm32")]
34-
#[cached]
3533
pub async fn fetch_include(name: String) -> Option<String> {
3634
let url = format!("https://compute-toys.github.io/include/{name}.wgsl");
35+
fetch(url).await
36+
}
3737

38-
#[cfg(target_arch = "wasm32")]
38+
#[cfg(target_arch = "wasm32")]
39+
#[cached]
40+
pub async fn fetch(url: String) -> Option<String> {
3941
let resp = gloo_net::http::Request::get(&url).send().await.ok()?;
40-
#[cfg(not(target_arch = "wasm32"))]
41-
let resp = reqwest::get(&url).await.ok()?;
4242

4343
if resp.status() == 200 {
4444
resp.text().await.ok()
@@ -48,9 +48,14 @@ pub async fn fetch_include(name: String) -> Option<String> {
4848
}
4949

5050
#[cfg(not(target_arch = "wasm32"))]
51-
pub async fn fetch_include(name: String) -> Option<String> {
52-
let filename = format!("./include/{name}.wgsl");
53-
std::fs::read_to_string(filename).ok()
51+
pub async fn fetch(url: String) -> Option<String> {
52+
let resp = reqwest::get(&url).await.ok()?;
53+
54+
if resp.status() == 200 {
55+
resp.text().await.ok()
56+
} else {
57+
None
58+
}
5459
}
5560

5661
#[cfg(target_arch = "wasm32")]

0 commit comments

Comments
 (0)