webxr/glwindow/
mod.rs

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