Skip to content

Commit 292b423

Browse files
committed
added new GUI/editor system
1 parent f52189e commit 292b423

File tree

20 files changed

+4468
-15
lines changed

20 files changed

+4468
-15
lines changed

.DS_Store

0 Bytes
Binary file not shown.

Cargo.lock

Lines changed: 794 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ members = [
1515
"crates/scheng-input-webcam",
1616
"crates/scheng-input-video",
1717
"crates/scrubbable_controls",
18+
"crates/scheng-bridge",
1819
"examples/minimal",
1920
"examples/pure_single_pass",
2021
"examples/render_target_only",
@@ -26,7 +27,7 @@ members = [
2627
"examples/graph_mixer_builtin",
2728
"examples/graph_matrix_mix4",
2829
"examples/graph_matrix_mix4_webcam",
29-
"examples/graph_webcam_luma_key",
30+
"examples/graph_webcam_luma_key",
3031
"examples/syphon_builtin_legacy",
3132
"examples/syphon_minimal",
3233
"examples/syphon_patchbay",

_config.yml

Lines changed: 0 additions & 8 deletions
This file was deleted.

crates.zip

140 KB
Binary file not shown.

crates/.DS_Store

0 Bytes
Binary file not shown.

crates/scheng-bridge/Cargo.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "scheng-bridge"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "WebSocket bridge: exposes the scheng engine to a visual frontend editor"
6+
7+
[[bin]]
8+
name = "scheng-bridge"
9+
path = "src/main.rs"
10+
11+
[dependencies]
12+
# Scheng engine crates (same path convention as every other crate in this workspace)
13+
scheng-core = { path = "../scheng-core" }
14+
scheng-graph = { path = "../scheng-graph" }
15+
scheng-runtime = { path = "../scheng-runtime" }
16+
scheng-runtime-glow = { path = "../scheng-runtime-glow" }
17+
glow = "0.13"
18+
19+
# Window + GL context (same version set used across all examples)
20+
winit = "0.28"
21+
glutin = "0.30"
22+
glutin-winit = "0.3"
23+
raw-window-handle = "0.5"
24+
25+
# WebSocket server
26+
tokio = { version = "1", features = ["full"] }
27+
tokio-tungstenite = "0.21"
28+
futures-util = "0.3"
29+
30+
# Serialization
31+
serde = { version = "1", features = ["derive"] }
32+
serde_json = "1"
33+
34+
# Logging / errors
35+
tracing = "0.1"
36+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
37+
anyhow = "1"

crates/scheng-bridge/src/main.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
mod protocol;
2+
mod state;
3+
mod ws;
4+
5+
use glow::HasContext;
6+
use raw_window_handle::HasRawWindowHandle;
7+
use scheng_graph::{Graph, NodeKind};
8+
use scheng_runtime_glow::{
9+
execute_plan_to_sink, EngineError, ExecOutput, FrameCtx, NodeProps, OutputSink,
10+
RuntimeState, ShaderSource, FULLSCREEN_VERT,
11+
};
12+
use std::num::NonZeroU32;
13+
use std::sync::{Arc, Mutex};
14+
use std::time::Instant;
15+
use winit::event::{Event, WindowEvent};
16+
use winit::event_loop::{ControlFlow, EventLoop};
17+
use winit::window::WindowBuilder;
18+
use glutin::display::GetGlDisplay;
19+
use glutin::prelude::*;
20+
use ws::{RenderBundle, SharedBundle};
21+
22+
struct PresentSink { w: i32, h: i32 }
23+
impl OutputSink for PresentSink {
24+
fn consume(&mut self, gl: &glow::Context, out: &ExecOutput) {
25+
unsafe {
26+
gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(out.fbo));
27+
gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
28+
gl.blit_framebuffer(0, 0, out.width, out.height, 0, 0, self.w, self.h,
29+
glow::COLOR_BUFFER_BIT, glow::LINEAR);
30+
gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);
31+
gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
32+
}
33+
}
34+
}
35+
36+
fn startup_bundle() -> Result<RenderBundle, EngineError> {
37+
// Exactly graph_minimal — proven working
38+
let mut graph = Graph::new();
39+
let src = graph.add_node(NodeKind::ShaderSource);
40+
let pass = graph.add_node(NodeKind::ShaderPass);
41+
let out = graph.add_node(NodeKind::PixelsOut);
42+
graph.connect_named(src, "out", pass, "in")?;
43+
graph.connect_named(pass, "out", out, "in")?;
44+
let plan = graph.compile()?;
45+
let mut props = NodeProps::default();
46+
props.shader_sources.insert(src, ShaderSource {
47+
vert: FULLSCREEN_VERT.to_string(),
48+
frag: r#"#version 330 core
49+
in vec2 v_uv;
50+
out vec4 fragColor;
51+
uniform float u_time;
52+
void main() {
53+
vec3 col = 0.5 + 0.5 * cos(u_time + v_uv.xyx + vec3(0.0, 2.1, 4.2));
54+
fragColor = vec4(col, 1.0);
55+
}
56+
"#.to_string(),
57+
origin: Some("startup".into()),
58+
});
59+
Ok(RenderBundle { graph, plan, props })
60+
}
61+
62+
fn main() {
63+
if let Err(e) = run() {
64+
eprintln!("[scheng-bridge] fatal: {e}");
65+
std::process::exit(1);
66+
}
67+
}
68+
69+
fn run() -> Result<(), EngineError> {
70+
// Start with the proven startup bundle. Compile from browser replaces it.
71+
let bundle: SharedBundle = Arc::new(Mutex::new(Some(startup_bundle()?)));
72+
73+
{
74+
let ws_state = Arc::new(Mutex::new(state::BridgeState::new()));
75+
let b = bundle.clone();
76+
std::thread::spawn(move || {
77+
let rt = tokio::runtime::Builder::new_current_thread()
78+
.enable_all().build().unwrap();
79+
let addr: std::net::SocketAddr = std::env::var("SCHENG_BRIDGE_ADDR")
80+
.unwrap_or_else(|_| "127.0.0.1:7777".into())
81+
.parse().unwrap();
82+
eprintln!("[bridge] ws on {addr}");
83+
rt.block_on(ws::run_ws_server(addr, ws_state, b));
84+
});
85+
}
86+
87+
let event_loop = EventLoop::new();
88+
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
89+
.with_window_builder(Some(WindowBuilder::new()
90+
.with_title("scheng-bridge")
91+
.with_inner_size(winit::dpi::LogicalSize::new(960.0_f64, 540.0_f64))))
92+
.build(&event_loop,
93+
glutin::config::ConfigTemplateBuilder::new()
94+
.with_alpha_size(8).with_depth_size(0).with_stencil_size(0).with_transparency(false),
95+
|configs| configs.reduce(|a, b| if b.num_samples() > a.num_samples() { b } else { a }).unwrap())
96+
.map_err(|e| EngineError::GlCreate(format!("{e}")))?;
97+
98+
let window = window.ok_or_else(|| EngineError::GlCreate("no window".into()))?;
99+
let gl_display = gl_config.display();
100+
let raw_handle = window.raw_window_handle();
101+
102+
let not_current = unsafe {
103+
let a = glutin::context::ContextAttributesBuilder::new()
104+
.with_profile(glutin::context::GlProfile::Core).build(Some(raw_handle));
105+
let b = glutin::context::ContextAttributesBuilder::new()
106+
.with_profile(glutin::context::GlProfile::Core).build(None);
107+
gl_display.create_context(&gl_config, &a)
108+
.or_else(|_| gl_display.create_context(&gl_config, &b))
109+
.map_err(|e| EngineError::GlCreate(format!("{e}")))?
110+
};
111+
112+
let (w0, h0) = { let s = window.inner_size(); (s.width.max(1), s.height.max(1)) };
113+
let gl_surface = unsafe {
114+
gl_display.create_window_surface(&gl_config,
115+
&glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
116+
.build(raw_handle, NonZeroU32::new(w0).unwrap(), NonZeroU32::new(h0).unwrap()))
117+
.map_err(|e| EngineError::GlCreate(format!("{e}")))?
118+
};
119+
let gl_context = not_current.make_current(&gl_surface)
120+
.map_err(|e| EngineError::GlCreate(format!("{e}")))?;
121+
let gl = unsafe {
122+
glow::Context::from_loader_function(|s| {
123+
gl_display.get_proc_address(std::ffi::CString::new(s).unwrap().as_c_str()) as *const _
124+
})
125+
};
126+
127+
let start = Instant::now();
128+
let mut rt_state = unsafe { RuntimeState::new(&gl)? };
129+
let mut frame_num: u64 = 0;
130+
131+
event_loop.run(move |event, _, control_flow| {
132+
*control_flow = ControlFlow::Poll;
133+
match event {
134+
Event::WindowEvent { event, .. } => match event {
135+
WindowEvent::CloseRequested => { unsafe { rt_state.destroy(&gl) }; *control_flow = ControlFlow::Exit; }
136+
WindowEvent::Resized(s) => { gl_surface.resize(&gl_context,
137+
NonZeroU32::new(s.width.max(1)).unwrap(), NonZeroU32::new(s.height.max(1)).unwrap()); }
138+
_ => {}
139+
},
140+
Event::MainEventsCleared => window.request_redraw(),
141+
Event::RedrawRequested(_) => {
142+
let (w, h) = { let s = window.inner_size(); (s.width.max(1) as i32, s.height.max(1) as i32) };
143+
frame_num += 1;
144+
let frame = FrameCtx { time: start.elapsed().as_secs_f32(), width: w, height: h, frame: frame_num };
145+
146+
let b = bundle.lock().unwrap();
147+
if let Some(ref b) = *b {
148+
let mut sink = PresentSink { w, h };
149+
if let Err(e) = unsafe {
150+
execute_plan_to_sink(&gl, &b.graph, &b.plan, &mut rt_state, &b.props, frame, &mut sink)
151+
} {
152+
eprintln!("[frame {frame_num}] ERROR: {e:?}");
153+
}
154+
} else {
155+
unsafe {
156+
gl.bind_framebuffer(glow::FRAMEBUFFER, None);
157+
gl.viewport(0, 0, w, h);
158+
gl.clear_color(0.08, 0.04, 0.14, 1.0);
159+
gl.clear(glow::COLOR_BUFFER_BIT);
160+
}
161+
}
162+
drop(b);
163+
gl_surface.swap_buffers(&gl_context).unwrap();
164+
}
165+
_ => {}
166+
}
167+
});
168+
}

0 commit comments

Comments
 (0)