Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure command line arguments in Android and WASM #443

Merged
merged 5 commits into from
Mar 5, 2024
Merged
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
20 changes: 18 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@
},
"wgsl-analyzer.diagnostics.nagaVersion": "main",
"wgsl-analyzer.preprocessor.shaderDefs": [
"full", "msaa16", "msaa"
]
"full",
"msaa16",
"msaa"
],
// These settings can be set to get Rust-analyzer working for Android compilation
// (unfortunately you need to do the variable expansion manually, because
// rust-analyzer doesn't do this for us)
// "rust-analyzer.cargo.target": "aarch64-linux-android",
// "rust-analyzer.cargo.extraEnv": {
// "CC_aarch64-linux-android": "${env:ANDROID_SDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang",
// "CFLAGS_aarch64-linux-android": "--target=aarch64-linux-android23",
// "CXX_aarch64-linux-android": "${env:ANDROID_SDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang",
// "CXXFLAGS_aarch64-linux-android": "--target=aarch64-linux-android23",
// "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER": "${env:ANDROID_SDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang",
// "RUSTFLAGS": "-Clink-arg=${env:ANDROID_SDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang -L --target=${workspaceFolder}/target/cargo-apk-temp-extra-link-libraries",
// "AR_aarch64-linux-android": "${env:ANDROID_SDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar",
// "CARGO_TARGET_AARCH64_LINUX_ANDROID_AR": "${env:ANDROID_SDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar",
// }
}
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ bytemuck = { workspace = true }
skrifa = { workspace = true }
peniko = { workspace = true }
wgpu = { workspace = true, optional = true }
log = { workspace = true }
raw-window-handle = { workspace = true }
futures-intrusive = { workspace = true }
wgpu-profiler = { workspace = true, optional = true }
Expand All @@ -66,7 +67,7 @@ raw-window-handle = "0.6.0"

# NOTE: Make sure to keep this in sync with the version badge in README.md
wgpu = { version = "0.19.3" }

log = "0.4.20"

# Used for examples
clap = "4.5.1"
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ path = "$HOME/.android/debug.keystore"
keystore_password = "android"
```

> [!NOTE]
> As `cargo apk` does not allow passing command line arguments or environment variables to the app when ran, these can be embedded into the
> program at compile time (currently for Android only)
> `with_winit` currently supports the environment variables:
> - `VELLO_STATIC_LOG`, which is equivalent to `RUST_LOG`
> - `VELLO_STATIC_ARGS`, which is equivalent to passing in command line arguments

For example (with unix shell environment variable syntax):
```sh
VELLO_STATIC_LOG="vello=trace" VELLO_STATIC_ARGS="--test-scenes" cargo apk run -p with_winit --lib
```

## Community

Discussion of Vello development happens in the [Xi Zulip](https://xi.zulipchat.com/), specifically the [#gpu stream](https://xi.zulipchat.com/#narrow/stream/197075-gpu). All public content can be read without logging in.
Expand Down
7 changes: 5 additions & 2 deletions examples/with_winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ wgpu-profiler = { workspace = true }

wgpu = { workspace = true }
winit = "0.29.12"
log = { workspace = true }

[target.'cfg(not(target_os = "android"))'.dependencies]
# We use android_logger on Android
env_logger = "0.11.2"
log = "0.4.21"

[target.'cfg(not(any(target_arch = "wasm32", target_os = "android")))'.dependencies]
vello = { path = "../../", features = ["hot_reload"] }
Expand All @@ -43,5 +46,5 @@ android_logger = "0.13.3"
console_error_panic_hook = "0.1.7"
console_log = "1.0.0"
wasm-bindgen-futures = "0.4.41"
web-sys = { version = "0.3.67", features = [ "HtmlCollection", "Text" ] }
web-sys = { version = "0.3.67", features = ["HtmlCollection", "Text"] }
getrandom = { version = "0.2.12", features = ["js"] }
84 changes: 61 additions & 23 deletions examples/with_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use std::num::NonZeroUsize;
use std::{collections::HashSet, sync::Arc};
use wgpu_profiler::GpuProfilerSettings;

use anyhow::Result;
use clap::{CommandFactory, Parser};
use scenes::{ImageCache, SceneParams, SceneSet, SimpleText};
use vello::peniko::Color;
Expand Down Expand Up @@ -88,6 +87,15 @@ fn run(
#[cfg(not(target_arch = "wasm32"))]
let mut render_state = None::<RenderState>;
let use_cpu = args.use_cpu;
// The available kinds of anti-aliasing
#[cfg(not(target_os = "android"))]
// TODO: Make this set configurable through the command line
// Alternatively, load anti-aliasing shaders on demand/asynchronously
let aa_configs = [AaConfig::Area, AaConfig::Msaa8, AaConfig::Msaa16];
#[cfg(target_os = "android")]
// Hard code to only one on Android whilst we are working on startup speed
let aa_configs = [AaConfig::Area];

// The design of `RenderContext` forces delayed renderer initialisation to
// not work on wasm, as WASM futures effectively must be 'static.
// Otherwise, this could work by sending the result to event_loop.proxy
Expand All @@ -101,7 +109,7 @@ fn run(
RendererOptions {
surface_format: Some(render_state.surface.format),
use_cpu,
antialiasing_support: vello::AaSupport::all(),
antialiasing_support: aa_configs.iter().copied().collect(),
// We currently initialise on one thread on WASM, but mark this here
// anyway
num_init_threads: NonZeroUsize::new(1),
Expand Down Expand Up @@ -137,7 +145,6 @@ fn run(
let mut vsync_on = !args.startup_vsync_off;
let mut gpu_profiling_on = args.startup_gpu_profiling_on;

const AA_CONFIGS: [AaConfig; 3] = [AaConfig::Area, AaConfig::Msaa8, AaConfig::Msaa16];
// We allow cycling through AA configs in either direction, so use a signed index
let mut aa_config_ix: i32 = 0;

Expand Down Expand Up @@ -242,7 +249,7 @@ fn run(
println!("Wrote trace to path {path:?}");
}
Err(e) => {
eprintln!("Failed to write trace {e}")
log::warn!("Failed to write trace {e}")
}
}
}
Expand Down Expand Up @@ -350,7 +357,7 @@ fn run(
* Affine::translate(-prior_position)
* transform;
} else {
eprintln!(
log::warn!(
"Scrolling without mouse in window; this shouldn't be possible"
);
}
Expand All @@ -375,7 +382,7 @@ fn run(

// Allow looping forever
scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32);
aa_config_ix = aa_config_ix.rem_euclid(AA_CONFIGS.len() as i32);
aa_config_ix = aa_config_ix.rem_euclid(aa_configs.len() as i32);

let example_scene = &mut scenes.scenes[scene_ix as usize];
if prev_scene_ix != scene_ix {
Expand Down Expand Up @@ -406,7 +413,7 @@ fn run(
.base_color
.or(scene_params.base_color)
.unwrap_or(Color::BLACK);
let antialiasing_method = AA_CONFIGS[aa_config_ix as usize];
let antialiasing_method = aa_configs[aa_config_ix as usize];
let render_params = vello::RenderParams {
base_color,
width,
Expand Down Expand Up @@ -528,21 +535,21 @@ fn run(
return;
};
let device_handle = &render_cx.devices[render_state.surface.dev_id];
eprintln!("==============\nReloading shaders");
log::info!("==============\nReloading shaders");
let start = Instant::now();
let result = renderers[render_state.surface.dev_id]
.as_mut()
.unwrap()
.reload_shaders(&device_handle.device);
// We know that the only async here (`pop_error_scope`) is actually sync, so blocking is fine
match pollster::block_on(result) {
Ok(_) => eprintln!("Reloading took {:?}", start.elapsed()),
Err(e) => eprintln!("Failed to reload shaders because of {e}"),
Ok(_) => log::info!("Reloading took {:?}", start.elapsed()),
Err(e) => log::warn!("Failed to reload shaders because of {e}"),
}
}
},
Event::Suspended => {
eprintln!("Suspending");
log::info!("Suspending");
#[cfg(not(target_arch = "wasm32"))]
// When we suspend, we need to remove the `wgpu` Surface
if let Some(render_state) = render_state.take() {
Expand Down Expand Up @@ -576,12 +583,12 @@ fn run(
RendererOptions {
surface_format: Some(render_state.surface.format),
use_cpu,
antialiasing_support: vello::AaSupport::all(),
antialiasing_support: aa_configs.iter().copied().collect(),
num_init_threads: NonZeroUsize::new(args.num_init_threads)
},
)
.expect("Could create renderer");
eprintln!("Creating renderer {id} took {:?}", start.elapsed());
log::info!("Creating renderer {id} took {:?}", start.elapsed());
renderer.profiler.change_settings(GpuProfilerSettings{
enable_timer_queries: gpu_profiling_on,
enable_debug_groups: gpu_profiling_on,
Expand Down Expand Up @@ -638,22 +645,23 @@ fn display_error_message() -> Option<()> {
Some(())
}

pub fn main() -> Result<()> {
#[cfg(not(target_os = "android"))]
pub fn main() -> anyhow::Result<()> {
// TODO: initializing both env_logger and console_logger fails on wasm.
// Figure out a more principled approach.
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
let args = Args::parse();
env_logger::builder()
.format_timestamp(Some(env_logger::TimestampPrecision::Millis))
.init();
let args = parse_arguments();
let scenes = args.args.select_scene_set(Args::command)?;
if let Some(scenes) = scenes {
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build()?;
#[allow(unused_mut)]
let mut render_cx = RenderContext::new().unwrap();
#[cfg(not(target_arch = "wasm32"))]
{
#[cfg(not(target_os = "android"))]
let proxy = event_loop.create_proxy();
#[cfg(not(target_os = "android"))]
let _keep = hot_reload::hot_reload(move || {
proxy.send_event(UserEvent::HotReload).ok().map(drop)
});
Expand Down Expand Up @@ -709,23 +717,53 @@ pub fn main() -> Result<()> {
Ok(())
}

fn parse_arguments() -> Args {
// We allow baking in arguments at compile time. This is especially useful for
// Android and WASM.
// This is used on desktop platforms to allow debugging the same settings
let args = if let Some(args) = option_env!("VELLO_STATIC_ARGS") {
// We split by whitespace here to allow passing multiple arguments
// In theory, we could do more advanced parsing/splitting (e.g. using quotes),
// but that would require a lot more effort

// We `chain` in a fake binary name, because clap ignores the first argument otherwise
// Ideally, we'd use the `no_binary_name` argument, but setting that at runtime would
// require globals or some worse hacks
Args::parse_from(std::iter::once("with_winit").chain(args.split_ascii_whitespace()))
} else {
Args::parse()
};
args
}

#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;

#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;

android_logger::init_once(
android_logger::Config::default().with_max_level(log::LevelFilter::Warn),
);
let config = android_logger::Config::default();
// We allow configuring the Android logging with an environment variable at build time
let config = if let Some(logging_config) = option_env!("VELLO_STATIC_LOG") {
let mut filter = android_logger::FilterBuilder::new();
filter.filter_level(log::LevelFilter::Warn);
filter.parse(logging_config);
let filter = filter.build();
// This shouldn't be needed in theory, but without this the max
// level is set to 0 (i.e. Off)
let config = config.with_max_level(filter.filter());
config.with_filter(filter)
} else {
config.with_max_level(log::LevelFilter::Warn)
};
android_logger::init_once(config);

let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build()
.expect("Required to continue");
let args = Args::parse();
let args = parse_arguments();
let scenes = args
.args
.select_scene_set(|| Args::command())
Expand Down
7 changes: 6 additions & 1 deletion examples/with_winit/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
use anyhow::Result;

fn main() -> Result<()> {
with_winit::main()
#[cfg(not(target_os = "android"))]
{
with_winit::main()
}
#[cfg(target_os = "android")]
unreachable!()
}
18 changes: 18 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ impl AaSupport {
}
}

impl FromIterator<AaConfig> for AaSupport {
fn from_iter<T: IntoIterator<Item = AaConfig>>(iter: T) -> Self {
let mut result = Self {
area: false,
msaa8: false,
msaa16: false,
};
for config in iter {
match config {
AaConfig::Area => result.area = true,
AaConfig::Msaa8 => result.msaa8 = true,
AaConfig::Msaa16 => result.msaa16 = true,
}
}
result
}
}

/// Renders a scene into a texture or surface.
#[cfg(feature = "wgpu")]
pub struct Renderer {
Expand Down
4 changes: 3 additions & 1 deletion src/shaders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ pub fn full_shaders(
if force_gpu_from == Some(stringify!($name)) {
force_gpu = true;
}
let processed =
preprocess::preprocess(shader!(stringify!($name)), &$defines, &imports).into();
engine.add_shader(
device,
$label,
preprocess::preprocess(shader!(stringify!($name)), &$defines, &imports).into(),
processed,
&$bindings,
if force_gpu {
CpuShaderType::Missing
Expand Down