use std::{alloc::System, cell::RefCell, rc::Rc};
use cranpose_app_shell::AppShell;
use cranpose_core::location_key;
use cranpose_foundation::lazy::{LazyItems, LazyListScope, LazyListState, rememberLazyListState};
use cranpose_ui::{
Color, LinearArrangement, Modifier, RenderEffect, TextStyle, composable,
widgets::{Box, BoxSpec, LazyColumn, LazyColumnSpec, Text},
};
use cranpose_ui_graphics::{LiquidGlassRect, LiquidGlassSpec, TileMode, liquid_glass_effect};
use stats_alloc::{INSTRUMENTED_SYSTEM, Region, Stats, StatsAlloc};
#[global_allocator]
static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
const FRAME_WIDTH: u32 = 1080;
const FRAME_HEIGHT: u32 = 2244;
const WARMUP_FRAMES: usize = 60;
fn measured_frames() -> usize {
std::env::var("PROFILE_FRAMES")
.ok()
.and_then(|raw| raw.parse().ok())
.unwrap_or(240)
}
const SCROLL_DELTA_PER_FRAME: f32 = -30.0;
fn add_stats(total: &mut Stats, delta: Stats) {
total.allocations += delta.allocations;
total.deallocations += delta.deallocations;
total.reallocations += delta.reallocations;
total.bytes_allocated += delta.bytes_allocated;
total.bytes_deallocated += delta.bytes_deallocated;
total.bytes_reallocated += delta.bytes_reallocated;
}
#[composable]
fn CardRow(index: usize) {
let fill = if index.is_multiple_of(2) {
Color(0.98, 0.98, 0.99, 1.0)
} else {
Color(0.94, 0.95, 0.97, 1.0)
};
Box(
Modifier::empty()
.fill_max_width()
.height(280.0)
.rounded_corners(24.0)
.shadow(12.0)
.background(fill)
.padding(28.0),
BoxSpec::new(),
move || {
Text(
format!("Receipt {index} — Grocery Market"),
Modifier::empty(),
TextStyle::default(),
);
},
);
}
fn chrome_blur_radius() -> f32 {
std::env::var("PROFILE_BLUR_RADIUS")
.ok()
.and_then(|raw| raw.parse().ok())
.unwrap_or(36.0)
}
fn chrome_glass_effect(rect_width: f32, rect_height: f32) -> RenderEffect {
let optical = liquid_glass_effect(
&LiquidGlassRect {
left: 0.0,
top: 0.0,
width: rect_width,
height: rect_height,
tint_color: Color(0.9, 0.9, 0.95, 0.35),
},
&LiquidGlassSpec {
corner_radius: 28.0,
blur_radius: 0.0,
..LiquidGlassSpec::default()
},
rect_width,
rect_height,
);
RenderEffect::blur_with_edge_treatment(chrome_blur_radius(), TileMode::Mirror).then(optical)
}
#[composable]
fn GlassBar(x: f32, y: f32, width: f32, height: f32) {
Box(
Modifier::empty()
.offset(x, y)
.width(width)
.height(height)
.rounded_corners(28.0)
.shadow(8.0)
.backdrop_effect(chrome_glass_effect(width, height))
.padding(24.0),
BoxSpec::new(),
|| {
Box(
Modifier::empty()
.width(180.0)
.height(32.0)
.background(Color(1.0, 1.0, 1.0, 0.8))
.rounded_corners(16.0),
BoxSpec::new(),
|| {},
);
},
);
}
#[composable]
fn CardListScene(list_state: LazyListState) {
Box(
Modifier::empty()
.size_points(FRAME_WIDTH as f32, FRAME_HEIGHT as f32)
.background(Color(0.85, 0.86, 0.88, 1.0)),
BoxSpec::new(),
move || {
LazyColumn(
Modifier::empty().fill_max_size().padding(32.0),
list_state,
LazyColumnSpec::new().vertical_arrangement(LinearArrangement::SpacedBy(28.0)),
move |scope| {
scope.items(LazyItems::new(400).key(|i: usize| i as u64), CardRow);
},
);
let bars: usize = std::env::var("PROFILE_BARS")
.ok()
.and_then(|raw| raw.parse().ok())
.unwrap_or(2);
if bars >= 1 {
GlassBar(24.0, 40.0, FRAME_WIDTH as f32 - 48.0, 200.0);
}
if bars >= 2 {
GlassBar(
24.0,
FRAME_HEIGHT as f32 - 240.0,
FRAME_WIDTH as f32 - 48.0,
200.0,
);
}
},
);
}
fn main() {
let instance = wgpu::Instance::default();
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
..wgpu::RequestAdapterOptions::default()
}))
.expect("adapter");
eprintln!(
"adapter: {} ({:?}) timestamps={}",
adapter.get_info().name,
adapter.get_info().backend,
adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY),
);
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("Pass Timing Profile Device"),
required_features: cranpose_render_wgpu::optional_device_features(&adapter),
required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
}))
.expect("device");
let mut renderer = cranpose_render_wgpu::WgpuRenderer::new(&[
cranpose_render_common::software_text_raster::DEFAULT_SOFTWARE_TEXT_FONT_BYTES,
]);
renderer.init_gpu_compiling_inline_for_tests(
std::sync::Arc::new(device),
std::sync::Arc::new(queue),
wgpu::TextureFormat::Bgra8UnormSrgb,
adapter.get_info().backend,
adapter.get_downlevel_capabilities().flags,
);
let root_key = location_key(file!(), line!(), column!());
let list_state: Rc<RefCell<Option<LazyListState>>> = Rc::new(RefCell::new(None));
let list_state_for_app = Rc::clone(&list_state);
let mut shell = AppShell::new(renderer, root_key, move || {
let state = rememberLazyListState();
*list_state_for_app.borrow_mut() = Some(state);
CardListScene(state);
});
shell.set_viewport(FRAME_WIDTH as f32, FRAME_HEIGHT as f32);
shell.set_buffer_size(FRAME_WIDTH, FRAME_HEIGHT);
shell.update();
let scroll = |shell: &mut AppShell<cranpose_render_wgpu::WgpuRenderer>| {
let state = list_state
.borrow()
.as_ref()
.copied()
.expect("list state captured");
shell.debug_enter_app_context(|| state.dispatch_scroll_delta(SCROLL_DELTA_PER_FRAME));
};
let target = {
let device = shell.renderer().try_device().expect("device");
device.create_texture(&wgpu::TextureDescriptor {
label: Some("Profile Render Target"),
size: wgpu::Extent3d {
width: FRAME_WIDTH,
height: FRAME_HEIGHT,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
})
};
let view = target.create_view(&wgpu::TextureViewDescriptor::default());
for _ in 0..WARMUP_FRAMES {
scroll(&mut shell);
shell.update();
shell
.renderer()
.render(&target, &view, FRAME_WIDTH, FRAME_HEIGHT)
.expect("frame render");
}
let measured_frames = measured_frames();
let region = Region::new(GLOBAL);
let mut renderer_allocations = Stats::default();
let start = std::time::Instant::now();
for _ in 0..measured_frames {
scroll(&mut shell);
shell.update();
let render_region = Region::new(GLOBAL);
shell
.renderer()
.render(&target, &view, FRAME_WIDTH, FRAME_HEIGHT)
.expect("frame render");
add_stats(&mut renderer_allocations, render_region.change());
}
let elapsed = start.elapsed().as_secs_f64();
let allocations = region.change();
println!(
"{{\"frames\":{measured_frames},\"elapsed_ns\":{},\"allocations\":{},\"reallocations\":{},\"bytes_allocated\":{},\"renderer_allocations\":{},\"renderer_reallocations\":{},\"renderer_bytes_allocated\":{}}}",
(elapsed * 1_000_000_000.0) as u128,
allocations.allocations,
allocations.reallocations,
allocations.bytes_allocated,
renderer_allocations.allocations,
renderer_allocations.reallocations,
renderer_allocations.bytes_allocated,
);
let report = shell.renderer().gpu_pass_timings();
let stats = shell.renderer().last_frame_stats().expect("frame stats");
println!(
"frames={} cpu_submission_fps={:.1} report_frames={} invalid_frames={} dropped_frames={} dropped_passes={}",
measured_frames,
measured_frames as f64 / elapsed,
report.frames,
report.invalid_frames,
report.dropped_frames,
report.dropped_passes,
);
println!(
"counters: passes={} blur={} composite={} shadow_hit_px={:.2}MP layer_miss_px={:.2}MP",
stats.pass_count,
stats.blur_passes,
stats.composite_passes,
stats.shadow_shape_cache_hit_pixels as f64 / 1_000_000.0,
stats.layer_cache_miss_pixels as f64 / 1_000_000.0,
);
if report.frames == 0
|| report.invalid_frames > 0
|| report.dropped_frames > 0
|| report.dropped_passes > 0
{
eprintln!(
"No complete current GPU timing window; inspect the [GPU-PASS] windows and their coverage counters."
);
return;
}
let frames = f64::from(report.frames);
let total_ms: f64 = report.entries.iter().map(|entry| entry.total_ms).sum();
println!(
"gpu_span={:.3}ms/frame occupancy={:.3}ms/frame",
report.span_ms / frames,
total_ms / frames,
);
for entry in &report.entries {
println!(
" {:<40} {:>8.3}ms/frame x{:>5.1}",
entry.label,
entry.total_ms / frames,
entry.passes as f64 / frames,
);
}
}