Initial attempt to implement Render Graph
This commit is contained in:
parent
173cfe2acf
commit
3732fc5e3d
2159
Cargo.lock
generated
2159
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||||
members = [
|
members = [
|
||||||
"khors-core",
|
"khors-core",
|
||||||
"vendor/egui-vulkano",
|
"vendor/egui-vulkano",
|
||||||
|
"vendor/egui-snarl",
|
||||||
"modules/khors-app",
|
"modules/khors-app",
|
||||||
"modules/khors-graphics",
|
"modules/khors-graphics",
|
||||||
"modules/khors-window",
|
"modules/khors-window",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use khors_config::ConfigModule;
|
use khors_config::ConfigModule;
|
||||||
use khors_app::App;
|
use khors_app::app::App;
|
||||||
use khors_graphics::RenderModule;
|
use khors_graphics::renderer::RenderModule;
|
||||||
use khors_window::WindowModule;
|
use khors_window::WindowModule;
|
||||||
use tokio::runtime::Builder;
|
use tokio::runtime::Builder;
|
||||||
use winit::event_loop::{ControlFlow, EventLoopBuilder};
|
use winit::event_loop::{ControlFlow, EventLoopBuilder};
|
||||||
|
|
318
modules/khors-app/src/app.rs
Normal file
318
modules/khors-app/src/app.rs
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
#![warn(dead_code)]
|
||||||
|
use khors_core::{
|
||||||
|
events::Events,
|
||||||
|
module::{Module, ModulesStack},
|
||||||
|
};
|
||||||
|
use khors_graphics::{
|
||||||
|
debug_gui::DebugGuiStack,
|
||||||
|
render_module::{RenderModule as ThreadLocalModule, RenderModulesStack},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use flax::{component, Schedule, World};
|
||||||
|
use vulkano::device::DeviceFeatures;
|
||||||
|
use vulkano_util::{
|
||||||
|
context::{VulkanoConfig, VulkanoContext},
|
||||||
|
window::VulkanoWindows,
|
||||||
|
};
|
||||||
|
use winit::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
window::WindowId,
|
||||||
|
};
|
||||||
|
|
||||||
|
component! {
|
||||||
|
window_id: WindowId,
|
||||||
|
|
||||||
|
resources,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct App {
|
||||||
|
name: String,
|
||||||
|
modules: ModulesStack,
|
||||||
|
thread_local_modules: RenderModulesStack,
|
||||||
|
world: World,
|
||||||
|
schedule: Schedule,
|
||||||
|
events: Events,
|
||||||
|
rx: flume::Receiver<AppEvent>,
|
||||||
|
running: bool,
|
||||||
|
event_cleanup_time: std::time::Duration,
|
||||||
|
vk_context: VulkanoContext,
|
||||||
|
vk_windows: VulkanoWindows,
|
||||||
|
debug_gui_stack: DebugGuiStack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut events = Events::new();
|
||||||
|
let (tx, rx) = flume::unbounded();
|
||||||
|
events.subscribe_custom(tx);
|
||||||
|
|
||||||
|
let schedule = Schedule::builder().build();
|
||||||
|
|
||||||
|
let vk_config = VulkanoConfig {
|
||||||
|
device_features: DeviceFeatures {
|
||||||
|
dynamic_rendering: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let vk_context = VulkanoContext::new(vk_config);
|
||||||
|
let vk_windows = VulkanoWindows::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name: "Khors".into(),
|
||||||
|
modules: ModulesStack::new(),
|
||||||
|
thread_local_modules: RenderModulesStack::new(),
|
||||||
|
world: World::new(),
|
||||||
|
schedule,
|
||||||
|
events,
|
||||||
|
rx,
|
||||||
|
running: false,
|
||||||
|
event_cleanup_time: std::time::Duration::from_secs(60),
|
||||||
|
vk_context,
|
||||||
|
vk_windows,
|
||||||
|
debug_gui_stack: DebugGuiStack::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> Result<()> {
|
||||||
|
self.running = true;
|
||||||
|
|
||||||
|
self.schedule.execute_par(&mut self.world).unwrap();
|
||||||
|
|
||||||
|
let vk_context = &mut self.vk_context;
|
||||||
|
let vk_windows = &mut self.vk_windows;
|
||||||
|
let world = &mut self.world;
|
||||||
|
let events = &mut self.events;
|
||||||
|
let frame_time = std::time::Duration::from_millis(16);
|
||||||
|
let gui_stack = &mut self.debug_gui_stack;
|
||||||
|
|
||||||
|
for module in self.modules.iter_mut() {
|
||||||
|
module.on_update(world, events, frame_time)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for module in self.thread_local_modules.iter_mut() {
|
||||||
|
module.on_update(gui_stack, vk_context, vk_windows, world, events, frame_time)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle_events();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_window<T>(&mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<T>)
|
||||||
|
where
|
||||||
|
T: Clone + Send + Sync,
|
||||||
|
{
|
||||||
|
let vk_window_id = self.vk_windows.create_window(
|
||||||
|
event_loop,
|
||||||
|
&self.vk_context,
|
||||||
|
&vulkano_util::window::WindowDescriptor {
|
||||||
|
title: self.name.clone(),
|
||||||
|
present_mode: vulkano::swapchain::PresentMode::Mailbox,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
|_| {},
|
||||||
|
);
|
||||||
|
|
||||||
|
let renderer = self.vk_windows.get_renderer(vk_window_id).unwrap();
|
||||||
|
|
||||||
|
self.world
|
||||||
|
.set(resources(), window_id(), vk_window_id)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.debug_gui_stack
|
||||||
|
.add_gui(vk_window_id, event_loop, renderer, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_event_loop<T>(
|
||||||
|
&mut self,
|
||||||
|
event: winit::event::Event<T>,
|
||||||
|
_elwt: &winit::event_loop::EventLoopWindowTarget<T>,
|
||||||
|
) -> Result<bool>
|
||||||
|
where
|
||||||
|
T: Clone + Send + Sync,
|
||||||
|
{
|
||||||
|
match &event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::Focused(_),
|
||||||
|
..
|
||||||
|
} => self.events().send(event.clone()),
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. },
|
||||||
|
window_id,
|
||||||
|
} => self
|
||||||
|
.vk_windows
|
||||||
|
.get_renderer_mut(*window_id)
|
||||||
|
.unwrap()
|
||||||
|
.resize(),
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::RedrawRequested,
|
||||||
|
window_id,
|
||||||
|
} => 'redraw: {
|
||||||
|
// Tasks for redrawing:
|
||||||
|
// 1. Update state based on events
|
||||||
|
// 2. Compute & Render
|
||||||
|
// 3. Reset input state
|
||||||
|
// 4. Update time & title
|
||||||
|
|
||||||
|
// The rendering part goes here:
|
||||||
|
match self
|
||||||
|
.vk_windows
|
||||||
|
.get_renderer(*window_id)
|
||||||
|
.unwrap()
|
||||||
|
.window_size()
|
||||||
|
{
|
||||||
|
[w, h] => {
|
||||||
|
// Skip this frame when minimized.
|
||||||
|
if w == 0.0 || h == 0.0 {
|
||||||
|
break 'redraw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run()?;
|
||||||
|
}
|
||||||
|
Event::WindowEvent { window_id, event } => {
|
||||||
|
let window = self.vk_windows.get_window(*window_id).unwrap();
|
||||||
|
let gui = self.debug_gui_stack.get_mut(*window_id).unwrap();
|
||||||
|
gui.update(window, event);
|
||||||
|
}
|
||||||
|
Event::AboutToWait => {
|
||||||
|
self.vk_windows.iter().for_each(|(window_id, _)| {
|
||||||
|
self.vk_windows
|
||||||
|
.get_window(*window_id)
|
||||||
|
.unwrap()
|
||||||
|
.request_redraw()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_events(&mut self) {
|
||||||
|
for event in self.rx.try_iter() {
|
||||||
|
match event {
|
||||||
|
AppEvent::Exit => self.running = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_schedule(&mut self, schedule: Schedule) {
|
||||||
|
self.schedule = schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn world(&self) -> &World {
|
||||||
|
&self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn world_mut(&mut self) -> &mut World {
|
||||||
|
&mut self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events(&self) -> &Events {
|
||||||
|
&self.events
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn events_mut(&mut self) -> &mut Events {
|
||||||
|
&mut self.events
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a module from the provided init closure to to the top of the layer stack. The provided
|
||||||
|
/// closure to construct the layer takes in the world and events.
|
||||||
|
pub fn push_render_module<F, T>(&mut self, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(
|
||||||
|
&mut VulkanoContext,
|
||||||
|
&mut VulkanoWindows,
|
||||||
|
&mut Schedule,
|
||||||
|
&mut World,
|
||||||
|
&mut Events,
|
||||||
|
) -> T,
|
||||||
|
T: 'static + ThreadLocalModule,
|
||||||
|
{
|
||||||
|
let module = func(
|
||||||
|
&mut self.vk_context,
|
||||||
|
&mut self.vk_windows,
|
||||||
|
&mut self.schedule,
|
||||||
|
&mut self.world,
|
||||||
|
&mut self.events,
|
||||||
|
);
|
||||||
|
self.thread_local_modules.push(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a layer from the provided init closure to to the top of the layer stack. The provided
|
||||||
|
/// closure to construct the layer takes in the world and events.
|
||||||
|
pub fn push_module<F, T>(&mut self, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Schedule, &mut World, &mut Events) -> T,
|
||||||
|
T: 'static + Module,
|
||||||
|
{
|
||||||
|
let module = func(&mut self.schedule, &mut self.world, &mut self.events);
|
||||||
|
self.modules.push(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a module from the provided init closure to to the top of the module stack. The provided
|
||||||
|
/// closure to construct the module takes in the world and events, and may return an error which
|
||||||
|
/// is propagated to the callee.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn try_push_module<F, T, E>(&mut self, func: F) -> Result<(), E>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut World, &mut Events) -> Result<T, E>,
|
||||||
|
T: 'static + Module,
|
||||||
|
{
|
||||||
|
let module = func(&mut self.world, &mut self.events)?;
|
||||||
|
self.modules.push(module);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a module from the provided init closure to to the top of the module stack. The provided
|
||||||
|
/// closure to construct the module takes in the world and events.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn insert_module<F, T>(&mut self, index: usize, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut World, &mut Events) -> T,
|
||||||
|
T: 'static + Module,
|
||||||
|
{
|
||||||
|
let module = func(&mut self.world, &mut self.events);
|
||||||
|
self.modules.insert(index, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a module from the provided init closure to to the top of the module stack. The provided
|
||||||
|
/// closure to construct the module takes in the world and events, and may return an error which
|
||||||
|
/// is propagated to the callee.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn try_insert_module<F, T, E>(&mut self, index: usize, func: F) -> Result<(), E>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut World, &mut Events) -> Result<T, E>,
|
||||||
|
T: 'static + Module,
|
||||||
|
{
|
||||||
|
let module = func(&mut self.world, &mut self.events)?;
|
||||||
|
self.modules.insert(index, module);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum AppEvent {
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,318 +1 @@
|
||||||
#![warn(dead_code)]
|
pub mod app;
|
||||||
use khors_core::{
|
|
||||||
events::Events,
|
|
||||||
module::{Module, ModulesStack},
|
|
||||||
};
|
|
||||||
use khors_graphics::{
|
|
||||||
debug_gui::DebugGuiStack,
|
|
||||||
render_module::{RenderModule as ThreadLocalModule, RenderModulesStack},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use flax::{component, Schedule, World};
|
|
||||||
use vulkano::device::DeviceFeatures;
|
|
||||||
use vulkano_util::{
|
|
||||||
context::{VulkanoConfig, VulkanoContext},
|
|
||||||
window::VulkanoWindows,
|
|
||||||
};
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
window::WindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
component! {
|
|
||||||
window_id: WindowId,
|
|
||||||
|
|
||||||
resources,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct App {
|
|
||||||
name: String,
|
|
||||||
modules: ModulesStack,
|
|
||||||
thread_local_modules: RenderModulesStack,
|
|
||||||
world: World,
|
|
||||||
schedule: Schedule,
|
|
||||||
events: Events,
|
|
||||||
rx: flume::Receiver<AppEvent>,
|
|
||||||
running: bool,
|
|
||||||
event_cleanup_time: std::time::Duration,
|
|
||||||
vk_context: VulkanoContext,
|
|
||||||
vk_windows: VulkanoWindows,
|
|
||||||
debug_gui_stack: DebugGuiStack,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let mut events = Events::new();
|
|
||||||
let (tx, rx) = flume::unbounded();
|
|
||||||
events.subscribe_custom(tx);
|
|
||||||
|
|
||||||
let schedule = Schedule::builder().build();
|
|
||||||
|
|
||||||
let vk_config = VulkanoConfig {
|
|
||||||
device_features: DeviceFeatures {
|
|
||||||
dynamic_rendering: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let vk_context = VulkanoContext::new(vk_config);
|
|
||||||
let vk_windows = VulkanoWindows::default();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
name: "Khors".into(),
|
|
||||||
modules: ModulesStack::new(),
|
|
||||||
thread_local_modules: RenderModulesStack::new(),
|
|
||||||
world: World::new(),
|
|
||||||
schedule,
|
|
||||||
events,
|
|
||||||
rx,
|
|
||||||
running: false,
|
|
||||||
event_cleanup_time: std::time::Duration::from_secs(60),
|
|
||||||
vk_context,
|
|
||||||
vk_windows,
|
|
||||||
debug_gui_stack: DebugGuiStack::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<()> {
|
|
||||||
self.running = true;
|
|
||||||
|
|
||||||
self.schedule.execute_par(&mut self.world).unwrap();
|
|
||||||
|
|
||||||
let vk_context = &mut self.vk_context;
|
|
||||||
let vk_windows = &mut self.vk_windows;
|
|
||||||
let world = &mut self.world;
|
|
||||||
let events = &mut self.events;
|
|
||||||
let frame_time = std::time::Duration::from_millis(16);
|
|
||||||
let gui_stack = &mut self.debug_gui_stack;
|
|
||||||
|
|
||||||
for module in self.modules.iter_mut() {
|
|
||||||
module.on_update(world, events, frame_time)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for module in self.thread_local_modules.iter_mut() {
|
|
||||||
module.on_update(gui_stack, vk_context, vk_windows, world, events, frame_time)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.handle_events();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_window<T>(&mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<T>)
|
|
||||||
where
|
|
||||||
T: Clone + Send + Sync,
|
|
||||||
{
|
|
||||||
let vk_window_id = self.vk_windows.create_window(
|
|
||||||
event_loop,
|
|
||||||
&self.vk_context,
|
|
||||||
&vulkano_util::window::WindowDescriptor {
|
|
||||||
title: self.name.clone(),
|
|
||||||
present_mode: vulkano::swapchain::PresentMode::Mailbox,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
|
|
||||||
let renderer = self.vk_windows.get_renderer(vk_window_id).unwrap();
|
|
||||||
|
|
||||||
self.world
|
|
||||||
.set(resources(), window_id(), vk_window_id)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.debug_gui_stack
|
|
||||||
.add_gui(vk_window_id, event_loop, renderer, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_event_loop<T>(
|
|
||||||
&mut self,
|
|
||||||
event: winit::event::Event<T>,
|
|
||||||
_elwt: &winit::event_loop::EventLoopWindowTarget<T>,
|
|
||||||
) -> Result<bool>
|
|
||||||
where
|
|
||||||
T: Clone + Send + Sync,
|
|
||||||
{
|
|
||||||
match &event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::Focused(_),
|
|
||||||
..
|
|
||||||
} => self.events().send(event.clone()),
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. },
|
|
||||||
window_id,
|
|
||||||
} => self
|
|
||||||
.vk_windows
|
|
||||||
.get_renderer_mut(*window_id)
|
|
||||||
.unwrap()
|
|
||||||
.resize(),
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
window_id,
|
|
||||||
} => 'redraw: {
|
|
||||||
// Tasks for redrawing:
|
|
||||||
// 1. Update state based on events
|
|
||||||
// 2. Compute & Render
|
|
||||||
// 3. Reset input state
|
|
||||||
// 4. Update time & title
|
|
||||||
|
|
||||||
// The rendering part goes here:
|
|
||||||
match self
|
|
||||||
.vk_windows
|
|
||||||
.get_renderer(*window_id)
|
|
||||||
.unwrap()
|
|
||||||
.window_size()
|
|
||||||
{
|
|
||||||
[w, h] => {
|
|
||||||
// Skip this frame when minimized.
|
|
||||||
if w == 0.0 || h == 0.0 {
|
|
||||||
break 'redraw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.run()?;
|
|
||||||
}
|
|
||||||
Event::WindowEvent { window_id, event } => {
|
|
||||||
let window = self.vk_windows.get_window(*window_id).unwrap();
|
|
||||||
let gui = self.debug_gui_stack.get_mut(*window_id).unwrap();
|
|
||||||
gui.update(window, event);
|
|
||||||
}
|
|
||||||
Event::AboutToWait => {
|
|
||||||
self.vk_windows.iter().for_each(|(window_id, _)| {
|
|
||||||
self.vk_windows
|
|
||||||
.get_window(*window_id)
|
|
||||||
.unwrap()
|
|
||||||
.request_redraw()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_events(&mut self) {
|
|
||||||
for event in self.rx.try_iter() {
|
|
||||||
match event {
|
|
||||||
AppEvent::Exit => self.running = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_schedule(&mut self, schedule: Schedule) {
|
|
||||||
self.schedule = schedule;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn world(&self) -> &World {
|
|
||||||
&self.world
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn world_mut(&mut self) -> &mut World {
|
|
||||||
&mut self.world
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn events(&self) -> &Events {
|
|
||||||
&self.events
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn events_mut(&mut self) -> &mut Events {
|
|
||||||
&mut self.events
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes a module from the provided init closure to to the top of the layer stack. The provided
|
|
||||||
/// closure to construct the layer takes in the world and events.
|
|
||||||
pub fn push_render_module<F, T>(&mut self, func: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(
|
|
||||||
&mut VulkanoContext,
|
|
||||||
&mut VulkanoWindows,
|
|
||||||
&mut Schedule,
|
|
||||||
&mut World,
|
|
||||||
&mut Events,
|
|
||||||
) -> T,
|
|
||||||
T: 'static + ThreadLocalModule,
|
|
||||||
{
|
|
||||||
let module = func(
|
|
||||||
&mut self.vk_context,
|
|
||||||
&mut self.vk_windows,
|
|
||||||
&mut self.schedule,
|
|
||||||
&mut self.world,
|
|
||||||
&mut self.events,
|
|
||||||
);
|
|
||||||
self.thread_local_modules.push(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes a layer from the provided init closure to to the top of the layer stack. The provided
|
|
||||||
/// closure to construct the layer takes in the world and events.
|
|
||||||
pub fn push_module<F, T>(&mut self, func: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Schedule, &mut World, &mut Events) -> T,
|
|
||||||
T: 'static + Module,
|
|
||||||
{
|
|
||||||
let module = func(&mut self.schedule, &mut self.world, &mut self.events);
|
|
||||||
self.modules.push(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes a module from the provided init closure to to the top of the module stack. The provided
|
|
||||||
/// closure to construct the module takes in the world and events, and may return an error which
|
|
||||||
/// is propagated to the callee.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn try_push_module<F, T, E>(&mut self, func: F) -> Result<(), E>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut World, &mut Events) -> Result<T, E>,
|
|
||||||
T: 'static + Module,
|
|
||||||
{
|
|
||||||
let module = func(&mut self.world, &mut self.events)?;
|
|
||||||
self.modules.push(module);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts a module from the provided init closure to to the top of the module stack. The provided
|
|
||||||
/// closure to construct the module takes in the world and events.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn insert_module<F, T>(&mut self, index: usize, func: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut World, &mut Events) -> T,
|
|
||||||
T: 'static + Module,
|
|
||||||
{
|
|
||||||
let module = func(&mut self.world, &mut self.events);
|
|
||||||
self.modules.insert(index, module);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes a module from the provided init closure to to the top of the module stack. The provided
|
|
||||||
/// closure to construct the module takes in the world and events, and may return an error which
|
|
||||||
/// is propagated to the callee.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn try_insert_module<F, T, E>(&mut self, index: usize, func: F) -> Result<(), E>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut World, &mut Events) -> Result<T, E>,
|
|
||||||
T: 'static + Module,
|
|
||||||
{
|
|
||||||
let module = func(&mut self.world, &mut self.events)?;
|
|
||||||
self.modules.insert(index, module);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum AppEvent {
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for App {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,9 +8,12 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
khors-core = { path = "../../khors-core", version = "0.1.0" }
|
khors-core = { path = "../../khors-core", version = "0.1.0" }
|
||||||
egui-vulkano = { path = "../../vendor/egui-vulkano", version = "0.1.0" }
|
egui-vulkano = { path = "../../vendor/egui-vulkano", version = "0.1.0" }
|
||||||
|
egui-snarl = { path = "../../vendor/egui-snarl", features = ["serde"] }
|
||||||
|
|
||||||
anyhow = "1.0.80"
|
anyhow = "1.0.80"
|
||||||
|
thiserror = "1.0.58"
|
||||||
egui = "0.27.1"
|
egui = "0.27.1"
|
||||||
|
syn = "2.0.58"
|
||||||
glam = "0.27.0"
|
glam = "0.27.0"
|
||||||
winit = { version = "0.29.15",features = ["rwh_05"] }
|
winit = { version = "0.29.15",features = ["rwh_05"] }
|
||||||
vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" }
|
vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" }
|
||||||
|
|
|
@ -1,555 +1,10 @@
|
||||||
pub mod debug_gui;
|
pub mod debug_gui;
|
||||||
pub mod render_module;
|
pub mod render_module;
|
||||||
|
|
||||||
use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World};
|
|
||||||
use glam::{
|
|
||||||
f32::{Mat3, Vec3},
|
|
||||||
Mat4,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use vulkano::{
|
|
||||||
buffer::{
|
|
||||||
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
|
|
||||||
Buffer, BufferCreateInfo, BufferUsage,
|
|
||||||
},
|
|
||||||
command_buffer::{
|
|
||||||
allocator::{CommandBufferAllocator, StandardCommandBufferAllocator},
|
|
||||||
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
|
|
||||||
RenderPassBeginInfo,
|
|
||||||
},
|
|
||||||
descriptor_set::{
|
|
||||||
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
|
|
||||||
},
|
|
||||||
device::DeviceOwned,
|
|
||||||
format::Format,
|
|
||||||
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage},
|
|
||||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
|
|
||||||
pipeline::{
|
|
||||||
graphics::{
|
|
||||||
color_blend::{ColorBlendAttachmentState, ColorBlendState},
|
|
||||||
depth_stencil::{DepthState, DepthStencilState},
|
|
||||||
input_assembly::InputAssemblyState,
|
|
||||||
multisample::MultisampleState,
|
|
||||||
rasterization::RasterizationState,
|
|
||||||
vertex_input::{Vertex, VertexDefinition},
|
|
||||||
viewport::{Viewport, ViewportState},
|
|
||||||
GraphicsPipelineCreateInfo,
|
|
||||||
},
|
|
||||||
layout::PipelineDescriptorSetLayoutCreateInfo,
|
|
||||||
GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
|
|
||||||
PipelineShaderStageCreateInfo,
|
|
||||||
},
|
|
||||||
render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass},
|
|
||||||
shader::EntryPoint,
|
|
||||||
sync::GpuFuture,
|
|
||||||
};
|
|
||||||
use vulkano_util::{
|
|
||||||
context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows,
|
|
||||||
};
|
|
||||||
|
|
||||||
use egui_vulkano::Gui;
|
|
||||||
use crate::debug_gui::DebugGuiStack;
|
|
||||||
use crate::render_module::RenderModule as ThreadLocalModule;
|
|
||||||
|
|
||||||
use self::{
|
|
||||||
model::{INDICES, NORMALS, POSITIONS},
|
|
||||||
vulkan::vertex::{Normal, Position},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod events;
|
pub mod events;
|
||||||
mod model;
|
pub mod renderer;
|
||||||
|
pub mod rendergraph;
|
||||||
|
pub mod node_editor;
|
||||||
|
mod temp;
|
||||||
mod test_pipeline;
|
mod test_pipeline;
|
||||||
mod vulkan;
|
mod vulkan;
|
||||||
|
|
||||||
pub struct RenderModule {
|
|
||||||
schedule: Schedule,
|
|
||||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
|
||||||
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
|
||||||
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
|
|
||||||
viewport: Viewport,
|
|
||||||
rotation_start: std::time::Instant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderModule {
|
|
||||||
pub fn new(
|
|
||||||
vk_context: &mut VulkanoContext,
|
|
||||||
_vk_windows: &mut VulkanoWindows,
|
|
||||||
_schedule: &mut Schedule,
|
|
||||||
_world: &mut World,
|
|
||||||
_events: &mut khors_core::events::Events,
|
|
||||||
) -> Self {
|
|
||||||
let schedule = Schedule::builder()
|
|
||||||
.with_system(add_distance_system())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(
|
|
||||||
vk_context.device().clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
|
|
||||||
vk_context.device().clone(),
|
|
||||||
Default::default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
|
|
||||||
vk_context.device().clone(),
|
|
||||||
Default::default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let viewport = Viewport {
|
|
||||||
offset: [0.0, 0.0],
|
|
||||||
extent: [0.0, 0.0],
|
|
||||||
depth_range: 0.0..=1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let rotation_start = std::time::Instant::now();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
schedule,
|
|
||||||
memory_allocator,
|
|
||||||
descriptor_set_allocator,
|
|
||||||
command_buffer_allocator,
|
|
||||||
viewport,
|
|
||||||
rotation_start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadLocalModule for RenderModule {
|
|
||||||
fn on_update(
|
|
||||||
&mut self,
|
|
||||||
gui_stack: &mut DebugGuiStack,
|
|
||||||
vk_context: &mut VulkanoContext,
|
|
||||||
vk_windows: &mut vulkano_util::window::VulkanoWindows,
|
|
||||||
world: &mut World,
|
|
||||||
_events: &mut khors_core::events::Events,
|
|
||||||
_frame_time: std::time::Duration,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
self.schedule.execute_seq(world).unwrap();
|
|
||||||
|
|
||||||
let viewport = &mut self.viewport;
|
|
||||||
|
|
||||||
for (window_id, renderer) in vk_windows.iter_mut() {
|
|
||||||
let gui = gui_stack.get_mut(*window_id).unwrap();
|
|
||||||
draw(
|
|
||||||
vk_context.device().clone(),
|
|
||||||
self.memory_allocator.clone(),
|
|
||||||
self.descriptor_set_allocator.clone(),
|
|
||||||
self.command_buffer_allocator.clone(),
|
|
||||||
viewport,
|
|
||||||
vk_context,
|
|
||||||
renderer,
|
|
||||||
gui,
|
|
||||||
self.rotation_start,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_distance_system() -> BoxedSystem {
|
|
||||||
let query = Query::new(entity_ids());
|
|
||||||
|
|
||||||
System::builder()
|
|
||||||
.with_query(query)
|
|
||||||
.build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| {
|
|
||||||
for _id in &mut query {
|
|
||||||
// println!("----------: {}", _id.index());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
device: Arc<vulkano::device::Device>,
|
|
||||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
|
||||||
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
|
||||||
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
|
|
||||||
_viewport: &mut Viewport,
|
|
||||||
context: &mut VulkanoContext,
|
|
||||||
renderer: &mut VulkanoWindowRenderer,
|
|
||||||
gui: &mut Gui,
|
|
||||||
rotation_start: std::time::Instant,
|
|
||||||
) {
|
|
||||||
let vertex_buffer = Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::VERTEX_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
POSITIONS,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let normals_buffer = Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::VERTEX_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
NORMALS,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let index_buffer = Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::INDEX_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
INDICES,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let uniform_buffer = SubbufferAllocator::new(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
SubbufferAllocatorCreateInfo {
|
|
||||||
buffer_usage: BufferUsage::UNIFORM_BUFFER,
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let render_pass = vulkano::single_pass_renderpass!(
|
|
||||||
device.clone(),
|
|
||||||
attachments: {
|
|
||||||
color: {
|
|
||||||
format: renderer.swapchain_format(),
|
|
||||||
samples: 1,
|
|
||||||
load_op: Clear,
|
|
||||||
store_op: Store,
|
|
||||||
},
|
|
||||||
depth_stencil: {
|
|
||||||
format: Format::D16_UNORM,
|
|
||||||
samples: 1,
|
|
||||||
load_op: Clear,
|
|
||||||
store_op: DontCare,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pass: {
|
|
||||||
color: [color],
|
|
||||||
depth_stencil: {depth_stencil},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vs = vs::load(device.clone())
|
|
||||||
.unwrap()
|
|
||||||
.entry_point("main")
|
|
||||||
.unwrap();
|
|
||||||
let fs = fs::load(device.clone())
|
|
||||||
.unwrap()
|
|
||||||
.entry_point("main")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (mut pipeline, mut framebuffers) = window_size_dependent_setup(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
vs.clone(),
|
|
||||||
fs.clone(),
|
|
||||||
renderer.swapchain_image_views(),
|
|
||||||
render_pass.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Do not draw the frame when the screen size is zero. On Windows, this can
|
|
||||||
// occur when minimizing the application.
|
|
||||||
let image_extent: [u32; 2] = renderer.window().inner_size().into();
|
|
||||||
|
|
||||||
if image_extent.contains(&0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin rendering by acquiring the gpu future from the window renderer.
|
|
||||||
let previous_frame_end = renderer
|
|
||||||
.acquire(None, |swapchain_images| {
|
|
||||||
// Whenever the window resizes we need to recreate everything dependent
|
|
||||||
// on the window size. In this example that
|
|
||||||
// includes the swapchain, the framebuffers
|
|
||||||
// and the dynamic state viewport.
|
|
||||||
let (new_pipeline, new_framebuffers) = window_size_dependent_setup(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
vs.clone(),
|
|
||||||
fs.clone(),
|
|
||||||
swapchain_images,
|
|
||||||
render_pass.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
pipeline = new_pipeline;
|
|
||||||
framebuffers = new_framebuffers;
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let uniform_buffer_subbuffer = {
|
|
||||||
let elapsed = rotation_start.elapsed();
|
|
||||||
let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0;
|
|
||||||
let rotation = Mat3::from_rotation_y(rotation as f32);
|
|
||||||
|
|
||||||
// NOTE: This teapot was meant for OpenGL where the origin is at the lower left
|
|
||||||
// instead the origin is at the upper left in Vulkan, so we reverse the Y axis.
|
|
||||||
let aspect_ratio = renderer.aspect_ratio();
|
|
||||||
|
|
||||||
let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0);
|
|
||||||
let view = Mat4::look_at_rh(
|
|
||||||
Vec3::new(0.4, 0.3, 1.0),
|
|
||||||
Vec3::new(0.0, 0.0, 0.0),
|
|
||||||
Vec3::new(0.0, -1.0, 0.0),
|
|
||||||
);
|
|
||||||
let scale = Mat4::from_scale(Vec3::splat(0.01));
|
|
||||||
|
|
||||||
let uniform_data = vs::Data {
|
|
||||||
world: Mat4::from_mat3(rotation).to_cols_array_2d(),
|
|
||||||
view: (view * scale).to_cols_array_2d(),
|
|
||||||
proj: proj.to_cols_array_2d(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let subbuffer = uniform_buffer.allocate_sized().unwrap();
|
|
||||||
*subbuffer.write().unwrap() = uniform_data;
|
|
||||||
|
|
||||||
subbuffer
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut builder = RecordingCommandBuffer::new(
|
|
||||||
command_buffer_allocator.clone(),
|
|
||||||
context.graphics_queue().queue_family_index(),
|
|
||||||
CommandBufferLevel::Primary,
|
|
||||||
CommandBufferBeginInfo {
|
|
||||||
usage: CommandBufferUsage::OneTimeSubmit,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let layout = &pipeline.layout().set_layouts()[0];
|
|
||||||
let set = DescriptorSet::new(
|
|
||||||
descriptor_set_allocator.clone(),
|
|
||||||
layout.clone(),
|
|
||||||
[WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.begin_render_pass(
|
|
||||||
RenderPassBeginInfo {
|
|
||||||
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into()), Some(1f32.into())],
|
|
||||||
..RenderPassBeginInfo::framebuffer(
|
|
||||||
framebuffers[renderer.image_index() as usize].clone(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.bind_pipeline_graphics(pipeline.clone())
|
|
||||||
.unwrap()
|
|
||||||
.bind_descriptor_sets(
|
|
||||||
PipelineBindPoint::Graphics,
|
|
||||||
pipeline.layout().clone(),
|
|
||||||
0,
|
|
||||||
set,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.bind_vertex_buffers(0, (vertex_buffer.clone(), normals_buffer.clone()))
|
|
||||||
.unwrap()
|
|
||||||
.bind_index_buffer(index_buffer.clone())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
builder
|
|
||||||
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.end_render_pass(Default::default()).unwrap();
|
|
||||||
// Finish recording the command buffer by calling `end`.
|
|
||||||
let command_buffer = builder.end().unwrap();
|
|
||||||
|
|
||||||
draw_gui(gui);
|
|
||||||
|
|
||||||
let before_future = previous_frame_end
|
|
||||||
.then_execute(context.graphics_queue().clone(), command_buffer)
|
|
||||||
.unwrap()
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
let after_future = gui
|
|
||||||
.draw_on_image(before_future, renderer.swapchain_image_view())
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
// The color output is now expected to contain our triangle. But in order to
|
|
||||||
// show it on the screen, we have to *present* the image by calling
|
|
||||||
// `present` on the window renderer.
|
|
||||||
//
|
|
||||||
// This function does not actually present the image immediately. Instead it
|
|
||||||
// submits a present command at the end of the queue. This means that it will
|
|
||||||
// only be presented once the GPU has finished executing the command buffer
|
|
||||||
// that draws the triangle.
|
|
||||||
renderer.present(after_future, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_gui(gui: &mut Gui) {
|
|
||||||
let mut code = CODE.to_owned();
|
|
||||||
gui.immediate_ui(|gui| {
|
|
||||||
let ctx = gui.context();
|
|
||||||
egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
ui.add(egui::widgets::Label::new("Hi there!"));
|
|
||||||
sized_text(ui, "Rich Text", 32.0);
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
ui.columns(2, |columns| {
|
|
||||||
egui::ScrollArea::vertical()
|
|
||||||
.id_source("source")
|
|
||||||
.show(&mut columns[0], |ui| {
|
|
||||||
ui.add(
|
|
||||||
egui::TextEdit::multiline(&mut code).font(egui::TextStyle::Monospace),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
egui::ScrollArea::vertical()
|
|
||||||
.id_source("rendered")
|
|
||||||
.show(&mut columns[1], |ui| {
|
|
||||||
ui.add(egui::widgets::Label::new("Good day!"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sized_text(ui: &mut egui::Ui, text: impl Into<String>, size: f32) {
|
|
||||||
ui.label(
|
|
||||||
egui::RichText::new(text)
|
|
||||||
.size(size)
|
|
||||||
.family(egui::FontFamily::Monospace),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const CODE: &str = r"
|
|
||||||
# Some markup
|
|
||||||
```
|
|
||||||
let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1);
|
|
||||||
```
|
|
||||||
";
|
|
||||||
|
|
||||||
fn window_size_dependent_setup(
|
|
||||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
|
||||||
vs: EntryPoint,
|
|
||||||
fs: EntryPoint,
|
|
||||||
image_views: &[Arc<ImageView>],
|
|
||||||
render_pass: Arc<RenderPass>,
|
|
||||||
) -> (Arc<GraphicsPipeline>, Vec<Arc<Framebuffer>>) {
|
|
||||||
let device = memory_allocator.device().clone();
|
|
||||||
|
|
||||||
let extent = image_views[0].image().extent();
|
|
||||||
|
|
||||||
let depth_buffer = ImageView::new_default(
|
|
||||||
Image::new(
|
|
||||||
memory_allocator,
|
|
||||||
ImageCreateInfo {
|
|
||||||
image_type: ImageType::Dim2d,
|
|
||||||
format: Format::D16_UNORM,
|
|
||||||
extent,
|
|
||||||
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo::default(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let framebuffers = image_views
|
|
||||||
.iter()
|
|
||||||
.map(|image_view| {
|
|
||||||
Framebuffer::new(
|
|
||||||
render_pass.clone(),
|
|
||||||
FramebufferCreateInfo {
|
|
||||||
attachments: vec![image_view.clone(), depth_buffer.clone()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// In the triangle example we use a dynamic viewport, as its a simple example. However in the
|
|
||||||
// teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the
|
|
||||||
// driver to optimize things, at the cost of slower window resizes.
|
|
||||||
// https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport
|
|
||||||
let pipeline = {
|
|
||||||
let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()]
|
|
||||||
.definition(&vs)
|
|
||||||
.unwrap();
|
|
||||||
let stages = [
|
|
||||||
PipelineShaderStageCreateInfo::new(vs),
|
|
||||||
PipelineShaderStageCreateInfo::new(fs),
|
|
||||||
];
|
|
||||||
let layout = PipelineLayout::new(
|
|
||||||
device.clone(),
|
|
||||||
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages)
|
|
||||||
.into_pipeline_layout_create_info(device.clone())
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let subpass = Subpass::from(render_pass, 0).unwrap();
|
|
||||||
|
|
||||||
GraphicsPipeline::new(
|
|
||||||
device,
|
|
||||||
None,
|
|
||||||
GraphicsPipelineCreateInfo {
|
|
||||||
stages: stages.into_iter().collect(),
|
|
||||||
vertex_input_state: Some(vertex_input_state),
|
|
||||||
input_assembly_state: Some(InputAssemblyState::default()),
|
|
||||||
viewport_state: Some(ViewportState {
|
|
||||||
viewports: [Viewport {
|
|
||||||
offset: [0.0, 0.0],
|
|
||||||
extent: [extent[0] as f32, extent[1] as f32],
|
|
||||||
depth_range: 0.0..=1.0,
|
|
||||||
}]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
rasterization_state: Some(RasterizationState::default()),
|
|
||||||
depth_stencil_state: Some(DepthStencilState {
|
|
||||||
depth: Some(DepthState::simple()),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
multisample_state: Some(MultisampleState::default()),
|
|
||||||
color_blend_state: Some(ColorBlendState::with_attachment_states(
|
|
||||||
subpass.num_color_attachments(),
|
|
||||||
ColorBlendAttachmentState::default(),
|
|
||||||
)),
|
|
||||||
subpass: Some(subpass.into()),
|
|
||||||
..GraphicsPipelineCreateInfo::layout(layout)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
(pipeline, framebuffers)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod vs {
|
|
||||||
vulkano_shaders::shader! {
|
|
||||||
ty: "vertex",
|
|
||||||
path: "src/vert.glsl",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod fs {
|
|
||||||
vulkano_shaders::shader! {
|
|
||||||
ty: "fragment",
|
|
||||||
path: "src/frag.glsl",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
1025
modules/khors-graphics/src/node_editor/mod.rs
Normal file
1025
modules/khors-graphics/src/node_editor/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
504
modules/khors-graphics/src/renderer.rs
Normal file
504
modules/khors-graphics/src/renderer.rs
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World};
|
||||||
|
use glam::{
|
||||||
|
f32::{Mat3, Vec3},
|
||||||
|
Mat4,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vulkano::{
|
||||||
|
buffer::{
|
||||||
|
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
|
||||||
|
Buffer, BufferCreateInfo, BufferUsage,
|
||||||
|
},
|
||||||
|
command_buffer::{
|
||||||
|
allocator::{CommandBufferAllocator, StandardCommandBufferAllocator},
|
||||||
|
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
|
||||||
|
RenderPassBeginInfo,
|
||||||
|
},
|
||||||
|
descriptor_set::{
|
||||||
|
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet
|
||||||
|
},
|
||||||
|
device::DeviceOwned,
|
||||||
|
format::Format,
|
||||||
|
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage},
|
||||||
|
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
|
||||||
|
pipeline::{graphics::viewport::Viewport, GraphicsPipeline, Pipeline, PipelineBindPoint},
|
||||||
|
render_pass::{Framebuffer, RenderPass},
|
||||||
|
shader::EntryPoint,
|
||||||
|
sync::GpuFuture,
|
||||||
|
};
|
||||||
|
use vulkano_util::{
|
||||||
|
context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{node_editor, render_module::RenderModule as ThreadLocalModule, vulkan::renderpass::make_render_pass};
|
||||||
|
use crate::{
|
||||||
|
debug_gui::DebugGuiStack,
|
||||||
|
vulkan::{
|
||||||
|
framebuffer::make_framebuffer,
|
||||||
|
pipeline::{make_pipeline, PassInfo, PipelineInfo},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use egui_vulkano::Gui;
|
||||||
|
use super::node_editor::DemoApp;
|
||||||
|
|
||||||
|
use crate::temp::model::{INDICES, NORMALS, POSITIONS};
|
||||||
|
|
||||||
|
pub struct RenderModule {
|
||||||
|
schedule: Schedule,
|
||||||
|
memory_allocator: Arc<StandardMemoryAllocator>,
|
||||||
|
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
||||||
|
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
|
||||||
|
viewport: Viewport,
|
||||||
|
rotation_start: std::time::Instant,
|
||||||
|
node_editor: DemoApp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderModule {
|
||||||
|
pub fn new(
|
||||||
|
vk_context: &mut VulkanoContext,
|
||||||
|
_vk_windows: &mut VulkanoWindows,
|
||||||
|
_schedule: &mut Schedule,
|
||||||
|
_world: &mut World,
|
||||||
|
_events: &mut khors_core::events::Events,
|
||||||
|
) -> Self {
|
||||||
|
let schedule = Schedule::builder()
|
||||||
|
.with_system(add_distance_system())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(
|
||||||
|
vk_context.device().clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
|
||||||
|
vk_context.device().clone(),
|
||||||
|
Default::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
|
||||||
|
vk_context.device().clone(),
|
||||||
|
Default::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let viewport = Viewport {
|
||||||
|
offset: [0.0, 0.0],
|
||||||
|
extent: [0.0, 0.0],
|
||||||
|
depth_range: 0.0..=1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let node_editor = DemoApp::new();
|
||||||
|
|
||||||
|
let rotation_start = std::time::Instant::now();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
schedule,
|
||||||
|
memory_allocator,
|
||||||
|
descriptor_set_allocator,
|
||||||
|
command_buffer_allocator,
|
||||||
|
viewport,
|
||||||
|
rotation_start,
|
||||||
|
node_editor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadLocalModule for RenderModule {
|
||||||
|
fn on_update(
|
||||||
|
&mut self,
|
||||||
|
gui_stack: &mut DebugGuiStack,
|
||||||
|
vk_context: &mut VulkanoContext,
|
||||||
|
vk_windows: &mut vulkano_util::window::VulkanoWindows,
|
||||||
|
world: &mut World,
|
||||||
|
_events: &mut khors_core::events::Events,
|
||||||
|
_frame_time: std::time::Duration,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.schedule.execute_seq(world).unwrap();
|
||||||
|
|
||||||
|
let viewport = &mut self.viewport;
|
||||||
|
|
||||||
|
for (window_id, renderer) in vk_windows.iter_mut() {
|
||||||
|
let gui = gui_stack.get_mut(*window_id).unwrap();
|
||||||
|
let node_editor = &mut self.node_editor;
|
||||||
|
draw(
|
||||||
|
vk_context.device().clone(),
|
||||||
|
self.memory_allocator.clone(),
|
||||||
|
self.descriptor_set_allocator.clone(),
|
||||||
|
self.command_buffer_allocator.clone(),
|
||||||
|
viewport,
|
||||||
|
vk_context,
|
||||||
|
renderer,
|
||||||
|
gui,
|
||||||
|
node_editor,
|
||||||
|
self.rotation_start,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_distance_system() -> BoxedSystem {
|
||||||
|
let query = Query::new(entity_ids());
|
||||||
|
|
||||||
|
System::builder()
|
||||||
|
.with_query(query)
|
||||||
|
.build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| {
|
||||||
|
for _id in &mut query {
|
||||||
|
// println!("----------: {}", _id.index());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn draw(
|
||||||
|
device: Arc<vulkano::device::Device>,
|
||||||
|
memory_allocator: Arc<StandardMemoryAllocator>,
|
||||||
|
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
||||||
|
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
|
||||||
|
_viewport: &mut Viewport,
|
||||||
|
context: &mut VulkanoContext,
|
||||||
|
renderer: &mut VulkanoWindowRenderer,
|
||||||
|
gui: &mut Gui,
|
||||||
|
node_editor: &mut DemoApp,
|
||||||
|
rotation_start: std::time::Instant,
|
||||||
|
) {
|
||||||
|
let vertex_buffer = Buffer::from_iter(
|
||||||
|
memory_allocator.clone(),
|
||||||
|
BufferCreateInfo {
|
||||||
|
usage: BufferUsage::VERTEX_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo {
|
||||||
|
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
||||||
|
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
POSITIONS,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let normals_buffer = Buffer::from_iter(
|
||||||
|
memory_allocator.clone(),
|
||||||
|
BufferCreateInfo {
|
||||||
|
usage: BufferUsage::VERTEX_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo {
|
||||||
|
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
||||||
|
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
NORMALS,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let index_buffer = Buffer::from_iter(
|
||||||
|
memory_allocator.clone(),
|
||||||
|
BufferCreateInfo {
|
||||||
|
usage: BufferUsage::INDEX_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo {
|
||||||
|
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
||||||
|
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
INDICES,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let uniform_buffer = SubbufferAllocator::new(
|
||||||
|
memory_allocator.clone(),
|
||||||
|
SubbufferAllocatorCreateInfo {
|
||||||
|
buffer_usage: BufferUsage::UNIFORM_BUFFER,
|
||||||
|
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
||||||
|
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// let render_pass = vulkano::single_pass_renderpass!(
|
||||||
|
// device.clone(),
|
||||||
|
// attachments: {
|
||||||
|
// color: {
|
||||||
|
// format: renderer.swapchain_format(),
|
||||||
|
// samples: 1,
|
||||||
|
// load_op: Clear,
|
||||||
|
// store_op: Store,
|
||||||
|
// },
|
||||||
|
// depth_stencil: {
|
||||||
|
// format: Format::D16_UNORM,
|
||||||
|
// samples: 1,
|
||||||
|
// load_op: Clear,
|
||||||
|
// store_op: DontCare,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// pass: {
|
||||||
|
// color: [color],
|
||||||
|
// depth_stencil: {depth_stencil},
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
let render_pass = make_render_pass(device.clone(), renderer);
|
||||||
|
|
||||||
|
let vs = vs::load(device.clone())
|
||||||
|
.unwrap()
|
||||||
|
.entry_point("main")
|
||||||
|
.unwrap();
|
||||||
|
let fs = fs::load(device.clone())
|
||||||
|
.unwrap()
|
||||||
|
.entry_point("main")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (mut pipeline, mut framebuffers) = window_size_dependent_setup(
|
||||||
|
memory_allocator.clone(),
|
||||||
|
vs.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
renderer.swapchain_image_views(),
|
||||||
|
render_pass.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Do not draw the frame when the screen size is zero. On Windows, this can
|
||||||
|
// occur when minimizing the application.
|
||||||
|
let image_extent: [u32; 2] = renderer.window().inner_size().into();
|
||||||
|
|
||||||
|
if image_extent.contains(&0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin rendering by acquiring the gpu future from the window renderer.
|
||||||
|
let previous_frame_end = renderer
|
||||||
|
.acquire(None, |swapchain_images| {
|
||||||
|
// Whenever the window resizes we need to recreate everything dependent
|
||||||
|
// on the window size. In this example that
|
||||||
|
// includes the swapchain, the framebuffers
|
||||||
|
// and the dynamic state viewport.
|
||||||
|
let (new_pipeline, new_framebuffers) = window_size_dependent_setup(
|
||||||
|
memory_allocator.clone(),
|
||||||
|
vs.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
swapchain_images,
|
||||||
|
render_pass.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
pipeline = new_pipeline;
|
||||||
|
framebuffers = new_framebuffers;
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let uniform_buffer_subbuffer = {
|
||||||
|
let elapsed = rotation_start.elapsed();
|
||||||
|
let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0;
|
||||||
|
let rotation = Mat3::from_rotation_y(rotation as f32);
|
||||||
|
|
||||||
|
// NOTE: This teapot was meant for OpenGL where the origin is at the lower left
|
||||||
|
// instead the origin is at the upper left in Vulkan, so we reverse the Y axis.
|
||||||
|
let aspect_ratio = renderer.aspect_ratio();
|
||||||
|
|
||||||
|
let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0);
|
||||||
|
let view = Mat4::look_at_rh(
|
||||||
|
Vec3::new(0.4, 0.3, 1.0),
|
||||||
|
Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
Vec3::new(0.0, -1.0, 0.0),
|
||||||
|
);
|
||||||
|
let scale = Mat4::from_scale(Vec3::splat(0.01));
|
||||||
|
|
||||||
|
let uniform_data = vs::Data {
|
||||||
|
world: Mat4::from_mat3(rotation).to_cols_array_2d(),
|
||||||
|
view: (view * scale).to_cols_array_2d(),
|
||||||
|
proj: proj.to_cols_array_2d(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let subbuffer = uniform_buffer.allocate_sized().unwrap();
|
||||||
|
*subbuffer.write().unwrap() = uniform_data;
|
||||||
|
|
||||||
|
subbuffer
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = RecordingCommandBuffer::new(
|
||||||
|
command_buffer_allocator.clone(),
|
||||||
|
context.graphics_queue().queue_family_index(),
|
||||||
|
CommandBufferLevel::Primary,
|
||||||
|
CommandBufferBeginInfo {
|
||||||
|
usage: CommandBufferUsage::OneTimeSubmit,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let layout = &pipeline.layout().set_layouts()[0];
|
||||||
|
let set = DescriptorSet::new(
|
||||||
|
descriptor_set_allocator.clone(),
|
||||||
|
layout.clone(),
|
||||||
|
[WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.begin_render_pass(
|
||||||
|
RenderPassBeginInfo {
|
||||||
|
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into()), Some(1f32.into())],
|
||||||
|
..RenderPassBeginInfo::framebuffer(
|
||||||
|
framebuffers[renderer.image_index() as usize].clone(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.bind_pipeline_graphics(pipeline.clone())
|
||||||
|
.unwrap()
|
||||||
|
.bind_descriptor_sets(
|
||||||
|
PipelineBindPoint::Graphics,
|
||||||
|
pipeline.layout().clone(),
|
||||||
|
0,
|
||||||
|
set,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.bind_vertex_buffers(0, (vertex_buffer.clone(), normals_buffer.clone()))
|
||||||
|
.unwrap()
|
||||||
|
.bind_index_buffer(index_buffer.clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
builder
|
||||||
|
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.end_render_pass(Default::default()).unwrap();
|
||||||
|
// Finish recording the command buffer by calling `end`.
|
||||||
|
let command_buffer = builder.end().unwrap();
|
||||||
|
|
||||||
|
draw_gui(gui, node_editor);
|
||||||
|
|
||||||
|
let before_future = previous_frame_end
|
||||||
|
.then_execute(context.graphics_queue().clone(), command_buffer)
|
||||||
|
.unwrap()
|
||||||
|
.boxed();
|
||||||
|
|
||||||
|
let after_future = gui
|
||||||
|
.draw_on_image(before_future, renderer.swapchain_image_view())
|
||||||
|
.boxed();
|
||||||
|
|
||||||
|
// The color output is now expected to contain our triangle. But in order to
|
||||||
|
// show it on the screen, we have to *present* the image by calling
|
||||||
|
// `present` on the window renderer.
|
||||||
|
//
|
||||||
|
// This function does not actually present the image immediately. Instead it
|
||||||
|
// submits a present command at the end of the queue. This means that it will
|
||||||
|
// only be presented once the GPU has finished executing the command buffer
|
||||||
|
// that draws the triangle.
|
||||||
|
renderer.present(after_future, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_gui(gui: &mut Gui, node_editor: &mut DemoApp) {
|
||||||
|
let mut code = CODE.to_owned();
|
||||||
|
|
||||||
|
gui.immediate_ui(|gui| {
|
||||||
|
let ctx = gui.context();
|
||||||
|
|
||||||
|
node_editor.update(&ctx);
|
||||||
|
|
||||||
|
egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.add(egui::widgets::Label::new("Hi there!"));
|
||||||
|
sized_text(ui, "Rich Text", 32.0);
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.columns(2, |columns| {
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.id_source("source")
|
||||||
|
.show(&mut columns[0], |ui| {
|
||||||
|
ui.add(
|
||||||
|
egui::TextEdit::multiline(&mut code).font(egui::TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.id_source("rendered")
|
||||||
|
.show(&mut columns[1], |ui| {
|
||||||
|
ui.add(egui::widgets::Label::new("Good day!"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sized_text(ui: &mut egui::Ui, text: impl Into<String>, size: f32) {
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(text)
|
||||||
|
.size(size)
|
||||||
|
.family(egui::FontFamily::Monospace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CODE: &str = r"
|
||||||
|
# Some markup
|
||||||
|
```
|
||||||
|
let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1);
|
||||||
|
```
|
||||||
|
";
|
||||||
|
|
||||||
|
fn window_size_dependent_setup(
|
||||||
|
memory_allocator: Arc<StandardMemoryAllocator>,
|
||||||
|
vs: EntryPoint,
|
||||||
|
fs: EntryPoint,
|
||||||
|
image_views: &[Arc<ImageView>],
|
||||||
|
render_pass: Arc<RenderPass>,
|
||||||
|
) -> (Arc<GraphicsPipeline>, Vec<Arc<Framebuffer>>) {
|
||||||
|
let device = memory_allocator.device().clone();
|
||||||
|
|
||||||
|
let extent = image_views[0].image().extent();
|
||||||
|
|
||||||
|
let depth_buffer = ImageView::new_default(
|
||||||
|
Image::new(
|
||||||
|
memory_allocator,
|
||||||
|
ImageCreateInfo {
|
||||||
|
image_type: ImageType::Dim2d,
|
||||||
|
format: Format::D16_UNORM,
|
||||||
|
extent,
|
||||||
|
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo::default(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let framebuffers = image_views
|
||||||
|
.iter()
|
||||||
|
.map(|image_view| {
|
||||||
|
make_framebuffer(
|
||||||
|
render_pass.clone(),
|
||||||
|
vec![image_view.clone(), depth_buffer.clone()],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// In the triangle example we use a dynamic viewport, as its a simple example. However in the
|
||||||
|
// teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the
|
||||||
|
// driver to optimize things, at the cost of slower window resizes.
|
||||||
|
// https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport
|
||||||
|
let pipeline_info = PipelineInfo { vs, fs };
|
||||||
|
|
||||||
|
let pass_info = PassInfo::new(render_pass.clone(), 0, extent);
|
||||||
|
|
||||||
|
let pipeline = make_pipeline(device.clone(), &pipeline_info, &pass_info);
|
||||||
|
|
||||||
|
(pipeline, framebuffers)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod vs {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "vertex",
|
||||||
|
path: "src/temp/vert.glsl",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod fs {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
path: "src/temp/frag.glsl",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
33
modules/khors-graphics/src/rendergraph/error.rs
Normal file
33
modules/khors-graphics/src/rendergraph/error.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::{node::NodeKind, rendergraph::{NodeIndex, ResourceKind}};
|
||||||
|
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Failed executing rendergraph node")]
|
||||||
|
NodeExecution(#[from] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("Rendergraph vulkan error")]
|
||||||
|
Vulkan(#[from] ivy_vulkan::Error),
|
||||||
|
|
||||||
|
#[error("Rendergraph graphics error")]
|
||||||
|
Graphics(#[from] ivy_graphics::Error),
|
||||||
|
|
||||||
|
#[error("Dependency cycle in rendergraph")]
|
||||||
|
DependencyCycle,
|
||||||
|
|
||||||
|
#[error("Node read attachment is missing corresponding write attachment for {2:?} required by node {0:?}: {1:?}")]
|
||||||
|
MissingWrite(NodeIndex, &'static str, ResourceKind),
|
||||||
|
|
||||||
|
#[error("Resource acquisition error")]
|
||||||
|
Resource(#[from] ivy_resources::Error),
|
||||||
|
|
||||||
|
#[error("Invalid node index {0:?}")]
|
||||||
|
InvalidNodeIndex(NodeIndex),
|
||||||
|
|
||||||
|
#[error("Specified node {0:?} is not the correct kind. Expected {1:?}, found {2:?}")]
|
||||||
|
InvalidNodeKind(NodeIndex, NodeKind, NodeKind),
|
||||||
|
}
|
7
modules/khors-graphics/src/rendergraph/mod.rs
Normal file
7
modules/khors-graphics/src/rendergraph/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// use flax::component;
|
||||||
|
// use vulkano::image::{view::ImageView, Image};
|
||||||
|
|
||||||
|
// pub mod node;
|
||||||
|
// pub mod pass;
|
||||||
|
// pub mod rendergraph;
|
||||||
|
// mod error;
|
172
modules/khors-graphics/src/rendergraph/node.rs
Normal file
172
modules/khors-graphics/src/rendergraph/node.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
use flax::World;
|
||||||
|
use vulkano::command_buffer::RecordingCommandBuffer;
|
||||||
|
|
||||||
|
use crate::vulkan::pipeline::PassInfo;
|
||||||
|
|
||||||
|
/// Represents a node in the renderpass.
|
||||||
|
pub trait Node: 'static + Send {
|
||||||
|
/// Returns the color attachments for this node. Should not be execution heavy function
|
||||||
|
fn color_attachments(&self) -> &[AttachmentInfo] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_attachments(&self) -> &[Handle<Texture>] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
/// Returns the read attachments for this node. Should not be execution heavy function
|
||||||
|
fn read_attachments(&self) -> &[Handle<Texture>] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
/// Partially sampled input attachments. Read from the same pixel coord we write to
|
||||||
|
fn input_attachments(&self) -> &[Handle<Texture>] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
/// Returns the optional depth attachment for this node. Should not be execution heavy function
|
||||||
|
fn depth_attachment(&self) -> Option<&AttachmentInfo> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_reads(&self) -> &[Buffer] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_writes(&self) -> &[Buffer] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the clear values to initiate this renderpass
|
||||||
|
fn clear_values(&self) -> &[ClearValue] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_kind(&self) -> NodeKind;
|
||||||
|
|
||||||
|
// Optional name, can be empty string
|
||||||
|
fn debug_name(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Execute this node inside a compatible renderpass
|
||||||
|
fn execute(
|
||||||
|
&mut self,
|
||||||
|
world: &mut World,
|
||||||
|
resources: &Resources,
|
||||||
|
cmd: &CommandBuffer,
|
||||||
|
pass_info: &PassInfo,
|
||||||
|
current_frame: usize,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum NodeKind {
|
||||||
|
// A graphics rendering node. Renderpass and framebuffer will automatically be created.
|
||||||
|
Graphics,
|
||||||
|
// execution
|
||||||
|
// A node that will be executed on the transfer queue. Appropriate pipeline barriers will
|
||||||
|
// be inserted
|
||||||
|
Transfer,
|
||||||
|
// Compute,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||||
|
pub struct AttachmentInfo {
|
||||||
|
// TODO, derive from edges
|
||||||
|
pub store_op: StoreOp,
|
||||||
|
pub load_op: LoadOp,
|
||||||
|
pub initial_layout: ImageLayout,
|
||||||
|
pub final_layout: ImageLayout,
|
||||||
|
pub resource: Handle<Texture>,
|
||||||
|
pub clear_value: ClearValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AttachmentInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
store_op: StoreOp::STORE,
|
||||||
|
load_op: LoadOp::DONT_CARE,
|
||||||
|
initial_layout: ImageLayout::UNDEFINED,
|
||||||
|
final_layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
resource: Handle::null(),
|
||||||
|
clear_value: ClearValue::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttachmentInfo {
|
||||||
|
pub fn color(resource: Handle<Texture>) -> Self {
|
||||||
|
Self {
|
||||||
|
store_op: StoreOp::STORE,
|
||||||
|
load_op: LoadOp::CLEAR,
|
||||||
|
initial_layout: ImageLayout::UNDEFINED,
|
||||||
|
final_layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
clear_value: ClearValue::color(0.0, 0.0, 0.0, 1.0),
|
||||||
|
resource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn depth_discard(resource: Handle<Texture>) -> Self {
|
||||||
|
Self {
|
||||||
|
store_op: StoreOp::DONT_CARE,
|
||||||
|
load_op: LoadOp::CLEAR,
|
||||||
|
initial_layout: ImageLayout::UNDEFINED,
|
||||||
|
final_layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||||
|
clear_value: ClearValue::depth_stencil(1.0, 0),
|
||||||
|
resource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn depth_store(resource: Handle<Texture>) -> Self {
|
||||||
|
Self {
|
||||||
|
store_op: StoreOp::STORE,
|
||||||
|
load_op: LoadOp::CLEAR,
|
||||||
|
initial_layout: ImageLayout::UNDEFINED,
|
||||||
|
final_layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||||
|
clear_value: ClearValue::depth_stencil(1.0, 0),
|
||||||
|
resource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple node for rendering a pass in the rendergraph
|
||||||
|
pub struct RenderNode<Pass, T> {
|
||||||
|
renderer: Handle<T>,
|
||||||
|
marker: PhantomData<Pass>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pass, T> RenderNode<Pass, T> {
|
||||||
|
pub fn new(renderer: Handle<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
renderer,
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pass, T, E> Node for RenderNode<Pass, T>
|
||||||
|
where
|
||||||
|
Pass: ShaderPass,
|
||||||
|
T: 'static + Renderer<Error = E> + Send + Sync,
|
||||||
|
E: Into<anyhow::Error>,
|
||||||
|
{
|
||||||
|
fn node_kind(&self) -> NodeKind {
|
||||||
|
NodeKind::Graphics
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(
|
||||||
|
&mut self,
|
||||||
|
world: &mut World,
|
||||||
|
resources: &Resources,
|
||||||
|
cmd: &CommandBuffer,
|
||||||
|
pass_info: &PassInfo,
|
||||||
|
current_frame: usize,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
resources
|
||||||
|
.get_mut(self.renderer)
|
||||||
|
.with_context(|| format!("Failed to borrow {:?} mutably", type_name::<T>()))?
|
||||||
|
.draw::<Pass>(world, resources, cmd, &[], pass_info, &[], current_frame)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.with_context(|| format!("Failed to draw using {:?}", type_name::<T>()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_name(&self) -> &'static str {
|
||||||
|
std::any::type_name::<RenderNode<Pass, T>>()
|
||||||
|
}
|
||||||
|
}
|
395
modules/khors-graphics/src/rendergraph/pass.rs
Normal file
395
modules/khors-graphics/src/rendergraph/pass.rs
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use vulkano::{render_pass::Framebuffer, sync::ImageMemoryBarrier};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
node::{Node, NodeKind},
|
||||||
|
rendergraph::{Edge, NodeIndex, ResourceKind},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Pass {
|
||||||
|
kind: PassKind,
|
||||||
|
nodes: Vec<NodeIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pass {
|
||||||
|
// Creates a new pass from a group of compatible nodes.
|
||||||
|
// Two nodes are compatible if:
|
||||||
|
// * They have no full read dependencies to another.
|
||||||
|
// * Belong to the same kind and queue.
|
||||||
|
// * Have the same dependency level.
|
||||||
|
pub fn new<T>(
|
||||||
|
context: &SharedVulkanContext,
|
||||||
|
nodes: &Vec<Box<dyn Node>>,
|
||||||
|
textures: &T,
|
||||||
|
dependencies: &HashMap<NodeIndex, Vec<Edge>>,
|
||||||
|
pass_nodes: Vec<NodeIndex>,
|
||||||
|
kind: NodeKind,
|
||||||
|
extent: Extent,
|
||||||
|
) -> Result<Self>
|
||||||
|
where
|
||||||
|
T: Deref<Target = ResourceCache<Texture>>,
|
||||||
|
{
|
||||||
|
let kind = match kind {
|
||||||
|
NodeKind::Graphics => {
|
||||||
|
PassKind::graphics(context, nodes, textures, dependencies, &pass_nodes, extent)?
|
||||||
|
}
|
||||||
|
NodeKind::Transfer => {
|
||||||
|
PassKind::transfer(context, nodes, textures, dependencies, &pass_nodes, extent)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
kind,
|
||||||
|
nodes: pass_nodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(
|
||||||
|
&self,
|
||||||
|
world: &mut World,
|
||||||
|
cmd: &CommandBuffer,
|
||||||
|
nodes: &mut Vec<Box<dyn Node>>,
|
||||||
|
current_frame: usize,
|
||||||
|
extent: Extent,
|
||||||
|
) -> Result<()> {
|
||||||
|
match &self.kind {
|
||||||
|
PassKind::Graphics {
|
||||||
|
renderpass,
|
||||||
|
framebuffer,
|
||||||
|
clear_values,
|
||||||
|
} => {
|
||||||
|
cmd.begin_renderpass(&renderpass, &framebuffer, extent, clear_values);
|
||||||
|
|
||||||
|
self.nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.try_for_each(|(subpass, index)| -> Result<_> {
|
||||||
|
if subpass > 0 {
|
||||||
|
cmd.next_subpass(vk::SubpassContents::INLINE);
|
||||||
|
}
|
||||||
|
let node = &mut nodes[*index];
|
||||||
|
|
||||||
|
node.execute(
|
||||||
|
world,
|
||||||
|
resources,
|
||||||
|
cmd,
|
||||||
|
&PassInfo {
|
||||||
|
renderpass: renderpass.renderpass(),
|
||||||
|
subpass: subpass as u32,
|
||||||
|
extent,
|
||||||
|
color_attachment_count: node.color_attachments().len() as u32,
|
||||||
|
depth_attachment: node.depth_attachment().is_some(),
|
||||||
|
},
|
||||||
|
current_frame,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cmd.end_renderpass();
|
||||||
|
}
|
||||||
|
PassKind::Transfer {
|
||||||
|
src_stage,
|
||||||
|
image_barriers,
|
||||||
|
} => {
|
||||||
|
if !image_barriers.is_empty() {
|
||||||
|
cmd.pipeline_barrier(
|
||||||
|
*src_stage,
|
||||||
|
vk::PipelineStageFlags::TRANSFER,
|
||||||
|
&[],
|
||||||
|
image_barriers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nodes.iter().try_for_each(|index| -> Result<_> {
|
||||||
|
nodes[*index]
|
||||||
|
.execute(world, resources, cmd, &PassInfo::default(), current_frame)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the pass's kind.
|
||||||
|
pub fn kind(&self) -> &PassKind {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the pass's nodes.
|
||||||
|
pub fn nodes(&self) -> &[NodeIndex] {
|
||||||
|
self.nodes.as_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PassKind {
|
||||||
|
Graphics {
|
||||||
|
renderpass: RenderPass,
|
||||||
|
framebuffer: Framebuffer,
|
||||||
|
clear_values: Vec<vk::ClearValue>,
|
||||||
|
// Index into first node of pass_nodes
|
||||||
|
},
|
||||||
|
|
||||||
|
Transfer {
|
||||||
|
src_stage: vk::PipelineStageFlags,
|
||||||
|
image_barriers: Vec<ImageMemoryBarrier>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for PassKind {}
|
||||||
|
unsafe impl Sync for PassKind {}
|
||||||
|
|
||||||
|
impl PassKind {
|
||||||
|
fn graphics<T>(
|
||||||
|
context: &SharedVulkanContext,
|
||||||
|
nodes: &Vec<Box<dyn Node>>,
|
||||||
|
textures: &T,
|
||||||
|
dependencies: &HashMap<NodeIndex, Vec<Edge>>,
|
||||||
|
pass_nodes: &[NodeIndex],
|
||||||
|
extent: Extent,
|
||||||
|
) -> Result<Self>
|
||||||
|
where
|
||||||
|
T: Deref<Target = ResourceCache<Texture>>,
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"Building pass with nodes: {:?}",
|
||||||
|
pass_nodes
|
||||||
|
.iter()
|
||||||
|
.map(|v| nodes[*v].debug_name())
|
||||||
|
.collect_vec()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Collect clear values
|
||||||
|
let clear_values = pass_nodes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|node| {
|
||||||
|
let node = &nodes[*node];
|
||||||
|
node.clear_values()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(repeat(ClearValue::default()).take(
|
||||||
|
node.color_attachments().len() + node.depth_attachment().iter().count()
|
||||||
|
- node.clear_values().len(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Generate subpass dependencies
|
||||||
|
let dependencies = pass_nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(subpass_index, node_index)| {
|
||||||
|
// Get the dependencies of node.
|
||||||
|
dependencies
|
||||||
|
.get(node_index)
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|val| val.iter())
|
||||||
|
.flat_map(move |edge| match edge.kind {
|
||||||
|
EdgeKind::Sampled => Some(SubpassDependency {
|
||||||
|
src_subpass: vk::SUBPASS_EXTERNAL,
|
||||||
|
dst_subpass: subpass_index as u32,
|
||||||
|
src_stage_mask: edge.write_stage,
|
||||||
|
dst_stage_mask: edge.read_stage,
|
||||||
|
src_access_mask: edge.write_access,
|
||||||
|
dst_access_mask: edge.read_access,
|
||||||
|
dependency_flags: Default::default(),
|
||||||
|
}),
|
||||||
|
EdgeKind::Input => Some(SubpassDependency {
|
||||||
|
src_subpass: pass_nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, node)| **node == edge.src)
|
||||||
|
.unwrap()
|
||||||
|
.0 as u32,
|
||||||
|
dst_subpass: subpass_index as u32,
|
||||||
|
src_stage_mask: edge.write_stage,
|
||||||
|
dst_stage_mask: edge.read_stage,
|
||||||
|
src_access_mask: edge.write_access,
|
||||||
|
dst_access_mask: edge.read_access,
|
||||||
|
dependency_flags: vk::DependencyFlags::BY_REGION,
|
||||||
|
}),
|
||||||
|
EdgeKind::Attachment => Some(SubpassDependency {
|
||||||
|
src_subpass: pass_nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, node)| **node == edge.src)
|
||||||
|
.map(|v| v.0 as u32)
|
||||||
|
.unwrap_or(vk::SUBPASS_EXTERNAL),
|
||||||
|
|
||||||
|
dst_subpass: subpass_index as u32,
|
||||||
|
src_stage_mask: edge.write_stage,
|
||||||
|
dst_stage_mask: edge.read_stage,
|
||||||
|
src_access_mask: edge.write_access,
|
||||||
|
dst_access_mask: edge.read_access,
|
||||||
|
dependency_flags: vk::DependencyFlags::BY_REGION,
|
||||||
|
}),
|
||||||
|
EdgeKind::Buffer => None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut attachment_descriptions = Vec::new();
|
||||||
|
let mut attachments = Vec::new();
|
||||||
|
|
||||||
|
let attachment_refs = pass_nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(_, node_index)| -> Result<_> {
|
||||||
|
let node = &nodes[*node_index];
|
||||||
|
|
||||||
|
let offset = attachments.len();
|
||||||
|
|
||||||
|
let color_attachments = node
|
||||||
|
.color_attachments()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, _)| AttachmentReference {
|
||||||
|
attachment: (i + offset) as u32,
|
||||||
|
layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let input_attachments = node
|
||||||
|
.input_attachments()
|
||||||
|
.iter()
|
||||||
|
.map(|tex| -> Result<_> {
|
||||||
|
let view = textures.get(*tex)?.image_view();
|
||||||
|
Ok(AttachmentReference {
|
||||||
|
attachment: attachments
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, val)| view == **val)
|
||||||
|
.unwrap()
|
||||||
|
.0 as u32,
|
||||||
|
layout: ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let depth_attachment =
|
||||||
|
node.depth_attachment()
|
||||||
|
.as_ref()
|
||||||
|
.map(|_| AttachmentReference {
|
||||||
|
attachment: (color_attachments.len() + input_attachments.len() + offset)
|
||||||
|
as u32,
|
||||||
|
layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||||
|
});
|
||||||
|
|
||||||
|
for attachment in node
|
||||||
|
.color_attachments()
|
||||||
|
.iter()
|
||||||
|
.chain(node.depth_attachment().into_iter())
|
||||||
|
{
|
||||||
|
let texture = textures.get(attachment.resource)?;
|
||||||
|
|
||||||
|
attachments.push(texture.image_view());
|
||||||
|
|
||||||
|
attachment_descriptions.push(AttachmentDescription {
|
||||||
|
flags: vk::AttachmentDescriptionFlags::default(),
|
||||||
|
format: texture.format(),
|
||||||
|
samples: texture.samples(),
|
||||||
|
load_op: attachment.load_op,
|
||||||
|
store_op: attachment.store_op,
|
||||||
|
stencil_load_op: LoadOp::DONT_CARE,
|
||||||
|
stencil_store_op: StoreOp::DONT_CARE,
|
||||||
|
initial_layout: attachment.initial_layout,
|
||||||
|
final_layout: attachment.final_layout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok((color_attachments, input_attachments, depth_attachment))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let subpasses = attachment_refs
|
||||||
|
.iter()
|
||||||
|
.map(
|
||||||
|
|(color_attachments, input_attachments, depth_attachment)| SubpassInfo {
|
||||||
|
color_attachments,
|
||||||
|
resolve_attachments: &[],
|
||||||
|
input_attachments: &input_attachments,
|
||||||
|
depth_attachment: *depth_attachment,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let renderpass_info = RenderPassInfo {
|
||||||
|
attachments: &attachment_descriptions,
|
||||||
|
subpasses: &subpasses,
|
||||||
|
dependencies: &dependencies,
|
||||||
|
};
|
||||||
|
|
||||||
|
let renderpass = RenderPass::new(context.device().clone(), &renderpass_info)?;
|
||||||
|
|
||||||
|
let framebuffer =
|
||||||
|
Framebuffer::new(context.device().clone(), &renderpass, &attachments, extent)?;
|
||||||
|
|
||||||
|
Ok(PassKind::Graphics {
|
||||||
|
renderpass,
|
||||||
|
framebuffer,
|
||||||
|
clear_values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer<T>(
|
||||||
|
_context: &SharedVulkanContext,
|
||||||
|
_nodes: &Vec<Box<dyn Node>>,
|
||||||
|
textures: &T,
|
||||||
|
dependencies: &HashMap<NodeIndex, Vec<Edge>>,
|
||||||
|
pass_nodes: &[NodeIndex],
|
||||||
|
_extent: Extent,
|
||||||
|
) -> Result<Self>
|
||||||
|
where
|
||||||
|
T: Deref<Target = ResourceCache<Texture>>,
|
||||||
|
{
|
||||||
|
// Get the dependencies of node.
|
||||||
|
let mut src_stage = vk::PipelineStageFlags::default();
|
||||||
|
|
||||||
|
let image_barriers = dependencies
|
||||||
|
.get(&pass_nodes[0])
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|val| val.iter())
|
||||||
|
.filter_map(|val| {
|
||||||
|
if let ResourceKind::Texture(tex) = val.resource {
|
||||||
|
Some((val, tex))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(edge, texture)| -> Result<_> {
|
||||||
|
let src = textures.get(texture)?;
|
||||||
|
|
||||||
|
let aspect_mask =
|
||||||
|
if edge.read_access == vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE {
|
||||||
|
vk::ImageAspectFlags::DEPTH
|
||||||
|
} else {
|
||||||
|
vk::ImageAspectFlags::COLOR
|
||||||
|
};
|
||||||
|
src_stage = edge.write_stage.max(src_stage);
|
||||||
|
|
||||||
|
Ok(ImageMemoryBarrier {
|
||||||
|
src_access_mask: edge.write_access,
|
||||||
|
dst_access_mask: vk::AccessFlags::TRANSFER_READ,
|
||||||
|
old_layout: edge.layout,
|
||||||
|
new_layout: ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
|
src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
image: src.image(),
|
||||||
|
subresource_range: vk::ImageSubresourceRange {
|
||||||
|
aspect_mask,
|
||||||
|
base_mip_level: 0,
|
||||||
|
level_count: src.mip_levels(),
|
||||||
|
base_array_layer: 0,
|
||||||
|
layer_count: 1,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(Self::Transfer {
|
||||||
|
src_stage,
|
||||||
|
image_barriers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
622
modules/khors-graphics/src/rendergraph/rendergraph.rs
Normal file
622
modules/khors-graphics/src/rendergraph/rendergraph.rs
Normal file
|
@ -0,0 +1,622 @@
|
||||||
|
use super::error::Error;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use vulkano::{
|
||||||
|
command_buffer::pool::{CommandPool, CommandPoolCreateFlags, CommandPoolCreateInfo},
|
||||||
|
sync::{fence::Fence, semaphore::Semaphore},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{node::Node, pass::Pass};
|
||||||
|
pub type PassIndex = usize;
|
||||||
|
pub type NodeIndex = usize;
|
||||||
|
|
||||||
|
/// Direct acyclic graph abstraction for renderpasses, barriers and subpass dependencies.
|
||||||
|
pub struct RenderGraph {
|
||||||
|
context: SharedVulkanContext,
|
||||||
|
/// The unordered nodes in the arena.
|
||||||
|
nodes: Vec<Box<dyn Node>>,
|
||||||
|
edges: HashMap<NodeIndex, Vec<Edge>>,
|
||||||
|
passes: Vec<Pass>,
|
||||||
|
// Maps a node to a pass index
|
||||||
|
node_pass_map: HashMap<NodeIndex, (PassIndex, u32)>,
|
||||||
|
|
||||||
|
// Data for each frame in flight
|
||||||
|
frames: Vec<FrameData>,
|
||||||
|
extent: Extent,
|
||||||
|
frames_in_flight: usize,
|
||||||
|
current_frame: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderGraph {
|
||||||
|
/// Creates a new empty rendergraph.
|
||||||
|
pub fn new(
|
||||||
|
context: SharedVulkanContext,
|
||||||
|
frames_in_flight: usize,
|
||||||
|
) -> super::error::Result<Self> {
|
||||||
|
let frames = (0..frames_in_flight)
|
||||||
|
.map(|_| FrameData::new(context.clone()).map_err(|e| e.into()))
|
||||||
|
.collect::<super::error::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
nodes: Default::default(),
|
||||||
|
edges: Default::default(),
|
||||||
|
passes: Vec::new(),
|
||||||
|
node_pass_map: Default::default(),
|
||||||
|
frames,
|
||||||
|
extent: Extent::new(0, 0),
|
||||||
|
frames_in_flight,
|
||||||
|
current_frame: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new node into the rendergraph.
|
||||||
|
/// **Note**: The new node won't take effect until [`RenderGraph::build`] is called.
|
||||||
|
pub fn add_node<T: 'static + Node>(&mut self, node: T) -> NodeIndex {
|
||||||
|
let i = self.nodes.len();
|
||||||
|
self.nodes.push(Box::new(node));
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add several nodes into the rendergraph.
|
||||||
|
/// Due to the concrete type of iterators, the nodes need to already be boxed.
|
||||||
|
/// Returns the node indices in order.
|
||||||
|
pub fn add_nodes<I: IntoIterator<Item = Box<dyn Node>>>(&mut self, nodes: I) -> Vec<NodeIndex> {
|
||||||
|
nodes
|
||||||
|
.into_iter()
|
||||||
|
.map(|node| {
|
||||||
|
let i = self.nodes.len();
|
||||||
|
self.nodes.push(node);
|
||||||
|
i
|
||||||
|
})
|
||||||
|
.collect_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_edges(
|
||||||
|
&mut self,
|
||||||
|
) -> crate::Result<(HashMap<usize, Vec<Edge>>, HashMap<usize, Vec<Edge>>)> {
|
||||||
|
// Iterate all node's read attachments, and find any node which has the same write
|
||||||
|
// attachment before the current node.
|
||||||
|
// Finally, automatically construct edges.
|
||||||
|
let nodes = &self.nodes;
|
||||||
|
let mut edges = HashMap::new();
|
||||||
|
let mut dependencies = HashMap::new();
|
||||||
|
|
||||||
|
nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|node| {
|
||||||
|
EdgeConstructor {
|
||||||
|
nodes,
|
||||||
|
dst: node.0,
|
||||||
|
reads: node.1.input_attachments().into_iter().cloned(),
|
||||||
|
kind: EdgeKind::Input,
|
||||||
|
}
|
||||||
|
.chain(BufferEdgeConstructor {
|
||||||
|
nodes,
|
||||||
|
dst: node.0,
|
||||||
|
reads: node.1.buffer_reads().into_iter().cloned(),
|
||||||
|
kind: EdgeKind::Buffer,
|
||||||
|
})
|
||||||
|
.chain(EdgeConstructor {
|
||||||
|
nodes,
|
||||||
|
dst: node.0,
|
||||||
|
reads: node.1.read_attachments().into_iter().cloned(),
|
||||||
|
kind: EdgeKind::Sampled,
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
EdgeConstructor {
|
||||||
|
nodes,
|
||||||
|
dst: node.0,
|
||||||
|
reads: node.1.color_attachments().iter().map(|v| v.resource),
|
||||||
|
kind: EdgeKind::Attachment,
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
.filter(|val| !matches!(*val, Err(Error::MissingWrite(_, _, _)))),
|
||||||
|
)
|
||||||
|
.chain(
|
||||||
|
EdgeConstructor {
|
||||||
|
nodes,
|
||||||
|
dst: node.0,
|
||||||
|
reads: node.1.output_attachments().into_iter().cloned(),
|
||||||
|
kind: EdgeKind::Sampled,
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
.filter(|val| !matches!(*val, Err(Error::MissingWrite(_, _, _)))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.try_for_each(
|
||||||
|
|edge: super::error::Result<Edge>| -> super::error::Result<_> {
|
||||||
|
let edge = edge?;
|
||||||
|
edges.entry(edge.src).or_insert_with(Vec::new).push(edge);
|
||||||
|
|
||||||
|
dependencies
|
||||||
|
.entry(edge.dst)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(edge);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((edges, dependencies))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node(&self, node: NodeIndex) -> super::error::Result<&dyn Node> {
|
||||||
|
self.nodes
|
||||||
|
.get(node)
|
||||||
|
.ok_or_else(|| Error::InvalidNodeIndex(node))
|
||||||
|
.map(|val| val.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_renderpass<'a>(
|
||||||
|
&'a self,
|
||||||
|
node: NodeIndex,
|
||||||
|
) -> super::error::Result<(&'a RenderPass, u32)> {
|
||||||
|
let (pass, index) = self
|
||||||
|
.node_pass_map
|
||||||
|
.get(&node)
|
||||||
|
.and_then(|(pass, subpass_index)| Some((self.passes.get(*pass)?, *subpass_index)))
|
||||||
|
.ok_or(Error::InvalidNodeIndex(node))?;
|
||||||
|
|
||||||
|
match pass.kind() {
|
||||||
|
PassKind::Graphics {
|
||||||
|
renderpass,
|
||||||
|
framebuffer: _,
|
||||||
|
clear_values: _,
|
||||||
|
} => Ok((renderpass, index)),
|
||||||
|
PassKind::Transfer { .. } => Err(Error::InvalidNodeKind(
|
||||||
|
node,
|
||||||
|
NodeKind::Graphics,
|
||||||
|
self.nodes[node].node_kind(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds or rebuilds the rendergraph and creates appropriate renderpasses and framebuffers.
|
||||||
|
pub fn build<T>(&mut self, textures: T, extent: Extent) -> super::error::Result<()>
|
||||||
|
where
|
||||||
|
T: Deref<Target = ResourceCache<Texture>>,
|
||||||
|
{
|
||||||
|
let (_edges, dependencies) = self.build_edges()?;
|
||||||
|
let (ordered, depths) = topological_sort(&self.nodes, &self.edges)?;
|
||||||
|
|
||||||
|
let context = &self.context;
|
||||||
|
|
||||||
|
let nodes = &self.nodes;
|
||||||
|
|
||||||
|
self.node_pass_map.clear();
|
||||||
|
|
||||||
|
let node_pass_map = &mut self.node_pass_map;
|
||||||
|
node_pass_map.clear();
|
||||||
|
|
||||||
|
let passes = &mut self.passes;
|
||||||
|
passes.clear();
|
||||||
|
|
||||||
|
// Build all graphics nodes
|
||||||
|
let groups = ordered.iter().cloned().group_by(|node| {
|
||||||
|
return (depths[node], nodes[*node].node_kind());
|
||||||
|
});
|
||||||
|
|
||||||
|
for (key, group) in &groups {
|
||||||
|
println!("New pass: {:?}", key);
|
||||||
|
let pass_nodes = group.collect_vec();
|
||||||
|
|
||||||
|
let pass = Pass::new(
|
||||||
|
context,
|
||||||
|
nodes,
|
||||||
|
&textures,
|
||||||
|
&dependencies,
|
||||||
|
pass_nodes,
|
||||||
|
key.1,
|
||||||
|
extent,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Insert pass into slotmap
|
||||||
|
let pass_index = passes.len();
|
||||||
|
passes.push(pass);
|
||||||
|
|
||||||
|
let pass_nodes = passes[pass_index].nodes();
|
||||||
|
|
||||||
|
// Map the node into the pass
|
||||||
|
for (i, node) in pass_nodes.iter().enumerate() {
|
||||||
|
node_pass_map.insert(*node, (pass_index, i as u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.extent = extent;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begins the current frame and ensures resources are ready by waiting on fences.
|
||||||
|
// Begins recording of the commandbuffers.
|
||||||
|
// Returns the current frame in flight
|
||||||
|
pub fn begin(&self) -> super::error::Result<usize> {
|
||||||
|
let frame = &self.frames[self.current_frame];
|
||||||
|
let device = self.context.device();
|
||||||
|
|
||||||
|
// Make sure frame is available before beginning execution
|
||||||
|
fence::wait(device, &[frame.fence], true)?;
|
||||||
|
fence::reset(device, &[frame.fence])?;
|
||||||
|
|
||||||
|
// Reset commandbuffers for this frame
|
||||||
|
frame.commandpool.reset(false)?;
|
||||||
|
|
||||||
|
// Get the commandbuffer for this frame
|
||||||
|
let commandbuffer = &frame.commandbuffer;
|
||||||
|
|
||||||
|
// Start recording
|
||||||
|
commandbuffer.begin(CommandBufferUsageFlags::ONE_TIME_SUBMIT)?;
|
||||||
|
|
||||||
|
Ok(self.current_frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes the whole rendergraph by starting renderpass recording and filling it using the
|
||||||
|
// node execution functions. Submits the resulting commandbuffer.
|
||||||
|
pub fn execute(&mut self, world: &mut World) -> super::error::Result<()> {
|
||||||
|
// Reset all commandbuffers for this frame
|
||||||
|
let frame = &mut self.frames[self.current_frame];
|
||||||
|
|
||||||
|
let nodes = &mut self.nodes;
|
||||||
|
let passes = &self.passes;
|
||||||
|
let extent = self.extent;
|
||||||
|
let current_frame = self.current_frame;
|
||||||
|
|
||||||
|
let cmd = &frame.commandbuffer;
|
||||||
|
|
||||||
|
// Execute all nodes
|
||||||
|
passes
|
||||||
|
.iter()
|
||||||
|
.try_for_each(|pass| -> super::error::Result<()> {
|
||||||
|
pass.execute(world, &cmd, nodes, current_frame, extent)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ends and submits recording of commandbuffer for the current frame, and increments the
|
||||||
|
/// current_frame.
|
||||||
|
pub fn end(&mut self) -> super::error::Result<()> {
|
||||||
|
let frame = &self.frames[self.current_frame];
|
||||||
|
let commandbuffer = &frame.commandbuffer;
|
||||||
|
commandbuffer.end()?;
|
||||||
|
|
||||||
|
// Submit the results
|
||||||
|
commandbuffer.submit(
|
||||||
|
self.context.graphics_queue(),
|
||||||
|
&[frame.wait_semaphore],
|
||||||
|
&[frame.signal_semaphore],
|
||||||
|
frame.fence,
|
||||||
|
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Move to the next frame in flight and wrap around to n-buffer
|
||||||
|
self.current_frame = (self.current_frame + 1) % self.frames_in_flight;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the current signal semaphore for the specified frame
|
||||||
|
pub fn signal_semaphore(&self, current_frame: usize) -> Semaphore {
|
||||||
|
self.frames[current_frame].signal_semaphore
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the current wait semaphore for the specified frame
|
||||||
|
pub fn wait_semaphore(&self, current_frame: usize) -> Semaphore {
|
||||||
|
self.frames[current_frame].wait_semaphore
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the current fence for the specified frame
|
||||||
|
pub fn fence(&self, current_frame: usize) -> Fence {
|
||||||
|
self.frames[current_frame].fence
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commandbuffer(&self, current_frame: usize) -> &CommandBuffer {
|
||||||
|
&self.frames[current_frame].commandbuffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum VisitedState {
|
||||||
|
Pending,
|
||||||
|
Visited,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Depth = u32;
|
||||||
|
|
||||||
|
/// Toplogically sorts the graph provided by nodes and edges.
|
||||||
|
/// Returns a tuple containing a dense array of ordered node indices, and a map containing each node's maximum depth.
|
||||||
|
// TODO: Move graph functionality into separate crate.
|
||||||
|
fn topological_sort(
|
||||||
|
nodes: &Vec<Box<dyn Node>>,
|
||||||
|
edges: &HashMap<NodeIndex, Vec<Edge>>,
|
||||||
|
) -> super::error::Result<(Vec<NodeIndex>, HashMap<NodeIndex, Depth>)> {
|
||||||
|
fn internal(
|
||||||
|
stack: &mut Vec<NodeIndex>,
|
||||||
|
visited: &mut HashMap<NodeIndex, VisitedState>,
|
||||||
|
depths: &mut HashMap<NodeIndex, Depth>,
|
||||||
|
current_node: NodeIndex,
|
||||||
|
edges: &HashMap<NodeIndex, Vec<Edge>>,
|
||||||
|
depth: Depth,
|
||||||
|
) -> super::error::Result<()> {
|
||||||
|
// Update maximum recursion depth
|
||||||
|
depths
|
||||||
|
.entry(current_node)
|
||||||
|
.and_modify(|d| *d = (*d).max(depth))
|
||||||
|
.or_insert(depth);
|
||||||
|
|
||||||
|
// Node is already visited
|
||||||
|
match visited.get(¤t_node) {
|
||||||
|
Some(VisitedState::Pending) => return Err(Error::DependencyCycle),
|
||||||
|
Some(VisitedState::Visited) => return Ok(()),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
visited.insert(current_node, VisitedState::Pending);
|
||||||
|
|
||||||
|
// Add all children of `node`, before node to the stack.
|
||||||
|
edges
|
||||||
|
.get(¤t_node)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|node_edges| node_edges.iter())
|
||||||
|
.try_for_each(|edge| {
|
||||||
|
internal(
|
||||||
|
stack,
|
||||||
|
visited,
|
||||||
|
depths,
|
||||||
|
edge.dst,
|
||||||
|
edges,
|
||||||
|
// Break depth if sampling is required since they can't share subpasses
|
||||||
|
match edge.kind {
|
||||||
|
EdgeKind::Sampled | EdgeKind::Buffer => depth + 1,
|
||||||
|
EdgeKind::Attachment | EdgeKind::Input => depth,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
stack.push(current_node);
|
||||||
|
|
||||||
|
visited.insert(current_node, VisitedState::Visited);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodes_iter = nodes.into_iter();
|
||||||
|
let cap = nodes_iter.size_hint().1.unwrap_or_default();
|
||||||
|
let mut stack = Vec::with_capacity(cap);
|
||||||
|
let mut visited = HashMap::with_capacity(cap);
|
||||||
|
let mut depths = HashMap::with_capacity(cap);
|
||||||
|
|
||||||
|
for (node, _) in nodes_iter.enumerate() {
|
||||||
|
internal(&mut stack, &mut visited, &mut depths, node, edges, 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack.reverse();
|
||||||
|
|
||||||
|
Ok((stack, depths))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Edge {
|
||||||
|
pub src: NodeIndex,
|
||||||
|
pub dst: NodeIndex,
|
||||||
|
pub resource: ResourceKind,
|
||||||
|
pub write_stage: PipelineStageFlags,
|
||||||
|
pub read_stage: PipelineStageFlags,
|
||||||
|
pub write_access: vk::AccessFlags,
|
||||||
|
pub read_access: vk::AccessFlags,
|
||||||
|
pub layout: ImageLayout,
|
||||||
|
pub kind: EdgeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Edge {
|
||||||
|
type Target = ResourceKind;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ResourceKind {
|
||||||
|
Texture(Handle<Texture>),
|
||||||
|
Buffer(vk::Buffer),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<vk::Buffer> for ResourceKind {
|
||||||
|
fn from(val: vk::Buffer) -> Self {
|
||||||
|
Self::Buffer(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Handle<Texture>> for ResourceKind {
|
||||||
|
fn from(val: Handle<Texture>) -> Self {
|
||||||
|
Self::Texture(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum EdgeKind {
|
||||||
|
/// Dependency is sampled and requires the whole attachment to be ready.
|
||||||
|
Sampled,
|
||||||
|
/// Dependency is used as input attachment and can use dependency by region.
|
||||||
|
Input,
|
||||||
|
/// The attachment is loaded and written to. Depend on earlier nodes
|
||||||
|
Attachment,
|
||||||
|
Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EdgeKind {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Sampled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FrameData {
|
||||||
|
context: SharedVulkanContext,
|
||||||
|
fence: Fence,
|
||||||
|
commandpool: CommandPool,
|
||||||
|
commandbuffer: CommandBuffer,
|
||||||
|
wait_semaphore: Semaphore,
|
||||||
|
signal_semaphore: Semaphore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameData {
|
||||||
|
pub fn new(context: SharedVulkanContext) -> super::error::Result<Self> {
|
||||||
|
let commandpool = CommandPool::new(
|
||||||
|
context.device().clone(),
|
||||||
|
CommandPoolCreateInfo {
|
||||||
|
flags: CommandPoolCreateFlags {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
queue_family_index: context.queue_families().graphics().unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let commandbuffer = commandpool.allocate_one()?;
|
||||||
|
let fence = fence::create(context.device(), true)?;
|
||||||
|
|
||||||
|
let wait_semaphore = semaphore::create(context.device())?;
|
||||||
|
let signal_semaphore = semaphore::create(context.device())?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
fence,
|
||||||
|
commandpool,
|
||||||
|
commandbuffer,
|
||||||
|
wait_semaphore,
|
||||||
|
signal_semaphore,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EdgeConstructor<'a, I> {
|
||||||
|
nodes: &'a Vec<Box<dyn Node>>,
|
||||||
|
dst: NodeIndex,
|
||||||
|
reads: I,
|
||||||
|
kind: EdgeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item = Handle<Texture>>> Iterator for EdgeConstructor<'a, I> {
|
||||||
|
type Item = super::error::Result<Edge>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.reads
|
||||||
|
.next()
|
||||||
|
// Find the corresponding write attachment
|
||||||
|
.map(move |read| {
|
||||||
|
self.nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.take_while(|(src, _)| *src != self.dst)
|
||||||
|
.filter_map(|(src, src_node)| {
|
||||||
|
// Found color attachment output
|
||||||
|
if let Some(write) = src_node
|
||||||
|
.color_attachments()
|
||||||
|
.iter()
|
||||||
|
.find(|w| w.resource == read)
|
||||||
|
{
|
||||||
|
Some(Edge {
|
||||||
|
src,
|
||||||
|
dst: self.dst,
|
||||||
|
resource: read.into(),
|
||||||
|
layout: write.final_layout,
|
||||||
|
write_stage: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
||||||
|
read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER,
|
||||||
|
write_access: vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||||
|
read_access: vk::AccessFlags::SHADER_READ,
|
||||||
|
kind: self.kind,
|
||||||
|
})
|
||||||
|
// Found transfer attachment output
|
||||||
|
} else if let Some(_) =
|
||||||
|
src_node.output_attachments().iter().find(|d| **d == read)
|
||||||
|
{
|
||||||
|
Some(Edge {
|
||||||
|
src,
|
||||||
|
dst: self.dst,
|
||||||
|
resource: read.into(),
|
||||||
|
layout: ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
write_stage: vk::PipelineStageFlags::TRANSFER,
|
||||||
|
read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER,
|
||||||
|
write_access: vk::AccessFlags::TRANSFER_WRITE,
|
||||||
|
read_access: vk::AccessFlags::SHADER_READ,
|
||||||
|
kind: self.kind,
|
||||||
|
})
|
||||||
|
} else if let Some(write) = src_node
|
||||||
|
.depth_attachment()
|
||||||
|
.as_ref()
|
||||||
|
.filter(|d| d.resource == read)
|
||||||
|
{
|
||||||
|
Some(Edge {
|
||||||
|
src,
|
||||||
|
dst: self.dst,
|
||||||
|
resource: read.into(),
|
||||||
|
layout: write.final_layout,
|
||||||
|
// Write stage is between
|
||||||
|
write_stage: vk::PipelineStageFlags::LATE_FRAGMENT_TESTS,
|
||||||
|
read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER,
|
||||||
|
write_access: vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
|
||||||
|
read_access: vk::AccessFlags::SHADER_READ,
|
||||||
|
kind: self.kind,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
.ok_or(Error::MissingWrite(
|
||||||
|
self.dst,
|
||||||
|
self.nodes[self.dst].debug_name(),
|
||||||
|
read.into(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BufferEdgeConstructor<'a, I> {
|
||||||
|
nodes: &'a Vec<Box<dyn Node>>,
|
||||||
|
dst: NodeIndex,
|
||||||
|
reads: I,
|
||||||
|
kind: EdgeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item = vk::Buffer>> Iterator for BufferEdgeConstructor<'a, I> {
|
||||||
|
type Item = crate::Result<Edge>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.reads
|
||||||
|
.next()
|
||||||
|
// Find the corresponding write attachment
|
||||||
|
.map(move |read| {
|
||||||
|
self.nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.take_while(|(src, _)| *src != self.dst)
|
||||||
|
.filter_map(|(src, src_node)| {
|
||||||
|
// Found color attachment output
|
||||||
|
if let Some(_) = src_node.buffer_writes().iter().find(|w| **w == read) {
|
||||||
|
Some(Edge {
|
||||||
|
src,
|
||||||
|
dst: self.dst,
|
||||||
|
resource: read.into(),
|
||||||
|
layout: Default::default(),
|
||||||
|
write_stage: vk::PipelineStageFlags::TRANSFER,
|
||||||
|
read_stage: vk::PipelineStageFlags::VERTEX_SHADER,
|
||||||
|
write_access: vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||||
|
read_access: vk::AccessFlags::SHADER_READ,
|
||||||
|
kind: self.kind,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
.ok_or(Error::MissingWrite(
|
||||||
|
self.dst,
|
||||||
|
self.nodes[self.dst].debug_name(),
|
||||||
|
read.into(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
modules/khors-graphics/src/temp/mod.rs
Normal file
1
modules/khors-graphics/src/temp/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod model;
|
|
@ -1,4 +1,4 @@
|
||||||
use super::vulkan::vertex::{Position, Normal};
|
use crate::vulkan::vertex::{Position, Normal};
|
||||||
|
|
||||||
pub const POSITIONS: [Position; 531] = [
|
pub const POSITIONS: [Position; 531] = [
|
||||||
Position {
|
Position {
|
14
modules/khors-graphics/src/vulkan/framebuffer.rs
Normal file
14
modules/khors-graphics/src/vulkan/framebuffer.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use vulkano::{image::view::ImageView, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass}};
|
||||||
|
|
||||||
|
pub fn make_framebuffer(render_pass: Arc<RenderPass>, attachments: Vec<Arc<ImageView>>) -> Result<Arc<Framebuffer>, vulkano::Validated<vulkano::VulkanError>> {
|
||||||
|
let extent = attachments[0].image().extent();
|
||||||
|
|
||||||
|
Framebuffer::new(render_pass.clone(), FramebufferCreateInfo {
|
||||||
|
attachments,
|
||||||
|
extent: [extent[0], extent[1]],
|
||||||
|
layers: 1,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,2 +1,4 @@
|
||||||
// pub mod pipeline;
|
pub mod pipeline;
|
||||||
|
pub mod framebuffer;
|
||||||
pub mod vertex;
|
pub mod vertex;
|
||||||
|
pub mod renderpass;
|
||||||
|
|
|
@ -17,14 +17,14 @@ use vulkano::{
|
||||||
GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
|
GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
|
||||||
},
|
},
|
||||||
render_pass::{RenderPass, Subpass},
|
render_pass::{RenderPass, Subpass},
|
||||||
shader::ShaderModule,
|
shader::EntryPoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::vertex::{Normal, Position};
|
use super::vertex::{Normal, Position};
|
||||||
|
|
||||||
pub struct PipelineInfo {
|
pub struct PipelineInfo {
|
||||||
pub vs: Arc<ShaderModule>,
|
pub vs: EntryPoint,
|
||||||
pub fs: Arc<ShaderModule>,
|
pub fs: EntryPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_pipeline(
|
pub fn make_pipeline(
|
||||||
|
@ -32,15 +32,12 @@ pub fn make_pipeline(
|
||||||
pipeline_info: &PipelineInfo,
|
pipeline_info: &PipelineInfo,
|
||||||
pass_info: &PassInfo,
|
pass_info: &PassInfo,
|
||||||
) -> Arc<GraphicsPipeline> {
|
) -> Arc<GraphicsPipeline> {
|
||||||
let vs = pipeline_info.vs.entry_point("main").unwrap();
|
|
||||||
let fs = pipeline_info.fs.entry_point("main").unwrap();
|
|
||||||
|
|
||||||
let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()]
|
let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()]
|
||||||
.definition(&vs)
|
.definition(&pipeline_info.vs)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let stages = [
|
let stages = [
|
||||||
PipelineShaderStageCreateInfo::new(vs),
|
PipelineShaderStageCreateInfo::new(pipeline_info.vs.clone()),
|
||||||
PipelineShaderStageCreateInfo::new(fs),
|
PipelineShaderStageCreateInfo::new(pipeline_info.fs.clone()),
|
||||||
];
|
];
|
||||||
let layout = PipelineLayout::new(
|
let layout = PipelineLayout::new(
|
||||||
device.clone(),
|
device.clone(),
|
||||||
|
|
116
modules/khors-graphics/src/vulkan/renderpass.rs
Normal file
116
modules/khors-graphics/src/vulkan/renderpass.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use vulkano::{device::Device, format::Format, render_pass::{AttachmentDescription, AttachmentReference, RenderPass as VkRenderPass, RenderPassCreateFlags, RenderPassCreateInfo, SubpassDependency, SubpassDescription, SubpassDescriptionFlags}};
|
||||||
|
use vulkano_util::renderer::VulkanoWindowRenderer;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct SubpassInfo {
|
||||||
|
pub color_attachments: Vec<Option<AttachmentReference>>,
|
||||||
|
pub color_resolve_attachments: Vec<Option<AttachmentReference>>,
|
||||||
|
pub input_attachments: Vec<Option<AttachmentReference>>,
|
||||||
|
pub depth_stencil_attachment: Option<AttachmentReference>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SubpassInfo> for SubpassDescription {
|
||||||
|
fn from(value: SubpassInfo) -> Self {
|
||||||
|
SubpassDescription {
|
||||||
|
flags: SubpassDescriptionFlags::default(),
|
||||||
|
color_attachments: value.color_attachments,
|
||||||
|
color_resolve_attachments: value.color_resolve_attachments,
|
||||||
|
input_attachments: value.input_attachments,
|
||||||
|
depth_stencil_attachment: value.depth_stencil_attachment,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_subpass_description(subpass_info: Arc<SubpassInfo>) -> SubpassDescription {
|
||||||
|
SubpassDescription {
|
||||||
|
flags: SubpassDescriptionFlags::default(),
|
||||||
|
color_attachments: subpass_info.color_attachments.clone(),
|
||||||
|
color_resolve_attachments: subpass_info.color_resolve_attachments.clone(),
|
||||||
|
input_attachments: subpass_info.input_attachments.clone(),
|
||||||
|
depth_stencil_attachment: subpass_info.depth_stencil_attachment.clone(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RenderPassInfo {
|
||||||
|
pub attachments: Vec<AttachmentDescription>,
|
||||||
|
pub subpasses: Vec<Arc<SubpassInfo>>,
|
||||||
|
pub dependencies: Vec<SubpassDependency>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RenderPass {
|
||||||
|
device: Arc<Device>,
|
||||||
|
render_pass: Arc<VkRenderPass>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderPass {
|
||||||
|
pub fn new(device: Arc<vulkano::device::Device>, info: Arc<RenderPassInfo>) -> Self {
|
||||||
|
let vk_subpasses = info.subpasses.iter().map(|subpass| make_subpass_description(subpass.clone())).collect::<Vec<SubpassDescription>>();
|
||||||
|
let render_pass_create_info = RenderPassCreateInfo {
|
||||||
|
attachments: info.attachments.clone(),
|
||||||
|
dependencies: info.dependencies.clone(),
|
||||||
|
subpasses: vk_subpasses,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_pass = VkRenderPass::new(device.clone(), render_pass_create_info).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
device: device.clone(),
|
||||||
|
render_pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn renderpass(&self) -> Arc<VkRenderPass> {
|
||||||
|
self.render_pass.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_render_pass(device: Arc<vulkano::device::Device>, renderer: &mut VulkanoWindowRenderer) -> Arc<VkRenderPass> {
|
||||||
|
let color_attachment = AttachmentDescription {
|
||||||
|
format: renderer.swapchain_format(),
|
||||||
|
samples: vulkano::image::SampleCount::Sample1,
|
||||||
|
load_op: vulkano::render_pass::AttachmentLoadOp::Clear,
|
||||||
|
store_op: vulkano::render_pass::AttachmentStoreOp::Store,
|
||||||
|
initial_layout: vulkano::image::ImageLayout::ColorAttachmentOptimal,
|
||||||
|
final_layout: vulkano::image::ImageLayout::ColorAttachmentOptimal,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let depth_stencil_attachment = AttachmentDescription {
|
||||||
|
format: Format::D16_UNORM,
|
||||||
|
samples: vulkano::image::SampleCount::Sample1,
|
||||||
|
load_op: vulkano::render_pass::AttachmentLoadOp::Clear,
|
||||||
|
store_op: vulkano::render_pass::AttachmentStoreOp::DontCare,
|
||||||
|
initial_layout: vulkano::image::ImageLayout::DepthStencilAttachmentOptimal,
|
||||||
|
final_layout: vulkano::image::ImageLayout::DepthStencilAttachmentOptimal,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let color_attachment_reference = AttachmentReference {
|
||||||
|
attachment: 0,
|
||||||
|
layout: color_attachment.initial_layout,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let depth_stencil_attachment_reference = AttachmentReference {
|
||||||
|
attachment: 1,
|
||||||
|
layout: depth_stencil_attachment.initial_layout,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let subpass_description = SubpassDescription {
|
||||||
|
color_attachments: vec![Some(color_attachment_reference)],
|
||||||
|
depth_stencil_attachment: Some(depth_stencil_attachment_reference),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkRenderPass::new(device.clone(), RenderPassCreateInfo {
|
||||||
|
attachments: vec![color_attachment, depth_stencil_attachment],
|
||||||
|
subpasses: vec![subpass_description],
|
||||||
|
..Default::default()
|
||||||
|
}).unwrap()
|
||||||
|
}
|
13
modules/khors-window/src/window.rs
Normal file
13
modules/khors-window/src/window.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use winit::event_loop::EventLoopBuilder;
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
handle: winit::window::Window,
|
||||||
|
event_loop: winit::event_loop::EventLoop<()>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let event_loop = EventLoopBuilder::new().build();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
29
vendor/egui-snarl/Cargo.toml
vendored
Normal file
29
vendor/egui-snarl/Cargo.toml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "egui-snarl"
|
||||||
|
version = "0.2.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Node-graphs for egui"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
documentation = "https://docs.rs/egui-snarl"
|
||||||
|
keywords = ["egui", "node", "graph", "ui", "node-graph"]
|
||||||
|
categories = ["gui", "visualization"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
serde = ["dep:serde", "egui/serde", "slab/serde"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
egui = { version = "0.27" }
|
||||||
|
slab = { version = "0.4" }
|
||||||
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
tiny-fn = { version = "0.1.6" }
|
||||||
|
|
||||||
|
egui-probe = { version = "0.2", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
eframe = { version = "0.26", features = ["serde", "persistence"] }
|
||||||
|
egui_extras = { version = "0.26", features = ["all_loaders"] }
|
||||||
|
syn = { version = "2.0", features = ["extra-traits"] }
|
||||||
|
serde_json = { version = "1.0" }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
|
wasm-bindgen-futures = "0.4"
|
696
vendor/egui-snarl/src/lib.rs
vendored
Normal file
696
vendor/egui-snarl/src/lib.rs
vendored
Normal file
|
@ -0,0 +1,696 @@
|
||||||
|
//!
|
||||||
|
//! # egui-snarl
|
||||||
|
//!
|
||||||
|
//! Provides a node-graph container for egui.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![deny(clippy::correctness, clippy::complexity, clippy::perf, clippy::style)]
|
||||||
|
// #![warn(clippy::pedantic)]
|
||||||
|
#![allow(clippy::inline_always)]
|
||||||
|
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
use egui::{ahash::HashSet, Pos2};
|
||||||
|
use slab::Slab;
|
||||||
|
|
||||||
|
impl<T> Default for Snarl<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Snarl::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node identifier.
|
||||||
|
///
|
||||||
|
/// This is newtype wrapper around [`usize`] that implements
|
||||||
|
/// necessary traits, but omits arithmetic operations.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
serde(transparent)
|
||||||
|
)]
|
||||||
|
pub struct NodeId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
struct Node<T> {
|
||||||
|
/// Node generic value.
|
||||||
|
value: T,
|
||||||
|
|
||||||
|
/// Position of the top-left corner of the node.
|
||||||
|
/// This does not include frame margin.
|
||||||
|
pos: egui::Pos2,
|
||||||
|
|
||||||
|
/// Flag indicating that the node is open - not collapsed.
|
||||||
|
open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output pin identifier.
|
||||||
|
/// Cosists of node id and pin index.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct OutPinId {
|
||||||
|
/// Node id.
|
||||||
|
pub node: NodeId,
|
||||||
|
|
||||||
|
/// Output pin index.
|
||||||
|
pub output: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input pin identifier. Cosists of node id and pin index.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct InPinId {
|
||||||
|
/// Node id.
|
||||||
|
pub node: NodeId,
|
||||||
|
|
||||||
|
/// Input pin index.
|
||||||
|
pub input: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection between two nodes.
|
||||||
|
///
|
||||||
|
/// Nodes may support multiple connections to the same input or output.
|
||||||
|
/// But duplicate connections between same input and the same output are not allowed.
|
||||||
|
/// Attempt to insert existing connection will be ignored.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
struct Wire {
|
||||||
|
out_pin: OutPinId,
|
||||||
|
in_pin: InPinId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Wires {
|
||||||
|
wires: HashSet<Wire>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl serde::Serialize for Wires {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
use serde::ser::SerializeSeq;
|
||||||
|
|
||||||
|
let mut seq = serializer.serialize_seq(Some(self.wires.len()))?;
|
||||||
|
for wire in &self.wires {
|
||||||
|
seq.serialize_element(&wire)?;
|
||||||
|
}
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl<'de> serde::Deserialize<'de> for Wires {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct Visitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||||
|
type Value = HashSet<Wire>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a sequence of wires")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: serde::de::SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut wires = HashSet::with_hasher(egui::ahash::RandomState::new());
|
||||||
|
while let Some(wire) = seq.next_element()? {
|
||||||
|
wires.insert(wire);
|
||||||
|
}
|
||||||
|
Ok(wires)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wires = deserializer.deserialize_seq(Visitor)?;
|
||||||
|
Ok(Wires { wires })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wires {
|
||||||
|
fn new() -> Self {
|
||||||
|
Wires {
|
||||||
|
wires: HashSet::with_hasher(egui::ahash::RandomState::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, wire: Wire) -> bool {
|
||||||
|
self.wires.insert(wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, wire: &Wire) -> bool {
|
||||||
|
self.wires.remove(wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_node(&mut self, node: NodeId) -> usize {
|
||||||
|
let count = self.wires.len();
|
||||||
|
self.wires
|
||||||
|
.retain(|wire| wire.out_pin.node != node && wire.in_pin.node != node);
|
||||||
|
count - self.wires.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_inputs(&mut self, pin: InPinId) -> usize {
|
||||||
|
let count = self.wires.len();
|
||||||
|
self.wires.retain(|wire| wire.in_pin != pin);
|
||||||
|
count - self.wires.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_outputs(&mut self, pin: OutPinId) -> usize {
|
||||||
|
let count = self.wires.len();
|
||||||
|
self.wires.retain(|wire| wire.out_pin != pin);
|
||||||
|
count - self.wires.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wired_inputs(&self, out_pin: OutPinId) -> impl Iterator<Item = InPinId> + '_ {
|
||||||
|
self.wires
|
||||||
|
.iter()
|
||||||
|
.filter(move |wire| wire.out_pin == out_pin)
|
||||||
|
.map(|wire| (wire.in_pin))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wired_outputs(&self, in_pin: InPinId) -> impl Iterator<Item = OutPinId> + '_ {
|
||||||
|
self.wires
|
||||||
|
.iter()
|
||||||
|
.filter(move |wire| wire.in_pin == in_pin)
|
||||||
|
.map(|wire| (wire.out_pin))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(&self) -> impl Iterator<Item = Wire> + '_ {
|
||||||
|
self.wires.iter().copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snarl is generic node-graph container.
|
||||||
|
///
|
||||||
|
/// It holds graph state - positioned nodes and wires between their pins.
|
||||||
|
/// It can be rendered using [`Snarl::show`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct Snarl<T> {
|
||||||
|
// #[cfg_attr(feature = "serde", serde(with = "serde_nodes"))]
|
||||||
|
nodes: Slab<Node<T>>,
|
||||||
|
draw_order: Vec<NodeId>,
|
||||||
|
wires: Wires,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Snarl<T> {
|
||||||
|
/// Create a new empty Snarl.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use egui_snarl::Snarl;
|
||||||
|
/// let snarl = Snarl::<()>::new();
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Snarl {
|
||||||
|
nodes: Slab::new(),
|
||||||
|
draw_order: Vec::new(),
|
||||||
|
wires: Wires::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a node to the Snarl.
|
||||||
|
/// Returns the index of the node.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use egui_snarl::Snarl;
|
||||||
|
/// let mut snarl = Snarl::<()>::new();
|
||||||
|
/// snarl.insert_node(egui::pos2(0.0, 0.0), ());
|
||||||
|
/// ```
|
||||||
|
pub fn insert_node(&mut self, pos: egui::Pos2, node: T) -> NodeId {
|
||||||
|
let idx = self.nodes.insert(Node {
|
||||||
|
value: node,
|
||||||
|
pos,
|
||||||
|
open: true,
|
||||||
|
});
|
||||||
|
let id = NodeId(idx);
|
||||||
|
self.draw_order.push(id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a node to the Snarl in collapsed state.
|
||||||
|
/// Returns the index of the node.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use egui_snarl::Snarl;
|
||||||
|
/// let mut snarl = Snarl::<()>::new();
|
||||||
|
/// snarl.insert_node_collapsed(egui::pos2(0.0, 0.0), ());
|
||||||
|
/// ```
|
||||||
|
pub fn insert_node_collapsed(&mut self, pos: egui::Pos2, node: T) -> NodeId {
|
||||||
|
let idx = self.nodes.insert(Node {
|
||||||
|
value: node,
|
||||||
|
pos,
|
||||||
|
open: false,
|
||||||
|
});
|
||||||
|
let id = NodeId(idx);
|
||||||
|
self.draw_order.push(id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens or collapses a node.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the node does not exist.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn open_node(&mut self, node: NodeId, open: bool) {
|
||||||
|
self.nodes[node.0].open = open;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a node from the Snarl.
|
||||||
|
/// Returns the node if it was removed.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the node does not exist.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use egui_snarl::Snarl;
|
||||||
|
/// let mut snarl = Snarl::<()>::new();
|
||||||
|
/// let node = snarl.insert_node(egui::pos2(0.0, 0.0), ());
|
||||||
|
/// snarl.remove_node(node);
|
||||||
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
|
pub fn remove_node(&mut self, idx: NodeId) -> T {
|
||||||
|
let value = self.nodes.remove(idx.0).value;
|
||||||
|
self.wires.drop_node(idx);
|
||||||
|
let order = self.draw_order.iter().position(|&i| i == idx).unwrap();
|
||||||
|
self.draw_order.remove(order);
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects two nodes.
|
||||||
|
/// Returns true if the connection was successful.
|
||||||
|
/// Returns false if the connection already exists.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if either node does not exist.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn connect(&mut self, from: OutPinId, to: InPinId) -> bool {
|
||||||
|
assert!(self.nodes.contains(from.node.0));
|
||||||
|
assert!(self.nodes.contains(to.node.0));
|
||||||
|
|
||||||
|
let wire = Wire {
|
||||||
|
out_pin: from,
|
||||||
|
in_pin: to,
|
||||||
|
};
|
||||||
|
self.wires.insert(wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnects two nodes.
|
||||||
|
/// Returns true if the connection was removed.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if either node does not exist.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn disconnect(&mut self, from: OutPinId, to: InPinId) -> bool {
|
||||||
|
assert!(self.nodes.contains(from.node.0));
|
||||||
|
assert!(self.nodes.contains(to.node.0));
|
||||||
|
|
||||||
|
let wire = Wire {
|
||||||
|
out_pin: from,
|
||||||
|
in_pin: to,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.wires.remove(&wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all connections to the node's pin.
|
||||||
|
///
|
||||||
|
/// Returns number of removed connections.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the node does not exist.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn drop_inputs(&mut self, pin: InPinId) -> usize {
|
||||||
|
assert!(self.nodes.contains(pin.node.0));
|
||||||
|
self.wires.drop_inputs(pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all connections from the node's pin.
|
||||||
|
/// Returns number of removed connections.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the node does not exist.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn drop_outputs(&mut self, pin: OutPinId) -> usize {
|
||||||
|
assert!(self.nodes.contains(pin.node.0));
|
||||||
|
self.wires.drop_outputs(pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to the node.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_node(&self, idx: NodeId) -> Option<&T> {
|
||||||
|
match self.nodes.get(idx.0) {
|
||||||
|
Some(node) => Some(&node.value),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to the node.
|
||||||
|
pub fn get_node_mut(&mut self, idx: NodeId) -> Option<&mut T> {
|
||||||
|
match self.nodes.get_mut(idx.0) {
|
||||||
|
Some(node) => Some(&mut node.value),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over shared references to each node.
|
||||||
|
pub fn nodes(&self) -> NodesIter<'_, T> {
|
||||||
|
NodesIter {
|
||||||
|
nodes: self.nodes.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over mutable references to each node.
|
||||||
|
pub fn nodes_mut(&mut self) -> NodesIterMut<'_, T> {
|
||||||
|
NodesIterMut {
|
||||||
|
nodes: self.nodes.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over shared references to each node and its position.
|
||||||
|
pub fn nodes_pos(&self) -> NodesPosIter<'_, T> {
|
||||||
|
NodesPosIter {
|
||||||
|
nodes: self.nodes.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over mutable references to each node and its position.
|
||||||
|
pub fn nodes_pos_mut(&mut self) -> NodesPosIterMut<'_, T> {
|
||||||
|
NodesPosIterMut {
|
||||||
|
nodes: self.nodes.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over shared references to each node and its identifier.
|
||||||
|
pub fn node_ids(&self) -> NodesIdsIter<'_, T> {
|
||||||
|
NodesIdsIter {
|
||||||
|
nodes: self.nodes.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over mutable references to each node and its identifier.
|
||||||
|
pub fn nodes_ids_mut(&mut self) -> NodesIdsIterMut<'_, T> {
|
||||||
|
NodesIdsIterMut {
|
||||||
|
nodes: self.nodes.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over shared references to each node, its position and its identifier.
|
||||||
|
pub fn nodes_pos_ids(&self) -> NodesPosIdsIter<'_, T> {
|
||||||
|
NodesPosIdsIter {
|
||||||
|
nodes: self.nodes.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over mutable references to each node, its position and its identifier.
|
||||||
|
pub fn nodes_pos_ids_mut(&mut self) -> NodesPosIdsIterMut<'_, T> {
|
||||||
|
NodesPosIdsIterMut {
|
||||||
|
nodes: self.nodes.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns input pin of the node.
|
||||||
|
#[must_use]
|
||||||
|
pub fn in_pin(&self, pin: InPinId) -> InPin {
|
||||||
|
InPin::new(self, pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns output pin of the node.
|
||||||
|
#[must_use]
|
||||||
|
pub fn out_pin(&self, pin: OutPinId) -> OutPin {
|
||||||
|
OutPin::new(self, pin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Index<NodeId> for Snarl<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
|
fn index(&self, idx: NodeId) -> &Self::Output {
|
||||||
|
&self.nodes[idx.0].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IndexMut<NodeId> for Snarl<T> {
|
||||||
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
|
fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output {
|
||||||
|
&mut self.nodes[idx.0].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over shared references to nodes.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesIter<'a, T> {
|
||||||
|
nodes: slab::Iter<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesIter<'a, T> {
|
||||||
|
type Item = &'a T;
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<&'a T> {
|
||||||
|
let (_, node) = self.nodes.next()?;
|
||||||
|
Some(&node.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<&'a T> {
|
||||||
|
let (_, node) = self.nodes.nth(n)?;
|
||||||
|
Some(&node.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over mutable references to nodes.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesIterMut<'a, T> {
|
||||||
|
nodes: slab::IterMut<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesIterMut<'a, T> {
|
||||||
|
type Item = &'a mut T;
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<&'a mut T> {
|
||||||
|
let (_, node) = self.nodes.next()?;
|
||||||
|
Some(&mut node.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<&'a mut T> {
|
||||||
|
let (_, node) = self.nodes.nth(n)?;
|
||||||
|
Some(&mut node.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over shared references to nodes and their positions.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesPosIter<'a, T> {
|
||||||
|
nodes: slab::Iter<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesPosIter<'a, T> {
|
||||||
|
type Item = (Pos2, &'a T);
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(Pos2, &'a T)> {
|
||||||
|
let (_, node) = self.nodes.next()?;
|
||||||
|
Some((node.pos, &node.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<(Pos2, &'a T)> {
|
||||||
|
let (_, node) = self.nodes.nth(n)?;
|
||||||
|
Some((node.pos, &node.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over mutable references to nodes and their positions.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesPosIterMut<'a, T> {
|
||||||
|
nodes: slab::IterMut<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesPosIterMut<'a, T> {
|
||||||
|
type Item = (Pos2, &'a mut T);
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(Pos2, &'a mut T)> {
|
||||||
|
let (_, node) = self.nodes.next()?;
|
||||||
|
Some((node.pos, &mut node.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<(Pos2, &'a mut T)> {
|
||||||
|
let (_, node) = self.nodes.nth(n)?;
|
||||||
|
Some((node.pos, &mut node.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over shared references to nodes and their identifiers.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesIdsIter<'a, T> {
|
||||||
|
nodes: slab::Iter<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesIdsIter<'a, T> {
|
||||||
|
type Item = (NodeId, &'a T);
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(NodeId, &'a T)> {
|
||||||
|
let (idx, node) = self.nodes.next()?;
|
||||||
|
Some((NodeId(idx), &node.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<(NodeId, &'a T)> {
|
||||||
|
let (idx, node) = self.nodes.nth(n)?;
|
||||||
|
Some((NodeId(idx), &node.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over mutable references to nodes and their identifiers.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesIdsIterMut<'a, T> {
|
||||||
|
nodes: slab::IterMut<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesIdsIterMut<'a, T> {
|
||||||
|
type Item = (NodeId, &'a mut T);
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(NodeId, &'a mut T)> {
|
||||||
|
let (idx, node) = self.nodes.next()?;
|
||||||
|
Some((NodeId(idx), &mut node.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<(NodeId, &'a mut T)> {
|
||||||
|
let (idx, node) = self.nodes.nth(n)?;
|
||||||
|
Some((NodeId(idx), &mut node.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over shared references to nodes, their positions and their identifiers.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesPosIdsIter<'a, T> {
|
||||||
|
nodes: slab::Iter<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesPosIdsIter<'a, T> {
|
||||||
|
type Item = (NodeId, Pos2, &'a T);
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(NodeId, Pos2, &'a T)> {
|
||||||
|
let (idx, node) = self.nodes.next()?;
|
||||||
|
Some((NodeId(idx), node.pos, &node.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<(NodeId, Pos2, &'a T)> {
|
||||||
|
let (idx, node) = self.nodes.nth(n)?;
|
||||||
|
Some((NodeId(idx), node.pos, &node.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over mutable references to nodes, their positions and their identifiers.
|
||||||
|
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||||
|
pub struct NodesPosIdsIterMut<'a, T> {
|
||||||
|
nodes: slab::IterMut<'a, Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for NodesPosIdsIterMut<'a, T> {
|
||||||
|
type Item = (NodeId, Pos2, &'a mut T);
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.nodes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(NodeId, Pos2, &'a mut T)> {
|
||||||
|
let (idx, node) = self.nodes.next()?;
|
||||||
|
Some((NodeId(idx), node.pos, &mut node.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<(NodeId, Pos2, &'a mut T)> {
|
||||||
|
let (idx, node) = self.nodes.nth(n)?;
|
||||||
|
Some((NodeId(idx), node.pos, &mut node.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node and its output pin.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct OutPin {
|
||||||
|
/// Output pin identifier.
|
||||||
|
pub id: OutPinId,
|
||||||
|
|
||||||
|
/// List of input pins connected to this output pin.
|
||||||
|
pub remotes: Vec<InPinId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node and its output pin.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InPin {
|
||||||
|
/// Input pin identifier.
|
||||||
|
pub id: InPinId,
|
||||||
|
|
||||||
|
/// List of output pins connected to this input pin.
|
||||||
|
pub remotes: Vec<OutPinId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutPin {
|
||||||
|
fn new<T>(snarl: &Snarl<T>, pin: OutPinId) -> Self {
|
||||||
|
OutPin {
|
||||||
|
id: pin,
|
||||||
|
remotes: snarl.wires.wired_inputs(pin).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InPin {
|
||||||
|
fn new<T>(snarl: &Snarl<T>, pin: InPinId) -> Self {
|
||||||
|
InPin {
|
||||||
|
id: pin,
|
||||||
|
remotes: snarl.wires.wired_outputs(pin).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1089
vendor/egui-snarl/src/ui.rs
vendored
Normal file
1089
vendor/egui-snarl/src/ui.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
259
vendor/egui-snarl/src/ui/background_pattern.rs
vendored
Normal file
259
vendor/egui-snarl/src/ui/background_pattern.rs
vendored
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use egui::{emath::Rot2, vec2, Pos2, Rect, Stroke, Ui, Vec2};
|
||||||
|
|
||||||
|
use super::SnarlStyle;
|
||||||
|
|
||||||
|
/// Viewport is a rectangle in graph space that is visible on screen.
|
||||||
|
pub struct Viewport {
|
||||||
|
/// Screen-space rectangle.
|
||||||
|
pub rect: Rect,
|
||||||
|
|
||||||
|
/// Scale of the viewport.
|
||||||
|
pub scale: f32,
|
||||||
|
|
||||||
|
/// Offset of the viewport.
|
||||||
|
pub offset: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Viewport {
|
||||||
|
/// Converts screen-space position to graph-space position.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn screen_pos_to_graph(&self, pos: Pos2) -> Pos2 {
|
||||||
|
(pos + self.offset - self.rect.center().to_vec2()) / self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts graph-space position to screen-space position.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn graph_pos_to_screen(&self, pos: Pos2) -> Pos2 {
|
||||||
|
pos * self.scale - self.offset + self.rect.center().to_vec2()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts screen-space vector to graph-space vector.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 {
|
||||||
|
size * self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts graph-space vector to screen-space vector.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
|
||||||
|
size / self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts screen-space size to graph-space size.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn graph_size_to_screen(&self, size: f32) -> f32 {
|
||||||
|
size * self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts graph-space size to screen-space size.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn screen_size_to_graph(&self, size: f32) -> f32 {
|
||||||
|
size / self.scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Grid background pattern.
|
||||||
|
///Use `SnarlStyle::background_pattern_stroke` for change stroke options
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
|
||||||
|
pub struct Grid {
|
||||||
|
/// Spacing between grid lines.
|
||||||
|
pub spacing: Vec2,
|
||||||
|
|
||||||
|
/// Angle of the grid.
|
||||||
|
#[cfg_attr(feature = "egui-probe", egui_probe(as egui_probe::angle))]
|
||||||
|
pub angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_GRID_SPACING: Vec2 = vec2(5.0, 5.0);
|
||||||
|
macro_rules! default_grid_spacing {
|
||||||
|
() => {
|
||||||
|
stringify!(vec2(5.0, 5.0))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_GRID_ANGLE: f32 = 1.0;
|
||||||
|
macro_rules! default_grid_angle {
|
||||||
|
() => {
|
||||||
|
stringify!(1.0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Grid {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
spacing: DEFAULT_GRID_SPACING,
|
||||||
|
angle: DEFAULT_GRID_ANGLE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
/// Create new grid with given spacing and angle.
|
||||||
|
pub const fn new(spacing: Vec2, angle: f32) -> Self {
|
||||||
|
Self { spacing, angle }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) {
|
||||||
|
let bg_stroke = style
|
||||||
|
.background_pattern_stroke
|
||||||
|
.unwrap_or_else(|| ui.visuals().widgets.noninteractive.bg_stroke);
|
||||||
|
|
||||||
|
let stroke = Stroke::new(
|
||||||
|
bg_stroke.width * viewport.scale.max(1.0),
|
||||||
|
bg_stroke.color.gamma_multiply(viewport.scale.min(1.0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let spacing =
|
||||||
|
ui.spacing().icon_width * vec2(self.spacing.x.max(1.0), self.spacing.y.max(1.0));
|
||||||
|
|
||||||
|
let rot = Rot2::from_angle(self.angle);
|
||||||
|
let rot_inv = rot.inverse();
|
||||||
|
|
||||||
|
let graph_viewport = Rect::from_min_max(
|
||||||
|
viewport.screen_pos_to_graph(viewport.rect.min),
|
||||||
|
viewport.screen_pos_to_graph(viewport.rect.max),
|
||||||
|
);
|
||||||
|
|
||||||
|
let pattern_bounds = graph_viewport.rotate_bb(rot_inv);
|
||||||
|
|
||||||
|
let min_x = (pattern_bounds.min.x / spacing.x).ceil();
|
||||||
|
let max_x = (pattern_bounds.max.x / spacing.x).floor();
|
||||||
|
|
||||||
|
for x in 0..=(max_x - min_x) as i64 {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let x = (x as f32 + min_x) * spacing.x;
|
||||||
|
|
||||||
|
let top = (rot * vec2(x, pattern_bounds.min.y)).to_pos2();
|
||||||
|
let bottom = (rot * vec2(x, pattern_bounds.max.y)).to_pos2();
|
||||||
|
|
||||||
|
let top = viewport.graph_pos_to_screen(top);
|
||||||
|
let bottom = viewport.graph_pos_to_screen(bottom);
|
||||||
|
|
||||||
|
ui.painter().line_segment([top, bottom], stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_y = (pattern_bounds.min.y / spacing.y).ceil();
|
||||||
|
let max_y = (pattern_bounds.max.y / spacing.y).floor();
|
||||||
|
|
||||||
|
for y in 0..=(max_y - min_y) as i64 {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let y = (y as f32 + min_y) * spacing.y;
|
||||||
|
|
||||||
|
let top = (rot * vec2(pattern_bounds.min.x, y)).to_pos2();
|
||||||
|
let bottom = (rot * vec2(pattern_bounds.max.x, y)).to_pos2();
|
||||||
|
|
||||||
|
let top = viewport.graph_pos_to_screen(top);
|
||||||
|
let bottom = viewport.graph_pos_to_screen(bottom);
|
||||||
|
|
||||||
|
ui.painter().line_segment([top, bottom], stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tiny_fn::tiny_fn! {
|
||||||
|
/// Custom background pattern function with signature
|
||||||
|
/// `Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui)`
|
||||||
|
pub struct CustomBackground = Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const INLINE_SIZE: usize> Default for CustomBackground<'_, INLINE_SIZE> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(|_, _, _| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "egui-probe")]
|
||||||
|
impl<const INLINE_SIZE: usize> egui_probe::EguiProbe for CustomBackground<'_, INLINE_SIZE> {
|
||||||
|
fn probe(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut egui_probe::egui::Ui,
|
||||||
|
_style: &egui_probe::Style,
|
||||||
|
) -> egui_probe::egui::Response {
|
||||||
|
ui.weak("Custom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Background pattern show beneath nodes and wires.
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
|
||||||
|
pub enum BackgroundPattern {
|
||||||
|
/// No pattern.
|
||||||
|
NoPattern,
|
||||||
|
|
||||||
|
/// Linear grid.
|
||||||
|
#[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
|
||||||
|
Grid(Grid),
|
||||||
|
|
||||||
|
/// Custom pattern.
|
||||||
|
/// Contains function with signature
|
||||||
|
/// `Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui)`
|
||||||
|
#[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
|
||||||
|
Custom(#[cfg_attr(feature = "serde", serde(skip))] CustomBackground<'static>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for BackgroundPattern {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(BackgroundPattern::Grid(l), BackgroundPattern::Grid(r)) => *l == *r,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for BackgroundPattern {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
BackgroundPattern::Grid(grid) => f
|
||||||
|
.debug_tuple("BackgroundPattern::Grid")
|
||||||
|
.field(grid)
|
||||||
|
.finish(),
|
||||||
|
BackgroundPattern::Custom(_) => f.write_str("BackgroundPattern::Custom"),
|
||||||
|
BackgroundPattern::NoPattern => f.write_str("BackgroundPattern::NoPattern"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BackgroundPattern {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Grid(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackgroundPattern {
|
||||||
|
/// Create new background pattern with default values.
|
||||||
|
///
|
||||||
|
/// Default patter is `Grid` with spacing - `
|
||||||
|
#[doc = default_grid_spacing!()]
|
||||||
|
/// ` and angle - `
|
||||||
|
#[doc = default_grid_angle!()]
|
||||||
|
/// ` radian.
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self::Grid(Grid::new(DEFAULT_GRID_SPACING, DEFAULT_GRID_ANGLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new grid background pattern with given spacing and angle.
|
||||||
|
pub const fn grid(spacing: Vec2, angle: f32) -> Self {
|
||||||
|
Self::Grid(Grid::new(spacing, angle))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new custom background pattern.
|
||||||
|
pub fn custom<F>(f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&SnarlStyle, &Viewport, &mut Ui) + 'static,
|
||||||
|
{
|
||||||
|
Self::Custom(CustomBackground::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws background pattern.
|
||||||
|
pub(super) fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) {
|
||||||
|
match self {
|
||||||
|
BackgroundPattern::Grid(g) => g.draw(style, viewport, ui),
|
||||||
|
BackgroundPattern::Custom(c) => c.call(style, viewport, ui),
|
||||||
|
BackgroundPattern::NoPattern => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
160
vendor/egui-snarl/src/ui/effect.rs
vendored
Normal file
160
vendor/egui-snarl/src/ui/effect.rs
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use egui::Pos2;
|
||||||
|
|
||||||
|
use crate::{wire_pins, InPinId, Node, OutPinId, Snarl};
|
||||||
|
|
||||||
|
pub enum Effect<T> {
|
||||||
|
/// Adds a new node to the Snarl.
|
||||||
|
InsertNode { pos: Pos2, node: T },
|
||||||
|
|
||||||
|
/// Removes a node from snarl.
|
||||||
|
RemoveNode { node: NodeId },
|
||||||
|
|
||||||
|
/// Opens/closes a node.
|
||||||
|
OpenNode { node: NodeId, open: bool },
|
||||||
|
|
||||||
|
/// Adds connection between two nodes.
|
||||||
|
Connect { from: OutPinId, to: InPinId },
|
||||||
|
|
||||||
|
/// Removes connection between two nodes.
|
||||||
|
Disconnect { from: OutPinId, to: InPinId },
|
||||||
|
|
||||||
|
/// Removes all connections from the output pin.
|
||||||
|
DropOutputs { pin: OutPinId },
|
||||||
|
|
||||||
|
/// Removes all connections to the input pin.
|
||||||
|
DropInputs { pin: InPinId },
|
||||||
|
|
||||||
|
/// Executes a closure with mutable reference to the Snarl.
|
||||||
|
Closure(Box<dyn FnOnce(&mut Snarl<T>)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contained for deferred execution of effects.
|
||||||
|
/// It is populated by [`SnarlViewer`] methods and then applied to the Snarl.
|
||||||
|
pub struct Effects<T> {
|
||||||
|
effects: Vec<Effect<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Effects<T> {
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self {
|
||||||
|
Effects {
|
||||||
|
effects: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Effects<T> {
|
||||||
|
#[inline(always)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Effects {
|
||||||
|
effects: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if there are no effects.
|
||||||
|
/// Returns `false` otherwise.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.effects.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a new node to the Snarl.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn insert_node(&mut self, pos: Pos2, node: T) {
|
||||||
|
self.effects.push(Effect::InsertNode { node, pos });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a node from the Snarl.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn remove_node(&mut self, node: NodeId) {
|
||||||
|
self.effects.push(Effect::RemoveNode { node });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens/closes a node.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn open_node(&mut self, node: NodeId, open: bool) {
|
||||||
|
self.effects.push(Effect::OpenNode { node, open });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects two nodes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn connect(&mut self, from: OutPinId, to: InPinId) {
|
||||||
|
self.effects.push(Effect::Connect { from, to });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnects two nodes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disconnect(&mut self, from: OutPinId, to: InPinId) {
|
||||||
|
self.effects.push(Effect::Disconnect { from, to });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all connections from the output pin.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn drop_inputs(&mut self, pin: InPinId) {
|
||||||
|
self.effects.push(Effect::DropInputs { pin });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all connections to the input pin.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn drop_outputs(&mut self, pin: OutPinId) {
|
||||||
|
self.effects.push(Effect::DropOutputs { pin });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Snarl<T> {
|
||||||
|
pub fn apply_effects(&mut self, effects: Effects<T>) {
|
||||||
|
if effects.effects.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for effect in effects.effects {
|
||||||
|
self.apply_effect(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_effect(&mut self, effect: Effect<T>) {
|
||||||
|
match effect {
|
||||||
|
Effect::InsertNode { node, pos } => {
|
||||||
|
let idx = self.nodes.insert(Node {
|
||||||
|
value: RefCell::new(node),
|
||||||
|
pos,
|
||||||
|
open: true,
|
||||||
|
});
|
||||||
|
self.draw_order.push(idx);
|
||||||
|
}
|
||||||
|
Effect::RemoveNode { node } => {
|
||||||
|
if self.nodes.contains(node) {
|
||||||
|
self.remove_node(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect::OpenNode { node, open } => {
|
||||||
|
if self.nodes.contains(node) {
|
||||||
|
self.nodes[node].open = open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect::Connect { from, to } => {
|
||||||
|
if self.nodes.contains(from.node) && self.nodes.contains(to.node) {
|
||||||
|
self.wires.insert(wire_pins(from, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect::Disconnect { from, to } => {
|
||||||
|
if self.nodes.contains(from.node) && self.nodes.contains(to.node) {
|
||||||
|
self.wires.remove(&wire_pins(from, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect::DropOutputs { pin } => {
|
||||||
|
if self.nodes.contains(pin.node) {
|
||||||
|
self.wires.drop_outputs(pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect::DropInputs { pin } => {
|
||||||
|
if self.nodes.contains(pin.node) {
|
||||||
|
self.wires.drop_inputs(pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect::Closure(f) => f(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
vendor/egui-snarl/src/ui/pin.rs
vendored
Normal file
171
vendor/egui-snarl/src/ui/pin.rs
vendored
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
use egui::{epaint::PathShape, vec2, Color32, Painter, Pos2, Rect, Shape, Stroke, Vec2};
|
||||||
|
|
||||||
|
use crate::{InPinId, OutPinId};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AnyPin {
|
||||||
|
Out(OutPinId),
|
||||||
|
In(InPinId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In the current context, these are the I/O pins of the 'source' node that the newly
|
||||||
|
/// created node's I/O pins will connect to.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AnyPins<'a> {
|
||||||
|
/// Output pins.
|
||||||
|
Out(&'a [OutPinId]),
|
||||||
|
/// Input pins
|
||||||
|
In(&'a [InPinId]),
|
||||||
|
}
|
||||||
|
|
||||||
|
tiny_fn::tiny_fn! {
|
||||||
|
/// Custom pin shape drawing function with signature
|
||||||
|
/// `Fn(painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke)`
|
||||||
|
pub struct CustomPinShape = Fn(painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shape of a pin.
|
||||||
|
pub enum PinShape {
|
||||||
|
/// Circle shape.
|
||||||
|
Circle,
|
||||||
|
|
||||||
|
/// Triangle shape.
|
||||||
|
Triangle,
|
||||||
|
|
||||||
|
/// Square shape.
|
||||||
|
Square,
|
||||||
|
|
||||||
|
/// Custom shape.
|
||||||
|
Custom(CustomPinShape<'static>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a pin returned by `SnarlViewer::show_input` and `SnarlViewer::show_output`.
|
||||||
|
pub struct PinInfo {
|
||||||
|
/// Shape of the pin.
|
||||||
|
pub shape: PinShape,
|
||||||
|
|
||||||
|
/// Size of the pin.
|
||||||
|
pub size: f32,
|
||||||
|
|
||||||
|
/// Fill color of the pin.
|
||||||
|
pub fill: Color32,
|
||||||
|
|
||||||
|
/// Outline stroke of the pin.
|
||||||
|
pub stroke: Stroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PinInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
PinInfo {
|
||||||
|
shape: PinShape::Circle,
|
||||||
|
size: 1.0,
|
||||||
|
fill: Color32::GRAY,
|
||||||
|
stroke: Stroke::new(1.0, Color32::BLACK),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinInfo {
|
||||||
|
/// Sets the shape of the pin.
|
||||||
|
pub fn with_shape(mut self, shape: PinShape) -> Self {
|
||||||
|
self.shape = shape;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the size of the pin.
|
||||||
|
pub fn with_size(mut self, size: f32) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the fill color of the pin.
|
||||||
|
pub fn with_fill(mut self, fill: Color32) -> Self {
|
||||||
|
self.fill = fill;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the outline stroke of the pin.
|
||||||
|
pub fn with_stroke(mut self, stroke: Stroke) -> Self {
|
||||||
|
self.stroke = stroke;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a circle pin.
|
||||||
|
pub fn circle() -> Self {
|
||||||
|
PinInfo {
|
||||||
|
shape: PinShape::Circle,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a triangle pin.
|
||||||
|
pub fn triangle() -> Self {
|
||||||
|
PinInfo {
|
||||||
|
shape: PinShape::Triangle,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a square pin.
|
||||||
|
pub fn square() -> Self {
|
||||||
|
PinInfo {
|
||||||
|
shape: PinShape::Square,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a square pin.
|
||||||
|
pub fn custom<F>(f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&Painter, Rect, Color32, Stroke) + 'static,
|
||||||
|
{
|
||||||
|
PinInfo {
|
||||||
|
shape: PinShape::Custom(CustomPinShape::new(f)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_pin(painter: &Painter, pin: PinInfo, pos: Pos2, base_size: f32) {
|
||||||
|
let size = base_size * pin.size;
|
||||||
|
match pin.shape {
|
||||||
|
PinShape::Circle => {
|
||||||
|
painter.circle(pos, size * 2.0 / std::f32::consts::PI, pin.fill, pin.stroke);
|
||||||
|
}
|
||||||
|
PinShape::Triangle => {
|
||||||
|
const A: Vec2 = vec2(-0.649_519, 0.4875);
|
||||||
|
const B: Vec2 = vec2(0.649_519, 0.4875);
|
||||||
|
const C: Vec2 = vec2(0.0, -0.6375);
|
||||||
|
|
||||||
|
let points = vec![pos + A * size, pos + B * size, pos + C * size];
|
||||||
|
|
||||||
|
painter.add(Shape::Path(PathShape {
|
||||||
|
points,
|
||||||
|
closed: true,
|
||||||
|
fill: pin.fill,
|
||||||
|
stroke: pin.stroke,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
PinShape::Square => {
|
||||||
|
let points = vec![
|
||||||
|
pos + vec2(-0.5, -0.5) * size,
|
||||||
|
pos + vec2(0.5, -0.5) * size,
|
||||||
|
pos + vec2(0.5, 0.5) * size,
|
||||||
|
pos + vec2(-0.5, 0.5) * size,
|
||||||
|
];
|
||||||
|
|
||||||
|
painter.add(Shape::Path(PathShape {
|
||||||
|
points,
|
||||||
|
closed: true,
|
||||||
|
fill: pin.fill,
|
||||||
|
stroke: pin.stroke,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
PinShape::Custom(f) => f.call(
|
||||||
|
painter,
|
||||||
|
Rect::from_center_size(pos, vec2(size, size)),
|
||||||
|
pin.fill,
|
||||||
|
pin.stroke,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
404
vendor/egui-snarl/src/ui/state.rs
vendored
Normal file
404
vendor/egui-snarl/src/ui/state.rs
vendored
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
use egui::{style::Spacing, Align, Context, Id, Pos2, Rect, Vec2};
|
||||||
|
|
||||||
|
use crate::{InPinId, OutPinId, Snarl};
|
||||||
|
|
||||||
|
use super::SnarlStyle;
|
||||||
|
|
||||||
|
/// Node UI state.
|
||||||
|
|
||||||
|
pub struct NodeState {
|
||||||
|
/// Node size for this frame.
|
||||||
|
/// It is updated to fit content.
|
||||||
|
size: Vec2,
|
||||||
|
header_height: f32,
|
||||||
|
body_width: f32,
|
||||||
|
footer_width: f32,
|
||||||
|
|
||||||
|
id: Id,
|
||||||
|
scale: f32,
|
||||||
|
dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
struct NodeData {
|
||||||
|
unscaled_size: Vec2,
|
||||||
|
unscaled_header_height: f32,
|
||||||
|
unscaled_body_width: f32,
|
||||||
|
unsacled_footer_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeState {
|
||||||
|
pub fn load(cx: &Context, id: Id, spacing: &Spacing, scale: f32) -> Self {
|
||||||
|
match cx.data_mut(|d| d.get_temp::<NodeData>(id)) {
|
||||||
|
Some(data) => NodeState {
|
||||||
|
size: data.unscaled_size * scale,
|
||||||
|
header_height: data.unscaled_header_height * scale,
|
||||||
|
body_width: data.unscaled_body_width * scale,
|
||||||
|
footer_width: data.unsacled_footer_width * scale,
|
||||||
|
id,
|
||||||
|
scale,
|
||||||
|
dirty: false,
|
||||||
|
},
|
||||||
|
None => Self::initial(id, spacing, scale),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self, cx: &Context) {
|
||||||
|
cx.data_mut(|d| d.remove::<Self>(self.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&self, cx: &Context) {
|
||||||
|
if self.dirty {
|
||||||
|
cx.data_mut(|d| {
|
||||||
|
d.insert_temp(
|
||||||
|
self.id,
|
||||||
|
NodeData {
|
||||||
|
unscaled_size: self.size / self.scale,
|
||||||
|
unscaled_header_height: self.header_height / self.scale,
|
||||||
|
unscaled_body_width: self.body_width / self.scale,
|
||||||
|
unsacled_footer_width: self.footer_width / self.scale,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds node rect at specific position (excluding node frame margin).
|
||||||
|
pub fn node_rect(&self, pos: Pos2, openness: f32) -> Rect {
|
||||||
|
Rect::from_min_size(
|
||||||
|
pos,
|
||||||
|
egui::vec2(
|
||||||
|
self.size.x,
|
||||||
|
f32::max(self.header_height, self.size.y * openness),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload_offset(&self, openness: f32) -> f32 {
|
||||||
|
(self.size.y) * (1.0 - openness)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_body(&mut self, rect: Rect) -> Rect {
|
||||||
|
let x_range = Align::Center.align_size_within_range(self.body_width, rect.x_range());
|
||||||
|
Rect::from_x_y_ranges(x_range, rect.y_range())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_footer(&mut self, rect: Rect) -> Rect {
|
||||||
|
let x_range = Align::Center.align_size_within_range(self.footer_width, rect.x_range());
|
||||||
|
Rect::from_x_y_ranges(x_range, rect.y_range())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_size(&mut self, size: Vec2) {
|
||||||
|
if self.size != size {
|
||||||
|
self.size = size;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_header_height(&mut self, height: f32) {
|
||||||
|
if self.header_height != height {
|
||||||
|
self.header_height = height;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_body_width(&mut self, width: f32) {
|
||||||
|
if self.body_width != width {
|
||||||
|
self.body_width = width;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_footer_width(&mut self, width: f32) {
|
||||||
|
if self.footer_width != width {
|
||||||
|
self.footer_width = width;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial(id: Id, spacing: &Spacing, scale: f32) -> Self {
|
||||||
|
NodeState {
|
||||||
|
size: spacing.interact_size,
|
||||||
|
header_height: spacing.interact_size.y,
|
||||||
|
body_width: 0.0,
|
||||||
|
footer_width: 0.0,
|
||||||
|
id,
|
||||||
|
dirty: true,
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum NewWires {
|
||||||
|
In(Vec<InPinId>),
|
||||||
|
Out(Vec<OutPinId>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SnarlState {
|
||||||
|
/// Where viewport's center in graph's space.
|
||||||
|
offset: Vec2,
|
||||||
|
|
||||||
|
/// Scale of the viewport.
|
||||||
|
scale: f32,
|
||||||
|
|
||||||
|
target_scale: f32,
|
||||||
|
|
||||||
|
new_wires: Option<NewWires>,
|
||||||
|
|
||||||
|
id: Id,
|
||||||
|
|
||||||
|
/// Flag indicating that the graph state is dirty must be saved.
|
||||||
|
dirty: bool,
|
||||||
|
|
||||||
|
/// Flag indicating that the link menu is open.
|
||||||
|
is_link_menu_open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SnarlStateData {
|
||||||
|
offset: Vec2,
|
||||||
|
scale: f32,
|
||||||
|
target_scale: f32,
|
||||||
|
new_wires: Option<NewWires>,
|
||||||
|
is_link_menu_open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SnarlState {
|
||||||
|
pub fn load<T>(
|
||||||
|
cx: &Context,
|
||||||
|
id: Id,
|
||||||
|
pivot: Pos2,
|
||||||
|
viewport: Rect,
|
||||||
|
snarl: &Snarl<T>,
|
||||||
|
style: &SnarlStyle,
|
||||||
|
) -> Self {
|
||||||
|
let Some(mut data) = cx.data_mut(|d| d.get_temp::<SnarlStateData>(id)) else {
|
||||||
|
return Self::initial(id, viewport, snarl, style);
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_scale = cx.animate_value_with_time(id.with("zoom-scale"), data.target_scale, 0.1);
|
||||||
|
|
||||||
|
let mut dirty = false;
|
||||||
|
if new_scale != data.scale {
|
||||||
|
let a = pivot + data.offset - viewport.center().to_vec2();
|
||||||
|
|
||||||
|
data.offset += a * new_scale / data.scale - a;
|
||||||
|
data.scale = new_scale;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnarlState {
|
||||||
|
offset: data.offset,
|
||||||
|
scale: data.scale,
|
||||||
|
target_scale: data.target_scale,
|
||||||
|
new_wires: data.new_wires,
|
||||||
|
is_link_menu_open: data.is_link_menu_open,
|
||||||
|
id,
|
||||||
|
dirty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial<T>(id: Id, viewport: Rect, snarl: &Snarl<T>, style: &SnarlStyle) -> Self {
|
||||||
|
let mut bb = Rect::NOTHING;
|
||||||
|
|
||||||
|
for (_, node) in snarl.nodes.iter() {
|
||||||
|
bb.extend_with(node.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bb.is_positive() {
|
||||||
|
let scale = 1.0f32.clamp(style.min_scale, style.max_scale);
|
||||||
|
|
||||||
|
return SnarlState {
|
||||||
|
offset: Vec2::ZERO,
|
||||||
|
scale,
|
||||||
|
target_scale: scale,
|
||||||
|
new_wires: None,
|
||||||
|
is_link_menu_open: false,
|
||||||
|
id,
|
||||||
|
dirty: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bb = bb.expand(100.0);
|
||||||
|
|
||||||
|
let bb_size = bb.size();
|
||||||
|
let viewport_size = viewport.size();
|
||||||
|
|
||||||
|
let scale = (viewport_size.x / bb_size.x)
|
||||||
|
.min(1.0)
|
||||||
|
.min(viewport_size.y / bb_size.y)
|
||||||
|
.min(style.max_scale)
|
||||||
|
.max(style.min_scale);
|
||||||
|
|
||||||
|
let offset = bb.center().to_vec2() * scale;
|
||||||
|
|
||||||
|
SnarlState {
|
||||||
|
offset,
|
||||||
|
scale,
|
||||||
|
target_scale: scale,
|
||||||
|
new_wires: None,
|
||||||
|
is_link_menu_open: false,
|
||||||
|
id,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn store(self, cx: &Context) {
|
||||||
|
if self.dirty {
|
||||||
|
cx.data_mut(|d| {
|
||||||
|
d.insert_temp(
|
||||||
|
self.id,
|
||||||
|
SnarlStateData {
|
||||||
|
offset: self.offset,
|
||||||
|
scale: self.scale,
|
||||||
|
target_scale: self.target_scale,
|
||||||
|
new_wires: self.new_wires,
|
||||||
|
is_link_menu_open: self.is_link_menu_open,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn pan(&mut self, delta: Vec2) {
|
||||||
|
self.offset += delta;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn scale(&self) -> f32 {
|
||||||
|
self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn offset(&self) -> Vec2 {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_scale(&mut self, scale: f32) {
|
||||||
|
self.target_scale = scale;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn screen_pos_to_graph(&self, pos: Pos2, viewport: Rect) -> Pos2 {
|
||||||
|
(pos + self.offset - viewport.center().to_vec2()) / self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn graph_pos_to_screen(&self, pos: Pos2, viewport: Rect) -> Pos2 {
|
||||||
|
pos * self.scale - self.offset + viewport.center().to_vec2()
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[inline(always)]
|
||||||
|
// pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 {
|
||||||
|
// size * self.scale
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
|
||||||
|
size / self.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[inline(always)]
|
||||||
|
// pub fn graph_value_to_screen(&self, value: f32) -> f32 {
|
||||||
|
// value * self.scale
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[inline(always)]
|
||||||
|
// pub fn screen_value_to_graph(&self, value: f32) -> f32 {
|
||||||
|
// value / self.scale
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn start_new_wire_in(&mut self, pin: InPinId) {
|
||||||
|
self.new_wires = Some(NewWires::In(vec![pin]));
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_new_wire_out(&mut self, pin: OutPinId) {
|
||||||
|
self.new_wires = Some(NewWires::Out(vec![pin]));
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_new_wires_in(&mut self, pins: &[InPinId]) {
|
||||||
|
self.new_wires = Some(NewWires::In(pins.to_vec()));
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_new_wires_out(&mut self, pins: &[OutPinId]) {
|
||||||
|
self.new_wires = Some(NewWires::Out(pins.to_vec()));
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_new_wire_in(&mut self, pin: InPinId) {
|
||||||
|
if let Some(NewWires::In(pins)) = &mut self.new_wires {
|
||||||
|
if !pins.contains(&pin) {
|
||||||
|
pins.push(pin);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_new_wire_out(&mut self, pin: OutPinId) {
|
||||||
|
if let Some(NewWires::Out(pins)) = &mut self.new_wires {
|
||||||
|
if !pins.contains(&pin) {
|
||||||
|
pins.push(pin);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_new_wire_in(&mut self, pin: InPinId) {
|
||||||
|
if let Some(NewWires::In(pins)) = &mut self.new_wires {
|
||||||
|
if let Some(idx) = pins.iter().position(|p| *p == pin) {
|
||||||
|
pins.swap_remove(idx);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_new_wire_out(&mut self, pin: OutPinId) {
|
||||||
|
if let Some(NewWires::Out(pins)) = &mut self.new_wires {
|
||||||
|
if let Some(idx) = pins.iter().position(|p| *p == pin) {
|
||||||
|
pins.swap_remove(idx);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_new_wires(&self) -> bool {
|
||||||
|
self.new_wires.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_wires(&self) -> Option<&NewWires> {
|
||||||
|
self.new_wires.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_wires(&mut self) -> Option<NewWires> {
|
||||||
|
self.dirty |= self.new_wires.is_some();
|
||||||
|
self.new_wires.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn revert_take_wires(&mut self, wires: NewWires) {
|
||||||
|
self.new_wires = Some(wires);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_link_menu(&mut self) {
|
||||||
|
self.is_link_menu_open = true;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn close_link_menu(&mut self) {
|
||||||
|
self.new_wires = None;
|
||||||
|
self.is_link_menu_open = false;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_link_menu_open(&self) -> bool {
|
||||||
|
self.is_link_menu_open
|
||||||
|
}
|
||||||
|
}
|
186
vendor/egui-snarl/src/ui/viewer.rs
vendored
Normal file
186
vendor/egui-snarl/src/ui/viewer.rs
vendored
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
use egui::{Color32, Pos2, Style, Ui};
|
||||||
|
|
||||||
|
use crate::{InPin, NodeId, OutPin, Snarl};
|
||||||
|
|
||||||
|
use super::pin::{AnyPins, PinInfo};
|
||||||
|
|
||||||
|
/// SnarlViewer is a trait for viewing a Snarl.
|
||||||
|
///
|
||||||
|
/// It can extract necessary data from the nodes and controls their
|
||||||
|
/// response to certain events.
|
||||||
|
pub trait SnarlViewer<T> {
|
||||||
|
/// Returns title of the node.
|
||||||
|
fn title(&mut self, node: &T) -> String;
|
||||||
|
|
||||||
|
/// Checks if node has something to show in body - between input and output pins.
|
||||||
|
fn has_body(&mut self, node: &T) -> bool {
|
||||||
|
let _ = node;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if node has something to show in footer - below pins and body.
|
||||||
|
fn has_footer(&mut self, node: &T) -> bool {
|
||||||
|
let _ = node;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if node has something to show in on-hover popup.
|
||||||
|
fn has_on_hover_popup(&mut self, node: &T) -> bool {
|
||||||
|
let _ = node;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the node's header.
|
||||||
|
fn show_header(
|
||||||
|
&mut self,
|
||||||
|
node: NodeId,
|
||||||
|
inputs: &[InPin],
|
||||||
|
outputs: &[OutPin],
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) {
|
||||||
|
let _ = (inputs, outputs, scale);
|
||||||
|
ui.label(self.title(&snarl[node]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of output pins of the node.
|
||||||
|
fn outputs(&mut self, node: &T) -> usize;
|
||||||
|
|
||||||
|
/// Returns number of input pins of the node.
|
||||||
|
fn inputs(&mut self, node: &T) -> usize;
|
||||||
|
|
||||||
|
/// Renders the node's input pin.
|
||||||
|
fn show_input(&mut self, pin: &InPin, ui: &mut Ui, scale: f32, snarl: &mut Snarl<T>)
|
||||||
|
-> PinInfo;
|
||||||
|
|
||||||
|
/// Renders the node's output pin.
|
||||||
|
fn show_output(
|
||||||
|
&mut self,
|
||||||
|
pin: &OutPin,
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) -> PinInfo;
|
||||||
|
|
||||||
|
/// Renders the node's body.
|
||||||
|
fn show_body(
|
||||||
|
&mut self,
|
||||||
|
node: NodeId,
|
||||||
|
inputs: &[InPin],
|
||||||
|
outputs: &[OutPin],
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) {
|
||||||
|
let _ = (node, inputs, outputs, ui, scale, snarl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the node's footer.
|
||||||
|
fn show_footer(
|
||||||
|
&mut self,
|
||||||
|
node: NodeId,
|
||||||
|
inputs: &[InPin],
|
||||||
|
outputs: &[OutPin],
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) {
|
||||||
|
let _ = (node, inputs, outputs, ui, scale, snarl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the node's on-hover popup.
|
||||||
|
fn show_on_hover_popup(
|
||||||
|
&mut self,
|
||||||
|
node: NodeId,
|
||||||
|
inputs: &[InPin],
|
||||||
|
outputs: &[OutPin],
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) {
|
||||||
|
let _ = (node, inputs, outputs, ui, scale, snarl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns color of the node's input pin.
|
||||||
|
/// Called when pin in not visible.
|
||||||
|
fn input_color(&mut self, pin: &InPin, style: &Style, snarl: &mut Snarl<T>) -> Color32;
|
||||||
|
|
||||||
|
/// Returns color of the node's output pin.
|
||||||
|
/// Called when pin in not visible.
|
||||||
|
fn output_color(&mut self, pin: &OutPin, style: &Style, snarl: &mut Snarl<T>) -> Color32;
|
||||||
|
|
||||||
|
/// Show context menu for the snarl.
|
||||||
|
///
|
||||||
|
/// This can be used to implement menu for adding new nodes.
|
||||||
|
fn graph_menu(&mut self, pos: Pos2, ui: &mut Ui, scale: f32, snarl: &mut Snarl<T>) {
|
||||||
|
let _ = (pos, ui, scale, snarl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show context menu for the snarl. This menu is opened when releasing a pin to empty
|
||||||
|
/// space. It can be used to implement menu for adding new node, and directly
|
||||||
|
/// connecting it to the released wire.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
fn graph_menu_for_dropped_wire(
|
||||||
|
&mut self,
|
||||||
|
pos: Pos2,
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
src_pins: AnyPins,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) {
|
||||||
|
let _ = (pos, scale, src_pins, snarl);
|
||||||
|
|
||||||
|
// Default implementation simply doesn't utilize wire drop.
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show context menu for the snarl.
|
||||||
|
///
|
||||||
|
/// This can be used to implement menu for adding new nodes.
|
||||||
|
fn node_menu(
|
||||||
|
&mut self,
|
||||||
|
node: NodeId,
|
||||||
|
inputs: &[InPin],
|
||||||
|
outputs: &[OutPin],
|
||||||
|
ui: &mut Ui,
|
||||||
|
scale: f32,
|
||||||
|
snarl: &mut Snarl<T>,
|
||||||
|
) {
|
||||||
|
let _ = (node, inputs, outputs, ui, scale, snarl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks the viewer to connect two pins.
|
||||||
|
///
|
||||||
|
/// This is usually happens when user drags a wire from one node's output pin to another node's input pin or vice versa.
|
||||||
|
/// By default this method connects the pins and returns `Ok(())`.
|
||||||
|
#[inline]
|
||||||
|
fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<T>) {
|
||||||
|
snarl.connect(from.id, to.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks the viewer to disconnect two pins.
|
||||||
|
#[inline]
|
||||||
|
fn disconnect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<T>) {
|
||||||
|
snarl.disconnect(from.id, to.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks the viewer to disconnect all wires from the output pin.
|
||||||
|
///
|
||||||
|
/// This is usually happens when right-clicking on an output pin.
|
||||||
|
/// By default this method disconnects the pins and returns `Ok(())`.
|
||||||
|
#[inline]
|
||||||
|
fn drop_outputs(&mut self, pin: &OutPin, snarl: &mut Snarl<T>) {
|
||||||
|
snarl.drop_outputs(pin.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks the viewer to disconnect all wires from the input pin.
|
||||||
|
///
|
||||||
|
/// This is usually happens when right-clicking on an input pin.
|
||||||
|
/// By default this method disconnects the pins and returns `Ok(())`.
|
||||||
|
#[inline]
|
||||||
|
fn drop_inputs(&mut self, pin: &InPin, snarl: &mut Snarl<T>) {
|
||||||
|
snarl.drop_inputs(pin.id);
|
||||||
|
}
|
||||||
|
}
|
323
vendor/egui-snarl/src/ui/wire.rs
vendored
Normal file
323
vendor/egui-snarl/src/ui/wire.rs
vendored
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
use egui::{epaint::PathShape, pos2, Color32, Pos2, Rect, Shape, Stroke, Ui};
|
||||||
|
|
||||||
|
/// Layer where wires are rendered.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
|
||||||
|
pub enum WireLayer {
|
||||||
|
/// Wires are rendered behind nodes.
|
||||||
|
/// This is default.
|
||||||
|
BehindNodes,
|
||||||
|
|
||||||
|
/// Wires are rendered above nodes.
|
||||||
|
AboveNodes,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns 6th degree bezier curve for the wire
|
||||||
|
fn wire_bezier(
|
||||||
|
mut frame_size: f32,
|
||||||
|
upscale: bool,
|
||||||
|
downscale: bool,
|
||||||
|
from: Pos2,
|
||||||
|
to: Pos2,
|
||||||
|
) -> [Pos2; 6] {
|
||||||
|
if upscale {
|
||||||
|
frame_size = frame_size.max((from - to).length() / 4.0);
|
||||||
|
}
|
||||||
|
if downscale {
|
||||||
|
frame_size = frame_size.min((from - to).length() / 4.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let from_norm_x = frame_size;
|
||||||
|
let from_2 = pos2(from.x + from_norm_x, from.y);
|
||||||
|
let to_norm_x = -from_norm_x;
|
||||||
|
let to_2 = pos2(to.x + to_norm_x, to.y);
|
||||||
|
|
||||||
|
let between = (from_2 - to_2).length();
|
||||||
|
|
||||||
|
if from_2.x <= to_2.x && between >= frame_size * 2.0 {
|
||||||
|
let middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size;
|
||||||
|
let middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size;
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if from_2.x <= to_2.x {
|
||||||
|
let t =
|
||||||
|
(between - (to_2.y - from_2.y).abs()) / (frame_size * 2.0 - (to_2.y - from_2.y).abs());
|
||||||
|
|
||||||
|
let mut middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size;
|
||||||
|
let mut middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size;
|
||||||
|
|
||||||
|
if from_2.y >= to_2.y + frame_size {
|
||||||
|
let u = (from_2.y - to_2.y - frame_size) / frame_size;
|
||||||
|
|
||||||
|
let t0_middle_1 = pos2(from_2.x + (1.0 - u) * frame_size, from_2.y - frame_size * u);
|
||||||
|
let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size);
|
||||||
|
|
||||||
|
middle_1 = t0_middle_1.lerp(middle_1, t);
|
||||||
|
middle_2 = t0_middle_2.lerp(middle_2, t);
|
||||||
|
} else if from_2.y >= to_2.y {
|
||||||
|
let u = (from_2.y - to_2.y) / frame_size;
|
||||||
|
|
||||||
|
let t0_middle_1 = pos2(from_2.x + u * frame_size, from_2.y + frame_size * (1.0 - u));
|
||||||
|
let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size);
|
||||||
|
|
||||||
|
middle_1 = t0_middle_1.lerp(middle_1, t);
|
||||||
|
middle_2 = t0_middle_2.lerp(middle_2, t);
|
||||||
|
} else if to_2.y >= from_2.y + frame_size {
|
||||||
|
let u = (to_2.y - from_2.y - frame_size) / frame_size;
|
||||||
|
|
||||||
|
let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size);
|
||||||
|
let t0_middle_2 = pos2(to_2.x - (1.0 - u) * frame_size, to_2.y - frame_size * u);
|
||||||
|
|
||||||
|
middle_1 = t0_middle_1.lerp(middle_1, t);
|
||||||
|
middle_2 = t0_middle_2.lerp(middle_2, t);
|
||||||
|
} else if to_2.y >= from_2.y {
|
||||||
|
let u = (to_2.y - from_2.y) / frame_size;
|
||||||
|
|
||||||
|
let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size);
|
||||||
|
let t0_middle_2 = pos2(to_2.x - u * frame_size, to_2.y + frame_size * (1.0 - u));
|
||||||
|
|
||||||
|
middle_1 = t0_middle_1.lerp(middle_1, t);
|
||||||
|
middle_2 = t0_middle_2.lerp(middle_2, t);
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if from_2.y >= to_2.y + frame_size * 2.0 {
|
||||||
|
let middle_1 = pos2(from_2.x, from_2.y - frame_size);
|
||||||
|
let middle_2 = pos2(to_2.x, to_2.y + frame_size);
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if from_2.y >= to_2.y + frame_size {
|
||||||
|
let t = (from_2.y - to_2.y - frame_size) / frame_size;
|
||||||
|
|
||||||
|
let middle_1 = pos2(from_2.x + (1.0 - t) * frame_size, from_2.y - frame_size * t);
|
||||||
|
let middle_2 = pos2(to_2.x, to_2.y + frame_size);
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if from_2.y >= to_2.y {
|
||||||
|
let t = (from_2.y - to_2.y) / frame_size;
|
||||||
|
|
||||||
|
let middle_1 = pos2(from_2.x + t * frame_size, from_2.y + frame_size * (1.0 - t));
|
||||||
|
let middle_2 = pos2(to_2.x, to_2.y + frame_size);
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if to_2.y >= from_2.y + frame_size * 2.0 {
|
||||||
|
let middle_1 = pos2(from_2.x, from_2.y + frame_size);
|
||||||
|
let middle_2 = pos2(to_2.x, to_2.y - frame_size);
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if to_2.y >= from_2.y + frame_size {
|
||||||
|
let t = (to_2.y - from_2.y - frame_size) / frame_size;
|
||||||
|
|
||||||
|
let middle_1 = pos2(from_2.x, from_2.y + frame_size);
|
||||||
|
let middle_2 = pos2(to_2.x - (1.0 - t) * frame_size, to_2.y - frame_size * t);
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else if to_2.y >= from_2.y {
|
||||||
|
let t = (to_2.y - from_2.y) / frame_size;
|
||||||
|
|
||||||
|
let middle_1 = pos2(from_2.x, from_2.y + frame_size);
|
||||||
|
let middle_2 = pos2(to_2.x - t * frame_size, to_2.y + frame_size * (1.0 - t));
|
||||||
|
|
||||||
|
[from, from_2, middle_1, middle_2, to_2, to]
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn draw_wire(
|
||||||
|
ui: &mut Ui,
|
||||||
|
shapes: &mut Vec<Shape>,
|
||||||
|
frame_size: f32,
|
||||||
|
upscale: bool,
|
||||||
|
downscale: bool,
|
||||||
|
from: Pos2,
|
||||||
|
to: Pos2,
|
||||||
|
stroke: Stroke,
|
||||||
|
) {
|
||||||
|
let points = wire_bezier(frame_size, upscale, downscale, from, to);
|
||||||
|
|
||||||
|
let bb = Rect::from_points(&points);
|
||||||
|
if ui.is_rect_visible(bb) {
|
||||||
|
draw_bezier(shapes, &points, stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hit_wire(
|
||||||
|
pos: Pos2,
|
||||||
|
frame_size: f32,
|
||||||
|
upscale: bool,
|
||||||
|
downscale: bool,
|
||||||
|
from: Pos2,
|
||||||
|
to: Pos2,
|
||||||
|
threshold: f32,
|
||||||
|
) -> bool {
|
||||||
|
let points = wire_bezier(frame_size, upscale, downscale, from, to);
|
||||||
|
hit_bezier(pos, &points, threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bezier_reference_size(points: &[Pos2; 6]) -> f32 {
|
||||||
|
let [p0, p1, p2, p3, p4, p5] = *points;
|
||||||
|
|
||||||
|
(p1 - p0).length()
|
||||||
|
+ (p2 - p1).length()
|
||||||
|
+ (p3 - p2).length()
|
||||||
|
+ (p4 - p3).length()
|
||||||
|
+ (p5 - p4).length()
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_BEZIER_SAMPLES: usize = 100;
|
||||||
|
|
||||||
|
fn bezier_samples_number(points: &[Pos2; 6], threshold: f32) -> usize {
|
||||||
|
let reference_size = bezier_reference_size(points);
|
||||||
|
|
||||||
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
((reference_size / threshold).ceil().max(0.0) as usize).min(MAX_BEZIER_SAMPLES)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bezier(shapes: &mut Vec<Shape>, points: &[Pos2; 6], mut stroke: Stroke) {
|
||||||
|
if stroke.width < 1.0 {
|
||||||
|
stroke.color = stroke.color.gamma_multiply(stroke.width);
|
||||||
|
stroke.width = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let samples = bezier_samples_number(points, stroke.width);
|
||||||
|
|
||||||
|
let mut path = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..samples {
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
let t = i as f32 / (samples - 1) as f32;
|
||||||
|
path.push(sample_bezier(points, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
let shape = Shape::Path(PathShape {
|
||||||
|
points: path,
|
||||||
|
closed: false,
|
||||||
|
fill: Color32::TRANSPARENT,
|
||||||
|
stroke,
|
||||||
|
});
|
||||||
|
|
||||||
|
shapes.push(shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
|
fn sample_bezier(points: &[Pos2; 6], t: f32) -> Pos2 {
|
||||||
|
let [p0, p1, p2, p3, p4, p5] = *points;
|
||||||
|
|
||||||
|
let p0_0 = p0;
|
||||||
|
let p1_0 = p1;
|
||||||
|
let p2_0 = p2;
|
||||||
|
let p3_0 = p3;
|
||||||
|
let p4_0 = p4;
|
||||||
|
let p5_0 = p5;
|
||||||
|
|
||||||
|
let p0_1 = p0_0.lerp(p1_0, t);
|
||||||
|
let p1_1 = p1_0.lerp(p2_0, t);
|
||||||
|
let p2_1 = p2_0.lerp(p3_0, t);
|
||||||
|
let p3_1 = p3_0.lerp(p4_0, t);
|
||||||
|
let p4_1 = p4_0.lerp(p5_0, t);
|
||||||
|
|
||||||
|
let p0_2 = p0_1.lerp(p1_1, t);
|
||||||
|
let p1_2 = p1_1.lerp(p2_1, t);
|
||||||
|
let p2_2 = p2_1.lerp(p3_1, t);
|
||||||
|
let p3_2 = p3_1.lerp(p4_1, t);
|
||||||
|
|
||||||
|
let p0_3 = p0_2.lerp(p1_2, t);
|
||||||
|
let p1_3 = p1_2.lerp(p2_2, t);
|
||||||
|
let p2_3 = p2_2.lerp(p3_2, t);
|
||||||
|
|
||||||
|
let p0_4 = p0_3.lerp(p1_3, t);
|
||||||
|
let p1_4 = p1_3.lerp(p2_3, t);
|
||||||
|
|
||||||
|
let p0_5 = p0_4.lerp(p1_4, t);
|
||||||
|
|
||||||
|
p0_5
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_bezier(points: &[Pos2; 6], t: f32) -> [[Pos2; 6]; 2] {
|
||||||
|
let [p0, p1, p2, p3, p4, p5] = *points;
|
||||||
|
|
||||||
|
let p0_0 = p0;
|
||||||
|
let p1_0 = p1;
|
||||||
|
let p2_0 = p2;
|
||||||
|
let p3_0 = p3;
|
||||||
|
let p4_0 = p4;
|
||||||
|
let p5_0 = p5;
|
||||||
|
|
||||||
|
let p0_1 = p0_0.lerp(p1_0, t);
|
||||||
|
let p1_1 = p1_0.lerp(p2_0, t);
|
||||||
|
let p2_1 = p2_0.lerp(p3_0, t);
|
||||||
|
let p3_1 = p3_0.lerp(p4_0, t);
|
||||||
|
let p4_1 = p4_0.lerp(p5_0, t);
|
||||||
|
|
||||||
|
let p0_2 = p0_1.lerp(p1_1, t);
|
||||||
|
let p1_2 = p1_1.lerp(p2_1, t);
|
||||||
|
let p2_2 = p2_1.lerp(p3_1, t);
|
||||||
|
let p3_2 = p3_1.lerp(p4_1, t);
|
||||||
|
|
||||||
|
let p0_3 = p0_2.lerp(p1_2, t);
|
||||||
|
let p1_3 = p1_2.lerp(p2_2, t);
|
||||||
|
let p2_3 = p2_2.lerp(p3_2, t);
|
||||||
|
|
||||||
|
let p0_4 = p0_3.lerp(p1_3, t);
|
||||||
|
let p1_4 = p1_3.lerp(p2_3, t);
|
||||||
|
|
||||||
|
let p0_5 = p0_4.lerp(p1_4, t);
|
||||||
|
|
||||||
|
[
|
||||||
|
[p0_0, p0_1, p0_2, p0_3, p0_4, p0_5],
|
||||||
|
[p0_5, p1_4, p2_3, p3_2, p4_1, p5_0],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hit_bezier(pos: Pos2, points: &[Pos2; 6], threshold: f32) -> bool {
|
||||||
|
let aabb = Rect::from_points(points);
|
||||||
|
|
||||||
|
if pos.x + threshold < aabb.left() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if pos.x - threshold > aabb.right() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if pos.y + threshold < aabb.top() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if pos.y - threshold > aabb.bottom() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let samples = bezier_samples_number(points, threshold);
|
||||||
|
if samples > 16 {
|
||||||
|
let [points1, points2] = split_bezier(points, 0.5);
|
||||||
|
|
||||||
|
return hit_bezier(pos, &points1, threshold) || hit_bezier(pos, &points2, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..samples {
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
let t = i as f32 / (samples - 1) as f32;
|
||||||
|
let p = sample_bezier(points, t);
|
||||||
|
if (p - pos).length() < threshold {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mix_colors(a: Color32, b: Color32) -> Color32 {
|
||||||
|
let [or, og, ob, oa] = a.to_array();
|
||||||
|
let [ir, ig, ib, ia] = b.to_array();
|
||||||
|
|
||||||
|
Color32::from_rgba_premultiplied(
|
||||||
|
or / 2 + ir / 2,
|
||||||
|
og / 2 + ig / 2,
|
||||||
|
ob / 2 + ib / 2,
|
||||||
|
oa / 2 + ia / 2,
|
||||||
|
)
|
||||||
|
}
|
192
vendor/egui-snarl/src/ui/zoom.rs
vendored
Normal file
192
vendor/egui-snarl/src/ui/zoom.rs
vendored
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
use egui::{
|
||||||
|
epaint::Shadow,
|
||||||
|
style::{Interaction, ScrollStyle, Spacing, WidgetVisuals, Widgets},
|
||||||
|
FontId, Frame, Margin, Rounding, Stroke, Style, Vec2, Visuals,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Zoom {
|
||||||
|
fn zoomed(mut self, zoom: f32) -> Self
|
||||||
|
where
|
||||||
|
Self: Copy,
|
||||||
|
{
|
||||||
|
self.zoom(zoom);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zoom(&mut self, zoom: f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for f32 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
*self *= zoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Vec2 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
*self *= zoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Rounding {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.nw.zoom(zoom);
|
||||||
|
self.ne.zoom(zoom);
|
||||||
|
self.se.zoom(zoom);
|
||||||
|
self.sw.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Margin {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.left.zoom(zoom);
|
||||||
|
self.right.zoom(zoom);
|
||||||
|
self.top.zoom(zoom);
|
||||||
|
self.bottom.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Shadow {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.spread = zoom;
|
||||||
|
// self.extrusion.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Stroke {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.width *= zoom;
|
||||||
|
if self.width < 1.0 {
|
||||||
|
self.color.gamma_multiply(self.width);
|
||||||
|
self.width = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for WidgetVisuals {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.bg_stroke.zoom(zoom);
|
||||||
|
self.rounding.zoom(zoom);
|
||||||
|
self.fg_stroke.zoom(zoom);
|
||||||
|
self.expansion.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Interaction {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.resize_grab_radius_corner.zoom(zoom);
|
||||||
|
self.resize_grab_radius_side.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Widgets {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.noninteractive.zoom(zoom);
|
||||||
|
self.inactive.zoom(zoom);
|
||||||
|
self.hovered.zoom(zoom);
|
||||||
|
self.active.zoom(zoom);
|
||||||
|
self.open.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Visuals {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.clip_rect_margin.zoom(zoom);
|
||||||
|
self.menu_rounding.zoom(zoom);
|
||||||
|
self.popup_shadow.zoom(zoom);
|
||||||
|
self.resize_corner_size.zoom(zoom);
|
||||||
|
self.selection.stroke.zoom(zoom);
|
||||||
|
self.text_cursor.zoom(zoom);
|
||||||
|
self.widgets.zoom(zoom);
|
||||||
|
self.window_rounding.zoom(zoom);
|
||||||
|
self.window_shadow.zoom(zoom);
|
||||||
|
self.window_stroke.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for ScrollStyle {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.bar_inner_margin.zoom(zoom);
|
||||||
|
self.bar_outer_margin.zoom(zoom);
|
||||||
|
self.bar_width.zoom(zoom);
|
||||||
|
self.floating_allocated_width.zoom(zoom);
|
||||||
|
self.floating_width.zoom(zoom);
|
||||||
|
self.handle_min_length.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Spacing {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.button_padding.zoom(zoom);
|
||||||
|
self.combo_height.zoom(zoom);
|
||||||
|
self.combo_width.zoom(zoom);
|
||||||
|
self.icon_spacing.zoom(zoom);
|
||||||
|
self.icon_width.zoom(zoom);
|
||||||
|
self.icon_width_inner.zoom(zoom);
|
||||||
|
self.indent.zoom(zoom);
|
||||||
|
self.interact_size.zoom(zoom);
|
||||||
|
self.item_spacing.zoom(zoom);
|
||||||
|
self.menu_margin.zoom(zoom);
|
||||||
|
self.scroll.zoom(zoom);
|
||||||
|
self.slider_width.zoom(zoom);
|
||||||
|
self.text_edit_width.zoom(zoom);
|
||||||
|
self.tooltip_width.zoom(zoom);
|
||||||
|
self.window_margin.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for FontId {
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.size.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Style {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
if let Some(font_id) = &mut self.override_font_id {
|
||||||
|
font_id.zoom(zoom);
|
||||||
|
}
|
||||||
|
for font_id in self.text_styles.values_mut() {
|
||||||
|
font_id.zoom(zoom);
|
||||||
|
}
|
||||||
|
self.interaction.zoom(zoom);
|
||||||
|
self.spacing.zoom(zoom);
|
||||||
|
self.visuals.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Zoom for Option<T>
|
||||||
|
where
|
||||||
|
T: Zoom,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
if let Some(value) = self {
|
||||||
|
value.zoom(zoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom for Frame {
|
||||||
|
#[inline(always)]
|
||||||
|
fn zoom(&mut self, zoom: f32) {
|
||||||
|
self.inner_margin.zoom(zoom);
|
||||||
|
self.outer_margin.zoom(zoom);
|
||||||
|
self.rounding.zoom(zoom);
|
||||||
|
self.shadow.zoom(zoom);
|
||||||
|
self.stroke.zoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue