webxr/
gl_utils.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 std::collections::HashMap;
6
7use glow as gl;
8use glow::{Context as Gl, HasContext};
9use webxr_api::{ContextId, GLContexts, LayerId};
10
11use crate::SurfmanGL;
12
13// A utility to clear a color texture and optional depth/stencil texture
14pub(crate) struct GlClearer {
15    fbos: HashMap<
16        (
17            LayerId,
18            Option<gl::NativeTexture>,
19            Option<gl::NativeTexture>,
20        ),
21        Option<gl::NativeFramebuffer>,
22    >,
23    should_reverse_winding: bool,
24}
25
26impl GlClearer {
27    pub(crate) fn new(should_reverse_winding: bool) -> GlClearer {
28        let fbos = HashMap::new();
29        GlClearer {
30            fbos,
31            should_reverse_winding,
32        }
33    }
34
35    fn fbo(
36        &mut self,
37        gl: &Gl,
38        layer_id: LayerId,
39        color: Option<gl::NativeTexture>,
40        color_target: u32,
41        depth_stencil: Option<gl::NativeTexture>,
42    ) -> Option<gl::NativeFramebuffer> {
43        let should_reverse_winding = self.should_reverse_winding;
44        *self
45            .fbos
46            .entry((layer_id, color, depth_stencil))
47            .or_insert_with(|| {
48                // Save the current GL state
49                unsafe {
50                    let draw_fbo = gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING);
51                    let read_fbo = gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING);
52
53                    // Generate and set attachments of a new FBO
54                    let fbo = gl.create_framebuffer().ok();
55
56                    gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
57                    gl.framebuffer_texture_2d(
58                        gl::FRAMEBUFFER,
59                        gl::COLOR_ATTACHMENT0,
60                        color_target,
61                        color,
62                        0,
63                    );
64                    gl.framebuffer_texture_2d(
65                        gl::FRAMEBUFFER,
66                        gl::DEPTH_STENCIL_ATTACHMENT,
67                        gl::TEXTURE_2D,
68                        depth_stencil,
69                        0,
70                    );
71
72                    // Necessary if using an OpenXR runtime that does not support mutable FOV,
73                    // as flipping the projection matrix necessitates reversing the winding order.
74                    if should_reverse_winding {
75                        gl.front_face(gl::CW);
76                    }
77
78                    // Restore the GL state
79                    gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, draw_fbo);
80                    gl.bind_framebuffer(gl::READ_FRAMEBUFFER, read_fbo);
81                    debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
82
83                    fbo
84                }
85            })
86    }
87
88    #[allow(clippy::too_many_arguments)]
89    pub(crate) fn clear(
90        &mut self,
91        contexts: &mut dyn GLContexts<SurfmanGL>,
92        context_id: ContextId,
93        layer_id: LayerId,
94        color: Option<glow::NativeTexture>,
95        color_target: u32,
96        depth_stencil: Option<glow::NativeTexture>,
97    ) {
98        let gl = match contexts.bindings(context_id) {
99            None => return,
100            Some(gl) => gl,
101        };
102        let fbo = self.fbo(gl, layer_id, color, color_target, depth_stencil);
103        unsafe {
104            // Save the current GL state
105            let mut clear_color = [0., 0., 0., 0.];
106            let mut clear_depth = [0.];
107            let mut clear_stencil = [0];
108            let mut stencil_mask = [0];
109            let scissor_enabled = gl.is_enabled(gl::SCISSOR_TEST);
110            let rasterizer_enabled = gl.is_enabled(gl::RASTERIZER_DISCARD);
111
112            let draw_fbo = gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING);
113            let read_fbo = gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING);
114            gl.get_parameter_f32_slice(gl::COLOR_CLEAR_VALUE, &mut clear_color[..]);
115            gl.get_parameter_f32_slice(gl::DEPTH_CLEAR_VALUE, &mut clear_depth[..]);
116            gl.get_parameter_i32_slice(gl::STENCIL_CLEAR_VALUE, &mut clear_stencil[..]);
117            let depth_mask = gl.get_parameter_bool(gl::DEPTH_WRITEMASK);
118            gl.get_parameter_i32_slice(gl::STENCIL_WRITEMASK, &mut stencil_mask[..]);
119            let color_mask = gl.get_parameter_bool_array::<4>(gl::COLOR_WRITEMASK);
120
121            // Clear it
122            gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
123            gl.clear_color(0., 0., 0., 1.);
124            gl.clear_depth(1.);
125            gl.clear_stencil(0);
126            gl.disable(gl::SCISSOR_TEST);
127            gl.disable(gl::RASTERIZER_DISCARD);
128            gl.depth_mask(true);
129            gl.stencil_mask(0xFFFFFFFF);
130            gl.color_mask(true, true, true, true);
131            gl.clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
132
133            // Restore the GL state
134            gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, draw_fbo);
135            gl.bind_framebuffer(gl::READ_FRAMEBUFFER, read_fbo);
136            gl.clear_color(
137                clear_color[0],
138                clear_color[1],
139                clear_color[2],
140                clear_color[3],
141            );
142            gl.color_mask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]);
143            gl.clear_depth(clear_depth[0] as f64);
144            gl.clear_stencil(clear_stencil[0]);
145            gl.depth_mask(depth_mask);
146            gl.stencil_mask(stencil_mask[0] as _);
147            if scissor_enabled {
148                gl.enable(gl::SCISSOR_TEST);
149            }
150            if rasterizer_enabled {
151                gl.enable(gl::RASTERIZER_DISCARD);
152            }
153            debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
154        }
155    }
156
157    pub(crate) fn destroy_layer(
158        &mut self,
159        contexts: &mut dyn GLContexts<SurfmanGL>,
160        context_id: ContextId,
161        layer_id: LayerId,
162    ) {
163        let gl = match contexts.bindings(context_id) {
164            None => return,
165            Some(gl) => gl,
166        };
167        self.fbos.retain(|&(other_id, _, _), &mut fbo| {
168            if layer_id != other_id {
169                true
170            } else {
171                if let Some(fbo) = fbo {
172                    unsafe { gl.delete_framebuffer(fbo) };
173                }
174                false
175            }
176        })
177    }
178}