1use crate::{Batch, Color, Conf};
6
7#[derive(Clone, Copy, Debug, PartialEq)]
10pub enum Event {
11 Key(u32, bool),
12 MouseMove(f32, f32),
13 MouseDelta(f32, f32),
14 MouseButton(u32, bool),
15 Wheel(f32),
16 Resize(u32, u32),
17 Quit,
18}
19
20#[cfg(target_os = "linux")]
21pub use linux::NativeWindow;
22
23#[cfg(not(target_os = "linux"))]
24pub struct NativeWindow;
25#[cfg(not(target_os = "linux"))]
26impl NativeWindow {
27 pub fn new(_: &Conf) -> Result<Self, String> {
28 Err("The original native renderer currently supports Linux/X11; this platform backend is not implemented".into())
29 }
30 pub fn size(&self) -> (u32, u32) {
31 (0, 0)
32 }
33 pub fn poll(&mut self) -> Vec<Event> {
34 vec![Event::Quit]
35 }
36 pub fn present(&mut self, _: &[[f32; 8]], _: &[Batch], _: Color) {}
37 pub(crate) fn present_scene(
38 &mut self,
39 _: &[[f32; 8]],
40 _: &[Batch],
41 _: Color,
42 _: Option<&crate::retained::Frame>,
43 ) {
44 }
45 pub fn last_mesh_upload_bytes(&self) -> u64 {
46 0
47 }
48 pub fn enable_gpu_timing(&mut self) -> bool {
49 false
50 }
51 pub fn drain_gpu_results(&mut self, _: impl FnMut(u64, f64)) {}
52 pub fn finish_gpu_timing(&mut self) {}
53 pub fn render_counts(&self) -> (u64, u64) {
54 (0, 0)
55 }
56 pub fn pixels(&self) -> Vec<u8> {
57 Vec::new()
58 }
59 pub fn set_cursor(&mut self, _: bool, _: bool) {}
60 pub fn set_swap_interval(&self, _: i32) {}
61 pub fn driver_info(&self) -> String {
62 "unsupported".into()
63 }
64}
65
66#[cfg(target_os = "linux")]
67mod linux {
68 use super::*;
69 use std::collections::{BTreeMap, BTreeSet};
70 use std::ffi::{CStr, CString, c_char, c_int, c_long, c_short, c_uint, c_ulong, c_void};
71 use std::ptr;
72
73 const KEY_PRESS: c_int = 2;
74 const KEY_RELEASE: c_int = 3;
75 const BUTTON_PRESS: c_int = 4;
76 const BUTTON_RELEASE: c_int = 5;
77 const MOTION_NOTIFY: c_int = 6;
78 const FOCUS_IN: c_int = 9;
79 const FOCUS_OUT: c_int = 10;
80 const DESTROY_NOTIFY: c_int = 17;
81 const CONFIGURE_NOTIFY: c_int = 22;
82 const CLIENT_MESSAGE: c_int = 33;
83 const GL_ARRAY_BUFFER: c_uint = 0x8892;
84 const GL_STREAM_DRAW: c_uint = 0x88e0;
85 const GL_FLOAT: c_uint = 0x1406;
86 const GL_TRIANGLES: c_uint = 0x0004;
87 const GL_DEPTH_TEST: c_uint = 0x0b71;
88 const GL_BLEND: c_uint = 0x0be2;
89 const GL_CULL_FACE: c_uint = 0x0b44;
90 const GL_SCISSOR_TEST: c_uint = 0x0c11;
91 const GL_LEQUAL: c_uint = 0x0203;
92 const GL_SRC_ALPHA: c_uint = 0x0302;
93 const GL_ONE_MINUS_SRC_ALPHA: c_uint = 0x0303;
94 const GL_COLOR_BUFFER_BIT: c_uint = 0x4000;
95 const GL_DEPTH_BUFFER_BIT: c_uint = 0x0100;
96 const GL_VERTEX_SHADER: c_uint = 0x8b31;
97 const GL_FRAGMENT_SHADER: c_uint = 0x8b30;
98 const GL_COMPILE_STATUS: c_uint = 0x8b81;
99 const GL_LINK_STATUS: c_uint = 0x8b82;
100 const GL_INFO_LOG_LENGTH: c_uint = 0x8b84;
101 const MAX_READBACK_PIXELS: usize = 16_777_216;
102
103 type Display = c_void;
105 type Visual = c_void;
106 type GlxContext = *mut c_void;
107 #[repr(C)]
108 struct XVisualInfo {
109 visual: *mut Visual,
110 visualid: c_ulong,
111 screen: c_int,
112 depth: c_int,
113 class: c_int,
114 red_mask: c_ulong,
115 green_mask: c_ulong,
116 blue_mask: c_ulong,
117 colormap_size: c_int,
118 bits_per_rgb: c_int,
119 }
120 #[repr(C)]
121 #[derive(Default)]
122 struct XSetWindowAttributes {
123 background_pixmap: c_ulong,
124 background_pixel: c_ulong,
125 border_pixmap: c_ulong,
126 border_pixel: c_ulong,
127 bit_gravity: c_int,
128 win_gravity: c_int,
129 backing_store: c_int,
130 backing_planes: c_ulong,
131 backing_pixel: c_ulong,
132 save_under: c_int,
133 event_mask: c_long,
134 do_not_propagate_mask: c_long,
135 override_redirect: c_int,
136 colormap: c_ulong,
137 cursor: c_ulong,
138 }
139 #[repr(C)]
140 #[derive(Clone, Copy)]
141 struct XKeyEvent {
142 kind: c_int,
143 serial: c_ulong,
144 send_event: c_int,
145 display: *mut Display,
146 window: c_ulong,
147 root: c_ulong,
148 subwindow: c_ulong,
149 time: c_ulong,
150 x: c_int,
151 y: c_int,
152 x_root: c_int,
153 y_root: c_int,
154 state: c_uint,
155 keycode: c_uint,
156 same_screen: c_int,
157 }
158 #[repr(C)]
160 #[derive(Clone, Copy)]
161 struct XMotionEvent {
162 kind: c_int,
163 serial: c_ulong,
164 send_event: c_int,
165 display: *mut Display,
166 window: c_ulong,
167 root: c_ulong,
168 subwindow: c_ulong,
169 time: c_ulong,
170 x: c_int,
171 y: c_int,
172 x_root: c_int,
173 y_root: c_int,
174 state: c_uint,
175 is_hint: c_char,
176 same_screen: c_int,
177 }
178 #[repr(C)]
179 #[derive(Clone, Copy)]
180 struct XConfigureEvent {
181 kind: c_int,
182 serial: c_ulong,
183 send_event: c_int,
184 display: *mut Display,
185 event: c_ulong,
186 window: c_ulong,
187 x: c_int,
188 y: c_int,
189 width: c_int,
190 height: c_int,
191 border_width: c_int,
192 above: c_ulong,
193 override_redirect: c_int,
194 }
195 #[repr(C)]
196 #[derive(Clone, Copy)]
197 union ClientData {
198 bytes: [c_char; 20],
199 shorts: [c_short; 10],
200 longs: [c_long; 5],
201 }
202 #[repr(C)]
203 #[derive(Clone, Copy)]
204 struct XClientMessageEvent {
205 kind: c_int,
206 serial: c_ulong,
207 send_event: c_int,
208 display: *mut Display,
209 window: c_ulong,
210 message_type: c_ulong,
211 format: c_int,
212 data: ClientData,
213 }
214 #[repr(C)]
215 struct XColor {
216 pixel: c_ulong,
217 red: u16,
218 green: u16,
219 blue: u16,
220 flags: c_char,
221 pad: c_char,
222 }
223 #[repr(C)]
224 union XEvent {
225 kind: c_int,
226 key: XKeyEvent,
227 motion: XMotionEvent,
228 configure: XConfigureEvent,
229 client: XClientMessageEvent,
230 padding: [c_long; 24],
231 }
232 impl XEvent {
233 fn empty() -> Self {
234 Self { padding: [0; 24] }
235 }
236 }
237
238 #[link(name = "X11")]
239 unsafe extern "C" {
240 fn XOpenDisplay(name: *const c_char) -> *mut Display;
241 fn XCloseDisplay(display: *mut Display) -> c_int;
242 fn XDefaultScreen(display: *mut Display) -> c_int;
243 fn XRootWindow(display: *mut Display, screen: c_int) -> c_ulong;
244 fn XCreateColormap(
245 display: *mut Display,
246 window: c_ulong,
247 visual: *mut Visual,
248 allocation: c_int,
249 ) -> c_ulong;
250 fn XFreeColormap(display: *mut Display, colormap: c_ulong) -> c_int;
251 fn XCreateWindow(
252 display: *mut Display,
253 parent: c_ulong,
254 x: c_int,
255 y: c_int,
256 width: c_uint,
257 height: c_uint,
258 border: c_uint,
259 depth: c_int,
260 class: c_uint,
261 visual: *mut Visual,
262 mask: c_ulong,
263 attributes: *mut XSetWindowAttributes,
264 ) -> c_ulong;
265 fn XDestroyWindow(display: *mut Display, window: c_ulong) -> c_int;
266 fn XStoreName(display: *mut Display, window: c_ulong, name: *const c_char) -> c_int;
267 fn XInternAtom(
268 display: *mut Display,
269 name: *const c_char,
270 only_if_exists: c_int,
271 ) -> c_ulong;
272 fn XSetWMProtocols(
273 display: *mut Display,
274 window: c_ulong,
275 protocols: *mut c_ulong,
276 count: c_int,
277 ) -> c_int;
278 fn XMapWindow(display: *mut Display, window: c_ulong) -> c_int;
279 fn XFlush(display: *mut Display) -> c_int;
280 fn XPending(display: *mut Display) -> c_int;
281 fn XNextEvent(display: *mut Display, event: *mut XEvent) -> c_int;
282 fn XPeekEvent(display: *mut Display, event: *mut XEvent) -> c_int;
283 fn XLookupKeysym(event: *mut XKeyEvent, index: c_int) -> c_ulong;
284 fn XFree(data: *mut c_void) -> c_int;
285 fn XGrabPointer(
286 display: *mut Display,
287 window: c_ulong,
288 owner_events: c_int,
289 event_mask: c_uint,
290 pointer_mode: c_int,
291 keyboard_mode: c_int,
292 confine_to: c_ulong,
293 cursor: c_ulong,
294 time: c_ulong,
295 ) -> c_int;
296 fn XUngrabPointer(display: *mut Display, time: c_ulong) -> c_int;
297 fn XWarpPointer(
298 display: *mut Display,
299 source: c_ulong,
300 destination: c_ulong,
301 source_x: c_int,
302 source_y: c_int,
303 source_width: c_uint,
304 source_height: c_uint,
305 destination_x: c_int,
306 destination_y: c_int,
307 ) -> c_int;
308 fn XCreateBitmapFromData(
309 display: *mut Display,
310 drawable: c_ulong,
311 data: *const c_char,
312 width: c_uint,
313 height: c_uint,
314 ) -> c_ulong;
315 fn XCreatePixmapCursor(
316 display: *mut Display,
317 source: c_ulong,
318 mask: c_ulong,
319 foreground: *mut XColor,
320 background: *mut XColor,
321 x: c_uint,
322 y: c_uint,
323 ) -> c_ulong;
324 fn XFreePixmap(display: *mut Display, pixmap: c_ulong) -> c_int;
325 fn XFreeCursor(display: *mut Display, cursor: c_ulong) -> c_int;
326 fn XDefineCursor(display: *mut Display, window: c_ulong, cursor: c_ulong) -> c_int;
327 fn XUndefineCursor(display: *mut Display, window: c_ulong) -> c_int;
328 }
329 #[link(name = "GL")]
330 unsafe extern "C" {
331 fn glXChooseVisual(
332 display: *mut Display,
333 screen: c_int,
334 attributes: *mut c_int,
335 ) -> *mut XVisualInfo;
336 fn glXCreateContext(
337 display: *mut Display,
338 visual: *mut XVisualInfo,
339 shared: GlxContext,
340 direct: c_int,
341 ) -> GlxContext;
342 fn glXMakeCurrent(display: *mut Display, drawable: c_ulong, context: GlxContext) -> c_int;
343 fn glXDestroyContext(display: *mut Display, context: GlxContext);
344 fn glXSwapBuffers(display: *mut Display, drawable: c_ulong);
345 fn glXGetProcAddressARB(name: *const u8) -> Option<unsafe extern "C" fn()>;
346 fn glXQueryExtensionsString(display: *mut Display, screen: c_int) -> *const c_char;
347 fn glViewport(x: c_int, y: c_int, width: c_int, height: c_int);
348 fn glClearColor(r: f32, g: f32, b: f32, a: f32);
349 fn glClearDepth(depth: f64);
350 fn glClear(mask: c_uint);
351 fn glEnable(capability: c_uint);
352 fn glDisable(capability: c_uint);
353 fn glDepthFunc(function: c_uint);
354 fn glDepthMask(enabled: u8);
355 fn glBlendFuncSeparate(
356 source_rgb: c_uint,
357 destination_rgb: c_uint,
358 source_alpha: c_uint,
359 destination_alpha: c_uint,
360 );
361 fn glCreateShader(kind: c_uint) -> c_uint;
362 fn glShaderSource(
363 shader: c_uint,
364 count: c_int,
365 source: *const *const c_char,
366 length: *const c_int,
367 );
368 fn glCompileShader(shader: c_uint);
369 fn glGetShaderiv(shader: c_uint, parameter: c_uint, value: *mut c_int);
370 fn glGetShaderInfoLog(shader: c_uint, size: c_int, length: *mut c_int, log: *mut c_char);
371 fn glDeleteShader(shader: c_uint);
372 fn glCreateProgram() -> c_uint;
373 fn glAttachShader(program: c_uint, shader: c_uint);
374 fn glBindAttribLocation(program: c_uint, index: c_uint, name: *const c_char);
375 fn glLinkProgram(program: c_uint);
376 fn glGetProgramiv(program: c_uint, parameter: c_uint, value: *mut c_int);
377 fn glGetProgramInfoLog(program: c_uint, size: c_int, length: *mut c_int, log: *mut c_char);
378 fn glDeleteProgram(program: c_uint);
379 fn glUseProgram(program: c_uint);
380 fn glGenBuffers(count: c_int, buffers: *mut c_uint);
381 fn glDeleteBuffers(count: c_int, buffers: *const c_uint);
382 fn glBindBuffer(target: c_uint, buffer: c_uint);
383 fn glBufferData(target: c_uint, bytes: isize, data: *const c_void, usage: c_uint);
384 fn glEnableVertexAttribArray(index: c_uint);
385 fn glDisableVertexAttribArray(index: c_uint);
386 fn glVertexAttribPointer(
387 index: c_uint,
388 components: c_int,
389 kind: c_uint,
390 normalized: u8,
391 stride: c_int,
392 offset: *const c_void,
393 );
394 fn glDrawArrays(mode: c_uint, first: c_int, count: c_int);
395 fn glDrawArraysInstanced(mode: c_uint, first: c_int, count: c_int, instances: c_int);
396 fn glVertexAttribDivisor(index: c_uint, divisor: c_uint);
397 fn glGetUniformLocation(program: c_uint, name: *const c_char) -> c_int;
398 fn glUniformMatrix4fv(location: c_int, count: c_int, transpose: u8, values: *const f32);
399 fn glGenQueries(count: c_int, queries: *mut c_uint);
400 fn glDeleteQueries(count: c_int, queries: *const c_uint);
401 fn glBeginQuery(target: c_uint, query: c_uint);
402 fn glEndQuery(target: c_uint);
403 fn glGetQueryObjectiv(query: c_uint, parameter: c_uint, value: *mut c_int);
404 fn glGetQueryObjectui64v(query: c_uint, parameter: c_uint, value: *mut u64);
405 fn glUniform1i(location: c_int, value: c_int);
406 fn glGenTextures(count: c_int, textures: *mut c_uint);
407 fn glDeleteTextures(count: c_int, textures: *const c_uint);
408 fn glBindTexture(target: c_uint, texture: c_uint);
409 fn glActiveTexture(texture: c_uint);
410 fn glTexParameteri(target: c_uint, parameter: c_uint, value: c_int);
411 fn glTexImage2D(
412 target: c_uint,
413 level: c_int,
414 internal: c_int,
415 width: c_int,
416 height: c_int,
417 border: c_int,
418 format: c_uint,
419 kind: c_uint,
420 data: *const c_void,
421 );
422 fn glGenFramebuffers(count: c_int, framebuffers: *mut c_uint);
423 fn glDeleteFramebuffers(count: c_int, framebuffers: *const c_uint);
424 fn glBindFramebuffer(target: c_uint, framebuffer: c_uint);
425 fn glFramebufferTexture2D(
426 target: c_uint,
427 attachment: c_uint,
428 texture_target: c_uint,
429 texture: c_uint,
430 level: c_int,
431 );
432 fn glCheckFramebufferStatus(target: c_uint) -> c_uint;
433 fn glDrawBuffer(buffer: c_uint);
434 fn glUniform4fv(location: c_int, count: c_int, values: *const f32);
435 fn glReadBuffer(buffer: c_uint);
436 fn glPixelStorei(parameter: c_uint, value: c_int);
437 fn glReadPixels(
438 x: c_int,
439 y: c_int,
440 width: c_int,
441 height: c_int,
442 format: c_uint,
443 kind: c_uint,
444 pixels: *mut c_void,
445 );
446 fn glFinish();
447 fn glGetString(name: c_uint) -> *const u8;
448 }
449
450 struct GpuTimer {
453 queries: [u32; 32],
454 free: Vec<u32>,
455 pending: std::collections::VecDeque<(u32, u64)>,
456 completed: Vec<(u64, f64)>,
457 }
458 impl GpuTimer {
459 fn new() -> Option<Self> {
460 unsafe {
462 let p = glGetString(0x1f02);
463 if p.is_null() {
464 return None;
465 }
466 let v = CStr::from_ptr(p.cast()).to_string_lossy();
467 let mut n = v.split(['.', ' ']);
468 let major = n.next()?.parse::<u32>().ok()?;
469 let minor = n.next()?.parse::<u32>().ok()?;
470 if (major, minor) < (3, 3) {
471 return None;
472 }
473 let mut queries = [0; 32];
474 glGenQueries(32, queries.as_mut_ptr());
475 if queries.contains(&0) {
476 glDeleteQueries(32, queries.as_ptr());
477 return None;
478 }
479 Some(Self {
480 free: queries.to_vec(),
481 queries,
482 pending: std::collections::VecDeque::with_capacity(32),
483 completed: Vec::with_capacity(32),
484 })
485 }
486 }
487 fn poll(&mut self) {
488 unsafe {
490 while let Some(&(query, frame)) = self.pending.front() {
491 let mut ready = 0;
492 glGetQueryObjectiv(query, 0x8867, &mut ready);
493 if ready == 0 {
494 break;
495 }
496 let mut nanoseconds = 0;
497 glGetQueryObjectui64v(query, 0x8866, &mut nanoseconds);
498 self.pending.pop_front();
499 self.free.push(query);
500 if self.completed.len() < 32 {
501 self.completed.push((frame, nanoseconds as f64 / 1000.));
502 }
503 }
504 }
505 }
506 fn begin(&mut self) -> Option<u32> {
507 self.poll();
508 let query = self.free.pop()?;
509 unsafe {
510 glBeginQuery(0x88bf, query);
511 }
512 Some(query)
513 }
514 fn end(&mut self, query: u32, frame: u64) {
515 unsafe {
516 glEndQuery(0x88bf);
517 }
518 self.pending.push_back((query, frame));
519 }
520 }
521 struct GpuProgram {
522 handle: c_uint,
523 camera: c_int,
524 light: c_int,
525 params: c_int,
526 points: c_int,
527 point_count: c_int,
528 shadow_matrix: c_int,
529 shadow_texture: c_int,
530 shadow_options: c_int,
531 shadow_extra: c_int,
532 }
533 struct ProgramPair {
534 color: GpuProgram,
535 depth: GpuProgram,
536 }
537 struct ShadowTarget {
538 framebuffer: c_uint,
539 texture: c_uint,
540 size: u32,
541 }
542 struct MaterialProgram {
543 revision: u32,
544 valid: Option<ProgramPair>,
545 }
546 struct RetainedGpu {
547 builtin: ProgramPair,
548 environment: crate::lighting::Environment,
549 white_texture: c_uint,
550 shadow: Option<ShadowTarget>,
551 shadow_failed_size: u32,
552 materials: BTreeMap<u32, MaterialProgram>,
553 instances: c_uint,
554 meshes: BTreeMap<u32, (c_uint, c_int)>,
555 batches: Vec<crate::retained::RetainedBatch>,
556 }
557 pub struct NativeWindow {
558 display: *mut Display,
559 window: c_ulong,
560 colormap: c_ulong,
561 context: GlxContext,
562 program: c_uint,
563 vertex_buffer: c_uint,
564 retained: Option<RetainedGpu>,
565 last_commands: Vec<crate::retained::Command>,
566 mesh_upload_bytes: u64,
567 gpu_timer: Option<GpuTimer>,
568 present_count: u64,
569 width: u32,
570 height: u32,
571 delete_atom: c_ulong,
572 protocols_atom: c_ulong,
573 alive: bool,
574 quit_sent: bool,
575 focused: bool,
576 grabbed: bool,
577 cursor_visible: bool,
578 hidden_cursor: c_ulong,
579 relative_position: (i32, i32),
580 warp_pending: bool,
581 last_clear: Color,
582 last_batches: Vec<(i32, i32, bool)>,
583 presented: bool,
584 keys: BTreeMap<u32, u32>,
585 buttons: BTreeSet<u32>,
586 }
587 impl NativeWindow {
588 pub fn new(conf: &Conf) -> Result<Self, String> {
589 if !(1..=16384).contains(&conf.window_width)
590 || !(1..=16384).contains(&conf.window_height)
591 {
592 return Err("Window dimensions must be between 1 and 16384 pixels".into());
593 }
594 unsafe {
597 let display = XOpenDisplay(ptr::null());
598 if display.is_null() {
599 return Err("Cannot open X11 display; set DISPLAY to a running X server".into());
600 }
601 let mut owner = Self {
602 display,
603 window: 0,
604 colormap: 0,
605 context: ptr::null_mut(),
606 program: 0,
607 vertex_buffer: 0,
608 retained: None,
609 last_commands: Vec::new(),
610 mesh_upload_bytes: 0,
611 gpu_timer: None,
612 present_count: 0,
613 width: conf.window_width as u32,
614 height: conf.window_height as u32,
615 delete_atom: 0,
616 protocols_atom: 0,
617 alive: false,
618 quit_sent: false,
619 focused: true,
620 grabbed: false,
621 cursor_visible: true,
622 hidden_cursor: 0,
623 relative_position: (0, 0),
624 warp_pending: false,
625 last_clear: Color::new(0.0, 0.0, 0.0, 1.0),
626 last_batches: Vec::new(),
627 presented: false,
628 keys: BTreeMap::new(),
629 buttons: BTreeSet::new(),
630 };
631 let screen = XDefaultScreen(display);
632 let base = [4, 5, 8, 8, 9, 8, 10, 8, 11, 8, 12, 24];
634 let mut attributes = base.to_vec();
635 if conf.sample_count > 1 {
636 attributes.extend([100000, 1, 100001, conf.sample_count.min(16)]);
637 }
638 attributes.push(0);
639 let mut visual = glXChooseVisual(display, screen, attributes.as_mut_ptr());
640 if visual.is_null() && conf.sample_count > 1 {
641 attributes = base.to_vec();
642 attributes.push(0);
643 visual = glXChooseVisual(display, screen, attributes.as_mut_ptr());
644 }
645 if visual.is_null() {
646 return Err("No double-buffered RGBA/depth GLX visual is available".into());
647 }
648 let root = XRootWindow(display, screen);
649 owner.colormap = XCreateColormap(display, root, (*visual).visual, 0);
650 let mut window_attributes = XSetWindowAttributes {
651 colormap: owner.colormap,
652 event_mask: (1 << 0)
653 | (1 << 1)
654 | (1 << 2)
655 | (1 << 3)
656 | (1 << 6)
657 | (1 << 15)
658 | (1 << 17)
659 | (1 << 21),
660 ..Default::default()
661 };
662 owner.window = XCreateWindow(
663 display,
664 root,
665 0,
666 0,
667 owner.width,
668 owner.height,
669 0,
670 (*visual).depth,
671 1,
672 (*visual).visual,
673 (1 << 3) | (1 << 11) | (1 << 13),
674 &mut window_attributes,
675 );
676 owner.alive = owner.window != 0;
677 if !owner.alive {
678 XFree(visual.cast());
679 return Err("X11 window creation failed".into());
680 }
681 owner.context = glXCreateContext(display, visual, ptr::null_mut(), 1);
682 XFree(visual.cast());
683 if owner.context.is_null() {
684 return Err("GLX could not create an OpenGL context".into());
685 }
686 let title = CString::new(conf.window_title.replace('\0', " "))
687 .map_err(|e| e.to_string())?;
688 XStoreName(display, owner.window, title.as_ptr());
689 owner.delete_atom = XInternAtom(display, c"WM_DELETE_WINDOW".as_ptr(), 0);
690 owner.protocols_atom = XInternAtom(display, c"WM_PROTOCOLS".as_ptr(), 0);
691 XSetWMProtocols(display, owner.window, &mut owner.delete_atom, 1);
692 XMapWindow(display, owner.window);
693 XFlush(display);
694 if glXMakeCurrent(display, owner.window, owner.context) == 0 {
695 return Err("Cannot make the GLX context current".into());
696 }
697 owner.program = create_program()?;
698 glGenBuffers(1, &mut owner.vertex_buffer);
699 if owner.vertex_buffer == 0 {
700 return Err("OpenGL could not allocate a vertex buffer".into());
701 }
702 glEnable(GL_BLEND);
703 glBlendFuncSeparate(
704 GL_SRC_ALPHA,
705 GL_ONE_MINUS_SRC_ALPHA,
706 1,
707 GL_ONE_MINUS_SRC_ALPHA,
708 );
709 glDisable(GL_CULL_FACE);
710 glDisable(GL_SCISSOR_TEST);
711 glDepthFunc(GL_LEQUAL);
712 glClearDepth(1.0);
713 owner.set_swap_interval(1);
714 Ok(owner)
715 }
716 }
717 pub fn size(&self) -> (u32, u32) {
718 (self.width, self.height)
719 }
720 pub fn driver_info(&self) -> String {
721 unsafe {
723 if !self.make_current() {
724 return "OpenGL context unavailable".into();
725 }
726 let read = |name| {
727 let bytes = glGetString(name);
728 if bytes.is_null() {
729 "unknown".into()
730 } else {
731 CStr::from_ptr(bytes.cast()).to_string_lossy().into_owned()
732 }
733 };
734 format!("{} / {}", read(0x1f01), read(0x1f02))
735 }
736 }
737 pub fn set_swap_interval(&mut self, interval: i32) {
739 if interval < 0 || !self.alive {
740 return;
741 }
742 unsafe {
745 if !self.make_current() {
746 return;
747 }
748 let extensions =
749 glXQueryExtensionsString(self.display, XDefaultScreen(self.display));
750 if extensions.is_null() {
751 return;
752 }
753 let extensions = CStr::from_ptr(extensions).to_string_lossy();
754 if extensions
755 .split_whitespace()
756 .any(|name| name == "GLX_EXT_swap_control")
757 {
758 if let Some(address) =
759 glXGetProcAddressARB(c"glXSwapIntervalEXT".as_ptr().cast())
760 {
761 let call = std::mem::transmute::<
762 unsafe extern "C" fn(),
763 unsafe extern "C" fn(*mut Display, c_ulong, c_int),
764 >(address);
765 call(self.display, self.window, interval);
766 }
767 } else if extensions
768 .split_whitespace()
769 .any(|name| name == "GLX_MESA_swap_control")
770 && let Some(address) =
771 glXGetProcAddressARB(c"glXSwapIntervalMESA".as_ptr().cast())
772 {
773 let call = std::mem::transmute::<
774 unsafe extern "C" fn(),
775 unsafe extern "C" fn(c_uint) -> c_int,
776 >(address);
777 call(interval as c_uint);
778 }
779 }
780 }
781 pub fn set_cursor(&mut self, grab: bool, visible: bool) {
784 if !self.alive {
785 return;
786 }
787 unsafe {
790 if visible != self.cursor_visible {
791 if visible {
792 XUndefineCursor(self.display, self.window);
793 } else {
794 if self.hidden_cursor == 0 {
795 let blank = [0_i8];
796 let pixmap = XCreateBitmapFromData(
797 self.display,
798 self.window,
799 blank.as_ptr(),
800 1,
801 1,
802 );
803 if pixmap != 0 {
804 let mut foreground = XColor {
805 pixel: 0,
806 red: 0,
807 green: 0,
808 blue: 0,
809 flags: 0,
810 pad: 0,
811 };
812 let mut background = XColor {
813 pixel: 0,
814 red: 0,
815 green: 0,
816 blue: 0,
817 flags: 0,
818 pad: 0,
819 };
820 self.hidden_cursor = XCreatePixmapCursor(
821 self.display,
822 pixmap,
823 pixmap,
824 &mut foreground,
825 &mut background,
826 0,
827 0,
828 );
829 XFreePixmap(self.display, pixmap);
830 }
831 }
832 if self.hidden_cursor != 0 {
833 XDefineCursor(self.display, self.window, self.hidden_cursor);
834 }
835 }
836 self.cursor_visible = visible;
837 }
838 let desired = grab && self.focused;
839 if !desired && self.grabbed {
840 XUngrabPointer(self.display, 0);
841 self.grabbed = false;
842 self.warp_pending = false;
843 } else if desired
844 && !self.grabbed
845 && XGrabPointer(
846 self.display,
847 self.window,
848 0,
849 (1 << 2) | (1 << 3) | (1 << 6),
850 1,
851 1,
852 self.window,
853 0,
854 0,
855 ) == 0
856 {
857 self.grabbed = true;
858 self.relative_position = self.center();
859 self.warp_to_center();
860 }
861 XFlush(self.display);
862 }
863 }
864 fn center(&self) -> (i32, i32) {
865 ((self.width / 2) as i32, (self.height / 2) as i32)
866 }
867 fn warp_to_center(&mut self) {
868 let (x, y) = self.center();
869 unsafe {
871 XWarpPointer(self.display, 0, self.window, 0, 0, 0, 0, x, y);
872 XFlush(self.display);
873 }
874 self.warp_pending = true;
875 }
876 pub fn poll(&mut self) -> Vec<Event> {
877 let mut output = Vec::new();
878 if !self.alive {
879 self.queue_quit(&mut output);
880 return output;
881 }
882 unsafe {
885 for _ in 0..4096 {
886 if XPending(self.display) <= 0 {
887 break;
888 }
889 let mut event = XEvent::empty();
890 XNextEvent(self.display, &mut event);
891 match event.kind {
892 KEY_PRESS => {
893 let mut key = event.key;
894 let symbol = XLookupKeysym(&mut key, 0) as u32;
895 if let std::collections::btree_map::Entry::Vacant(entry) =
896 self.keys.entry(key.keycode)
897 {
898 entry.insert(symbol);
899 output.push(Event::Key(symbol, true));
900 }
901 }
902 KEY_RELEASE => {
903 let key = event.key;
904 let mut next = XEvent::empty();
907 if XPending(self.display) > 0 {
908 XPeekEvent(self.display, &mut next);
909 if next.kind == KEY_PRESS
910 && next.key.keycode == key.keycode
911 && next.key.time == key.time
912 {
913 continue;
914 }
915 }
916 if let Some(symbol) = self.keys.remove(&key.keycode) {
917 output.push(Event::Key(symbol, false));
918 }
919 }
920 BUTTON_PRESS | BUTTON_RELEASE => {
921 let button = event.key;
922 let down = event.kind == BUTTON_PRESS;
923 if !self.grabbed {
926 output.push(Event::MouseMove(button.x as f32, button.y as f32));
927 }
928 match button.keycode {
929 4 if down => output.push(Event::Wheel(1.0)),
930 5 if down => output.push(Event::Wheel(-1.0)),
931 4..=7 => {}
932 code => {
933 let changed = if down {
934 self.buttons.insert(code)
935 } else {
936 self.buttons.remove(&code)
937 };
938 if changed {
939 output.push(Event::MouseButton(code, down));
940 }
941 }
942 }
943 }
944 MOTION_NOTIFY => {
945 let position = (event.motion.x, event.motion.y);
946 if self.grabbed {
947 if self.warp_pending && position == self.center() {
948 self.warp_pending = false;
949 self.relative_position = position;
950 } else {
951 let delta = (
952 position.0 - self.relative_position.0,
953 position.1 - self.relative_position.1,
954 );
955 self.relative_position = position;
956 if delta != (0, 0) {
957 output.push(Event::MouseDelta(
958 delta.0 as f32,
959 delta.1 as f32,
960 ));
961 }
962 }
963 } else {
964 output.push(Event::MouseMove(position.0 as f32, position.1 as f32));
965 }
966 }
967 CONFIGURE_NOTIFY => {
968 let configure = event.configure;
969 let size = (
970 configure.width.max(1) as u32,
971 configure.height.max(1) as u32,
972 );
973 if size != self.size() {
974 self.width = size.0;
975 self.height = size.1;
976 output.push(Event::Resize(size.0, size.1));
977 }
978 }
979 FOCUS_IN => {
980 self.focused = true;
981 }
982 FOCUS_OUT => {
983 self.focused = false;
984 if self.grabbed {
985 XUngrabPointer(self.display, 0);
986 self.grabbed = false;
987 self.warp_pending = false;
988 }
989 for (_, symbol) in std::mem::take(&mut self.keys) {
990 output.push(Event::Key(symbol, false));
991 }
992 for button in std::mem::take(&mut self.buttons) {
993 output.push(Event::MouseButton(button, false));
994 }
995 }
996 CLIENT_MESSAGE => {
997 let message = event.client;
998 if message.message_type == self.protocols_atom
999 && message.format == 32
1000 && message.data.longs[0] as c_ulong == self.delete_atom
1001 {
1002 self.queue_quit(&mut output);
1003 }
1004 }
1005 DESTROY_NOTIFY => {
1006 self.alive = false;
1007 self.queue_quit(&mut output);
1008 }
1009 _ => {}
1010 }
1011 }
1012 }
1013 if self.alive && self.grabbed && self.relative_position != self.center() {
1014 self.warp_to_center();
1015 }
1016 output
1017 }
1018 fn queue_quit(&mut self, events: &mut Vec<Event>) {
1019 if !self.quit_sent {
1020 self.quit_sent = true;
1021 events.push(Event::Quit);
1022 }
1023 }
1024 fn make_current(&self) -> bool {
1025 unsafe {
1027 self.alive
1028 && !self.context.is_null()
1029 && glXMakeCurrent(self.display, self.window, self.context) != 0
1030 }
1031 }
1032 pub fn present(&mut self, vertices: &[[f32; 8]], batches: &[Batch], clear: Color) {
1035 self.present_scene(vertices, batches, clear, None);
1036 }
1037 pub fn last_mesh_upload_bytes(&self) -> u64 {
1038 self.mesh_upload_bytes
1039 }
1040 pub fn enable_gpu_timing(&mut self) -> bool {
1041 if !self.make_current() {
1042 return false;
1043 }
1044 if self.gpu_timer.is_none() {
1045 self.gpu_timer = GpuTimer::new();
1046 }
1047 self.gpu_timer.is_some()
1048 }
1049 pub fn drain_gpu_results(&mut self, mut f: impl FnMut(u64, f64)) {
1050 if !self.make_current() {
1051 return;
1052 }
1053 if let Some(timer) = &mut self.gpu_timer {
1054 timer.poll();
1055 for (frame, time) in timer.completed.drain(..) {
1056 f(frame, time);
1057 }
1058 }
1059 }
1060 pub fn finish_gpu_timing(&mut self) {
1061 if self.gpu_timer.is_some() && self.make_current() {
1062 unsafe {
1063 glFinish();
1064 }
1065 if let Some(timer) = &mut self.gpu_timer {
1066 timer.poll();
1067 }
1068 }
1069 }
1070 pub fn render_counts(&self) -> (u64, u64) {
1071 let mut calls = 0;
1072 let mut vertices = 0;
1073 for &(_, count, _) in &self.last_batches {
1074 if count > 0 {
1075 calls += 1;
1076 vertices += count as u64;
1077 }
1078 }
1079 if let Some(gpu) = &self.retained {
1080 for b in &gpu.batches {
1081 if let Some((_, count)) = gpu.meshes.get(&b.mesh) {
1082 let main = b.depth & 32 == 0;
1083 let shadow = gpu.environment.shadow_size > 0
1084 && gpu.shadow.is_some()
1085 && b.depth & 1 != 0
1086 && b.depth & 8 == 0
1087 && b.light[15] > 0.5;
1088 let passes = main as u64 + shadow as u64;
1089 calls += passes;
1090 vertices += *count as u64 * b.count as u64 * passes;
1091 }
1092 }
1093 }
1094 (calls, vertices)
1095 }
1096
1097 pub(crate) fn present_scene(
1098 &mut self,
1099 vertices: &[[f32; 8]],
1100 batches: &[Batch],
1101 clear: Color,
1102 frame: Option<&crate::retained::Frame>,
1103 ) {
1104 if !self.make_current() {
1105 return;
1106 }
1107 let Ok(byte_count) = isize::try_from(std::mem::size_of_val(vertices)) else {
1108 return;
1109 };
1110 self.last_clear = clear;
1111 self.last_commands.clear();
1112 if let Some(frame) = frame {
1113 self.last_commands.extend_from_slice(&frame.commands);
1114 }
1115 self.mesh_upload_bytes = 0;
1116 if let Some(frame) = frame {
1117 self.upload_retained(frame);
1118 }
1119 if let Some(gpu) = &mut self.retained {
1120 gpu.batches.clear();
1121 if let Some(frame) = frame {
1122 gpu.batches.extend_from_slice(&frame.batches);
1123 }
1124 }
1125 self.last_batches.clear();
1126 for batch in batches {
1127 if batch.count == 0
1128 || !batch.count.is_multiple_of(3)
1129 || batch.start > i32::MAX as u32
1130 || batch.count > i32::MAX as u32
1131 || batch
1132 .start
1133 .checked_add(batch.count)
1134 .is_none_or(|end| end as usize > vertices.len())
1135 {
1136 self.last_batches.push((0, 0, false));
1137 continue;
1138 }
1139 if frame.is_none() {
1140 self.last_commands.push(crate::retained::Command {
1141 kind: 0,
1142 index: self.last_batches.len() as u32,
1143 });
1144 }
1145 self.last_batches
1146 .push((batch.start as i32, batch.count as i32, batch.depth));
1147 }
1148 unsafe {
1151 glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer);
1152 glBufferData(
1153 GL_ARRAY_BUFFER,
1154 byte_count,
1155 vertices.as_ptr().cast(),
1156 GL_STREAM_DRAW,
1157 );
1158 }
1159 self.present_count += 1;
1160 let query = self.gpu_timer.as_mut().and_then(|t| t.begin());
1161 self.draw_uploaded();
1162 if let (Some(timer), Some(query)) = (&mut self.gpu_timer, query) {
1163 timer.end(query, self.present_count);
1164 }
1165 unsafe {
1167 glXSwapBuffers(self.display, self.window);
1168 }
1169 self.presented = true;
1170 }
1171 fn upload_retained(&mut self, frame: &crate::retained::Frame) {
1172 if frame.batches.is_empty() && crate::material::count() == 0 && self.retained.is_none()
1173 {
1174 return;
1175 }
1176 if self.retained.is_none() {
1177 let version_pointer = unsafe { glGetString(0x1f02) };
1179 assert!(!version_pointer.is_null(), "OpenGL version unavailable");
1180 let version = unsafe { CStr::from_ptr(version_pointer.cast()) }.to_string_lossy();
1182 let mut numbers = version.split(['.', ' ']);
1183 let major = numbers
1184 .next()
1185 .and_then(|s| s.parse::<u32>().ok())
1186 .unwrap_or(0);
1187 let minor = numbers
1188 .next()
1189 .and_then(|s| s.parse::<u32>().ok())
1190 .unwrap_or(0);
1191 assert!(
1192 (major, minor) >= (3, 3),
1193 "retained rendering requires OpenGL 3.3 or newer: {version}"
1194 );
1195 let builtin = ProgramPair::compile(&crate::material::ShaderSource::default())
1196 .expect("native retained shader");
1197 unsafe {
1199 let mut instances = 0;
1200 glGenBuffers(1, &mut instances);
1201 assert_ne!(instances, 0, "allocate retained instance buffer");
1202 self.retained = Some(RetainedGpu {
1203 builtin,
1204 environment: Default::default(),
1205 white_texture: create_texture(1, false).expect("fallback texture"),
1206 shadow: None,
1207 shadow_failed_size: 0,
1208 materials: BTreeMap::new(),
1209 instances,
1210 meshes: BTreeMap::new(),
1211 batches: Vec::new(),
1212 });
1213 }
1214 }
1215 let gpu = self.retained.as_mut().unwrap();
1216 gpu.environment = frame.environment;
1217 gpu.configure_shadow();
1218 gpu.prepare_materials();
1219 unsafe {
1221 gpu.meshes.retain(|id, (buffer, _)| {
1222 if crate::retained::is_live(*id) {
1223 true
1224 } else {
1225 glDeleteBuffers(1, buffer);
1226 false
1227 }
1228 });
1229 for batch in &frame.batches {
1230 if let std::collections::btree_map::Entry::Vacant(slot) =
1231 gpu.meshes.entry(batch.mesh)
1232 {
1233 let mesh = crate::retained::geometry(batch.mesh).expect("live frame mesh");
1234 let mut buffer = 0;
1235 glGenBuffers(1, &mut buffer);
1236 assert_ne!(buffer, 0, "allocate retained mesh buffer");
1237 glBindBuffer(GL_ARRAY_BUFFER, buffer);
1238 let bytes = mesh.vertices.len() * 40;
1239 glBufferData(
1240 GL_ARRAY_BUFFER,
1241 bytes as isize,
1242 mesh.vertices.as_ptr().cast(),
1243 0x88e4,
1244 );
1245 self.mesh_upload_bytes += bytes as u64;
1246 slot.insert((buffer, mesh.vertices.len() as i32));
1247 }
1248 }
1249 glBindBuffer(GL_ARRAY_BUFFER, gpu.instances);
1250 glBufferData(
1251 GL_ARRAY_BUFFER,
1252 (frame.instances.len() * 112) as isize,
1253 frame.instances.as_ptr().cast(),
1254 GL_STREAM_DRAW,
1255 );
1256 }
1257 }
1258 fn draw_uploaded(&self) {
1259 unsafe {
1262 if let Some(gpu) = &self.retained {
1263 gpu.render_shadow();
1264 }
1265 glBindFramebuffer(0x8d40, 0);
1266 glDrawBuffer(0x0405);
1267 glViewport(
1268 0,
1269 0,
1270 self.width.min(i32::MAX as u32) as i32,
1271 self.height.min(i32::MAX as u32) as i32,
1272 );
1273 glDepthMask(1);
1274 glClearColor(
1275 self.last_clear.r,
1276 self.last_clear.g,
1277 self.last_clear.b,
1278 self.last_clear.a,
1279 );
1280 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1281 for command in &self.last_commands {
1282 if command.kind == 1 {
1283 if let Some(gpu) = &self.retained {
1284 gpu.draw(command.index as usize, false);
1285 }
1286 } else if let Some(&(start, count, depth)) =
1287 self.last_batches.get(command.index as usize)
1288 {
1289 glUseProgram(self.program);
1290 glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer);
1291 glEnableVertexAttribArray(0);
1292 glEnableVertexAttribArray(1);
1293 glVertexAttribPointer(0, 4, GL_FLOAT, 0, 32, ptr::null());
1294 glVertexAttribPointer(1, 4, GL_FLOAT, 0, 32, 16_usize as *const c_void);
1295 set_depth(depth);
1296 glDrawArrays(GL_TRIANGLES, start, count);
1297 glDisableVertexAttribArray(0);
1298 glDisableVertexAttribArray(1);
1299 }
1300 }
1301 glBindBuffer(GL_ARRAY_BUFFER, 0);
1302 glUseProgram(0);
1303 glDepthMask(1);
1304 }
1305 }
1306 pub fn pixels(&self) -> Vec<u8> {
1309 let Some(count) = (self.width as usize)
1310 .checked_mul(self.height as usize)
1311 .filter(|&count| count <= MAX_READBACK_PIXELS)
1312 else {
1313 return Vec::new();
1314 };
1315 if !self.presented || !self.make_current() {
1316 return Vec::new();
1317 }
1318 self.draw_uploaded();
1322 let mut bytes = vec![0_u8; count * 4];
1323 unsafe {
1326 glFinish();
1327 glReadBuffer(0x0405); glPixelStorei(0x0d05, 1); glReadPixels(
1330 0,
1331 0,
1332 self.width as i32,
1333 self.height as i32,
1334 0x1908,
1335 0x1401,
1336 bytes.as_mut_ptr().cast(),
1337 );
1338 }
1339 flip_rows(&mut bytes, self.width as usize * 4, self.height as usize);
1340 bytes
1341 }
1342 }
1343 impl Drop for NativeWindow {
1344 fn drop(&mut self) {
1345 unsafe {
1348 if self.grabbed {
1349 XUngrabPointer(self.display, 0);
1350 }
1351 if self.hidden_cursor != 0 {
1352 XFreeCursor(self.display, self.hidden_cursor);
1353 }
1354 if !self.context.is_null() {
1355 if self.make_current() {
1356 if let Some(timer) = &self.gpu_timer {
1357 glDeleteQueries(timer.queries.len() as i32, timer.queries.as_ptr());
1358 }
1359 if self.vertex_buffer != 0 {
1360 glDeleteBuffers(1, &self.vertex_buffer);
1361 }
1362 if self.program != 0 {
1363 glDeleteProgram(self.program);
1364 }
1365 if let Some(gpu) = &self.retained {
1366 for (buffer, _) in gpu.meshes.values() {
1367 glDeleteBuffers(1, buffer);
1368 }
1369 glDeleteBuffers(1, &gpu.instances);
1370 gpu.builtin.delete();
1371 glDeleteTextures(1, &gpu.white_texture);
1372 if let Some(target) = &gpu.shadow {
1373 target.delete();
1374 }
1375 for m in gpu.materials.values() {
1376 if let Some(p) = &m.valid {
1377 p.delete();
1378 }
1379 }
1380 }
1381 }
1382 glXMakeCurrent(self.display, 0, ptr::null_mut());
1383 glXDestroyContext(self.display, self.context);
1384 }
1385 if self.alive && self.window != 0 {
1386 XDestroyWindow(self.display, self.window);
1387 }
1388 if self.colormap != 0 {
1389 XFreeColormap(self.display, self.colormap);
1390 }
1391 if !self.display.is_null() {
1392 XCloseDisplay(self.display);
1393 }
1394 }
1395 }
1396 }
1397 unsafe fn set_depth(depth: bool) {
1398 unsafe {
1400 if depth {
1401 glEnable(GL_DEPTH_TEST);
1402 glDepthMask(1);
1403 } else {
1404 glDisable(GL_DEPTH_TEST);
1405 glDepthMask(0);
1406 }
1407 }
1408 }
1409 fn create_texture(size: u32, depth: bool) -> Result<c_uint, String> {
1410 unsafe {
1412 let mut texture = 0;
1413 glGenTextures(1, &mut texture);
1414 if texture == 0 {
1415 return Err("texture allocation failed".into());
1416 }
1417 glBindTexture(0x0de1, texture);
1418 glTexParameteri(0x0de1, 0x2801, 0x2600);
1419 glTexParameteri(0x0de1, 0x2800, 0x2600);
1420 glTexParameteri(0x0de1, 0x2802, 0x812f);
1421 glTexParameteri(0x0de1, 0x2803, 0x812f);
1422 if depth {
1423 glTexImage2D(
1424 0x0de1,
1425 0,
1426 0x81a6,
1427 size as i32,
1428 size as i32,
1429 0,
1430 0x1902,
1431 0x1405,
1432 ptr::null(),
1433 );
1434 } else {
1435 let white = [255u8; 4];
1436 glTexImage2D(
1437 0x0de1,
1438 0,
1439 0x8058,
1440 1,
1441 1,
1442 0,
1443 0x1908,
1444 0x1401,
1445 white.as_ptr().cast(),
1446 );
1447 }
1448 Ok(texture)
1449 }
1450 }
1451 impl ShadowTarget {
1452 fn new(size: u32) -> Result<Self, String> {
1453 let texture = create_texture(size, true)?;
1454 unsafe {
1456 let mut framebuffer = 0;
1457 glGenFramebuffers(1, &mut framebuffer);
1458 glBindFramebuffer(0x8d40, framebuffer);
1459 glFramebufferTexture2D(0x8d40, 0x8d00, 0x0de1, texture, 0);
1460 glDrawBuffer(0);
1461 glReadBuffer(0);
1462 let status = glCheckFramebufferStatus(0x8d40);
1463 glBindFramebuffer(0x8d40, 0);
1464 glDrawBuffer(0x0405);
1465 glReadBuffer(0x0405);
1466 if framebuffer == 0 || status != 0x8cd5 {
1467 glDeleteFramebuffers(1, &framebuffer);
1468 glDeleteTextures(1, &texture);
1469 return Err(format!("shadow framebuffer incomplete: {status:#x}"));
1470 }
1471 Ok(Self {
1472 framebuffer,
1473 texture,
1474 size,
1475 })
1476 }
1477 }
1478 unsafe fn delete(&self) {
1479 unsafe {
1480 glDeleteFramebuffers(1, &self.framebuffer);
1481 glDeleteTextures(1, &self.texture);
1482 }
1483 }
1484 }
1485 impl ProgramPair {
1486 fn compile(source: &crate::material::ShaderSource) -> Result<Self, String> {
1487 let color = GpuProgram::compile_stage(source, false)?;
1488 match GpuProgram::compile_stage(source, true) {
1489 Ok(depth) => Ok(Self { color, depth }),
1490 Err(error) => {
1491 unsafe {
1492 glDeleteProgram(color.handle);
1493 }
1494 Err(error)
1495 }
1496 }
1497 }
1498 unsafe fn delete(&self) {
1499 unsafe {
1500 glDeleteProgram(self.color.handle);
1501 glDeleteProgram(self.depth.handle);
1502 }
1503 }
1504 }
1505 impl GpuProgram {
1506 fn compile_stage(
1507 source: &crate::material::ShaderSource,
1508 depth: bool,
1509 ) -> Result<Self, String> {
1510 let vertex = CString::new(format!(
1511 "#version 330\n{}\n{}",
1512 crate::retained::VERTEX_BODY
1513 .replace("attribute ", "in ")
1514 .replace("varying ", "out "),
1515 source.vertex
1516 ))
1517 .map_err(|e| e.to_string())?;
1518 let fragment = if depth {
1519 CString::new("#version 330\nvoid main(){}\n").unwrap()
1520 } else {
1521 CString::new(format!(
1522 "#version 330\nout vec4 ferravex_pixel;\n{}\n{}",
1523 crate::material::FRAGMENT_BODY
1524 .replace("varying ", "in ")
1525 .replace("gl_FragColor", "ferravex_pixel"),
1526 source.fragment
1527 ))
1528 .map_err(|e| e.to_string())?
1529 };
1530 let attributes = [
1531 (0, c"a_position"),
1532 (1, c"a_normal"),
1533 (2, c"a_color"),
1534 (3, c"a_model0"),
1535 (4, c"a_model1"),
1536 (5, c"a_model2"),
1537 (6, c"a_model3"),
1538 (7, c"a_normal0"),
1539 (8, c"a_normal1"),
1540 (9, c"a_normal2"),
1541 ];
1542 let handle = create_program_stages(&vertex, &fragment, &attributes)?;
1543 unsafe {
1545 Ok(Self {
1546 handle,
1547 camera: glGetUniformLocation(handle, c"u_camera".as_ptr()),
1548 light: glGetUniformLocation(handle, c"u_light[0]".as_ptr()),
1549 params: glGetUniformLocation(handle, c"u_params[0]".as_ptr()),
1550 points: glGetUniformLocation(handle, c"u_points[0]".as_ptr()),
1551 point_count: glGetUniformLocation(handle, c"u_point_count".as_ptr()),
1552 shadow_matrix: glGetUniformLocation(handle, c"u_shadow_matrix".as_ptr()),
1553 shadow_texture: glGetUniformLocation(handle, c"u_shadow_texture".as_ptr()),
1554 shadow_options: glGetUniformLocation(handle, c"u_shadow_options".as_ptr()),
1555 shadow_extra: glGetUniformLocation(handle, c"u_shadow_extra".as_ptr()),
1556 })
1557 }
1558 }
1559 }
1560 impl RetainedGpu {
1561 fn configure_shadow(&mut self) {
1562 let wanted = self.environment.shadow_size;
1563 if wanted == 0 {
1564 if let Some(old) = self.shadow.take() {
1565 unsafe {
1566 old.delete();
1567 }
1568 }
1569 self.shadow_failed_size = 0;
1570 return;
1571 }
1572 if self.shadow.as_ref().is_some_and(|s| s.size == wanted)
1573 || self.shadow_failed_size == wanted
1574 {
1575 return;
1576 }
1577 match ShadowTarget::new(wanted) {
1578 Ok(target) => {
1579 if let Some(old) = self.shadow.replace(target) {
1580 unsafe {
1581 old.delete();
1582 }
1583 }
1584 self.shadow_failed_size = 0;
1585 }
1586 Err(error) => {
1587 eprintln!("Ferravex shadows: {error}");
1588 self.shadow_failed_size = wanted;
1589 }
1590 }
1591 }
1592 unsafe fn render_shadow(&self) {
1593 let Some(target) = &self.shadow else {
1594 return;
1595 };
1596 if self.environment.shadow_size == 0 {
1597 return;
1598 }
1599 unsafe {
1601 glActiveTexture(0x84c0);
1602 glBindTexture(0x0de1, self.white_texture);
1603 glBindFramebuffer(0x8d40, target.framebuffer);
1604 glViewport(0, 0, target.size as i32, target.size as i32);
1605 glDepthMask(1);
1606 glClear(GL_DEPTH_BUFFER_BIT);
1607 glDisable(GL_BLEND);
1608 for index in 0..self.batches.len() {
1609 self.draw(index, true);
1610 }
1611 glEnable(GL_BLEND);
1612 glBindFramebuffer(0x8d40, 0);
1613 glDrawBuffer(0x0405);
1614 }
1615 }
1616 fn prepare_materials(&mut self) {
1617 self.materials.retain(|id, entry| {
1618 if crate::material::lookup(*id).is_some() {
1619 true
1620 } else {
1621 if let Some(program) = &entry.valid {
1623 unsafe {
1624 program.delete();
1625 }
1626 }
1627 false
1628 }
1629 });
1630 crate::material::for_each(|data| {
1631 let id = data.id;
1632 let revision = data.revision.get();
1633 let entry = self.materials.entry(id).or_insert(MaterialProgram {
1634 revision: 0,
1635 valid: None,
1636 });
1637 if entry.revision == revision {
1638 return;
1639 }
1640 entry.revision = revision;
1641 match ProgramPair::compile(&data.source.borrow()) {
1642 Ok(program) => {
1643 if let Some(old) = entry.valid.replace(program) {
1644 unsafe {
1645 old.delete();
1646 }
1647 }
1648 crate::material::feedback(id, revision, Ok(()));
1649 }
1650 Err(error) => {
1651 eprintln!("Ferravex material {id} revision {revision} rejected: {error}");
1652 crate::material::feedback(id, revision, Err(&error));
1653 }
1654 }
1655 });
1656 }
1657 unsafe fn draw(&self, index: usize, shadow: bool) {
1658 let Some(batch) = self.batches.get(index) else {
1659 return;
1660 };
1661 if (shadow && (batch.depth & 1 == 0 || batch.depth & 8 != 0 || batch.light[15] < 0.5))
1662 || (!shadow && batch.depth & 32 != 0)
1663 {
1664 return;
1665 }
1666 let Some(&(buffer, count)) = self.meshes.get(&batch.mesh) else {
1667 return;
1668 };
1669 unsafe {
1672 set_depth(batch.depth & 1 != 0);
1673 glDepthMask((shadow || (batch.depth & 1 != 0 && batch.depth & 2 == 0)) as u8);
1674 if shadow {
1675 glDisable(GL_BLEND);
1676 } else {
1677 glEnable(GL_BLEND);
1678 glBlendFuncSeparate(
1679 GL_SRC_ALPHA,
1680 if batch.depth & 4 != 0 {
1681 1
1682 } else {
1683 GL_ONE_MINUS_SRC_ALPHA
1684 },
1685 1,
1686 GL_ONE_MINUS_SRC_ALPHA,
1687 );
1688 }
1689 let pair = self
1690 .materials
1691 .get(&batch.material)
1692 .and_then(|m| m.valid.as_ref())
1693 .unwrap_or(&self.builtin);
1694 let program = if shadow { &pair.depth } else { &pair.color };
1695 glUseProgram(program.handle);
1696 glUniformMatrix4fv(
1697 program.camera,
1698 1,
1699 0,
1700 if shadow {
1701 self.environment.shadow_matrix.as_ptr()
1702 } else {
1703 batch.camera.as_ptr()
1704 },
1705 );
1706 glUniform4fv(program.light, 4, batch.light.as_ptr());
1707 glUniform4fv(program.params, 4, batch.params.as_ptr());
1708 if !shadow {
1709 let e = &self.environment;
1710 let count = if batch.light[3] > 0.5 {
1711 e.light_count
1712 } else {
1713 0
1714 };
1715 glUniform1i(program.point_count, count as i32);
1716 if count > 0 {
1717 glUniform4fv(program.points, 16, e.points.as_ptr());
1718 }
1719 glUniformMatrix4fv(program.shadow_matrix, 1, 0, e.shadow_matrix.as_ptr());
1720 let mut options = e.shadow_options;
1721 let matches = batch.light[0] * e.shadow_direction[0]
1722 + batch.light[1] * e.shadow_direction[1]
1723 + batch.light[2] * e.shadow_direction[2]
1724 > 0.999;
1725 options[3] = if self.shadow.is_some()
1726 && e.shadow_size > 0
1727 && batch.depth & 16 == 0
1728 && matches
1729 {
1730 1.
1731 } else {
1732 0.
1733 };
1734 let mut extra = e.shadow_extra;
1735 if let Some(target) = &self.shadow {
1736 extra[1] = 1. / target.size as f32;
1737 }
1738 glUniform4fv(program.shadow_options, 1, options.as_ptr());
1739 glUniform4fv(program.shadow_extra, 1, extra.as_ptr());
1740 glUniform1i(program.shadow_texture, 0);
1741 glActiveTexture(0x84c0);
1742 glBindTexture(
1743 0x0de1,
1744 self.shadow
1745 .as_ref()
1746 .filter(|_| e.shadow_size > 0)
1747 .map_or(self.white_texture, |s| s.texture),
1748 );
1749 }
1750 glBindBuffer(GL_ARRAY_BUFFER, buffer);
1751 for (attribute, size, offset) in [(0, 3, 0), (1, 3, 12), (2, 4, 24)] {
1752 glEnableVertexAttribArray(attribute);
1753 glVertexAttribPointer(
1754 attribute,
1755 size,
1756 GL_FLOAT,
1757 0,
1758 40,
1759 offset as *const c_void,
1760 );
1761 }
1762 glBindBuffer(GL_ARRAY_BUFFER, self.instances);
1763 for i in 0..7 {
1764 let at = 3 + i;
1765 glEnableVertexAttribArray(at);
1766 glVertexAttribPointer(
1767 at,
1768 4,
1769 GL_FLOAT,
1770 0,
1771 112,
1772 (batch.first as usize * 112 + i as usize * 16) as *const c_void,
1773 );
1774 glVertexAttribDivisor(at, 1);
1775 }
1776 glDrawArraysInstanced(GL_TRIANGLES, 0, count, batch.count as i32);
1777 glBlendFuncSeparate(
1778 GL_SRC_ALPHA,
1779 GL_ONE_MINUS_SRC_ALPHA,
1780 1,
1781 GL_ONE_MINUS_SRC_ALPHA,
1782 );
1783 for at in 0..10 {
1784 glDisableVertexAttribArray(at);
1785 if at >= 3 {
1786 glVertexAttribDivisor(at, 0);
1787 }
1788 }
1789 }
1790 }
1791 }
1792 fn flip_rows(bytes: &mut [u8], stride: usize, height: usize) {
1793 for row in 0..height / 2 {
1794 let opposite = (height - 1 - row) * stride;
1795 let (top, bottom) = bytes.split_at_mut(opposite);
1796 top[row * stride..(row + 1) * stride].swap_with_slice(&mut bottom[..stride]);
1797 }
1798 }
1799 fn compile_shader(kind: c_uint, source: &CStr) -> Result<c_uint, String> {
1800 unsafe {
1803 let shader = glCreateShader(kind);
1804 if shader == 0 {
1805 return Err("OpenGL 2.0 shader creation failed".into());
1806 }
1807 let pointer = source.as_ptr();
1808 glShaderSource(shader, 1, &pointer, ptr::null());
1809 glCompileShader(shader);
1810 let mut success = 0;
1811 glGetShaderiv(shader, GL_COMPILE_STATUS, &mut success);
1812 if success == 0 {
1813 let message = shader_log(shader);
1814 glDeleteShader(shader);
1815 return Err(format!("Native shader compilation failed: {message}"));
1816 }
1817 Ok(shader)
1818 }
1819 }
1820 fn shader_log(shader: c_uint) -> String {
1821 unsafe {
1823 let mut count = 0;
1824 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &mut count);
1825 let mut bytes = vec![0; count.clamp(1, 16384) as usize];
1826 let mut written = 0;
1827 glGetShaderInfoLog(
1828 shader,
1829 bytes.len() as i32,
1830 &mut written,
1831 bytes.as_mut_ptr().cast(),
1832 );
1833 String::from_utf8_lossy(&bytes[..(written.max(0) as usize).min(bytes.len())])
1834 .into_owned()
1835 }
1836 }
1837 fn create_program() -> Result<c_uint, String> {
1838 create_program_with(c"#version 120\nattribute vec4 a_position;\nattribute vec4 a_color;\nvarying vec4 v_color;\nvoid main(){ gl_Position=a_position; v_color=a_color; }\n", &[(0,c"a_position"),(1,c"a_color")])
1839 }
1840 fn create_program_with(source: &CStr, attributes: &[(u32, &CStr)]) -> Result<c_uint, String> {
1841 create_program_stages(
1842 source,
1843 c"#version 120\nvarying vec4 v_color;\nvoid main(){ gl_FragColor=v_color; }\n",
1844 attributes,
1845 )
1846 }
1847 fn create_program_stages(
1848 source: &CStr,
1849 fragment_source: &CStr,
1850 attributes: &[(u32, &CStr)],
1851 ) -> Result<c_uint, String> {
1852 let vertex = compile_shader(GL_VERTEX_SHADER, source)?;
1853 let fragment = match compile_shader(GL_FRAGMENT_SHADER, fragment_source) {
1854 Ok(shader) => shader,
1855 Err(error) => {
1856 unsafe {
1858 glDeleteShader(vertex);
1859 }
1860 return Err(error);
1861 }
1862 };
1863 unsafe {
1865 let program = glCreateProgram();
1866 if program == 0 {
1867 glDeleteShader(vertex);
1868 glDeleteShader(fragment);
1869 return Err("OpenGL program allocation failed".into());
1870 }
1871 glAttachShader(program, vertex);
1872 glAttachShader(program, fragment);
1873 for &(index, name) in attributes {
1874 glBindAttribLocation(program, index, name.as_ptr());
1875 }
1876 glLinkProgram(program);
1877 glDeleteShader(vertex);
1878 glDeleteShader(fragment);
1879 let mut success = 0;
1880 glGetProgramiv(program, GL_LINK_STATUS, &mut success);
1881 if success == 0 {
1882 let mut count = 0;
1883 glGetProgramiv(program, GL_INFO_LOG_LENGTH, &mut count);
1884 let mut bytes = vec![0; count.clamp(1, 16384) as usize];
1885 let mut written = 0;
1886 glGetProgramInfoLog(
1887 program,
1888 bytes.len() as i32,
1889 &mut written,
1890 bytes.as_mut_ptr().cast(),
1891 );
1892 let message =
1893 String::from_utf8_lossy(&bytes[..(written.max(0) as usize).min(bytes.len())]);
1894 glDeleteProgram(program);
1895 return Err(format!("Native shader linkage failed: {message}"));
1896 }
1897 Ok(program)
1898 }
1899 }
1900 #[cfg(test)]
1901 mod tests {
1902 use super::*;
1903 #[test]
1904 #[ignore = "requires a live X11 display and OpenGL context"]
1905 fn shared_shadow_map_and_point_lights_change_rendered_pixels() {
1906 use crate::{
1907 Mat4, Vec3,
1908 lighting::{Environment, PointLight, Quality, SunShadow},
1909 mesh::Mesh3d,
1910 retained::Frame,
1911 shaders::LitSettings,
1912 };
1913 let mut window = NativeWindow::new(&Conf {
1914 window_width: 192,
1915 window_height: 128,
1916 ..Default::default()
1917 })
1918 .unwrap();
1919 window.set_swap_interval(0);
1920 let floor = Mesh3d::plane(crate::Vec2::splat(8.), crate::WHITE, 1);
1921 let cube = Mesh3d::cuboid(Vec3::ONE, crate::RED);
1922 let eye = Vec3::new(4., 5., 6.);
1923 let camera = Mat4::perspective_rh_gl(0.8, 1.5, 0.1, 50.)
1924 * Mat4::look_at_rh(eye, Vec3::ZERO, Vec3::Y);
1925 let sun = SunShadow {
1926 direction: Vec3::new(-0.5, 1., -0.2),
1927 extent: 6.,
1928 depth: 15.,
1929 strength: 1.,
1930 ..Default::default()
1931 };
1932 let light = LitSettings {
1933 camera_position: eye,
1934 light_direction: sun.direction,
1935 ambient: 0.3,
1936 fog_start: 100.,
1937 fog_end: 200.,
1938 ..Default::default()
1939 }
1940 .sanitized();
1941 let mut frame = Frame::default();
1942 frame.record(floor.resident(), Mat4::IDENTITY, camera, true, Some(light));
1943 frame.record(
1944 cube.resident(),
1945 Mat4::from_translation(Vec3::Y * 0.5),
1946 camera,
1947 true,
1948 Some(light),
1949 );
1950 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
1951 let lit = window.pixels();
1952 frame
1953 .environment
1954 .update(&[], Some(sun), Quality::High, Some(eye));
1955 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
1956 let shaded = window.pixels();
1957 let darker = lit
1958 .chunks_exact(4)
1959 .zip(shaded.chunks_exact(4))
1960 .filter(|(a, b)| {
1961 a[0] > 150
1962 && a[0].abs_diff(a[1]) < 3
1963 && a[1].abs_diff(a[2]) < 3
1964 && a[0].saturating_sub(b[0]) > 40
1965 })
1966 .count();
1967 assert!(darker > 20, "no cast shadow found: {darker}");
1968 assert_eq!(
1969 window
1970 .retained
1971 .as_ref()
1972 .unwrap()
1973 .shadow
1974 .as_ref()
1975 .unwrap()
1976 .size,
1977 1024
1978 );
1979 frame
1980 .environment
1981 .update(&[], Some(sun), Quality::Medium, Some(eye));
1982 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
1983 assert_eq!(
1984 window
1985 .retained
1986 .as_ref()
1987 .unwrap()
1988 .shadow
1989 .as_ref()
1990 .unwrap()
1991 .size,
1992 512
1993 );
1994 drop(cube);
1995 frame.clear();
1996 frame.environment = Environment::default();
1997 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
1998 assert!(
1999 window.retained.as_ref().unwrap().shadow.is_none(),
2000 "empty Low frame must release the old shadow target"
2001 );
2002 assert_eq!(
2003 window.retained.as_ref().unwrap().meshes.len(),
2004 1,
2005 "empty frame must collect expired GPU meshes"
2006 );
2007 let light = LitSettings {
2008 light_direction: Vec3::ZERO,
2009 ambient: 0.1,
2010 ..light
2011 };
2012 frame.record(floor.resident(), Mat4::IDENTITY, camera, true, Some(light));
2013 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
2014 let dark = window.pixels();
2015 frame.environment.update(
2016 &[PointLight {
2017 position: Vec3::new(0., 1., 0.),
2018 color: Color::new(1., 0., 0., 1.),
2019 radius: 4.,
2020 intensity: 2.,
2021 }],
2022 None,
2023 Quality::Medium,
2024 Some(eye),
2025 );
2026 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
2027 let points = window.pixels();
2028 assert!(
2029 dark.chunks_exact(4)
2030 .zip(points.chunks_exact(4))
2031 .filter(
2032 |(a, b)| b[0] > a[0].saturating_add(20) && b[0] > b[1].saturating_add(20)
2033 )
2034 .count()
2035 > 50,
2036 "point light did not illuminate floor"
2037 );
2038 }
2039 #[test]
2040 #[ignore = "requires a live X11 display and OpenGL context"]
2041 fn material_alpha_and_additive_preserve_depth_and_following_ui() {
2042 use crate::{
2043 Mat4,
2044 material::{BlendMode, MaterialOptions, ShaderMaterial, ShaderSource},
2045 mesh::Mesh3d,
2046 retained::{Command, DrawStyle, Frame},
2047 };
2048 let mut window = NativeWindow::new(&Conf {
2049 window_width: 96,
2050 window_height: 64,
2051 ..Default::default()
2052 })
2053 .unwrap();
2054 window.set_swap_interval(0);
2055 let mesh = Mesh3d::plane(crate::Vec2::ONE, crate::WHITE, 1);
2056 let model = Mat4::from_rotation_x(std::f32::consts::FRAC_PI_2);
2057 let alpha = ShaderMaterial::new(
2058 ShaderSource::fragment(
2059 "vec4 ferravex_fragment(vec4 c,vec3 w,vec3 n){return vec4(0.0,0.0,1.0,0.5);}",
2060 ),
2061 Default::default(),
2062 )
2063 .unwrap();
2064 let mut frame = Frame::default();
2065 frame.record_style(
2066 mesh.resident(),
2067 model,
2068 Mat4::IDENTITY,
2069 true,
2070 DrawStyle {
2071 material: Some(&alpha),
2072 ..Default::default()
2073 },
2074 );
2075 let center = |window: &NativeWindow| {
2076 let pixels = window.pixels();
2077 let (w, h) = window.size();
2078 let at = ((h / 2 * w + w / 2) * 4) as usize;
2079 pixels[at..at + 4].to_vec()
2080 };
2081 window.present_scene(&[], &[], Color::new(1., 0., 0., 1.), Some(&frame));
2082 let p = center(&window);
2083 assert!((127..=129).contains(&p[0]) && (127..=129).contains(&p[2]));
2084 let behind = [
2085 [-1., -1., 0.8, 1., 0., 1., 0., 1.],
2086 [3., -1., 0.8, 1., 0., 1., 0., 1.],
2087 [-1., 3., 0.8, 1., 0., 1., 0., 1.],
2088 ];
2089 frame.commands.push(Command { kind: 0, index: 0 });
2090 window.present_scene(
2091 &behind,
2092 &[Batch {
2093 start: 0,
2094 count: 3,
2095 depth: true,
2096 }],
2097 Color::new(1., 0., 0., 1.),
2098 Some(&frame),
2099 );
2100 assert_eq!(
2101 center(&window),
2102 [0, 255, 0, 255],
2103 "alpha must not write depth"
2104 );
2105 let additive = ShaderMaterial::new(
2106 ShaderSource::fragment(
2107 "vec4 ferravex_fragment(vec4 c,vec3 w,vec3 n){return vec4(1.0,0.0,0.0,0.25);}",
2108 ),
2109 MaterialOptions {
2110 blend: BlendMode::Additive,
2111 ..Default::default()
2112 },
2113 )
2114 .unwrap();
2115 frame.clear();
2116 frame.record_style(
2117 mesh.resident(),
2118 model,
2119 Mat4::IDENTITY,
2120 true,
2121 DrawStyle {
2122 material: Some(&additive),
2123 ..Default::default()
2124 },
2125 );
2126 window.present_scene(&[], &[], Color::new(0., 1., 0., 1.), Some(&frame));
2127 let p = center(&window);
2128 assert!((63..=65).contains(&p[0]));
2129 assert_eq!(p[1], 255);
2130 let mut ui = behind;
2131 for v in &mut ui {
2132 v[4..8].copy_from_slice(&[0., 0., 1., 1.]);
2133 }
2134 frame.commands.push(Command { kind: 0, index: 0 });
2135 window.present_scene(
2136 &ui,
2137 &[Batch {
2138 start: 0,
2139 count: 3,
2140 depth: false,
2141 }],
2142 Color::new(0., 1., 0., 1.),
2143 Some(&frame),
2144 );
2145 assert_eq!(
2146 center(&window),
2147 [0, 0, 255, 255],
2148 "additive blend must not leak into UI"
2149 );
2150 }
2151 #[test]
2152 #[ignore = "requires a live X11 display and OpenGL context"]
2153 fn material_reload_keeps_last_valid_program_and_updates_vertex_hooks() {
2154 use crate::{
2155 Mat4, Vec3,
2156 material::{CompileStatus, MaterialOptions, ShaderMaterial, ShaderSource},
2157 mesh::Mesh3d,
2158 retained::{DrawStyle, Frame},
2159 };
2160 let mut window = NativeWindow::new(&Conf {
2161 window_width: 96,
2162 window_height: 64,
2163 ..Default::default()
2164 })
2165 .unwrap();
2166 window.set_swap_interval(0);
2167 let mesh = Mesh3d::cuboid(Vec3::ONE, crate::GREEN);
2168 let material = ShaderMaterial::new(
2169 ShaderSource::fragment(
2170 "vec4 ferravex_fragment(vec4 c,vec3 w,vec3 n){return u_params[0];}",
2171 ),
2172 MaterialOptions {
2173 blend: crate::material::BlendMode::Opaque,
2174 bounds_padding: 1.,
2175 ..Default::default()
2176 },
2177 )
2178 .unwrap();
2179 let mut frame = Frame::default();
2180 let mut params = [0.; 16];
2181 params[..4].copy_from_slice(&[1., 0., 0., 1.]);
2182 frame.record_style(
2183 mesh.resident(),
2184 Mat4::IDENTITY,
2185 Mat4::IDENTITY,
2186 true,
2187 DrawStyle {
2188 material: Some(&material),
2189 params,
2190 ..Default::default()
2191 },
2192 );
2193 let center = |window: &NativeWindow| {
2194 let pixels = window.pixels();
2195 let (w, h) = window.size();
2196 let at = ((h / 2 * w + w / 2) * 4) as usize;
2197 pixels[at..at + 4].to_vec()
2198 };
2199 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
2200 assert_eq!(center(&window), [255, 0, 0, 255]);
2201 assert_eq!(material.status().status, CompileStatus::Ready);
2202 material
2203 .reload(ShaderSource::fragment("this is invalid GLSL"))
2204 .unwrap();
2205 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
2206 assert_eq!(center(&window), [255, 0, 0, 255]);
2207 assert!(matches!(material.status().status, CompileStatus::Failed(_)));
2208 assert_eq!(material.status().last_valid_revision, Some(1));
2209 material
2210 .reload(ShaderSource::fragment(
2211 "vec4 ferravex_fragment(vec4 c,vec3 w,vec3 n){return vec4(0.0,0.0,1.0,1.0);}",
2212 ))
2213 .unwrap();
2214 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
2215 assert_eq!(center(&window), [0, 0, 255, 255]);
2216 assert_eq!(material.status().last_valid_revision, Some(3));
2217 material.reload(ShaderSource{vertex:"vec3 ferravex_vertex(vec3 p){return p+vec3(0.8,0.0,0.0);}\nvec3 ferravex_normal(vec3 n){return n;}".into(),..Default::default()}).unwrap();
2218 window.present_scene(&[], &[], crate::BLACK, Some(&frame));
2219 assert_eq!(center(&window), [0, 0, 0, 255]);
2220 assert_eq!(material.status().last_valid_revision, Some(4));
2221 }
2222 #[test]
2223 #[ignore = "requires a live X11 display and OpenGL context"]
2224 fn retained_instances_share_upload_and_preserve_ui_order() {
2225 use crate::{
2226 Mat4, Mesh, Vec2, Vec3, Vertex,
2227 retained::{Command, Frame, ResidentMesh},
2228 };
2229 let mut window = NativeWindow::new(&Conf {
2230 window_width: 96,
2231 window_height: 64,
2232 ..Default::default()
2233 })
2234 .unwrap();
2235 window.set_swap_interval(0);
2236 let mesh = ResidentMesh::new(&Mesh {
2237 vertices: vec![
2238 Vertex::new2(Vec3::new(-0.6, -0.6, -0.5), Vec2::ZERO, crate::GREEN),
2239 Vertex::new2(Vec3::new(0.6, -0.6, -0.5), Vec2::ZERO, crate::GREEN),
2240 Vertex::new2(Vec3::new(0., 0.6, -0.5), Vec2::ZERO, crate::GREEN),
2241 ],
2242 indices: vec![0, 1, 2],
2243 texture: None,
2244 })
2245 .unwrap();
2246 let mut frame = Frame::default();
2247 frame.commands.push(Command { kind: 0, index: 0 });
2248 for x in [-0.5, 0.5] {
2249 frame.record(
2250 &mesh,
2251 Mat4::from_translation(Vec3::new(x, 0., 0.))
2252 * Mat4::from_scale(Vec3::splat(0.5)),
2253 Mat4::IDENTITY,
2254 true,
2255 None,
2256 );
2257 }
2258 frame.commands.push(Command { kind: 0, index: 1 });
2259 assert_eq!(frame.batches[0].count, 2);
2260 let vertices = [
2261 [-1., -1., 0.8, 1., 1., 0., 0., 1.],
2262 [3., -1., 0.8, 1., 1., 0., 0., 1.],
2263 [-1., 3., 0.8, 1., 1., 0., 0., 1.],
2264 [-0.15, -0.15, 0., 1., 0., 0., 1., 1.],
2265 [0.15, -0.15, 0., 1., 0., 0., 1., 1.],
2266 [0., 0.15, 0., 1., 0., 0., 1., 1.],
2267 ];
2268 let batches = [
2269 Batch {
2270 start: 0,
2271 count: 3,
2272 depth: true,
2273 },
2274 Batch {
2275 start: 3,
2276 count: 3,
2277 depth: false,
2278 },
2279 ];
2280 window.present_scene(&vertices, &batches, crate::BLACK, Some(&frame));
2281 assert_eq!(window.last_mesh_upload_bytes(), 120);
2282 window.present_scene(&vertices, &batches, crate::BLACK, Some(&frame));
2283 assert_eq!(window.last_mesh_upload_bytes(), 0);
2284 let pixels = window.pixels();
2285 let (w, h) = window.size();
2286 for x in [w / 4, w * 3 / 4] {
2287 let at = ((h / 2 * w + x) * 4) as usize;
2288 assert!(
2289 pixels[at + 1] > 150 && pixels[at] < 80,
2290 "instanced green triangle missing"
2291 );
2292 }
2293 let at = ((h / 2 * w + w / 2) * 4) as usize;
2294 assert_eq!(&pixels[at..at + 4], &[0, 0, 255, 255]);
2295 }
2296 #[test]
2297 fn event_union_covers_all_public_records() {
2298 assert_eq!(
2299 std::mem::size_of::<XEvent>(),
2300 24 * std::mem::size_of::<c_long>()
2301 );
2302 assert!(std::mem::size_of::<XKeyEvent>() <= std::mem::size_of::<XEvent>());
2303 assert!(std::mem::size_of::<XMotionEvent>() <= std::mem::size_of::<XEvent>());
2304 assert!(std::mem::size_of::<XConfigureEvent>() <= std::mem::size_of::<XEvent>());
2305 assert!(std::mem::size_of::<XClientMessageEvent>() <= std::mem::size_of::<XEvent>());
2306 }
2307 #[test]
2308 #[ignore = "requires a live X11 display and OpenGL context"]
2309 fn native_gpu_depth_and_readback() {
2310 let conf = Conf {
2311 window_title: "Ferravex native GPU verification".into(),
2312 window_width: 96,
2313 window_height: 64,
2314 high_dpi: false,
2315 sample_count: 0,
2316 };
2317 let mut window = NativeWindow::new(&conf).expect("create native window");
2318 window.set_swap_interval(0);
2319 println!("{}", window.driver_info());
2320 let vertices = [
2321 [-1.0, -1.0, 0.7, 1.0, 1.0, 0.0, 0.0, 1.0],
2322 [3.0, -1.0, 0.7, 1.0, 1.0, 0.0, 0.0, 1.0],
2323 [-1.0, 3.0, 0.7, 1.0, 1.0, 0.0, 0.0, 1.0],
2324 [-0.8, -0.8, -0.5, 1.0, 0.0, 1.0, 0.0, 1.0],
2325 [0.8, -0.8, -0.5, 1.0, 0.0, 1.0, 0.0, 1.0],
2326 [0.0, 0.8, -0.5, 1.0, 0.0, 1.0, 0.0, 1.0],
2327 ];
2328 let batches = [
2329 Batch {
2330 start: 3,
2331 count: 3,
2332 depth: true,
2333 },
2334 Batch {
2335 start: 0,
2336 count: 3,
2337 depth: true,
2338 },
2339 ];
2340 window.poll();
2341 window.present(&vertices, &batches, Color::new(0.0, 0.0, 0.0, 1.0));
2342 let (width, height) = window.size();
2343 let pixels = window.pixels();
2344 assert_eq!(pixels.len(), (width * height * 4) as usize);
2345 let center = ((height / 2 * width + width / 2) * 4) as usize;
2346 assert_eq!(
2347 &pixels[center..center + 4],
2348 &[0, 255, 0, 255],
2349 "near triangle must occlude later far triangle"
2350 );
2351 let corner = ((2 * width + 2) * 4) as usize;
2352 assert_eq!(&pixels[corner..corner + 4], &[255, 0, 0, 255]);
2353 window.set_cursor(true, false);
2354 window.poll();
2355 window.set_cursor(false, true);
2356 if let Ok(path) = std::env::var("FERRAVEX_NATIVE_SCREENSHOT") {
2357 let mut frame = ferravex_graphics::Framebuffer::new(width, height).unwrap();
2358 for (destination, source) in
2359 frame.pixels_mut().iter_mut().zip(pixels.as_chunks::<4>().0)
2360 {
2361 destination.copy_from_slice(source);
2362 }
2363 frame.save_png(path).unwrap();
2364 }
2365 }
2366 #[test]
2367 #[ignore = "requires a live X11 display and OpenGL context"]
2368 fn timer_results_preserve_frame_tags_and_render_counts() {
2369 let mut window = NativeWindow::new(&Conf {
2370 window_width: 96,
2371 window_height: 64,
2372 ..Default::default()
2373 })
2374 .unwrap();
2375 window.set_swap_interval(0);
2376 assert!(window.enable_gpu_timing());
2377 for _ in 0..4 {
2378 window.present(&[], &[], crate::BLACK);
2379 }
2380 window.finish_gpu_timing();
2381 let mut results = Vec::new();
2382 window.drain_gpu_results(|frame, us| results.push((frame, us)));
2383 assert_eq!(
2384 results.iter().map(|r| r.0).collect::<Vec<_>>(),
2385 [1, 2, 3, 4]
2386 );
2387 assert!(results.iter().all(|r| r.1.is_finite() && r.1 >= 0.));
2388 assert_eq!(window.render_counts(), (0, 0));
2389 }
2390 #[test]
2391 fn screenshot_rows_flip_without_channel_or_center_row_changes() {
2392 let mut bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
2393 flip_rows(&mut bytes, 4, 3);
2394 assert_eq!(bytes, [9, 10, 11, 12, 5, 6, 7, 8, 1, 2, 3, 4]);
2395 }
2396 }
2397}