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