blob: 82c97dfa520714b4de591c15e3f1334579ae13af [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::{
collections::HashSet,
fmt,
path::PathBuf,
str::FromStr,
time::{Duration, Instant},
};
use clap::{Parser, Subcommand};
use forma::{Color, Composition};
use runner::{CpuRunner, GpuRunner};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
pub mod demos;
mod runner;
enum Device {
Cpu,
GpuLowPower,
GpuHighPerformance,
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Device::Cpu => write!(f, "cpu"),
Device::GpuLowPower => write!(f, "gpu low power"),
Device::GpuHighPerformance => write!(f, "gpu high performance"),
}
}
}
impl FromStr for Device {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"c" | "cpu" => Ok(Device::Cpu),
"l" | "gpu-low" => Ok(Device::GpuLowPower),
"h" | "gpu-high" => Ok(Device::GpuHighPerformance),
_ => Err("must be c|cpu or l|gpu-low or h|gpu-high"),
}
}
}
#[derive(Parser)]
#[clap(about = "forma demo with multiple modes")]
struct Demo {
/// Device to run the demo on
#[clap(default_value = "cpu")]
device: Device,
#[clap(subcommand)]
mode: Mode,
}
#[derive(Subcommand)]
enum Mode {
/// Renders random circles
Circles {
/// Amount of circles to draw
#[clap(default_value = "100")]
count: usize,
},
/// Renders a Rive animation
Rive {
/// .riv input file
#[clap(parse(from_os_str))]
file: PathBuf,
},
/// Renders an SVG
Svg {
/// .svg input file
#[clap(parse(from_os_str))]
file: PathBuf,
/// Scale of the SVG
#[clap(short, long, default_value = "1.0")]
scale: f32,
},
/// Renders a spaceship game
Spaceship,
/// Renders a rotating texture
Texture,
}
trait App {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn set_width(&mut self, width: usize);
fn set_height(&mut self, height: usize);
fn compose(&mut self, composition: &mut Composition, elapsed: Duration, keyboard: &Keyboard);
}
trait Runner {
fn resize(&mut self, width: u32, height: u32);
fn render(&mut self, app: &mut dyn App, elapsed: Duration, keyboard: &Keyboard);
}
struct Keyboard {
pressed: HashSet<VirtualKeyCode>,
}
impl Keyboard {
fn new() -> Self {
Self { pressed: HashSet::new() }
}
fn is_key_down(&self, key: VirtualKeyCode) -> bool {
self.pressed.contains(&key)
}
fn on_keyboard_input(&mut self, input: winit::event::KeyboardInput) {
if let Some(code) = input.virtual_keycode {
match input.state {
ElementState::Pressed => self.pressed.insert(code),
ElementState::Released => self.pressed.remove(&code),
};
}
}
}
pub fn to_linear(rgb: [u8; 3]) -> Color {
fn conv(l: u8) -> f32 {
let l = f32::from(l) * 255.0f32.recip();
if l <= 0.04045 {
l * 12.92f32.recip()
} else {
((l + 0.055) * 1.055f32.recip()).powf(2.4)
}
}
forma::Color { r: conv(rgb[0]), g: conv(rgb[1]), b: conv(rgb[2]), a: 1.0 }
}
fn main() {
let opts = Demo::parse();
let mut app: Box<dyn App> = match opts.mode {
Mode::Circles { count } => Box::new(demos::Circles::new(count)),
Mode::Rive { file } => Box::new(demos::Rive::new(file)),
Mode::Svg { file, scale } => Box::new(demos::Svg::new(file, scale)),
Mode::Spaceship => Box::new(demos::Spaceship::new()),
Mode::Texture {} => Box::new(demos::Texture::new()),
};
let width = app.width();
let height = app.height();
let event_loop = EventLoop::new();
let mut runner: Box<dyn Runner> = match opts.device {
Device::Cpu => Box::new(CpuRunner::new(&event_loop, width as u32, height as u32)),
Device::GpuLowPower => Box::new(GpuRunner::new(
&event_loop,
width as u32,
height as u32,
wgpu::PowerPreference::LowPower,
)),
Device::GpuHighPerformance => Box::new(GpuRunner::new(
&event_loop,
width as u32,
height as u32,
wgpu::PowerPreference::HighPerformance,
)),
};
let mut instant = Instant::now();
let mut keyboard = Keyboard::new();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
Event::WindowEvent {
event:
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Escape), .. },
..
},
..
} => {
*control_flow = ControlFlow::Exit;
}
Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. } => {
keyboard.on_keyboard_input(input);
}
Event::WindowEvent {
event:
WindowEvent::Resized(size)
| WindowEvent::ScaleFactorChanged { new_inner_size: &mut size, .. },
..
} => {
runner.resize(size.width, size.height);
app.set_width(size.width as usize);
app.set_height(size.height as usize);
}
Event::MainEventsCleared => {
let elapsed = instant.elapsed();
instant = Instant::now();
runner.render(&mut *app, elapsed, &keyboard);
}
_ => (),
}
});
}