#![expect(unsafe_code)]
use std::{
path::Path,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
use cranpose_render_wgpu::WgpuRenderer;
use jni::{
EnvUnowned, Outcome,
objects::{JClass, JString},
};
use web_time::Instant;
const PREPARE_TIMEOUT: Duration = Duration::from_secs(45);
static APP_LAUNCHED: AtomicBool = AtomicBool::new(false);
pub(crate) fn note_app_launched() {
APP_LAUNCHED.store(true, Ordering::Release);
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_cranpose_android_CranposePipelines_nativePrepareAfterUpdate<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
data_dir: JString<'local>,
) {
crate::android::init_logging(crate::android::DEFAULT_LOG_TAG);
match env
.with_env(|env| data_dir.try_to_string(env))
.into_outcome()
{
Outcome::Ok(data_dir) => prepare_after_update(Path::new(&data_dir)),
Outcome::Err(_) | Outcome::Panic(_) => {
log::warn!("[pipeline-cache] the app's data directory did not decode");
}
}
}
fn prepare_after_update(data_dir: &Path) {
let started = Instant::now();
crate::android::keep_pipeline_cache_in(data_dir);
let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
descriptor.backends = wgpu::Backends::VULKAN;
descriptor.flags = wgpu::InstanceFlags::empty();
let instance = wgpu::Instance::new(descriptor);
let adapter = match pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
apply_limit_buckets: false,
})) {
Ok(adapter) => adapter,
Err(error) => {
log::warn!("[pipeline-cache] no Vulkan adapter to prepare pipelines on: {error}");
return;
}
};
let info = adapter.get_info();
crate::android::name_pipeline_cache_file(&info);
let (device, queue) =
match pollster::block_on(adapter.request_device(&crate::android::android_device(&adapter)))
{
Ok(device) => device,
Err(error) => {
log::warn!("[pipeline-cache] no device to prepare pipelines on: {error}");
return;
}
};
let mut renderer = WgpuRenderer::default();
renderer.init_gpu(
Arc::new(device),
Arc::new(queue),
wgpu::TextureFormat::Rgba8Unorm,
info.backend,
adapter.get_downlevel_capabilities().flags,
);
let built = renderer
.prepare_recorded_pipelines(PREPARE_TIMEOUT, &|| APP_LAUNCHED.load(Ordering::Acquire));
drop(renderer);
log::info!(
"[pipeline-cache] prepared the pipelines after an update in {:.0} ms{}",
started.elapsed().as_secs_f64() * 1000.0,
if built { "" } else { ", unfinished" },
);
}