Skip to main content

servo_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 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
30// How far off the ground are the viewer's eyes?
31const HEIGHT: f32 = 1.0;
32
33// What is half the vertical field of view?
34const FOV_UP: f32 = 45.0;
35
36// Some guesstimated numbers, hopefully it doesn't matter if these are off by a bit.
37
38// What the distance between the viewer's eyes?
39const INTER_PUPILLARY_DISTANCE: f32 = 0.06;
40
41// What is the size of a pixel?
42const 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                // Rendering to a surfman swap chain
282                target_swap_chain
283                    .swap_buffers(&self.device, &mut self.context, PreserveBuffer::No)
284                    .unwrap();
285            },
286            None => {
287                // Rendering to a native widget
288                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        // Glwindow currently doesn't have any way to end its own session
319        // XXXManishearth add something for this that listens for the window
320        // being closed
321    }
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                // This device has a slightly odd characteristic, which is that anaglyphic stereo
492                // renders both eyes to the same surface. If we want the two eyes to be parallel,
493                // and to agree at distance infinity, this means gettng the XR content to render some
494                // wasted pixels, which are stripped off when we render to the target surface.
495                // (The wasted pixels are on the right of the left eye and vice versa.)
496                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                // Cubemap viewports should be square
501                let size = 1.max(window_size.width / 3).max(window_size.height / 2);
502                Size2D::new(size, size)
503            },
504            GlWindowMode::Spherical => {
505                // Cubemap viewports should be square
506                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        // https://github.com/toji/gl-matrix/blob/bd3307196563fbb331b40fc6ebecbbfcc2a4722c/src/mat4.js#L1271
581        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        // Sigh, row-major vs column-major
593        Transform3D::from_arrays([
594            [f / aspect, 0.0, 0.0, 0.0],
595            [0.0, f, 0.0, 0.0],
596            [0.0, 0.0, (far + near) * nf, -1.0],
597            [0.0, 0.0, 2.0 * far * near * nf, 0.0],
598        ])
599    }
600}
601
602struct GlWindowShader {
603    gl: Rc<Gl>,
604    buffer: Option<gl::NativeBuffer>,
605    vao: Option<gl::NativeVertexArray>,
606    program: gl::NativeProgram,
607    mode: GlWindowMode,
608}
609
610const VERTEX_ATTRIBUTE: u32 = 0;
611const VERTICES: &[[f32; 2]; 4] = &[[-1.0, -1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, 1.0]];
612
613const PASSTHROUGH_VERTEX_SHADER: &str = "
614  #version 330 core
615  layout(location=0) in vec2 coord;
616  out vec2 vTexCoord;
617  void main(void) {
618    gl_Position = vec4(coord, 0.0, 1.0);
619    vTexCoord = coord * 0.5 + 0.5;
620  }
621";
622
623const PASSTHROUGH_FRAGMENT_SHADER: &str = "
624  #version 330 core
625  layout(location=0) out vec4 color;
626  uniform sampler2D image;
627  in vec2 vTexCoord;
628  void main() {
629    color = texture(image, vTexCoord);
630  }
631";
632
633const ANAGLYPH_VERTEX_SHADER: &str = "
634  #version 330 core
635  layout(location=0) in vec2 coord;
636  uniform float wasted; // What fraction of the image is wasted?
637  out vec2 left_coord;
638  out vec2 right_coord;
639  void main(void) {
640    gl_Position = vec4(coord, 0.0, 1.0);
641    vec2 coordn = coord * 0.5 + 0.5;
642    left_coord = vec2(mix(wasted/2, 0.5, coordn.x), coordn.y);
643    right_coord = vec2(mix(0.5, 1-wasted/2, coordn.x), coordn.y);
644  }
645";
646
647const ANAGLYPH_RED_CYAN_FRAGMENT_SHADER: &str = "
648  #version 330 core
649  layout(location=0) out vec4 color;
650  uniform sampler2D image;
651  in vec2 left_coord;
652  in vec2 right_coord;
653  void main() {
654    vec4 left_color = texture(image, left_coord);
655    vec4 right_color = texture(image, right_coord);
656    float red = left_color.x;
657    float green = right_color.y;
658    float blue = right_color.z;
659    color = vec4(red, green, blue, 1.0);
660  }
661";
662
663const SPHERICAL_VERTEX_SHADER: &str = "
664  #version 330 core
665  layout(location=0) in vec2 coord;
666  out vec2 lon_lat;
667  const float PI = 3.141592654;
668  void main(void) {
669    lon_lat = coord * vec2(PI, 0.5*PI);
670    gl_Position = vec4(coord, 0.0, 1.0);
671  }
672";
673
674const SPHERICAL_FRAGMENT_SHADER: &str = "
675  #version 330 core
676  layout(location=0) out vec4 color;
677  uniform sampler2D image;
678  in vec2 lon_lat;
679  void main() {
680    vec3 direction = vec3(
681      sin(lon_lat.x)*cos(lon_lat.y),
682      sin(lon_lat.y),
683      cos(lon_lat.x)*cos(lon_lat.y)
684    );
685    vec2 vTexCoord;
686    if ((direction.y > abs(direction.x)) && (direction.y > abs(direction.z))) {
687      // Looking up
688      vTexCoord.x = direction.z / (direction.y*6.0) + 5.0/6.0;
689      vTexCoord.y = direction.x / (direction.y*4.0) + 1.0/4.0;
690    } else if ((direction.y < -abs(direction.x)) && (direction.y < -abs(direction.z))) {
691      // Looking down
692      vTexCoord.x = direction.z / (direction.y*6.0) + 1.0/6.0;
693      vTexCoord.y = -direction.x / (direction.y*4.0) + 1.0/4.0;
694    } else if (direction.z < -abs(direction.x)) {
695      // Looking back
696      vTexCoord.x = -direction.y / (direction.z*6.0) + 3.0/6.0;
697      vTexCoord.y = -direction.x / (direction.z*4.0) + 1.0/4.0;
698    } else if (direction.x < -abs(direction.z)) {
699      // Looking left
700      vTexCoord.x = -direction.z / (direction.x*6.0) + 1.0/6.0;
701      vTexCoord.y = -direction.y / (direction.x*4.0) + 3.0/4.0;
702    } else if (direction.x > abs(direction.z)) {
703      // Looking right
704      vTexCoord.x = -direction.z / (direction.x*6.0) + 5.0/6.0;
705      vTexCoord.y = direction.y / (direction.x*4.0) + 3.0/4.0;
706    } else {
707      // Looking ahead
708      vTexCoord.x = direction.x / (direction.z*6.0) + 3.0/6.0;
709      vTexCoord.y = direction.y / (direction.z*4.0) + 3.0/4.0;
710    }
711    color = texture(image, vTexCoord);
712  }
713";
714
715impl GlWindowShader {
716    fn new(gl: Rc<Gl>, mode: GlWindowMode) -> Option<GlWindowShader> {
717        // The shader source
718        let (vertex_source, fragment_source) = match mode {
719            GlWindowMode::Blit => {
720                return None;
721            },
722            GlWindowMode::StereoLeftRight | GlWindowMode::Cubemap => {
723                (PASSTHROUGH_VERTEX_SHADER, PASSTHROUGH_FRAGMENT_SHADER)
724            },
725            GlWindowMode::StereoRedCyan => {
726                (ANAGLYPH_VERTEX_SHADER, ANAGLYPH_RED_CYAN_FRAGMENT_SHADER)
727            },
728            GlWindowMode::Spherical => (SPHERICAL_VERTEX_SHADER, SPHERICAL_FRAGMENT_SHADER),
729        };
730
731        // TODO: work out why shaders don't work on macos
732        if cfg!(target_os = "macos") {
733            log::warn!("XR shaders may not render on MacOS.");
734        }
735
736        unsafe {
737            // The four corners of the window in a VAO, set to attribute 0
738            let buffer = gl.create_buffer().ok();
739            let vao = gl.create_vertex_array().ok();
740            gl.bind_buffer(gl::ARRAY_BUFFER, buffer);
741
742            let data =
743                slice::from_raw_parts(VERTICES as *const _ as _, std::mem::size_of_val(VERTICES));
744            gl.buffer_data_u8_slice(gl::ARRAY_BUFFER, data, gl::STATIC_DRAW);
745
746            gl.bind_vertex_array(vao);
747            gl.vertex_attrib_pointer_f32(
748                VERTEX_ATTRIBUTE,
749                VERTICES[0].len() as i32,
750                gl::FLOAT,
751                false,
752                0,
753                0,
754            );
755            gl.enable_vertex_attrib_array(VERTEX_ATTRIBUTE);
756            debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
757
758            // The shader program
759            let program = gl.create_program().unwrap();
760            let vertex_shader = gl.create_shader(gl::VERTEX_SHADER).unwrap();
761            let fragment_shader = gl.create_shader(gl::FRAGMENT_SHADER).unwrap();
762            gl.shader_source(vertex_shader, vertex_source);
763            gl.compile_shader(vertex_shader);
764            gl.attach_shader(program, vertex_shader);
765            gl.shader_source(fragment_shader, fragment_source);
766            gl.compile_shader(fragment_shader);
767            gl.attach_shader(program, fragment_shader);
768            gl.link_program(program);
769            debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
770
771            // Check for errors
772            // TODO: something other than panic?
773            let status = gl.get_shader_compile_status(vertex_shader);
774            assert!(
775                status,
776                "Failed to compile vertex shader: {}",
777                gl.get_shader_info_log(vertex_shader)
778            );
779            let status = gl.get_shader_compile_status(fragment_shader);
780            assert!(
781                status,
782                "Failed to compile fragment shader: {}",
783                gl.get_shader_info_log(fragment_shader)
784            );
785            let status = gl.get_program_link_status(program);
786            assert!(
787                status,
788                "Failed to link: {}",
789                gl.get_program_info_log(program)
790            );
791
792            // Clean up
793            gl.delete_shader(vertex_shader);
794            debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
795            gl.delete_shader(fragment_shader);
796            debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
797
798            // And we're done
799            Some(GlWindowShader {
800                gl,
801                buffer,
802                vao,
803                program,
804                mode,
805            })
806        }
807    }
808
809    fn draw_texture(
810        &self,
811        texture_id: Option<gl::NativeTexture>,
812        texture_target: u32,
813        texture_size: Size2D<i32, UnknownUnit>,
814        viewport_size: Size2D<i32, Viewport>,
815        window_size: Size2D<i32, Viewport>,
816    ) {
817        unsafe {
818            self.gl.use_program(Some(self.program));
819
820            self.gl.enable_vertex_attrib_array(VERTEX_ATTRIBUTE);
821            self.gl.vertex_attrib_pointer_f32(
822                VERTEX_ATTRIBUTE,
823                VERTICES[0].len() as i32,
824                gl::FLOAT,
825                false,
826                0,
827                0,
828            );
829
830            debug_assert_eq!(self.gl.get_error(), gl::NO_ERROR);
831
832            self.gl.active_texture(gl::TEXTURE0);
833            self.gl.bind_texture(texture_target, texture_id);
834
835            match self.mode {
836                GlWindowMode::StereoRedCyan => {
837                    let wasted = 1.0 -
838                        (texture_size.width as f32 / viewport_size.width as f32).clamp(0.0, 1.0);
839                    let wasted_location = self.gl.get_uniform_location(self.program, "wasted");
840                    self.gl.uniform_1_f32(wasted_location.as_ref(), wasted);
841                },
842                GlWindowMode::Blit |
843                GlWindowMode::Cubemap |
844                GlWindowMode::Spherical |
845                GlWindowMode::StereoLeftRight => {},
846            }
847
848            self.gl
849                .viewport(0, 0, window_size.width, window_size.height);
850            self.gl
851                .draw_arrays(gl::TRIANGLE_STRIP, 0, VERTICES.len() as i32);
852            self.gl.disable_vertex_attrib_array(VERTEX_ATTRIBUTE);
853            debug_assert_eq!(self.gl.get_error(), gl::NO_ERROR);
854        }
855    }
856}
857
858impl Drop for GlWindowShader {
859    fn drop(&mut self) {
860        unsafe {
861            if let Some(buffer) = self.buffer {
862                self.gl.delete_buffer(buffer);
863            }
864            if let Some(vao) = self.vao {
865                self.gl.delete_vertex_array(vao);
866            }
867            self.gl.delete_program(self.program);
868        }
869    }
870}