/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::collections::HashMap; use glow as gl; use glow::{Context as Gl, HasContext}; use surfman::Device as SurfmanDevice; use webxr_api::{ContextId, GLContexts, LayerId}; use crate::SurfmanGL; // A utility to clear a color texture and optional depth/stencil texture pub(crate) struct GlClearer { fbos: HashMap< ( LayerId, Option, Option, ), Option, >, should_reverse_winding: bool, } impl GlClearer { pub(crate) fn new(should_reverse_winding: bool) -> GlClearer { let fbos = HashMap::new(); GlClearer { fbos, should_reverse_winding, } } fn fbo( &mut self, gl: &Gl, layer_id: LayerId, color: Option, color_target: u32, depth_stencil: Option, ) -> Option { let should_reverse_winding = self.should_reverse_winding; *self .fbos .entry((layer_id, color, depth_stencil)) .or_insert_with(|| { // Save the current GL state unsafe { let draw_fbo = gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING); let read_fbo = gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING); // Generate and set attachments of a new FBO let fbo = gl.create_framebuffer().ok(); gl.bind_framebuffer(gl::FRAMEBUFFER, fbo); gl.framebuffer_texture_2d( gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, color_target, color, 0, ); gl.framebuffer_texture_2d( gl::FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, gl::TEXTURE_2D, depth_stencil, 0, ); // Necessary if using an OpenXR runtime that does not support mutable FOV, // as flipping the projection matrix necessitates reversing the winding order. if should_reverse_winding { gl.front_face(gl::CW); } // Restore the GL state gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, draw_fbo); gl.bind_framebuffer(gl::READ_FRAMEBUFFER, read_fbo); debug_assert_eq!(gl.get_error(), gl::NO_ERROR); fbo } }) } #[allow(clippy::too_many_arguments)] pub(crate) fn clear( &mut self, device: &mut SurfmanDevice, contexts: &mut dyn GLContexts, context_id: ContextId, layer_id: LayerId, color: Option, color_target: u32, depth_stencil: Option, ) { let gl = match contexts.bindings(device, context_id) { None => return, Some(gl) => gl, }; let fbo = self.fbo(gl, layer_id, color, color_target, depth_stencil); unsafe { // Save the current GL state let mut clear_color = [0., 0., 0., 0.]; let mut clear_depth = [0.]; let mut clear_stencil = [0]; let mut stencil_mask = [0]; let scissor_enabled = gl.is_enabled(gl::SCISSOR_TEST); let rasterizer_enabled = gl.is_enabled(gl::RASTERIZER_DISCARD); let draw_fbo = gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING); let read_fbo = gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING); gl.get_parameter_f32_slice(gl::COLOR_CLEAR_VALUE, &mut clear_color[..]); gl.get_parameter_f32_slice(gl::DEPTH_CLEAR_VALUE, &mut clear_depth[..]); gl.get_parameter_i32_slice(gl::STENCIL_CLEAR_VALUE, &mut clear_stencil[..]); let depth_mask = gl.get_parameter_bool(gl::DEPTH_WRITEMASK); gl.get_parameter_i32_slice(gl::STENCIL_WRITEMASK, &mut stencil_mask[..]); let color_mask = gl.get_parameter_bool_array::<4>(gl::COLOR_WRITEMASK); // Clear it gl.bind_framebuffer(gl::FRAMEBUFFER, fbo); gl.clear_color(0., 0., 0., 1.); gl.clear_depth(1.); gl.clear_stencil(0); gl.disable(gl::SCISSOR_TEST); gl.disable(gl::RASTERIZER_DISCARD); gl.depth_mask(true); gl.stencil_mask(0xFFFFFFFF); gl.color_mask(true, true, true, true); gl.clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); // Restore the GL state gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, draw_fbo); gl.bind_framebuffer(gl::READ_FRAMEBUFFER, read_fbo); gl.clear_color( clear_color[0], clear_color[1], clear_color[2], clear_color[3], ); gl.color_mask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]); gl.clear_depth(clear_depth[0] as f64); gl.clear_stencil(clear_stencil[0]); gl.depth_mask(depth_mask); gl.stencil_mask(stencil_mask[0] as _); if scissor_enabled { gl.enable(gl::SCISSOR_TEST); } if rasterizer_enabled { gl.enable(gl::RASTERIZER_DISCARD); } debug_assert_eq!(gl.get_error(), gl::NO_ERROR); } } pub(crate) fn destroy_layer( &mut self, device: &mut SurfmanDevice, contexts: &mut dyn GLContexts, context_id: ContextId, layer_id: LayerId, ) { let gl = match contexts.bindings(device, context_id) { None => return, Some(gl) => gl, }; self.fbos.retain(|&(other_id, _, _), &mut fbo| { if layer_id != other_id { true } else { if let Some(fbo) = fbo { unsafe { gl.delete_framebuffer(fbo) }; } false } }) } }