Skip to content

Commit

Permalink
feat: add modeling-workspace with dragable triangle demo
Browse files Browse the repository at this point in the history
This workspace is for now mainly for testing purposes while developing
the viewport mechansim, but will later be expanded for 3D modeling.
  • Loading branch information
maximmaxim345 committed Sep 9, 2024
1 parent e815b34 commit a90437c
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 6 deletions.
24 changes: 22 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/cadara",
"crates/module",
"crates/workspace",
"crates/modeling-workspace",
"crates/modeling-module",
]
resolver = "2"
8 changes: 8 additions & 0 deletions crates/cadara/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ license = "AGPL-3.0-only"

[dependencies]
iced = "0.12.1"
viewport = { path = "../viewport" }
computegraph = { path = "../computegraph" }
workspace = { path = "../workspace" }
modeling-workspace = { path = "../modeling-workspace" }
modeling-module = { path = "../modeling-module" }
module = { path = "../module" }
project = { path = "../project" }
utils = { path = "../utils" }
31 changes: 27 additions & 4 deletions crates/cadara/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,47 @@
#![allow(clippy::cognitive_complexity)]

use iced::Sandbox;
use modeling_module::ModelingModule;
use project::data::transaction::TransactionArgs;
use utils::Transaction;
use workspace::Workspace;

struct App {}
struct App {
viewport: viewport::Viewport,
}

impl iced::Sandbox for App {
type Message = ();

fn new() -> Self {
Self {}
let project = project::Project::new("project".to_string()).create_session();
let doc = project.create_document();
let doc = project.open_document(doc).unwrap();
let data = doc.create_data::<ModelingModule>();
let mut data = doc.open_data_by_uuid::<ModelingModule>(data).unwrap();

data.apply(TransactionArgs::Persistent(()))
.expect("apply transaction");
let mut viewport = viewport::Viewport::new(project);
let workspace = modeling_workspace::ModelingWorkspace::default();
// TODO: this should dynamically select the first fitting plugin
let plugin = workspace.viewport_plugins()[0].clone();
viewport.pipeline.add_dynamic_plugin(plugin).unwrap();
Self { viewport }
}

fn title(&self) -> String {
"Hello".to_string()
"CADara".to_string()
}

fn update(&mut self, _message: Self::Message) {}

fn view(&self) -> iced::Element<'_, Self::Message> {
iced::widget::button("Hello").into()
let viewport_shader = iced::widget::shader(&self.viewport)
.width(iced::Length::Fill)
.height(iced::Length::Fill);

iced::widget::column!(iced::widget::text("Viewport:"), viewport_shader).into()
}
}

Expand Down
14 changes: 14 additions & 0 deletions crates/modeling-workspace/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "modeling-workspace"
version = "0.1.0"
edition = "2021"
license = "AGPL-3.0-only"
publish = false

[dependencies]
workspace = { path = "../workspace" }
viewport = { path = "../viewport" }
computegraph = { path = "../computegraph" }
project = { path = "../project" }
iced = { version = "0.12.1", features = ["advanced"] }
bytemuck = "1.17.0"
30 changes: 30 additions & 0 deletions crates/modeling-workspace/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![warn(clippy::nursery)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::cognitive_complexity)]

mod viewport;

use ::viewport::DynamicViewportPlugin;

#[derive(Debug, Default)]
pub struct ModelingWorkspace {}

impl workspace::Workspace for ModelingWorkspace {
fn tools(&self) -> Vec<workspace::Toolgroup> {
use workspace::{Action, Tool, Toolgroup};
vec![Toolgroup {
name: "Some Group".to_string(),
tools: vec![Tool {
name: "Some Tool".to_string(),
action: Action(),
}],
}]
}

fn viewport_plugins(&self) -> Vec<DynamicViewportPlugin> {
vec![
DynamicViewportPlugin::new(viewport::ModelingViewportPlugin::default().into()).unwrap(),
]
}
}
47 changes: 47 additions & 0 deletions crates/modeling-workspace/src/viewport.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use computegraph::{node, ComputeGraph};
use viewport::{RenderNodePorts, SceneGraphBuilder, UpdateNodePorts};

mod rendering;
mod scene_nodes;
mod state;

#[derive(Clone, Debug)]
pub struct ModelingViewportPluginOutput {}

#[derive(Clone, Default, Debug)]
pub struct ModelingViewportPlugin {}

#[node(ModelingViewportPlugin -> (scene, output))]
fn run(
&self,
_project: &project::ProjectSession,
) -> (viewport::SceneGraph, ModelingViewportPluginOutput) {
let mut graph = ComputeGraph::new();
let render_node = graph
.add_node(scene_nodes::RenderNode {}, "render".to_string())
.unwrap();
let update_node = graph
.add_node(scene_nodes::UpdateStateNode {}, "update".to_string())
.unwrap();
let init_node = graph
.add_node(scene_nodes::InitStateNode {}, "init".to_string())
.unwrap();

(
SceneGraphBuilder {
graph,
initial_state: init_node.output(),
render_node: RenderNodePorts {
state_in: render_node.input_state(),
primitive_out: render_node.output(),
},
update_node: UpdateNodePorts {
event_in: update_node.input_event(),
state_in: update_node.input_state(),
state_out: update_node.output(),
},
}
.into(),
ModelingViewportPluginOutput {},
)
}
180 changes: 180 additions & 0 deletions crates/modeling-workspace/src/viewport/rendering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#![allow(clippy::cast_precision_loss)]

use iced::widget::shader::{self, wgpu};

use super::state::{Uniforms, ViewportState};

#[derive(Debug)]
pub struct RenderPrimitive {
pub(crate) state: ViewportState,
}

impl shader::Primitive for RenderPrimitive {
fn prepare(
&self,
format: wgpu::TextureFormat,
device: &wgpu::Device,
queue: &wgpu::Queue,
bounds: iced::Rectangle,
target_size: iced::Size<u32>,
scale_factor: f32,
storage: &mut shader::Storage,
) {
if !storage.has::<RenderPipeline>() {
storage.store(RenderPipeline::new(device, queue, format, target_size));
}
let pipeline = storage.get_mut::<RenderPipeline>().unwrap();
pipeline.update(device, queue, bounds, target_size, scale_factor, self);
}

fn render(
&self,
storage: &shader::Storage,
target: &wgpu::TextureView,
_target_size: iced::Size<u32>,
viewport: iced::Rectangle<u32>,
encoder: &mut wgpu::CommandEncoder,
) {
let pipeline = storage.get::<RenderPipeline>().unwrap();

pipeline.render(encoder, target, viewport);
}
}

#[derive(Debug)]
struct RenderPipeline {
pipeline: wgpu::RenderPipeline,
camera_bind_group: wgpu::BindGroup,
uniforms: wgpu::Buffer,
}

impl RenderPipeline {
fn new(
device: &wgpu::Device,
_queue: &wgpu::Queue,
format: wgpu::TextureFormat,
_target_size: iced::Size<u32>,
) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
"shader.wgsl"
))),
});

let camera_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("camera_bind_group_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: std::mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});

let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &camera_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniforms.as_entire_binding(),
}],
});

let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&camera_bind_group_layout],
push_constant_ranges: &[],
});

let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});

Self {
pipeline,
camera_bind_group,
uniforms,
}
}

fn update(
&self,
_device: &wgpu::Device,
queue: &wgpu::Queue,
bounds: iced::Rectangle,
_target_size: iced::Size<u32>,
_scale_factor: f32,
primitive: &RenderPrimitive,
) {
let p = primitive.state.camera_offset;
let x = ((p.x - bounds.x) / bounds.width).mul_add(2.0, -1.0);
let y = ((p.y - bounds.y) / bounds.height).mul_add(-2.0, 1.0);
let uniforms = Uniforms { x, y };
queue.write_buffer(&self.uniforms, 0, bytemuck::cast_slice(&[uniforms]));
}

fn render(
&self,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
viewport: iced::Rectangle<u32>,
) {
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});

render_pass.set_pipeline(&self.pipeline);
render_pass.set_viewport(
viewport.x as f32,
viewport.y as f32,
viewport.width as f32,
viewport.height as f32,
0.0,
1.0,
);
render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
render_pass.draw(0..3, 0..1);
}
}
Loading

0 comments on commit a90437c

Please sign in to comment.