Skip to content

Commit

Permalink
Configure command line arguments in Android and WASM (#443)
Browse files Browse the repository at this point in the history
* Add example settings to get android working

Resolve the diagnostics issue for the `main` on Android

* Add code used for startup time explorations

Allow storing vscode settings in a suitably named local file

Get the Android cache directory

Make the cache folder for Vello directly

Skip MSAA only on Android

* Document the new features in the readme

Remove the cache directory finding

Remove instant usage

* Fix logging on Android

* Work around clap issue

This seems to have regressed
  • Loading branch information
DJMcNab authored Mar 5, 2024
1 parent af72e66 commit 0347b39
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 30 deletions.
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

0 comments on commit 0347b39

Please sign in to comment.