Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 12 additions & 68 deletions src/bin/toy.rs
Original file line number Diff line number Diff line change
@@ -1,91 +1,37 @@
use std::error::Error;

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

#[cfg(feature = "winit")]
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
return winit::main();
}

#[cfg(feature = "winit")]
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
mod winit {
use serde::{Deserialize, Serialize};
use std::error::Error;
use wgputoy::context::init_wgpu;
use wgputoy::shader::{FolderLoader, WebLoader, load_shader};
use wgputoy::WgpuToyRenderer;

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ShaderMeta {
uniforms: Vec<Uniform>,
textures: Vec<Texture>,
#[serde(default)]
float32_enabled: bool,
}

#[derive(Serialize, Deserialize, Debug)]
struct Uniform {
name: String,
value: f32,
}

#[derive(Serialize, Deserialize, Debug)]
struct Texture {
img: String,
}

async fn init() -> Result<WgpuToyRenderer, Box<dyn Error>> {
let wgpu = init_wgpu(1280, 720, "").await?;
let mut wgputoy = WgpuToyRenderer::new(wgpu);

let filename = if std::env::args().len() > 1 {
let name = if std::env::args().len() > 1 {
std::env::args().nth(1).unwrap()
} else {
"examples/default.wgsl".to_string()
"default".to_string()
};
let shader = std::fs::read_to_string(&filename)?;

let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
.with(reqwest_middleware_cache::Cache {
mode: reqwest_middleware_cache::CacheMode::Default,
cache_manager: reqwest_middleware_cache::managers::CACacheManager::default(),
})
.build();

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

for (i, texture) in metadata.textures.iter().enumerate() {
let url = if texture.img.starts_with("http") {
texture.img.clone()
} else {
std::format!("https://compute.toys/{}", texture.img)
};
let resp = client.get(&url).send().await?;
let img = resp.bytes().await?.to_vec();
if texture.img.ends_with(".hdr") {
wgputoy.load_channel_hdr(i, &img)?;
} else {
wgputoy.load_channel(i, &img);
}
}
let shader = load_shader(&source_loader, &texture_loader, &name)?;

let uniform_names: Vec<String> =
metadata.uniforms.iter().map(|u| u.name.clone()).collect();
let uniform_values: Vec<f32> = metadata.uniforms.iter().map(|u| u.value).collect();
if !uniform_names.is_empty() {
wgputoy.set_custom_floats(uniform_names, uniform_values);
}
let wgpu = init_wgpu(1280, 720, "").await?;
let mut wgputoy = WgpuToyRenderer::new(wgpu);

wgputoy.set_pass_f32(metadata.float32_enabled);
}
wgputoy.load_shader(shader).await?;

if let Some(source) = wgputoy.preprocess_async(&shader).await {
println!("{}", source.source);
wgputoy.compile(source);
}
Ok(wgputoy)
}

Expand Down Expand Up @@ -139,7 +85,5 @@ mod winit {
_ => (),
}
});

Ok(())
}
}
30 changes: 30 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ mod bind;
mod blit;
pub mod context;
mod pp;
#[cfg(not(target_arch = "wasm32"))]
pub mod shader;
mod utils;

#[cfg(feature = "winit")]
Expand All @@ -10,6 +12,8 @@ use context::WgpuContext;
use lazy_regex::regex;
use num::Integer;
use pp::{SourceMap, WGSLError};
#[cfg(not(target_arch = "wasm32"))]
use shader::Shader;
use std::collections::HashMap;
use std::mem::{size_of, take};
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -737,6 +741,32 @@ fn passSampleLevelBilinearRepeat(pass_index: int, uv: float2, lod: float) -> flo
log::info!("Channel {index} loaded in {}s", now.elapsed().as_secs_f32());
Ok(())
}

#[cfg(not(target_arch = "wasm32"))]
pub async fn load_shader(&mut self, shader: Shader) -> Result<(), String> {
for (i, texture) in shader.textures.iter().enumerate() {
if texture.img.ends_with(".hdr") {
self.load_channel_hdr(i, &texture.data)?;
} else {
self.load_channel(i, &texture.data);
}
}

let meta = shader.meta;
let uniform_names: Vec<String> = meta.uniforms.iter().map(|u| u.name.clone()).collect();
let uniform_values: Vec<f32> = meta.uniforms.iter().map(|u| u.value).collect();
if !uniform_names.is_empty() {
self.set_custom_floats(uniform_names, uniform_values);
}

self.set_pass_f32(meta.float32_enabled);

if let Some(source) = self.preprocess_async(&shader.shader).await {
self.compile(source);
}

Ok(())
}
}

fn create_texture_from_image(
Expand Down
7 changes: 2 additions & 5 deletions src/pp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
bind::NUM_ASSERT_COUNTERS,
utils::{fetch_include, parse_u32},
};
use crate::{bind::NUM_ASSERT_COUNTERS, utils::{parse_u32, fetch_include}};
use async_recursion::async_recursion;
use itertools::Itertools;
use lazy_regex::*;
Expand Down Expand Up @@ -144,7 +141,7 @@ impl Preprocessor {
if path == "string" {
self.enable_strings = true;
}
fetch_include(format!("std/{path}")).await
fetch_include(path.to_string()).await
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this change?

}
},
Some(cap) => fetch_include(cap[1].to_string()).await,
Expand Down
137 changes: 137 additions & 0 deletions src/shader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use pollster::block_on;
use reqwest_middleware::ClientWithMiddleware;
use serde::{Deserialize, Serialize};
use std::error::Error;

use crate::utils::fetch;

pub struct Shader {
pub shader: String,
pub meta: ShaderMeta,
pub textures: Vec<LoadedTexture>,
}

#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct ShaderMeta {
pub uniforms: Vec<Uniform>,
pub textures: Vec<Texture>,
#[serde(default)]
pub float32_enabled: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Uniform {
pub name: String,
pub value: f32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Texture {
pub img: String,
}

pub struct LoadedTexture {
pub img: String,
pub data: Vec<u8>,
}

pub fn load_shader_meta(json: String) -> Result<ShaderMeta, Box<dyn Error>> {
Ok(serde_json::from_str(&json)?)
}

pub fn load_shader<S: Loader, T: Loader>(
source_loader: S,
texture_loader: T,
name: &String,
) -> Result<Shader, String> {
let shader_filename = format!("{name}.wgsl");
let meta_filename = format!("{name}.wgsl.json");

let shader_source = source_loader.load_string(&shader_filename)?;
let meta = if let Ok(meta_json) = source_loader.load_string(&meta_filename) {
load_shader_meta(meta_json)
.map_err(|e| format!("error loading meta for {}: {:?}", name, e))?
} else {
ShaderMeta::default()
};

let textures = meta
.textures
.iter()
.map(|t| {
let data = texture_loader.load_bytes(&t.img)?;
Ok(LoadedTexture {
img: t.img.clone(),
data,
})
})
.collect::<Result<Vec<LoadedTexture>, String>>()?;

Ok(Shader {
shader: shader_source,
meta,
textures,
})
}

pub trait Loader {
fn load_bytes(&self, path: &String) -> Result<Vec<u8>, String>;

fn load_string(&self, path: &String) -> Result<String, String> {
String::from_utf8(self.load_bytes(path)?)
.map_err(|e| format!("error reading {} as utf8: {:?}", path, e))
}
}

pub struct FolderLoader {
base_path: String,
}

impl FolderLoader {
pub fn new(base_path: String) -> Self {
Self { base_path }
}
}

impl Loader for &FolderLoader {
fn load_bytes(&self, path: &String) -> Result<Vec<u8>, String> {
Ok(std::fs::read(format!("{}/{path}", self.base_path))
.map_err(|e| format!("error including file {}: {:?}", path, e))?)
}
}

pub struct WebLoader {
client: ClientWithMiddleware,
}

impl WebLoader {
pub fn new() -> Self {
let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
.with(reqwest_middleware_cache::Cache {
mode: reqwest_middleware_cache::CacheMode::Default,
cache_manager: reqwest_middleware_cache::managers::CACacheManager::default(),
})
.build();
Self { client }
}
}

impl Loader for &WebLoader {
fn load_bytes(&self, path: &String) -> Result<Vec<u8>, String> {
block_on(async {
let url = if path.starts_with("http") {
path.clone()
} else {
std::format!("https://compute.toys/{}", path)
};
let resp = self
.client
.get(&url)
.send()
.await
.map_err(|e| format!("{:?}", e))?;
Ok(resp.bytes().await.map_err(|e| format!("{:?}", e))?.to_vec())
})
}
}
21 changes: 13 additions & 8 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ pub fn parse_u32(value: &str, line: usize) -> Result<u32, WGSLError> {
)))
}

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

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

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

#[cfg(not(target_arch = "wasm32"))]
pub async fn fetch_include(name: String) -> Option<String> {
let filename = format!("./include/{name}.wgsl");
std::fs::read_to_string(filename).ok()
pub async fn fetch(url: String) -> Option<String> {
let resp = reqwest::get(&url).await.ok()?;

if resp.status() == 200 {
resp.text().await.ok()
} else {
None
}
}

#[cfg(target_arch = "wasm32")]
Expand Down