Initial attempt to implement Render Graph

This commit is contained in:
Tony Klink 2024-04-06 22:31:24 -06:00
parent 173cfe2acf
commit 3732fc5e3d
Signed by: klink
GPG key ID: 85175567C4D19231
33 changed files with 8851 additions and 938 deletions

2159
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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};

View 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()
}
}

View file

@ -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()
}
}

View file

@ -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" }

View file

@ -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",
}
}

File diff suppressed because it is too large Load diff

View 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",
}
}

View 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),
}

View 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;

View 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>>()
}
}

View 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,
})
}
}

View 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(&current_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(&current_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(),
))
})
}
}

View file

@ -0,0 +1 @@
pub mod model;

View file

@ -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 {

View 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()
})
}

View file

@ -1,2 +1,4 @@
// pub mod pipeline; pub mod pipeline;
pub mod framebuffer;
pub mod vertex; pub mod vertex;
pub mod renderpass;

View file

@ -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(),

View 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()
}

View 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
View 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
View 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

File diff suppressed because it is too large Load diff

View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}