1use core::slice;
6use std::rc::Rc;
7
8use euclid::{
9 Angle, Point2D, Rect, RigidTransform3D, Rotation3D, Size2D, Transform3D, UnknownUnit, Vector3D,
10};
11use glow::{self as gl, Context as Gl, HasContext};
12use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
13use raw_window_handle::DisplayHandle;
14use surfman::chains::{PreserveBuffer, SwapChain, SwapChainAPI, SwapChains, SwapChainsAPI};
15use surfman::{
16 Adapter, Connection, Context as SurfmanContext, ContextAttributeFlags, ContextAttributes,
17 Device as SurfmanDevice, GLApi, GLVersion, NativeWidget, SurfaceAccess, SurfaceType,
18};
19use webxr_api::util::ClipPlanes;
20use webxr_api::{
21 CUBE_BACK, CUBE_BOTTOM, CUBE_LEFT, CUBE_RIGHT, CUBE_TOP, ContextId, DeviceAPI, DiscoveryAPI,
22 Display, Error, Event, EventBuffer, Floor, Frame, InputSource, LEFT_EYE, LayerGrandManager,
23 LayerId, LayerInit, LayerManager, Native, Quitter, RIGHT_EYE, Session, SessionBuilder,
24 SessionInit, SessionMode, SomeEye, VIEWER, View, Viewer, ViewerPose, Viewport, Viewports,
25 Views,
26};
27
28use crate::{SurfmanGL, SurfmanLayerManager};
29
30const HEIGHT: f32 = 1.0;
32
33const FOV_UP: f32 = 45.0;
35
36const INTER_PUPILLARY_DISTANCE: f32 = 0.06;
40
41const PIXELS_PER_METRE: f32 = 6000.0;
43
44pub trait GlWindow {
45 fn get_render_target(
46 &self,
47 device: &mut SurfmanDevice,
48 context: &mut SurfmanContext,
49 ) -> GlWindowRenderTarget;
50 fn get_rotation(&self) -> Rotation3D<f32, UnknownUnit, UnknownUnit>;
51 fn get_translation(&self) -> Vector3D<f32, UnknownUnit>;
52
53 fn get_mode(&self) -> GlWindowMode {
54 GlWindowMode::Blit
55 }
56 fn display_handle(&self) -> DisplayHandle<'_>;
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum GlWindowMode {
61 Blit,
62 StereoLeftRight,
63 StereoRedCyan,
64 Cubemap,
65 Spherical,
66}
67
68pub enum GlWindowRenderTarget {
69 NativeWidget(NativeWidget),
70 SwapChain(SwapChain<SurfmanDevice>),
71}
72
73pub struct GlWindowDiscovery {
74 connection: Connection,
75 adapter: Adapter,
76 context_attributes: ContextAttributes,
77 window: Rc<dyn GlWindow>,
78}
79
80impl GlWindowDiscovery {
81 pub fn new(window: Rc<dyn GlWindow>) -> GlWindowDiscovery {
82 let connection = Connection::from_display_handle(window.display_handle()).unwrap();
83 let adapter = connection.create_adapter().unwrap();
84 let flags = ContextAttributeFlags::ALPHA |
85 ContextAttributeFlags::DEPTH |
86 ContextAttributeFlags::STENCIL;
87 let version = match connection.gl_api() {
88 GLApi::GLES => GLVersion { major: 3, minor: 0 },
89 GLApi::GL => GLVersion { major: 3, minor: 2 },
90 };
91 let context_attributes = ContextAttributes { flags, version };
92 GlWindowDiscovery {
93 connection,
94 adapter,
95 context_attributes,
96 window,
97 }
98 }
99}
100
101impl DiscoveryAPI<SurfmanGL> for GlWindowDiscovery {
102 fn request_session(
103 &mut self,
104 mode: SessionMode,
105 init: &SessionInit,
106 xr: SessionBuilder<SurfmanGL>,
107 ) -> Result<Session, Error> {
108 if self.supports_session(mode) {
109 let granted_features = init.validate(mode, &["local-floor".into()])?;
110 let connection = self.connection.clone();
111 let adapter = self.adapter.clone();
112 let context_attributes = self.context_attributes;
113 let window = self.window.clone();
114 xr.run_on_main_thread(move |grand_manager| {
115 GlWindowDevice::new(
116 connection,
117 adapter,
118 context_attributes,
119 window,
120 granted_features,
121 grand_manager,
122 )
123 })
124 } else {
125 Err(Error::NoMatchingDevice)
126 }
127 }
128
129 fn supports_session(&self, mode: SessionMode) -> bool {
130 mode == SessionMode::ImmersiveVR || mode == SessionMode::ImmersiveAR
131 }
132}
133
134pub struct GlWindowDevice {
135 device: SurfmanDevice,
136 context: SurfmanContext,
137 gl: Rc<Gl>,
138 window: Rc<dyn GlWindow>,
139 grand_manager: LayerGrandManager<SurfmanGL>,
140 layer_manager: Option<LayerManager>,
141 target_swap_chain: Option<SwapChain<SurfmanDevice>>,
142 swap_chains: SwapChains<LayerId, SurfmanDevice>,
143 read_fbo: Option<gl::NativeFramebuffer>,
144 events: EventBuffer,
145 clip_planes: ClipPlanes,
146 granted_features: Vec<String>,
147 shader: Option<GlWindowShader>,
148}
149
150impl DeviceAPI for GlWindowDevice {
151 fn floor_transform(&self) -> Option<RigidTransform3D<f32, Native, Floor>> {
152 let translation = Vector3D::new(0.0, HEIGHT, 0.0);
153 Some(RigidTransform3D::from_translation(translation))
154 }
155
156 fn viewports(&self) -> Viewports {
157 let size = self.viewport_size();
158 let viewports = match self.window.get_mode() {
159 #[expect(clippy::erasing_op, clippy::identity_op)]
160 GlWindowMode::Cubemap | GlWindowMode::Spherical => vec![
161 Rect::new(Point2D::new(size.width * 1, size.height * 1), size),
162 Rect::new(Point2D::new(size.width * 0, size.height * 1), size),
163 Rect::new(Point2D::new(size.width * 2, size.height * 1), size),
164 Rect::new(Point2D::new(size.width * 2, size.height * 0), size),
165 Rect::new(Point2D::new(size.width * 0, size.height * 0), size),
166 Rect::new(Point2D::new(size.width * 1, size.height * 0), size),
167 ],
168 GlWindowMode::Blit | GlWindowMode::StereoLeftRight | GlWindowMode::StereoRedCyan => {
169 vec![
170 Rect::new(Point2D::default(), size),
171 Rect::new(Point2D::new(size.width, 0), size),
172 ]
173 },
174 };
175 Viewports { viewports }
176 }
177
178 fn create_layer(&mut self, context_id: ContextId, init: LayerInit) -> Result<LayerId, Error> {
179 self.layer_manager()?.create_layer(context_id, init)
180 }
181
182 fn destroy_layer(&mut self, context_id: ContextId, layer_id: LayerId) {
183 self.layer_manager()
184 .unwrap()
185 .destroy_layer(context_id, layer_id)
186 }
187
188 fn begin_animation_frame(&mut self, layers: &[(ContextId, LayerId)]) -> Option<Frame> {
189 log::debug!("Begin animation frame for layers {:?}", layers);
190 let translation = Vector3D::from_untyped(self.window.get_translation());
191 let translation: RigidTransform3D<_, _, Native> =
192 RigidTransform3D::from_translation(translation);
193 let rotation = Rotation3D::from_untyped(&self.window.get_rotation());
194 let rotation = RigidTransform3D::from_rotation(rotation);
195 let transform = translation.then(&rotation);
196 let sub_images = self.layer_manager().ok()?.begin_frame(layers).ok()?;
197 Some(Frame {
198 pose: Some(ViewerPose {
199 transform,
200 views: self.views(transform),
201 }),
202 inputs: vec![],
203 events: vec![],
204 sub_images,
205 hit_test_results: vec![],
206 predicted_display_time: 0.0,
207 })
208 }
209
210 fn end_animation_frame(&mut self, layers: &[(ContextId, LayerId)]) {
211 log::debug!("End animation frame for layers {:?}", layers);
212 self.device.make_context_current(&self.context).unwrap();
213 debug_assert_eq!(unsafe { self.gl.get_error() }, gl::NO_ERROR);
214
215 let _ = self.layer_manager().unwrap().end_frame(layers);
216
217 let window_size = self.window_size();
218 let viewport_size = self.viewport_size();
219
220 let framebuffer_object = self
221 .device
222 .context_surface_info(&self.context)
223 .unwrap()
224 .and_then(|info| info.framebuffer_object);
225 unsafe {
226 self.gl
227 .bind_framebuffer(gl::FRAMEBUFFER, framebuffer_object);
228 debug_assert_eq!(
229 (
230 self.gl.get_error(),
231 self.gl.check_framebuffer_status(gl::FRAMEBUFFER)
232 ),
233 (gl::NO_ERROR, gl::FRAMEBUFFER_COMPLETE)
234 );
235
236 self.gl.clear_color(0.0, 0.0, 0.0, 0.0);
237 self.gl.clear(gl::COLOR_BUFFER_BIT);
238 debug_assert_eq!(self.gl.get_error(), gl::NO_ERROR);
239 }
240
241 for &(_, layer_id) in layers {
242 let swap_chain = match self.swap_chains.get(layer_id) {
243 Some(swap_chain) => swap_chain,
244 None => continue,
245 };
246 let surface = match swap_chain.take_surface() {
247 Some(surface) => surface,
248 None => return,
249 };
250 let texture_size = self.device.surface_info(&surface).size;
251 let surface_texture = self
252 .device
253 .create_surface_texture(&mut self.context, surface)
254 .unwrap();
255 let texture_id = self.device.surface_texture_object(&surface_texture);
256 let texture_target = self.device.surface_gl_texture_target();
257 log::debug!("Presenting texture {:?}", texture_id);
258
259 if let Some(ref shader) = self.shader {
260 shader.draw_texture(
261 texture_id,
262 texture_target,
263 texture_size,
264 viewport_size,
265 window_size,
266 );
267 } else {
268 self.blit_texture(texture_id, texture_target, texture_size, window_size);
269 }
270 debug_assert_eq!(unsafe { self.gl.get_error() }, gl::NO_ERROR);
271
272 let surface = self
273 .device
274 .destroy_surface_texture(&mut self.context, surface_texture)
275 .unwrap();
276 swap_chain.recycle_surface(surface);
277 }
278
279 match self.target_swap_chain.as_ref() {
280 Some(target_swap_chain) => {
281 target_swap_chain
283 .swap_buffers(&self.device, &mut self.context, PreserveBuffer::No)
284 .unwrap();
285 },
286 None => {
287 let mut surface = self
289 .device
290 .unbind_surface_from_context(&mut self.context)
291 .unwrap()
292 .unwrap();
293 self.device
294 .present_surface(&self.context, &mut surface)
295 .unwrap();
296 self.device
297 .bind_surface_to_context(&mut self.context, surface)
298 .unwrap();
299 },
300 }
301
302 debug_assert_eq!(unsafe { self.gl.get_error() }, gl::NO_ERROR);
303 }
304
305 fn initial_inputs(&self) -> Vec<InputSource> {
306 vec![]
307 }
308
309 fn set_event_dest(&mut self, dest: ProfileGenericCallback<Event>) {
310 self.events.upgrade(dest)
311 }
312
313 fn quit(&mut self) {
314 self.events.callback(Event::SessionEnd);
315 }
316
317 fn set_quitter(&mut self, _: Quitter) {
318 }
322
323 fn update_clip_planes(&mut self, near: f32, far: f32) {
324 self.clip_planes.update(near, far)
325 }
326
327 fn granted_features(&self) -> &[String] {
328 &self.granted_features
329 }
330}
331
332impl Drop for GlWindowDevice {
333 fn drop(&mut self) {
334 if let Some(read_fbo) = self.read_fbo {
335 unsafe {
336 self.gl.delete_framebuffer(read_fbo);
337 }
338 }
339 let _ = self.device.destroy_context(&mut self.context);
340 }
341}
342
343impl GlWindowDevice {
344 fn new(
345 connection: Connection,
346 adapter: Adapter,
347 context_attributes: ContextAttributes,
348 window: Rc<dyn GlWindow>,
349 granted_features: Vec<String>,
350 grand_manager: LayerGrandManager<SurfmanGL>,
351 ) -> Result<GlWindowDevice, Error> {
352 let mut device = connection.create_device(&adapter).unwrap();
353 let context_descriptor = device
354 .create_context_descriptor(&context_attributes)
355 .unwrap();
356 let mut context = device.create_context(&context_descriptor, None).unwrap();
357 device.make_context_current(&context).unwrap();
358
359 let gl = Rc::new(unsafe {
360 match device.gl_api() {
361 GLApi::GL => Gl::from_loader_function(|symbol_name| {
362 device.get_proc_address(&context, symbol_name)
363 }),
364 GLApi::GLES => Gl::from_loader_function(|symbol_name| {
365 device.get_proc_address(&context, symbol_name)
366 }),
367 }
368 });
369
370 let target_swap_chain = match window.get_render_target(&mut device, &mut context) {
371 GlWindowRenderTarget::NativeWidget(native_widget) => {
372 let surface_type = SurfaceType::Widget { native_widget };
373 let surface = device
374 .create_surface(&context, SurfaceAccess::GPUOnly, surface_type)
375 .unwrap();
376 device
377 .bind_surface_to_context(&mut context, surface)
378 .unwrap();
379 None
380 },
381 GlWindowRenderTarget::SwapChain(target_swap_chain) => {
382 debug_assert!(target_swap_chain.is_attached());
383 Some(target_swap_chain)
384 },
385 };
386
387 let read_fbo = unsafe { gl.create_framebuffer().ok() };
388 unsafe {
389 let framebuffer_object = device
390 .context_surface_info(&context)
391 .unwrap()
392 .and_then(|info| info.framebuffer_object);
393 gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_object);
394 debug_assert_eq!(
395 (gl.get_error(), gl.check_framebuffer_status(gl::FRAMEBUFFER)),
396 (gl::NO_ERROR, gl::FRAMEBUFFER_COMPLETE)
397 );
398
399 gl.enable(gl::BLEND);
400 gl.blend_func_separate(
401 gl::SRC_ALPHA,
402 gl::ONE_MINUS_SRC_ALPHA,
403 gl::ONE,
404 gl::ONE_MINUS_SRC_ALPHA,
405 );
406 }
407
408 let swap_chains = SwapChains::new();
409 let layer_manager = None;
410
411 let shader = GlWindowShader::new(gl.clone(), window.get_mode());
412 debug_assert_eq!(unsafe { gl.get_error() }, gl::NO_ERROR);
413
414 Ok(GlWindowDevice {
415 gl,
416 window,
417 device,
418 context,
419 read_fbo,
420 swap_chains,
421 target_swap_chain,
422 grand_manager,
423 layer_manager,
424 events: Default::default(),
425 clip_planes: Default::default(),
426 granted_features,
427 shader,
428 })
429 }
430
431 fn blit_texture(
432 &self,
433 texture_id: Option<gl::NativeTexture>,
434 texture_target: u32,
435 texture_size: Size2D<i32, UnknownUnit>,
436 window_size: Size2D<i32, Viewport>,
437 ) {
438 unsafe {
439 self.gl
440 .bind_framebuffer(gl::READ_FRAMEBUFFER, self.read_fbo);
441 self.gl.framebuffer_texture_2d(
442 gl::READ_FRAMEBUFFER,
443 gl::COLOR_ATTACHMENT0,
444 texture_target,
445 texture_id,
446 0,
447 );
448 self.gl.blit_framebuffer(
449 0,
450 0,
451 texture_size.width,
452 texture_size.height,
453 0,
454 0,
455 window_size.width,
456 window_size.height,
457 gl::COLOR_BUFFER_BIT,
458 gl::NEAREST,
459 );
460 }
461 }
462
463 fn layer_manager(&mut self) -> Result<&mut LayerManager, Error> {
464 if let Some(ref mut manager) = self.layer_manager {
465 return Ok(manager);
466 }
467 let swap_chains = self.swap_chains.clone();
468 let viewports = self.viewports();
469 let layer_manager = self
470 .grand_manager
471 .create_layer_manager(move |_| Ok(SurfmanLayerManager::new(viewports, swap_chains)))?;
472 self.layer_manager = Some(layer_manager);
473 Ok(self.layer_manager.as_mut().unwrap())
474 }
475
476 fn window_size(&self) -> Size2D<i32, Viewport> {
477 let window_size = self
478 .device
479 .context_surface_info(&self.context)
480 .unwrap()
481 .unwrap()
482 .size
483 .to_i32();
484 Size2D::from_untyped(window_size)
485 }
486
487 fn viewport_size(&self) -> Size2D<i32, Viewport> {
488 let window_size = self.window_size();
489 match self.window.get_mode() {
490 GlWindowMode::StereoRedCyan => {
491 let wasted_pixels = (INTER_PUPILLARY_DISTANCE / PIXELS_PER_METRE) as i32;
497 Size2D::new(window_size.width + wasted_pixels, window_size.height)
498 },
499 GlWindowMode::Cubemap => {
500 let size = 1.max(window_size.width / 3).max(window_size.height / 2);
502 Size2D::new(size, size)
503 },
504 GlWindowMode::Spherical => {
505 let size = 1.max(window_size.width / 2).max(window_size.height);
507 Size2D::new(size, size)
508 },
509 GlWindowMode::StereoLeftRight | GlWindowMode::Blit => {
510 Size2D::new(window_size.width / 2, window_size.height)
511 },
512 }
513 }
514
515 fn views(&self, viewer: RigidTransform3D<f32, Viewer, Native>) -> Views {
516 match self.window.get_mode() {
517 GlWindowMode::Cubemap | GlWindowMode::Spherical => Views::Cubemap(
518 self.view(viewer, VIEWER),
519 self.view(viewer, CUBE_LEFT),
520 self.view(viewer, CUBE_RIGHT),
521 self.view(viewer, CUBE_TOP),
522 self.view(viewer, CUBE_BOTTOM),
523 self.view(viewer, CUBE_BACK),
524 ),
525 GlWindowMode::Blit | GlWindowMode::StereoLeftRight | GlWindowMode::StereoRedCyan => {
526 Views::Stereo(self.view(viewer, LEFT_EYE), self.view(viewer, RIGHT_EYE))
527 },
528 }
529 }
530
531 fn view<Eye>(
532 &self,
533 viewer: RigidTransform3D<f32, Viewer, Native>,
534 eye: SomeEye<Eye>,
535 ) -> View<Eye> {
536 let projection = self.perspective();
537 let translation = if eye == RIGHT_EYE {
538 Vector3D::new(-INTER_PUPILLARY_DISTANCE / 2.0, 0.0, 0.0)
539 } else if eye == LEFT_EYE {
540 Vector3D::new(INTER_PUPILLARY_DISTANCE / 2.0, 0.0, 0.0)
541 } else {
542 Vector3D::zero()
543 };
544 let rotation = if eye == CUBE_TOP {
545 Rotation3D::euler(
546 Angle::degrees(270.0),
547 Angle::degrees(0.0),
548 Angle::degrees(90.0),
549 )
550 } else if eye == CUBE_BOTTOM {
551 Rotation3D::euler(
552 Angle::degrees(90.0),
553 Angle::degrees(0.0),
554 Angle::degrees(90.0),
555 )
556 } else if eye == CUBE_LEFT {
557 Rotation3D::around_y(Angle::degrees(-90.0))
558 } else if eye == CUBE_RIGHT {
559 Rotation3D::around_y(Angle::degrees(90.0))
560 } else if eye == CUBE_BACK {
561 Rotation3D::euler(
562 Angle::degrees(180.0),
563 Angle::degrees(0.0),
564 Angle::degrees(90.0),
565 )
566 } else {
567 Rotation3D::identity()
568 };
569 let transform: RigidTransform3D<f32, Viewer, Eye> =
570 RigidTransform3D::new(rotation, translation);
571 View {
572 transform: transform.inverse().then(&viewer),
573 projection,
574 }
575 }
576
577 fn perspective<Eye>(&self) -> Transform3D<f32, Eye, Display> {
578 let near = self.clip_planes.near;
579 let far = self.clip_planes.far;
580 let fov_up = match self.window.get_mode() {
582 GlWindowMode::Spherical | GlWindowMode::Cubemap => Angle::degrees(45.0),
583 GlWindowMode::Blit | GlWindowMode::StereoLeftRight | GlWindowMode::StereoRedCyan => {
584 Angle::degrees(FOV_UP)
585 },
586 };
587 let f = 1.0 / fov_up.radians.tan();
588 let nf = 1.0 / (near - far);
589 let viewport_size = self.viewport_size();
590 let aspect = viewport_size.width as f32 / viewport_size.height as f32;
591
592 {
594 #[rustfmt::skip]
595 return Transform3D::new(
597 f / aspect, 0.0, 0.0, 0.0,
598 0.0, f, 0.0, 0.0,
599 0.0, 0.0, (far + near) * nf, -1.0,
600 0.0, 0.0, 2.0 * far * near * nf, 0.0,
601 );
602 }
603 }
604}
605
606struct GlWindowShader {
607 gl: Rc<Gl>,
608 buffer: Option<gl::NativeBuffer>,
609 vao: Option<gl::NativeVertexArray>,
610 program: gl::NativeProgram,
611 mode: GlWindowMode,
612}
613
614const VERTEX_ATTRIBUTE: u32 = 0;
615const VERTICES: &[[f32; 2]; 4] = &[[-1.0, -1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, 1.0]];
616
617const PASSTHROUGH_VERTEX_SHADER: &str = "
618 #version 330 core
619 layout(location=0) in vec2 coord;
620 out vec2 vTexCoord;
621 void main(void) {
622 gl_Position = vec4(coord, 0.0, 1.0);
623 vTexCoord = coord * 0.5 + 0.5;
624 }
625";
626
627const PASSTHROUGH_FRAGMENT_SHADER: &str = "
628 #version 330 core
629 layout(location=0) out vec4 color;
630 uniform sampler2D image;
631 in vec2 vTexCoord;
632 void main() {
633 color = texture(image, vTexCoord);
634 }
635";
636
637const ANAGLYPH_VERTEX_SHADER: &str = "
638 #version 330 core
639 layout(location=0) in vec2 coord;
640 uniform float wasted; // What fraction of the image is wasted?
641 out vec2 left_coord;
642 out vec2 right_coord;
643 void main(void) {
644 gl_Position = vec4(coord, 0.0, 1.0);
645 vec2 coordn = coord * 0.5 + 0.5;
646 left_coord = vec2(mix(wasted/2, 0.5, coordn.x), coordn.y);
647 right_coord = vec2(mix(0.5, 1-wasted/2, coordn.x), coordn.y);
648 }
649";
650
651const ANAGLYPH_RED_CYAN_FRAGMENT_SHADER: &str = "
652 #version 330 core
653 layout(location=0) out vec4 color;
654 uniform sampler2D image;
655 in vec2 left_coord;
656 in vec2 right_coord;
657 void main() {
658 vec4 left_color = texture(image, left_coord);
659 vec4 right_color = texture(image, right_coord);
660 float red = left_color.x;
661 float green = right_color.y;
662 float blue = right_color.z;
663 color = vec4(red, green, blue, 1.0);
664 }
665";
666
667const SPHERICAL_VERTEX_SHADER: &str = "
668 #version 330 core
669 layout(location=0) in vec2 coord;
670 out vec2 lon_lat;
671 const float PI = 3.141592654;
672 void main(void) {
673 lon_lat = coord * vec2(PI, 0.5*PI);
674 gl_Position = vec4(coord, 0.0, 1.0);
675 }
676";
677
678const SPHERICAL_FRAGMENT_SHADER: &str = "
679 #version 330 core
680 layout(location=0) out vec4 color;
681 uniform sampler2D image;
682 in vec2 lon_lat;
683 void main() {
684 vec3 direction = vec3(
685 sin(lon_lat.x)*cos(lon_lat.y),
686 sin(lon_lat.y),
687 cos(lon_lat.x)*cos(lon_lat.y)
688 );
689 vec2 vTexCoord;
690 if ((direction.y > abs(direction.x)) && (direction.y > abs(direction.z))) {
691 // Looking up
692 vTexCoord.x = direction.z / (direction.y*6.0) + 5.0/6.0;
693 vTexCoord.y = direction.x / (direction.y*4.0) + 1.0/4.0;
694 } else if ((direction.y < -abs(direction.x)) && (direction.y < -abs(direction.z))) {
695 // Looking down
696 vTexCoord.x = direction.z / (direction.y*6.0) + 1.0/6.0;
697 vTexCoord.y = -direction.x / (direction.y*4.0) + 1.0/4.0;
698 } else if (direction.z < -abs(direction.x)) {
699 // Looking back
700 vTexCoord.x = -direction.y / (direction.z*6.0) + 3.0/6.0;
701 vTexCoord.y = -direction.x / (direction.z*4.0) + 1.0/4.0;
702 } else if (direction.x < -abs(direction.z)) {
703 // Looking left
704 vTexCoord.x = -direction.z / (direction.x*6.0) + 1.0/6.0;
705 vTexCoord.y = -direction.y / (direction.x*4.0) + 3.0/4.0;
706 } else if (direction.x > abs(direction.z)) {
707 // Looking right
708 vTexCoord.x = -direction.z / (direction.x*6.0) + 5.0/6.0;
709 vTexCoord.y = direction.y / (direction.x*4.0) + 3.0/4.0;
710 } else {
711 // Looking ahead
712 vTexCoord.x = direction.x / (direction.z*6.0) + 3.0/6.0;
713 vTexCoord.y = direction.y / (direction.z*4.0) + 3.0/4.0;
714 }
715 color = texture(image, vTexCoord);
716 }
717";
718
719impl GlWindowShader {
720 fn new(gl: Rc<Gl>, mode: GlWindowMode) -> Option<GlWindowShader> {
721 let (vertex_source, fragment_source) = match mode {
723 GlWindowMode::Blit => {
724 return None;
725 },
726 GlWindowMode::StereoLeftRight | GlWindowMode::Cubemap => {
727 (PASSTHROUGH_VERTEX_SHADER, PASSTHROUGH_FRAGMENT_SHADER)
728 },
729 GlWindowMode::StereoRedCyan => {
730 (ANAGLYPH_VERTEX_SHADER, ANAGLYPH_RED_CYAN_FRAGMENT_SHADER)
731 },
732 GlWindowMode::Spherical => (SPHERICAL_VERTEX_SHADER, SPHERICAL_FRAGMENT_SHADER),
733 };
734
735 if cfg!(target_os = "macos") {
737 log::warn!("XR shaders may not render on MacOS.");
738 }
739
740 unsafe {
741 let buffer = gl.create_buffer().ok();
743 let vao = gl.create_vertex_array().ok();
744 gl.bind_buffer(gl::ARRAY_BUFFER, buffer);
745
746 let data =
747 slice::from_raw_parts(VERTICES as *const _ as _, std::mem::size_of_val(VERTICES));
748 gl.buffer_data_u8_slice(gl::ARRAY_BUFFER, data, gl::STATIC_DRAW);
749
750 gl.bind_vertex_array(vao);
751 gl.vertex_attrib_pointer_f32(
752 VERTEX_ATTRIBUTE,
753 VERTICES[0].len() as i32,
754 gl::FLOAT,
755 false,
756 0,
757 0,
758 );
759 gl.enable_vertex_attrib_array(VERTEX_ATTRIBUTE);
760 debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
761
762 let program = gl.create_program().unwrap();
764 let vertex_shader = gl.create_shader(gl::VERTEX_SHADER).unwrap();
765 let fragment_shader = gl.create_shader(gl::FRAGMENT_SHADER).unwrap();
766 gl.shader_source(vertex_shader, vertex_source);
767 gl.compile_shader(vertex_shader);
768 gl.attach_shader(program, vertex_shader);
769 gl.shader_source(fragment_shader, fragment_source);
770 gl.compile_shader(fragment_shader);
771 gl.attach_shader(program, fragment_shader);
772 gl.link_program(program);
773 debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
774
775 let status = gl.get_shader_compile_status(vertex_shader);
778 assert!(
779 status,
780 "Failed to compile vertex shader: {}",
781 gl.get_shader_info_log(vertex_shader)
782 );
783 let status = gl.get_shader_compile_status(fragment_shader);
784 assert!(
785 status,
786 "Failed to compile fragment shader: {}",
787 gl.get_shader_info_log(fragment_shader)
788 );
789 let status = gl.get_program_link_status(program);
790 assert!(
791 status,
792 "Failed to link: {}",
793 gl.get_program_info_log(program)
794 );
795
796 gl.delete_shader(vertex_shader);
798 debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
799 gl.delete_shader(fragment_shader);
800 debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
801
802 Some(GlWindowShader {
804 gl,
805 buffer,
806 vao,
807 program,
808 mode,
809 })
810 }
811 }
812
813 fn draw_texture(
814 &self,
815 texture_id: Option<gl::NativeTexture>,
816 texture_target: u32,
817 texture_size: Size2D<i32, UnknownUnit>,
818 viewport_size: Size2D<i32, Viewport>,
819 window_size: Size2D<i32, Viewport>,
820 ) {
821 unsafe {
822 self.gl.use_program(Some(self.program));
823
824 self.gl.enable_vertex_attrib_array(VERTEX_ATTRIBUTE);
825 self.gl.vertex_attrib_pointer_f32(
826 VERTEX_ATTRIBUTE,
827 VERTICES[0].len() as i32,
828 gl::FLOAT,
829 false,
830 0,
831 0,
832 );
833
834 debug_assert_eq!(self.gl.get_error(), gl::NO_ERROR);
835
836 self.gl.active_texture(gl::TEXTURE0);
837 self.gl.bind_texture(texture_target, texture_id);
838
839 match self.mode {
840 GlWindowMode::StereoRedCyan => {
841 let wasted = 1.0 -
842 (texture_size.width as f32 / viewport_size.width as f32).clamp(0.0, 1.0);
843 let wasted_location = self.gl.get_uniform_location(self.program, "wasted");
844 self.gl.uniform_1_f32(wasted_location.as_ref(), wasted);
845 },
846 GlWindowMode::Blit |
847 GlWindowMode::Cubemap |
848 GlWindowMode::Spherical |
849 GlWindowMode::StereoLeftRight => {},
850 }
851
852 self.gl
853 .viewport(0, 0, window_size.width, window_size.height);
854 self.gl
855 .draw_arrays(gl::TRIANGLE_STRIP, 0, VERTICES.len() as i32);
856 self.gl.disable_vertex_attrib_array(VERTEX_ATTRIBUTE);
857 debug_assert_eq!(self.gl.get_error(), gl::NO_ERROR);
858 }
859 }
860}
861
862impl Drop for GlWindowShader {
863 fn drop(&mut self) {
864 unsafe {
865 if let Some(buffer) = self.buffer {
866 self.gl.delete_buffer(buffer);
867 }
868 if let Some(vao) = self.vao {
869 self.gl.delete_vertex_array(vao);
870 }
871 self.gl.delete_program(self.program);
872 }
873 }
874}