Skip to content

Commit 83627b3

Browse files
authored
Create render.rs
1 parent 7b37375 commit 83627b3

1 file changed

Lines changed: 316 additions & 0 deletions

File tree

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
use calloop::LoopHandle;
2+
use smithay::{
3+
backend::{
4+
allocator::gbm::GbmDevice,
5+
drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmNode},
6+
libinput::{LibinputInputBackend, LibinputSessionInterface},
7+
renderer::{
8+
damage::OutputDamageTracker,
9+
element::surface::WaylandSurfaceRenderElement,
10+
gles::{GlesRenderer, GlesTarget},
11+
Bind,
12+
},
13+
session::{libseat::LibSeatSession, Session},
14+
udev::{primary_gpu, UdevBackend, UdevEvent},
15+
winit::{self, WinitGraphicsBackend},
16+
},
17+
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
18+
reexports::{
19+
calloop::timer::{TimeoutAction, Timer},
20+
drm::control::{connector, Device as DrmControlDevice, ModeTypeFlags},
21+
},
22+
utils::{DeviceFd, Point, Size, Transform},
23+
};
24+
use std::{collections::HashMap, os::unix::io::FromRawFd, time::Duration};
25+
use tracing::{error, info, warn};
26+
27+
use crate::state::BlueState;
28+
29+
pub struct UdevData {
30+
pub session: LibSeatSession,
31+
pub primary_gpu: DrmNode,
32+
pub devices: HashMap<DrmNode, GpuDevice>,
33+
}
34+
35+
pub struct GpuDevice {
36+
pub drm: DrmDevice,
37+
pub gbm: GbmDevice<DrmDeviceFd>,
38+
}
39+
40+
pub struct WinitData {
41+
pub backend: WinitGraphicsBackend<GlesRenderer>,
42+
pub output: Output,
43+
pub damage_tracker: OutputDamageTracker,
44+
}
45+
46+
// ── DRM/KMS udev backend ───────────────────────────────────────────────────
47+
48+
pub fn init_udev(
49+
state: &mut BlueState,
50+
session: LibSeatSession,
51+
loop_handle: &LoopHandle<'static, BlueState>,
52+
) {
53+
let seat_name = session.seat();
54+
info!("Initializing udev backend on seat: {}", seat_name);
55+
56+
let primary_gpu_path = match primary_gpu(&seat_name) {
57+
Ok(Some(p)) => p,
58+
Ok(None) => { error!("No primary GPU found"); return; }
59+
Err(e) => { error!("GPU detection error: {}", e); return; }
60+
};
61+
62+
let primary_gpu = match DrmNode::from_path(&primary_gpu_path) {
63+
Ok(n) => n,
64+
Err(e) => { error!("Failed to get DRM node: {}", e); return; }
65+
};
66+
67+
info!("Primary GPU: {:?}", primary_gpu);
68+
69+
state.backend_data = crate::state::BackendData::Udev(Box::new(UdevData {
70+
session: session.clone(),
71+
primary_gpu,
72+
devices: HashMap::new(),
73+
}));
74+
75+
let udev_backend = match UdevBackend::new(&seat_name) {
76+
Ok(b) => b,
77+
Err(e) => { error!("Failed to create udev backend: {}", e); return; }
78+
};
79+
80+
for (_, path) in udev_backend.device_list() {
81+
if let Ok(node) = DrmNode::from_path(&path) {
82+
add_gpu_device(state, node, &path, loop_handle);
83+
}
84+
}
85+
86+
let lh = loop_handle.clone();
87+
loop_handle
88+
.insert_source(udev_backend, move |event, _, state| match event {
89+
UdevEvent::Added { path, .. } => {
90+
if let Ok(node) = DrmNode::from_path(&path) {
91+
add_gpu_device(state, node, &path, &lh);
92+
}
93+
}
94+
_ => {}
95+
})
96+
.expect("Failed to insert udev source");
97+
98+
// libinput
99+
let mut libinput_ctx =
100+
input::Libinput::new_with_udev(LibinputSessionInterface::from(session));
101+
libinput_ctx.udev_assign_seat(&seat_name).unwrap();
102+
loop_handle
103+
.insert_source(LibinputInputBackend::new(libinput_ctx), |event, _, state| {
104+
crate::input::handle_input(state, event);
105+
})
106+
.expect("Failed to insert libinput source");
107+
108+
loop_handle
109+
.insert_source(Timer::from_duration(Duration::from_millis(16)), |_, _, state| {
110+
let outputs = state.outputs.clone();
111+
for out in outputs { render_output(state, &out); }
112+
TimeoutAction::ToDuration(Duration::from_millis(16))
113+
})
114+
.expect("Failed to insert render timer");
115+
}
116+
117+
fn add_gpu_device(
118+
state: &mut BlueState,
119+
node: DrmNode,
120+
path: &std::path::Path,
121+
loop_handle: &LoopHandle<'static, BlueState>,
122+
) {
123+
let session = match &state.backend_data {
124+
crate::state::BackendData::Udev(d) => d.session.clone(),
125+
_ => return,
126+
};
127+
128+
let owned_fd = match session.open(
129+
path,
130+
rustix::fs::OFlags::RDWR | rustix::fs::OFlags::CLOEXEC | rustix::fs::OFlags::NONBLOCK,
131+
) {
132+
Ok(f) => f,
133+
Err(e) => { error!("Failed to open DRM device: {}", e); return; }
134+
};
135+
136+
let raw_fd = std::os::unix::io::IntoRawFd::into_raw_fd(owned_fd);
137+
let drm_fd = unsafe { DrmDeviceFd::new(DeviceFd::from_raw_fd(raw_fd)) };
138+
139+
let (drm, notifier) = match DrmDevice::new(drm_fd.clone(), true) {
140+
Ok(d) => d,
141+
Err(e) => { error!("Failed to create DRM device: {}", e); return; }
142+
};
143+
let gbm = match GbmDevice::new(drm_fd) {
144+
Ok(g) => g,
145+
Err(e) => { error!("Failed to create GBM device: {}", e); return; }
146+
};
147+
148+
loop_handle
149+
.insert_source(notifier, move |event, _, _state| {
150+
if let DrmEvent::VBlank(_) = event {}
151+
})
152+
.expect("Failed to insert DRM source");
153+
154+
init_drm_outputs(state, &drm);
155+
156+
if let crate::state::BackendData::Udev(d) = &mut state.backend_data {
157+
d.devices.insert(node, GpuDevice { drm, gbm });
158+
}
159+
}
160+
161+
fn init_drm_outputs(state: &mut BlueState, drm: &DrmDevice) {
162+
let res: smithay::reexports::drm::control::ResourceHandles =
163+
match drm.resource_handles() {
164+
Ok(r) => r,
165+
Err(e) => { warn!("Failed to get DRM resources: {}", e); return; }
166+
};
167+
168+
for connector in res.connectors() {
169+
let conn_info: smithay::reexports::drm::control::connector::Info =
170+
match drm.get_connector(*connector, false) {
171+
Ok(c) => c,
172+
Err(_) => continue,
173+
};
174+
175+
if conn_info.state() != connector::State::Connected { continue; }
176+
177+
let mode: Option<&smithay::reexports::drm::control::Mode> = conn_info
178+
.modes()
179+
.iter()
180+
.max_by_key(|m| {
181+
let preferred = m.mode_type().contains(ModeTypeFlags::PREFERRED) as u64;
182+
let area = m.size().0 as u64 * m.size().1 as u64;
183+
(preferred << 32) | (area * m.vrefresh() as u64)
184+
});
185+
186+
let mode = match mode { Some(m) => m, None => continue };
187+
let (w, h) = mode.size();
188+
info!("Connector {:?}: {}x{}@{}Hz", connector, w, h, mode.vrefresh());
189+
190+
let output = Output::new(
191+
format!("{:?}", conn_info.interface()),
192+
PhysicalProperties {
193+
size: conn_info
194+
.size()
195+
.map(|(pw, ph)| Size::from((pw as i32, ph as i32)))
196+
.unwrap_or_default(),
197+
subpixel: Subpixel::Unknown,
198+
make: "Blue".to_string(),
199+
model: "Compositor".to_string(),
200+
serial_number: None,
201+
},
202+
);
203+
204+
let smithay_mode = OutputMode {
205+
size: Size::from((w as i32, h as i32)),
206+
refresh: mode.vrefresh() as i32 * 1000,
207+
};
208+
output.change_current_state(
209+
Some(smithay_mode),
210+
Some(Transform::Normal),
211+
Some(Scale::Integer(1)),
212+
Some(Point::from((0, 0))),
213+
);
214+
output.set_preferred(smithay_mode);
215+
state.space.map_output(&output, Point::from((0, 0)));
216+
state.outputs.push(output);
217+
break; // one output for now
218+
}
219+
}
220+
221+
// ── Winit backend ──────────────────────────────────────────────────────────
222+
223+
pub fn init_winit(
224+
state: &mut BlueState,
225+
backend: WinitGraphicsBackend<GlesRenderer>,
226+
events: winit::WinitEventLoop,
227+
loop_handle: &LoopHandle<'static, BlueState>,
228+
) {
229+
let size = backend.window_size();
230+
info!("Winit window: {}x{}", size.w, size.h);
231+
232+
let output = Output::new(
233+
"winit".to_string(),
234+
PhysicalProperties {
235+
size: Size::from((0, 0)),
236+
subpixel: Subpixel::Unknown,
237+
make: "Blue".to_string(),
238+
model: "Winit".to_string(),
239+
serial_number: None,
240+
},
241+
);
242+
243+
let mode = OutputMode {
244+
size: Size::from((size.w as i32, size.h as i32)),
245+
refresh: 60_000,
246+
};
247+
output.change_current_state(
248+
Some(mode),
249+
Some(Transform::Normal),
250+
Some(Scale::Integer(1)),
251+
Some(Point::from((0, 0))),
252+
);
253+
output.set_preferred(mode);
254+
state.space.map_output(&output, Point::from((0, 0)));
255+
256+
let damage_tracker = OutputDamageTracker::from_output(&output);
257+
state.backend_data = crate::state::BackendData::Winit(Box::new(WinitData {
258+
backend,
259+
output: output.clone(),
260+
damage_tracker,
261+
}));
262+
state.outputs.push(output);
263+
264+
loop_handle
265+
.insert_source(events, |event, _, state| {
266+
use winit::WinitEvent;
267+
match event {
268+
WinitEvent::Resized { size, .. } => {
269+
if let crate::state::BackendData::Winit(d) = &mut state.backend_data {
270+
let m = OutputMode {
271+
size: Size::from((size.w as i32, size.h as i32)),
272+
refresh: 60_000,
273+
};
274+
d.output.change_current_state(Some(m), None, None, None);
275+
d.damage_tracker = OutputDamageTracker::from_output(&d.output);
276+
}
277+
}
278+
WinitEvent::Input(event) => crate::input::handle_input(state, event),
279+
WinitEvent::CloseRequested => state.should_exit = true,
280+
WinitEvent::Redraw => {
281+
let output = match &state.backend_data {
282+
crate::state::BackendData::Winit(d) => d.output.clone(),
283+
_ => return,
284+
};
285+
render_output(state, &output);
286+
}
287+
_ => {}
288+
}
289+
})
290+
.expect("Failed to insert winit source");
291+
}
292+
293+
// ── Common render path ─────────────────────────────────────────────────────
294+
295+
pub fn render_output(state: &mut BlueState, output: &Output) {
296+
if let crate::state::BackendData::Winit(ref mut d) = state.backend_data {
297+
let renderer = d.backend.renderer();
298+
let mut frame_target = d.backend.bind().expect("Failed to bind winit target");
299+
300+
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = state
301+
.space
302+
.render_elements_for_output(renderer, output, 1.0)
303+
.unwrap_or_default();
304+
305+
let _ = d.damage_tracker.render_output(
306+
renderer,
307+
&mut frame_target,
308+
0,
309+
&elements,
310+
[0.08, 0.10, 0.15, 1.0],
311+
);
312+
313+
d.backend.submit(None).ok();
314+
d.backend.window().request_redraw();
315+
}
316+
}

0 commit comments

Comments
 (0)