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 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
29const HEIGHT: f32 = 1.0;
31
32const FOV_UP: f32 = 45.0;
34
35const INTER_PUPILLARY_DISTANCE: f32 = 0.06;
39
40const 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 target_swap_chain
282 .swap_buffers(&mut self.device, &mut self.context, PreserveBuffer::No)
283 .unwrap();
284 },
285 None => {
286 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 }
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 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 let size = 1.max(window_size.width / 3).max(window_size.height / 2);
501 Size2D::new(size, size)
502 },
503 GlWindowMode::Spherical => {
504 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 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 {
593 #[rustfmt::skip]
594 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 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 if cfg!(target_os = "macos") {
736 log::warn!("XR shaders may not render on MacOS.");
737 }
738
739 unsafe {
740 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 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 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 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 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}