use goldy::{
Buffer, BufferUsage, Color, CommandEncoder, DeviceType, Surface,
Instance, RenderPipeline, RenderPipelineDesc, ShaderModule,
VertexBufferLayout, VertexAttribute, VertexFormat,
shaders,
};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
event::{ElementState, MouseButton, WindowEvent},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
window::{Window, WindowAttributes, WindowId},
};
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct QuadVertex {
position: [f32; 2],
uv: [f32; 2],
time: f32,
}
impl QuadVertex {
fn layout() -> VertexBufferLayout {
VertexBufferLayout {
stride: std::mem::size_of::<Self>() as u32,
attributes: vec![
VertexAttribute { location: 0, format: VertexFormat::Float32x2, offset: 0 },
VertexAttribute { location: 1, format: VertexFormat::Float32x2, offset: 8 },
VertexAttribute { location: 2, format: VertexFormat::Float32, offset: 16 },
],
}
}
}
fn create_quad(time: f32) -> [QuadVertex; 6] {
[
QuadVertex { position: [-1.0, -1.0], uv: [0.0, 1.0], time },
QuadVertex { position: [1.0, -1.0], uv: [1.0, 1.0], time },
QuadVertex { position: [1.0, 1.0], uv: [1.0, 0.0], time },
QuadVertex { position: [-1.0, -1.0], uv: [0.0, 1.0], time },
QuadVertex { position: [1.0, 1.0], uv: [1.0, 0.0], time },
QuadVertex { position: [-1.0, 1.0], uv: [0.0, 0.0], time },
]
}
#[derive(Clone, Copy, PartialEq)]
enum EffectType {
Plasma,
Tunnel,
Starfield,
}
impl EffectType {
fn title(&self) -> &'static str {
match self {
EffectType::Plasma => "Plasma [Space=pause, Click=reset]",
EffectType::Tunnel => "Tunnel [Space=reverse, Click=reset]",
EffectType::Starfield => "Starfield [Space=warp, Click=reset]",
}
}
fn shader_source(&self) -> &'static str {
match self {
EffectType::Plasma => shaders::PLASMA,
EffectType::Tunnel => shaders::TUNNEL,
EffectType::Starfield => shaders::STARFIELD,
}
}
}
struct WindowState {
window: Arc<Window>,
surface: Surface,
pipeline: RenderPipeline,
effect_type: EffectType,
start_time: Instant,
paused: bool,
paused_at: f32,
time_multiplier: f32,
vertex_buffers: Vec<Buffer>,
has_focus: bool,
}
const MAX_FRAMES_IN_FLIGHT: usize = 2;
impl WindowState {
fn new(
window: Arc<Window>,
device: &Arc<goldy::Device>,
effect_type: EffectType,
) -> anyhow::Result<Self> {
let surface = Surface::new(&device, window.as_ref())?;
let shader = ShaderModule::from_slang(device, effect_type.shader_source())?;
let pipeline = RenderPipeline::new(device, &shader, &shader, &RenderPipelineDesc {
vertex_layout: QuadVertex::layout(),
target_format: surface.format(),
..Default::default()
})?;
Ok(Self {
window,
surface,
pipeline,
effect_type,
start_time: Instant::now(),
paused: false,
paused_at: 0.0,
time_multiplier: 1.0,
vertex_buffers: Vec::with_capacity(MAX_FRAMES_IN_FLIGHT),
has_focus: false,
})
}
fn current_time(&self) -> f32 {
if self.paused {
self.paused_at
} else {
self.paused_at + self.start_time.elapsed().as_secs_f32() * self.time_multiplier
}
}
fn toggle_pause(&mut self) {
if self.paused {
self.start_time = Instant::now();
self.paused = false;
} else {
self.paused_at = self.current_time();
self.paused = true;
}
}
fn toggle_effect_modifier(&mut self) {
match self.effect_type {
EffectType::Plasma => self.toggle_pause(),
EffectType::Tunnel => {
self.paused_at = self.current_time();
self.start_time = Instant::now();
self.time_multiplier *= -1.0;
}
EffectType::Starfield => {
self.paused_at = self.current_time();
self.start_time = Instant::now();
self.time_multiplier = if self.time_multiplier > 2.0 { 1.0 } else { 5.0 };
}
}
}
fn reset(&mut self) {
self.start_time = Instant::now();
self.paused = false;
self.paused_at = 0.0;
self.time_multiplier = 1.0;
}
fn render(&mut self, device: &goldy::Device) -> anyhow::Result<()> {
let size = self.window.inner_size();
if size.width == 0 || size.height == 0 {
return Ok(());
}
let time = self.current_time();
let vertices = create_quad(time);
let vertex_buffer = Buffer::with_data(device, &vertices, BufferUsage::VERTEX)?;
let frame = self.surface.acquire()?;
if self.vertex_buffers.len() >= MAX_FRAMES_IN_FLIGHT {
self.vertex_buffers.remove(0);
}
let mut encoder = CommandEncoder::new();
{
let mut pass = encoder.begin_render_pass();
pass.clear(Color::BLACK);
pass.set_pipeline(&self.pipeline);
pass.set_vertex_buffer(0, &vertex_buffer);
pass.draw(0..6, 0..1);
}
frame.render(encoder)?;
self.surface.present(frame)?;
self.vertex_buffers.push(vertex_buffer);
Ok(())
}
fn handle_resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
let _ = self.surface.resize(width, height);
}
}
}
struct App {
instance: Instance,
device: Option<Arc<goldy::Device>>,
windows: HashMap<WindowId, WindowState>,
effects_to_create: Vec<EffectType>,
}
impl App {
fn new() -> anyhow::Result<Self> {
Ok(Self {
instance: Instance::new()?,
device: None,
windows: HashMap::new(),
effects_to_create: vec![EffectType::Plasma, EffectType::Tunnel, EffectType::Starfield],
})
}
fn create_window(
&mut self,
event_loop: &ActiveEventLoop,
effect_type: EffectType,
position: (i32, i32),
) -> anyhow::Result<()> {
let device = self.device.as_ref().unwrap().clone();
let attrs = WindowAttributes::default()
.with_title(format!("Goldy - {}", effect_type.title()))
.with_inner_size(LogicalSize::new(500, 500))
.with_position(winit::dpi::LogicalPosition::new(position.0, position.1));
let window = Arc::new(event_loop.create_window(attrs)?);
let window_id = window.id();
let mut state = WindowState::new(window.clone(), &device, effect_type)?;
state.render(&device)?;
window.request_redraw();
self.windows.insert(window_id, state);
Ok(())
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.device.is_none() {
match self.instance.create_device(DeviceType::DiscreteGpu) {
Ok(device) => self.device = Some(Arc::new(device)),
Err(e) => {
eprintln!("Failed to create device: {}", e);
event_loop.exit();
return;
}
}
}
let effects: Vec<_> = self.effects_to_create.drain(..).collect();
for (i, effect) in effects.into_iter().enumerate() {
let x = 50 + (i as i32) * 520;
let y = 100;
if let Err(e) = self.create_window(event_loop, effect, (x, y)) {
eprintln!("Failed to create window for {:?}: {}", effect.title(), e);
}
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
let state = match self.windows.get_mut(&window_id) {
Some(s) => s,
None => return,
};
match event {
WindowEvent::CloseRequested => {
self.windows.remove(&window_id);
if self.windows.is_empty() {
event_loop.exit();
}
}
WindowEvent::Focused(focused) => {
state.has_focus = focused;
if focused {
println!("Focus: {} ({})", state.effect_type.title(),
if state.paused { "paused" } else { "running" });
}
}
WindowEvent::KeyboardInput { event, .. } if event.state == ElementState::Pressed => {
match event.logical_key {
Key::Named(NamedKey::Escape) => {
self.windows.remove(&window_id);
if self.windows.is_empty() {
event_loop.exit();
}
}
Key::Named(NamedKey::Space) => {
if let Some(s) = self.windows.get_mut(&window_id) {
s.toggle_effect_modifier();
println!("[{}] Modifier toggled", s.effect_type.title());
}
}
Key::Character(ref c) if c == "r" || c == "R" => {
if let Some(s) = self.windows.get_mut(&window_id) {
s.reset();
println!("[{}] Reset", s.effect_type.title());
}
}
_ => {}
}
}
WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, .. } => {
if let Some(s) = self.windows.get_mut(&window_id) {
s.reset();
println!("[{}] Reset (click)", s.effect_type.title());
}
}
WindowEvent::RedrawRequested => {
}
WindowEvent::Resized(new_size) => {
if let Some(s) = self.windows.get_mut(&window_id) {
s.handle_resize(new_size.width, new_size.height);
}
}
_ => {}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let device = match &self.device {
Some(d) => d.clone(),
None => return,
};
for state in self.windows.values_mut() {
if let Err(e) = state.render(&device) {
eprintln!("[{}] Render error: {}", state.effect_type.title(), e);
}
}
}
}
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt().with_env_filter("info").init();
println!("Goldy Multi-Window Example");
println!("========================");
println!("Three windows, three effects, independent controls:");
println!();
println!(" Plasma: Space=pause Click/R=reset");
println!(" Tunnel: Space=reverse Click/R=reset");
println!(" Starfield: Space=warp Click/R=reset");
println!();
println!("Escape closes the focused window. Close all to exit.");
println!();
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
event_loop.run_app(&mut App::new()?)?;
Ok(())
}