Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/wr/webrender/src/device/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 167 kB image not shown  

Quellcode-Bibliothek gl.rs   Sprache: unbekannt

 
Columbo aufrufen.rs Download desUnknown {[0] [0] [0]}Datei anzeigen

/* 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 http://mozilla.org/MPL/2.0/. */

use super::super::shader_source::{OPTIMIZED_SHADERS, UNOPTIMIZED_SHADERS};
use api::{ImageDescriptor, ImageFormat, Parameter, BoolParameter, IntParameter, ImageRendering};
use api::{MixBlendMode, ImageBufferKind, VoidPtrToSizeFn};
use api::{CrashAnnotator, CrashAnnotation, CrashAnnotatorGuard};
use api::units::*;
use euclid::default::Transform3D;
use gleam::gl;
use crate::render_api::MemoryReport;
use crate::internal_types::{FastHashMap, RenderTargetInfo, Swizzle, SwizzleSettings};
use crate::util::round_up_to_multiple;
use crate::profiler;
use log::Level;
use smallvec::SmallVec;
use std::{
    borrow::Cow,
    cell::{Cell, RefCell},
    cmp,
    collections::hash_map::Entry,
    marker::PhantomData,
    mem,
    num::NonZeroUsize,
    os::raw::c_void,
    ops::Add,
    path::PathBuf,
    ptr,
    rc::Rc,
    slice,
    sync::Arc,
    thread,
    time::Duration,
};
use webrender_build::shader::{
    ProgramSourceDigest, ShaderKind, ShaderVersion, build_shader_main_string,
    build_shader_prefix_string, do_build_shader_string, shader_source_from_file,
};
use malloc_size_of::MallocSizeOfOps;

/// Sequence number for frames, as tracked by the device layer.
#[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct GpuFrameId(usize);

impl GpuFrameId {
    pub fn new(value: usize) -> Self {
        GpuFrameId(value)
    }
}

impl Add<usize> for GpuFrameId {
    type Output = GpuFrameId;

    fn add(self, other: usize) -> GpuFrameId {
        GpuFrameId(self.0 + other)
    }
}

pub struct TextureSlot(pub usize);

// In some places we need to temporarily bind a texture to any slot.
const DEFAULT_TEXTURE: TextureSlot = TextureSlot(0);

#[repr(u32)]
pub enum DepthFunction {
    Always = gl::ALWAYS,
    Less = gl::LESS,
    LessEqual = gl::LEQUAL,
}

#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum TextureFilter {
    Nearest,
    Linear,
    Trilinear,
}

/// A structure defining a particular workflow of texture transfers.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TextureFormatPair<T> {
    /// Format the GPU natively stores texels in.
    pub internal: T,
    /// Format we expect the users to provide the texels in.
    pub external: T,
}

impl<T: Copy> From<T> for TextureFormatPair<T> {
    fn from(value: T) -> Self {
        TextureFormatPair {
            internal: value,
            external: value,
        }
    }
}

#[derive(Debug)]
pub enum VertexAttributeKind {
    F32,
    U8Norm,
    U16Norm,
    I32,
    U16,
}

#[derive(Debug)]
pub struct VertexAttribute {
    pub name: &'static str,
    pub count: u32,
    pub kind: VertexAttributeKind,
}

#[derive(Debug)]
pub struct VertexDescriptor {
    pub vertex_attributes: &'static [VertexAttribute],
    pub instance_attributes: &'static [VertexAttribute],
}

enum FBOTarget {
    Read,
    Draw,
}

/// Method of uploading texel data from CPU to GPU.
#[derive(Debug, Clone)]
pub enum UploadMethod {
    /// Just call `glTexSubImage` directly with the CPU data pointer
    Immediate,
    /// Accumulate the changes in PBO first before transferring to a texture.
    PixelBuffer(VertexUsageHint),
}

/// Plain old data that can be used to initialize a texture.
pub unsafe trait Texel: Copy + Default {
    fn image_format() -> ImageFormat;
}

unsafe impl Texel for u8 {
    fn image_format() -> ImageFormat { ImageFormat::R8 }
}

/// Returns the size in bytes of a depth target with the given dimensions.
fn depth_target_size_in_bytes(dimensions: &DeviceIntSize) -> usize {
    // DEPTH24 textures generally reserve 3 bytes for depth and 1 byte
    // for stencil, so we measure them as 32 bits.
    let pixels = dimensions.width * dimensions.height;
    (pixels as usize) * 4
}

pub fn get_gl_target(target: ImageBufferKind) -> gl::GLuint {
    match target {
        ImageBufferKind::Texture2D => gl::TEXTURE_2D,
        ImageBufferKind::TextureRect => gl::TEXTURE_RECTANGLE,
        ImageBufferKind::TextureExternal => gl::TEXTURE_EXTERNAL_OES,
        ImageBufferKind::TextureExternalBT709 => gl::TEXTURE_EXTERNAL_OES,
    }
}

pub fn from_gl_target(target: gl::GLuint) -> ImageBufferKind {
    match target {
        gl::TEXTURE_2D => ImageBufferKind::Texture2D,
        gl::TEXTURE_RECTANGLE => ImageBufferKind::TextureRect,
        gl::TEXTURE_EXTERNAL_OES => ImageBufferKind::TextureExternal,
        _ => panic!("Unexpected target {:?}", target),
    }
}

fn supports_extension(extensions: &[String], extension: &str) -> bool {
    extensions.iter().any(|s| s == extension)
}

fn get_shader_version(gl: &dyn gl::Gl) -> ShaderVersion {
    match gl.get_type() {
        gl::GlType::Gl => ShaderVersion::Gl,
        gl::GlType::Gles => ShaderVersion::Gles,
    }
}

// Get an unoptimized shader string by name, from the built in resources or
// an override path, if supplied.
pub fn get_unoptimized_shader_source(shader_name: &str, base_path: Option<&PathBuf>) -> Cow<'static, str> {
    if let Some(ref base) = base_path {
        let shader_path = base.join(&format!("{}.glsl", shader_name));
        Cow::Owned(shader_source_from_file(&shader_path))
    } else {
        Cow::Borrowed(
            UNOPTIMIZED_SHADERS
            .get(shader_name)
            .expect("Shader not found")
            .source
        )
    }
}

impl VertexAttributeKind {
    fn size_in_bytes(&self) -> u32 {
        match *self {
            VertexAttributeKind::F32 => 4,
            VertexAttributeKind::U8Norm => 1,
            VertexAttributeKind::U16Norm => 2,
            VertexAttributeKind::I32 => 4,
            VertexAttributeKind::U16 => 2,
        }
    }
}

impl VertexAttribute {
    fn size_in_bytes(&self) -> u32 {
        self.count * self.kind.size_in_bytes()
    }

    fn bind_to_vao(
        &self,
        attr_index: gl::GLuint,
        divisor: gl::GLuint,
        stride: gl::GLint,
        offset: gl::GLuint,
        gl: &dyn gl::Gl,
    ) {
        gl.enable_vertex_attrib_array(attr_index);
        gl.vertex_attrib_divisor(attr_index, divisor);

        match self.kind {
            VertexAttributeKind::F32 => {
                gl.vertex_attrib_pointer(
                    attr_index,
                    self.count as gl::GLint,
                    gl::FLOAT,
                    false,
                    stride,
                    offset,
                );
            }
            VertexAttributeKind::U8Norm => {
                gl.vertex_attrib_pointer(
                    attr_index,
                    self.count as gl::GLint,
                    gl::UNSIGNED_BYTE,
                    true,
                    stride,
                    offset,
                );
            }
            VertexAttributeKind::U16Norm => {
                gl.vertex_attrib_pointer(
                    attr_index,
                    self.count as gl::GLint,
                    gl::UNSIGNED_SHORT,
                    true,
                    stride,
                    offset,
                );
            }
            VertexAttributeKind::I32 => {
                gl.vertex_attrib_i_pointer(
                    attr_index,
                    self.count as gl::GLint,
                    gl::INT,
                    stride,
                    offset,
                );
            }
            VertexAttributeKind::U16 => {
                gl.vertex_attrib_i_pointer(
                    attr_index,
                    self.count as gl::GLint,
                    gl::UNSIGNED_SHORT,
                    stride,
                    offset,
                );
            }
        }
    }
}

impl VertexDescriptor {
    fn instance_stride(&self) -> u32 {
        self.instance_attributes
            .iter()
            .map(|attr| attr.size_in_bytes())
            .sum()
    }

    fn bind_attributes(
        attributes: &[VertexAttribute],
        start_index: usize,
        divisor: u32,
        gl: &dyn gl::Gl,
        vbo: VBOId,
    ) {
        vbo.bind(gl);

        let stride: u32 = attributes
            .iter()
            .map(|attr| attr.size_in_bytes())
            .sum();

        let mut offset = 0;
        for (i, attr) in attributes.iter().enumerate() {
            let attr_index = (start_index + i) as gl::GLuint;
            attr.bind_to_vao(attr_index, divisor, stride as _, offset, gl);
            offset += attr.size_in_bytes();
        }
    }

    fn bind(&self, gl: &dyn gl::Gl, main: VBOId, instance: VBOId, instance_divisor: u32) {
        Self::bind_attributes(self.vertex_attributes, 0, 0, gl, main);

        if !self.instance_attributes.is_empty() {
            Self::bind_attributes(
                self.instance_attributes,
                self.vertex_attributes.len(),
                instance_divisor,
                gl,
                instance,
            );
        }
    }
}

impl VBOId {
    fn bind(&self, gl: &dyn gl::Gl) {
        gl.bind_buffer(gl::ARRAY_BUFFER, self.0);
    }
}

impl IBOId {
    fn bind(&self, gl: &dyn gl::Gl) {
        gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.0);
    }
}

impl FBOId {
    fn bind(&self, gl: &dyn gl::Gl, target: FBOTarget) {
        let target = match target {
            FBOTarget::Read => gl::READ_FRAMEBUFFER,
            FBOTarget::Draw => gl::DRAW_FRAMEBUFFER,
        };
        gl.bind_framebuffer(target, self.0);
    }
}

pub struct Stream<'a> {
    attributes: &'a [VertexAttribute],
    vbo: VBOId,
}

pub struct VBO<V> {
    id: gl::GLuint,
    target: gl::GLenum,
    allocated_count: usize,
    marker: PhantomData<V>,
}

impl<V> VBO<V> {
    pub fn allocated_count(&self) -> usize {
        self.allocated_count
    }

    pub fn stream_with<'a>(&self, attributes: &'a [VertexAttribute]) -> Stream<'a> {
        debug_assert_eq!(
            mem::size_of::<V>(),
            attributes.iter().map(|a| a.size_in_bytes() as usize).sum::<usize>()
        );
        Stream {
            attributes,
            vbo: VBOId(self.id),
        }
    }
}

impl<T> Drop for VBO<T> {
    fn drop(&mut self) {
        debug_assert!(thread::panicking() || self.id == 0);
    }
}

#[cfg_attr(feature = "replay", derive(Clone))]
#[derive(Debug)]
pub struct ExternalTexture {
    id: gl::GLuint,
    target: gl::GLuint,
    uv_rect: TexelRect,
    image_rendering: ImageRendering,
}

impl ExternalTexture {
    pub fn new(
        id: u32,
        target: ImageBufferKind,
        uv_rect: TexelRect,
        image_rendering: ImageRendering,
    ) -> Self {
        ExternalTexture {
            id,
            target: get_gl_target(target),
            uv_rect,
            image_rendering,
        }
    }

    #[cfg(feature = "replay")]
    pub fn internal_id(&self) -> gl::GLuint {
        self.id
    }

    pub fn get_uv_rect(&self) -> TexelRect {
        self.uv_rect
    }
}

bitflags! {
    #[derive(Default, Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
    pub struct TextureFlags: u32 {
        /// This texture corresponds to one of the shared texture caches.
        const IS_SHARED_TEXTURE_CACHE = 1 << 0;
    }
}

/// WebRender interface to an OpenGL texture.
///
/// Because freeing a texture requires various device handles that are not
/// reachable from this struct, manual destruction via `Device` is required.
/// Our `Drop` implementation asserts that this has happened.
#[derive(Debug)]
pub struct Texture {
    id: gl::GLuint,
    target: gl::GLuint,
    format: ImageFormat,
    size: DeviceIntSize,
    filter: TextureFilter,
    flags: TextureFlags,
    /// An internally mutable swizzling state that may change between batches.
    active_swizzle: Cell<Swizzle>,
    /// Framebuffer Object allowing this texture to be rendered to.
    ///
    /// Empty if this texture is not used as a render target or if a depth buffer is needed.
    fbo: Option<FBOId>,
    /// Same as the above, but with a depth buffer attached.
    ///
    /// FBOs are cheap to create but expensive to reconfigure (since doing so
    /// invalidates framebuffer completeness caching). Moreover, rendering with
    /// a depth buffer attached but the depth write+test disabled relies on the
    /// driver to optimize it out of the rendering pass, which most drivers
    /// probably do but, according to jgilbert, is best not to rely on.
    ///
    /// So we lazily generate a second list of FBOs with depth. This list is
    /// empty if this texture is not used as a render target _or_ if it is, but
    /// the depth buffer has never been requested.
    ///
    /// Note that we always fill fbo, and then lazily create fbo_with_depth
    /// when needed. We could make both lazy (i.e. render targets would have one
    /// or the other, but not both, unless they were actually used in both
    /// configurations). But that would complicate a lot of logic in this module,
    /// and FBOs are cheap enough to create.
    fbo_with_depth: Option<FBOId>,
    last_frame_used: GpuFrameId,
}

impl Texture {
    pub fn get_dimensions(&self) -> DeviceIntSize {
        self.size
    }

    pub fn get_format(&self) -> ImageFormat {
        self.format
    }

    pub fn get_filter(&self) -> TextureFilter {
        self.filter
    }

    pub fn get_target(&self) -> ImageBufferKind {
        from_gl_target(self.target)
    }

    pub fn supports_depth(&self) -> bool {
        self.fbo_with_depth.is_some()
    }

    pub fn last_frame_used(&self) -> GpuFrameId {
        self.last_frame_used
    }

    pub fn used_in_frame(&self, frame_id: GpuFrameId) -> bool {
        self.last_frame_used == frame_id
    }

    pub fn is_render_target(&self) -> bool {
        self.fbo.is_some()
    }

    /// Returns true if this texture was used within `threshold` frames of
    /// the current frame.
    pub fn used_recently(&self, current_frame_id: GpuFrameId, threshold: usize) -> bool {
        self.last_frame_used + threshold >= current_frame_id
    }

    /// Returns the flags for this texture.
    pub fn flags(&self) -> &TextureFlags {
        &self.flags
    }

    /// Returns a mutable borrow of the flags for this texture.
    pub fn flags_mut(&mut self) -> &mut TextureFlags {
        &mut self.flags
    }

    /// Returns the number of bytes (generally in GPU memory) that this texture
    /// consumes.
    pub fn size_in_bytes(&self) -> usize {
        let bpp = self.format.bytes_per_pixel() as usize;
        let w = self.size.width as usize;
        let h = self.size.height as usize;
        bpp * w * h
    }

    #[cfg(feature = "replay")]
    pub fn into_external(mut self) -> ExternalTexture {
        let ext = ExternalTexture {
            id: self.id,
            target: self.target,
            // TODO(gw): Support custom UV rect for external textures during captures
            uv_rect: TexelRect::new(
                0.0,
                0.0,
                self.size.width as f32,
                self.size.height as f32,
            ),
            image_rendering: ImageRendering::Auto,
        };
        self.id = 0; // don't complain, moved out
        ext
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        debug_assert!(thread::panicking() || self.id == 0);
    }
}

pub struct Program {
    id: gl::GLuint,
    u_transform: gl::GLint,
    u_texture_size: gl::GLint,
    source_info: ProgramSourceInfo,
    is_initialized: bool,
}

impl Program {
    pub fn is_initialized(&self) -> bool {
        self.is_initialized
    }
}

impl Drop for Program {
    fn drop(&mut self) {
        debug_assert!(
            thread::panicking() || self.id == 0,
            "renderer::deinit not called"
        );
    }
}

pub struct CustomVAO {
    id: gl::GLuint,
}

impl Drop for CustomVAO {
    fn drop(&mut self) {
        debug_assert!(
            thread::panicking() || self.id == 0,
            "renderer::deinit not called"
        );
    }
}

pub struct VAO {
    id: gl::GLuint,
    ibo_id: IBOId,
    main_vbo_id: VBOId,
    instance_vbo_id: VBOId,
    instance_stride: usize,
    instance_divisor: u32,
    owns_vertices_and_indices: bool,
}

impl Drop for VAO {
    fn drop(&mut self) {
        debug_assert!(
            thread::panicking() || self.id == 0,
            "renderer::deinit not called"
        );
    }
}

#[derive(Debug)]
pub struct PBO {
    id: gl::GLuint,
    reserved_size: usize,
}

impl PBO {
    pub fn get_reserved_size(&self) -> usize {
        self.reserved_size
    }
}

impl Drop for PBO {
    fn drop(&mut self) {
        debug_assert!(
            thread::panicking() || self.id == 0,
            "renderer::deinit not called or PBO not returned to pool"
        );
    }
}

pub struct BoundPBO<'a> {
    device: &'a mut Device,
    pub data: &'a [u8]
}

impl<'a> Drop for BoundPBO<'a> {
    fn drop(&mut self) {
        self.device.gl.unmap_buffer(gl::PIXEL_PACK_BUFFER);
        self.device.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, 0);
    }
}

#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
pub struct FBOId(gl::GLuint);

#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
pub struct RBOId(gl::GLuint);

#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
pub struct VBOId(gl::GLuint);

#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
struct IBOId(gl::GLuint);

#[derive(Clone, Debug)]
enum ProgramSourceType {
    Unoptimized,
    Optimized(ShaderVersion),
}

#[derive(Clone, Debug)]
pub struct ProgramSourceInfo {
    base_filename: &'static str,
    features: Vec<&'static str>,
    full_name_cstr: Rc<std::ffi::CString>,
    source_type: ProgramSourceType,
    digest: ProgramSourceDigest,
}

impl ProgramSourceInfo {
    fn new(
        device: &Device,
        name: &'static str,
        features: &[&'static str],
    ) -> Self {

        // Compute the digest. Assuming the device has a `ProgramCache`, this
        // will always be needed, whereas the source is rarely needed.

        use std::collections::hash_map::DefaultHasher;
        use std::hash::Hasher;

        // Setup.
        let mut hasher = DefaultHasher::new();
        let gl_version = get_shader_version(&*device.gl());

        // Hash the renderer name.
        hasher.write(device.capabilities.renderer_name.as_bytes());

        let full_name = Self::make_full_name(name, features);

        let optimized_source = if device.use_optimized_shaders {
            OPTIMIZED_SHADERS.get(&(gl_version, &full_name)).or_else(|| {
                warn!("Missing optimized shader source for {}", &full_name);
                None
            })
        } else {
            None
        };

        let source_type = match optimized_source {
            Some(source_and_digest) => {
                // Optimized shader sources are used as-is, without any run-time processing.
                // The vertex and fragment shaders are different, so must both be hashed.
                // We use the hashes that were computed at build time, and verify it in debug builds.
                if cfg!(debug_assertions) {
                    let mut h = DefaultHasher::new();
                    h.write(source_and_digest.vert_source.as_bytes());
                    h.write(source_and_digest.frag_source.as_bytes());
                    let d: ProgramSourceDigest = h.into();
                    let digest = d.to_string();
                    debug_assert_eq!(digest, source_and_digest.digest);
                    hasher.write(digest.as_bytes());
                } else {
                    hasher.write(source_and_digest.digest.as_bytes());
                }

                ProgramSourceType::Optimized(gl_version)
            }
            None => {
                // For non-optimized sources we compute the hash by walking the static strings
                // in the same order as we would when concatenating the source, to avoid
                // heap-allocating in the common case.
                //
                // Note that we cheat a bit to make the hashing more efficient. First, the only
                // difference between the vertex and fragment shader is a single deterministic
                // define, so we don't need to hash both. Second, we precompute the digest of the
                // expanded source file at build time, and then just hash that digest here.
                let override_path = device.resource_override_path.as_ref();
                let source_and_digest = UNOPTIMIZED_SHADERS.get(&name).expect("Shader not found");

                // Hash the prefix string.
                build_shader_prefix_string(
                    gl_version,
                    &features,
                    ShaderKind::Vertex,
                    &name,
                    &mut |s| hasher.write(s.as_bytes()),
                );

                // Hash the shader file contents. We use a precomputed digest, and
                // verify it in debug builds.
                if override_path.is_some() || cfg!(debug_assertions) {
                    let mut h = DefaultHasher::new();
                    build_shader_main_string(
                        &name,
                        &|f| get_unoptimized_shader_source(f, override_path),
                        &mut |s| h.write(s.as_bytes())
                    );
                    let d: ProgramSourceDigest = h.into();
                    let digest = format!("{}", d);
                    debug_assert!(override_path.is_some() || digest == source_and_digest.digest);
                    hasher.write(digest.as_bytes());
                } else {
                    hasher.write(source_and_digest.digest.as_bytes());
                }

                ProgramSourceType::Unoptimized
            }
        };

        // Finish.
        ProgramSourceInfo {
            base_filename: name,
            features: features.to_vec(),
            full_name_cstr: Rc::new(std::ffi::CString::new(full_name).unwrap()),
            source_type,
            digest: hasher.into(),
        }
    }

    fn compute_source(&self, device: &Device, kind: ShaderKind) -> String {
        let full_name = self.full_name();
        match self.source_type {
            ProgramSourceType::Optimized(gl_version) => {
                let shader = OPTIMIZED_SHADERS
                    .get(&(gl_version, &full_name))
                    .unwrap_or_else(|| panic!("Missing optimized shader source for {}", full_name));

                match kind {
                    ShaderKind::Vertex => shader.vert_source.to_string(),
                    ShaderKind::Fragment => shader.frag_source.to_string(),
                }
            },
            ProgramSourceType::Unoptimized => {
                let mut src = String::new();
                device.build_shader_string(
                    &self.features,
                    kind,
                    self.base_filename,
                    |s| src.push_str(s),
                );
                src
            }
        }
    }

    fn make_full_name(base_filename: &'static str, features: &[&'static str]) -> String {
        if features.is_empty() {
            base_filename.to_string()
        } else {
            format!("{}_{}", base_filename, features.join("_"))
        }
    }

    fn full_name(&self) -> String {
        Self::make_full_name(self.base_filename, &self.features)
    }
}

#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
pub struct ProgramBinary {
    bytes: Vec<u8>,
    format: gl::GLenum,
    source_digest: ProgramSourceDigest,
}

impl ProgramBinary {
    fn new(bytes: Vec<u8>,
           format: gl::GLenum,
           source_digest: ProgramSourceDigest) -> Self {
        ProgramBinary {
            bytes,
            format,
            source_digest,
        }
    }

    /// Returns a reference to the source digest hash.
    pub fn source_digest(&self) -> &ProgramSourceDigest {
        &self.source_digest
    }
}

/// The interfaces that an application can implement to handle ProgramCache update
pub trait ProgramCacheObserver {
    fn save_shaders_to_disk(&self, entries: Vec<Arc<ProgramBinary>>);
    fn set_startup_shaders(&self, entries: Vec<Arc<ProgramBinary>>);
    fn try_load_shader_from_disk(&self, digest: &ProgramSourceDigest, program_cache: &Rc<ProgramCache>);
    fn notify_program_binary_failed(&self, program_binary: &Arc<ProgramBinary>);
}

struct ProgramCacheEntry {
    /// The binary.
    binary: Arc<ProgramBinary>,
    /// True if the binary has been linked, i.e. used for rendering.
    linked: bool,
}

pub struct ProgramCache {
    entries: RefCell<FastHashMap<ProgramSourceDigest, ProgramCacheEntry>>,

    /// Optional trait object that allows the client
    /// application to handle ProgramCache updating
    program_cache_handler: Option<Box<dyn ProgramCacheObserver>>,

    /// Programs that have not yet been cached to disk (by program_cache_handler)
    pending_entries: RefCell<Vec<Arc<ProgramBinary>>>,
}

impl ProgramCache {
    pub fn new(program_cache_observer: Option<Box<dyn ProgramCacheObserver>>) -> Rc<Self> {
        Rc::new(
            ProgramCache {
                entries: RefCell::new(FastHashMap::default()),
                program_cache_handler: program_cache_observer,
                pending_entries: RefCell::new(Vec::default()),
            }
        )
    }

    /// Save any new program binaries to the disk cache, and if startup has
    /// just completed then write the list of shaders to load on next startup.
    fn update_disk_cache(&self, startup_complete: bool) {
        if let Some(ref handler) = self.program_cache_handler {
            if !self.pending_entries.borrow().is_empty() {
                let pending_entries = self.pending_entries.replace(Vec::default());
                handler.save_shaders_to_disk(pending_entries);
            }

            if startup_complete {
                let startup_shaders = self.entries.borrow().values()
                    .filter(|e| e.linked).map(|e| e.binary.clone())
                    .collect::<Vec<_>>();
                handler.set_startup_shaders(startup_shaders);
            }
        }
    }

    /// Add a new ProgramBinary to the cache.
    /// This function is typically used after compiling and linking a new program.
    /// The binary will be saved to disk the next time update_disk_cache() is called.
    fn add_new_program_binary(&self, program_binary: Arc<ProgramBinary>) {
        self.pending_entries.borrow_mut().push(program_binary.clone());

        let digest = program_binary.source_digest.clone();
        let entry = ProgramCacheEntry {
            binary: program_binary,
            linked: true,
        };
        self.entries.borrow_mut().insert(digest, entry);
    }

    /// Load ProgramBinary to ProgramCache.
    /// The function is typically used to load ProgramBinary from disk.
    #[cfg(feature = "serialize_program")]
    pub fn load_program_binary(&self, program_binary: Arc<ProgramBinary>) {
        let digest = program_binary.source_digest.clone();
        let entry = ProgramCacheEntry {
            binary: program_binary,
            linked: false,
        };
        self.entries.borrow_mut().insert(digest, entry);
    }

    /// Returns the number of bytes allocated for shaders in the cache.
    pub fn report_memory(&self, op: VoidPtrToSizeFn) -> usize {
        self.entries.borrow().values()
            .map(|e| unsafe { op(e.binary.bytes.as_ptr() as *const c_void ) })
            .sum()
    }
}

#[derive(Debug, Copy, Clone)]
pub enum VertexUsageHint {
    Static,
    Dynamic,
    Stream,
}

impl VertexUsageHint {
    fn to_gl(&self) -> gl::GLuint {
        match *self {
            VertexUsageHint::Static => gl::STATIC_DRAW,
            VertexUsageHint::Dynamic => gl::DYNAMIC_DRAW,
            VertexUsageHint::Stream => gl::STREAM_DRAW,
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub struct UniformLocation(#[allow(dead_code)] gl::GLint);

impl UniformLocation {
    pub const INVALID: Self = UniformLocation(-1);
}

#[derive(Debug)]
pub struct Capabilities {
    /// Whether multisampled render targets are supported.
    pub supports_multisampling: bool,
    /// Whether the function `glCopyImageSubData` is available.
    pub supports_copy_image_sub_data: bool,
    /// Whether the RGBAF32 textures can be bound to framebuffers.
    pub supports_color_buffer_float: bool,
    /// Whether the device supports persistently mapped buffers, via glBufferStorage.
    pub supports_buffer_storage: bool,
    /// Whether advanced blend equations are supported.
    pub supports_advanced_blend_equation: bool,
    /// Whether dual-source blending is supported.
    pub supports_dual_source_blending: bool,
    /// Whether KHR_debug is supported for getting debug messages from
    /// the driver.
    pub supports_khr_debug: bool,
    /// Whether we can configure texture units to do swizzling on sampling.
    pub supports_texture_swizzle: bool,
    /// Whether the driver supports uploading to textures from a non-zero
    /// offset within a PBO.
    pub supports_nonzero_pbo_offsets: bool,
    /// Whether the driver supports specifying the texture usage up front.
    pub supports_texture_usage: bool,
    /// Whether offscreen render targets can be partially updated.
    pub supports_render_target_partial_update: bool,
    /// Whether we can use SSBOs.
    pub supports_shader_storage_object: bool,
    /// Whether to enforce that texture uploads be batched regardless of what
    /// the pref says.
    pub requires_batched_texture_uploads: Option<bool>,
    /// Whether we are able to ue glClear to clear regions of an alpha render target.
    /// If false, we must use a shader to clear instead.
    pub supports_alpha_target_clears: bool,
    /// Whether we must perform a full unscissored glClear on alpha targets
    /// prior to rendering.
    pub requires_alpha_target_full_clear: bool,
    /// Whether clearing a render target (immediately after binding it) is faster using a scissor
    /// rect to clear just the required area, or clearing the entire target without a scissor rect.
    pub prefers_clear_scissor: bool,
    /// Whether the driver can correctly invalidate render targets. This can be
    /// a worthwhile optimization, but is buggy on some devices.
    pub supports_render_target_invalidate: bool,
    /// Whether the driver can reliably upload data to R8 format textures.
    pub supports_r8_texture_upload: bool,
    /// Whether the extension QCOM_tiled_rendering is supported.
    pub supports_qcom_tiled_rendering: bool,
    /// Whether clip-masking is supported natively by the GL implementation
    /// rather than emulated in shaders.
    pub uses_native_clip_mask: bool,
    /// Whether anti-aliasing is supported natively by the GL implementation
    /// rather than emulated in shaders.
    pub uses_native_antialiasing: bool,
    /// Whether the extension GL_OES_EGL_image_external_essl3 is supported. If true, external
    /// textures can be used as normal. If false, external textures can only be rendered with
    /// certain shaders, and must first be copied in to regular textures for others.
    pub supports_image_external_essl3: bool,
    /// Whether the VAO must be rebound after an attached VBO has been orphaned.
    pub requires_vao_rebind_after_orphaning: bool,
    /// The name of the renderer, as reported by GL
    pub renderer_name: String,
}

#[derive(Clone, Debug)]
pub enum ShaderError {
    Compilation(String, String), // name, error message
    Link(String, String),        // name, error message
}

/// A refcounted depth target, which may be shared by multiple textures across
/// the device.
struct SharedDepthTarget {
    /// The Render Buffer Object representing the depth target.
    rbo_id: RBOId,
    /// Reference count. When this drops to zero, the RBO is deleted.
    refcount: usize,
}

#[cfg(debug_assertions)]
impl Drop for SharedDepthTarget {
    fn drop(&mut self) {
        debug_assert!(thread::panicking() || self.refcount == 0);
    }
}

/// Describes for which texture formats to use the glTexStorage*
/// family of functions.
#[derive(PartialEq, Debug)]
enum TexStorageUsage {
    Never,
    NonBGRA8,
    Always,
}

/// Describes a required alignment for a stride,
/// which can either be represented in bytes or pixels.
#[derive(Copy, Clone, Debug)]
pub enum StrideAlignment {
    Bytes(NonZeroUsize),
    Pixels(NonZeroUsize),
}

impl StrideAlignment {
    pub fn num_bytes(&self, format: ImageFormat) -> NonZeroUsize {
        match *self {
            Self::Bytes(bytes) => bytes,
            Self::Pixels(pixels) => {
                assert!(format.bytes_per_pixel() > 0);
                NonZeroUsize::new(pixels.get() * format.bytes_per_pixel() as usize).unwrap()
            }
        }
    }
}

// We get 24 bits of Z value - use up 22 bits of it to give us
// 4 bits to account for GPU issues. This seems to manifest on
// some GPUs under certain perspectives due to z interpolation
// precision problems.
const RESERVE_DEPTH_BITS: i32 = 2;

pub struct Device {
    gl: Rc<dyn gl::Gl>,

    /// If non-None, |gl| points to a profiling wrapper, and this points to the
    /// underling Gl instance.
    base_gl: Option<Rc<dyn gl::Gl>>,

    // device state
    bound_textures: [gl::GLuint; 16],
    bound_program: gl::GLuint,
    bound_program_name: Rc<std::ffi::CString>,
    bound_vao: gl::GLuint,
    bound_read_fbo: (FBOId, DeviceIntPoint),
    bound_draw_fbo: FBOId,
    default_read_fbo: FBOId,
    default_draw_fbo: FBOId,

    /// Track depth state for assertions. Note that the default FBO has depth,
    /// so this defaults to true.
    depth_available: bool,

    upload_method: UploadMethod,
    use_batched_texture_uploads: bool,
    /// Whether to use draw calls instead of regular blitting commands.
    ///
    /// Note: this currently only applies to the batched texture uploads
    /// path.
    use_draw_calls_for_texture_copy: bool,
    /// Number of pixels below which we prefer batched uploads.
    batched_upload_threshold: i32,

    // HW or API capabilities
    capabilities: Capabilities,

    color_formats: TextureFormatPair<ImageFormat>,
    bgra_formats: TextureFormatPair<gl::GLuint>,
    bgra_pixel_type: gl::GLuint,
    swizzle_settings: SwizzleSettings,
    depth_format: gl::GLuint,

    /// Map from texture dimensions to shared depth buffers for render targets.
    ///
    /// Render targets often have the same width/height, so we can save memory
    /// by sharing these across targets.
    depth_targets: FastHashMap<DeviceIntSize, SharedDepthTarget>,

    // debug
    inside_frame: bool,
    crash_annotator: Option<Box<dyn CrashAnnotator>>,
    annotate_draw_call_crashes: bool,

    // resources
    resource_override_path: Option<PathBuf>,

    /// Whether to use shaders that have been optimized at build time.
    use_optimized_shaders: bool,

    max_texture_size: i32,
    cached_programs: Option<Rc<ProgramCache>>,

    // Frame counter. This is used to map between CPU
    // frames and GPU frames.
    frame_id: GpuFrameId,

    /// When to use glTexStorage*. We prefer this over glTexImage* because it
    /// guarantees that mipmaps won't be generated (which they otherwise are on
    /// some drivers, particularly ANGLE). However, it is not always supported
    /// at all, or for BGRA8 format. If it's not supported for the required
    /// format, we fall back to glTexImage*.
    texture_storage_usage: TexStorageUsage,

    /// Required stride alignment for pixel transfers. This may be required for
    /// correctness reasons due to driver bugs, or for performance reasons to
    /// ensure we remain on the fast-path for transfers.
    required_pbo_stride: StrideAlignment,

    /// Whether we must ensure the source strings passed to glShaderSource()
    /// are null-terminated, to work around driver bugs.
    requires_null_terminated_shader_source: bool,

    /// Whether we must unbind any texture from GL_TEXTURE_EXTERNAL_OES before
    /// binding to GL_TEXTURE_2D, to work around an android emulator bug.
    requires_texture_external_unbind: bool,

    ///
    is_software_webrender: bool,

    // GL extensions
    extensions: Vec<String>,

    /// Dumps the source of the shader with the given name
    dump_shader_source: Option<String>,

    surface_origin_is_top_left: bool,

    /// A debug boolean for tracking if the shader program has been set after
    /// a blend mode change.
    ///
    /// This is needed for compatibility with next-gen
    /// GPU APIs that switch states using "pipeline object" that bundles
    /// together the blending state with the shader.
    ///
    /// Having the constraint of always binding the shader last would allow
    /// us to have the "pipeline object" bound at that time. Without this
    /// constraint, we'd either have to eagerly bind the "pipeline object"
    /// on changing either the shader or the blend more, or lazily bind it
    /// at draw call time, neither of which is desirable.
    #[cfg(debug_assertions)]
    shader_is_ready: bool,

    // count created/deleted textures to report in the profiler.
    pub textures_created: u32,
    pub textures_deleted: u32,
}

/// Contains the parameters necessary to bind a draw target.
#[derive(Clone, Copy, Debug)]
pub enum DrawTarget {
    /// Use the device's default draw target, with the provided dimensions,
    /// which are used to set the viewport.
    Default {
        /// Target rectangle to draw.
        rect: FramebufferIntRect,
        /// Total size of the target.
        total_size: FramebufferIntSize,
        surface_origin_is_top_left: bool,
    },
    /// Use the provided texture.
    Texture {
        /// Size of the texture in pixels
        dimensions: DeviceIntSize,
        /// Whether to draw with the texture's associated depth target
        with_depth: bool,
        /// FBO that corresponds to the selected layer / depth mode
        fbo_id: FBOId,
        /// Native GL texture ID
        id: gl::GLuint,
        /// Native GL texture target
        target: gl::GLuint,
    },
    /// Use an FBO attached to an external texture.
    External {
        fbo: FBOId,
        size: FramebufferIntSize,
    },
    /// An OS compositor surface
    NativeSurface {
        offset: DeviceIntPoint,
        external_fbo_id: u32,
        dimensions: DeviceIntSize,
    },
}

impl DrawTarget {
    pub fn new_default(size: DeviceIntSize, surface_origin_is_top_left: bool) -> Self {
        let total_size = device_size_as_framebuffer_size(size);
        DrawTarget::Default {
            rect: total_size.into(),
            total_size,
            surface_origin_is_top_left,
        }
    }

    /// Returns true if this draw target corresponds to the default framebuffer.
    pub fn is_default(&self) -> bool {
        match *self {
            DrawTarget::Default {..} => true,
            _ => false,
        }
    }

    pub fn from_texture(
        texture: &Texture,
        with_depth: bool,
    ) -> Self {
        let fbo_id = if with_depth {
            texture.fbo_with_depth.unwrap()
        } else {
            texture.fbo.unwrap()
        };

        DrawTarget::Texture {
            dimensions: texture.get_dimensions(),
            fbo_id,
            with_depth,
            id: texture.id,
            target: texture.target,
        }
    }

    /// Returns the dimensions of this draw-target.
    pub fn dimensions(&self) -> DeviceIntSize {
        match *self {
            DrawTarget::Default { total_size, .. } => total_size.cast_unit(),
            DrawTarget::Texture { dimensions, .. } => dimensions,
            DrawTarget::External { size, .. } => size.cast_unit(),
            DrawTarget::NativeSurface { dimensions, .. } => dimensions,
        }
    }

    pub fn offset(&self) -> DeviceIntPoint {
        match *self {
            DrawTarget::Default { .. } |
            DrawTarget::Texture { .. } |
            DrawTarget::External { .. } => {
                DeviceIntPoint::zero()
            }
            DrawTarget::NativeSurface { offset, .. } => offset,
        }
    }

    pub fn to_framebuffer_rect(&self, device_rect: DeviceIntRect) -> FramebufferIntRect {
        let mut fb_rect = device_rect_as_framebuffer_rect(&device_rect);
        match *self {
            DrawTarget::Default { ref rect, surface_origin_is_top_left, .. } => {
                // perform a Y-flip here
                if !surface_origin_is_top_left {
                    let w = fb_rect.width();
                    let h = fb_rect.height();
                    fb_rect.min.x = fb_rect.min.x + rect.min.x;
                    fb_rect.min.y = rect.max.y - fb_rect.max.y;
                    fb_rect.max.x = fb_rect.min.x + w;
                    fb_rect.max.y = fb_rect.min.y + h;
                }
            }
            DrawTarget::Texture { .. } | DrawTarget::External { .. } | DrawTarget::NativeSurface { .. } => (),
        }
        fb_rect
    }

    pub fn surface_origin_is_top_left(&self) -> bool {
        match *self {
            DrawTarget::Default { surface_origin_is_top_left, .. } => surface_origin_is_top_left,
            DrawTarget::Texture { .. } | DrawTarget::External { .. } | DrawTarget::NativeSurface { .. } => true,
        }
    }

    /// Given a scissor rect, convert it to the right coordinate space
    /// depending on the draw target kind. If no scissor rect was supplied,
    /// returns a scissor rect that encloses the entire render target.
    pub fn build_scissor_rect(
        &self,
        scissor_rect: Option<DeviceIntRect>,
    ) -> FramebufferIntRect {
        let dimensions = self.dimensions();

        match scissor_rect {
            Some(scissor_rect) => match *self {
                DrawTarget::Default { ref rect, .. } => {
                    self.to_framebuffer_rect(scissor_rect)
                        .intersection(rect)
                        .unwrap_or_else(FramebufferIntRect::zero)
                }
                DrawTarget::NativeSurface { offset, .. } => {
                    device_rect_as_framebuffer_rect(&scissor_rect.translate(offset.to_vector()))
                }
                DrawTarget::Texture { .. } | DrawTarget::External { .. } => {
                    device_rect_as_framebuffer_rect(&scissor_rect)
                }
            }
            None => {
                FramebufferIntRect::from_size(
                    device_size_as_framebuffer_size(dimensions),
                )
            }
        }
    }
}

/// Contains the parameters necessary to bind a texture-backed read target.
#[derive(Clone, Copy, Debug)]
pub enum ReadTarget {
    /// Use the device's default draw target.
    Default,
    /// Use the provided texture,
    Texture {
        /// ID of the FBO to read from.
        fbo_id: FBOId,
    },
    /// Use an FBO attached to an external texture.
    External {
        fbo: FBOId,
    },
    /// An FBO bound to a native (OS compositor) surface
    NativeSurface {
        fbo_id: FBOId,
        offset: DeviceIntPoint,
    },
}

impl ReadTarget {
    pub fn from_texture(
        texture: &Texture,
    ) -> Self {
        ReadTarget::Texture {
            fbo_id: texture.fbo.unwrap(),
        }
    }

    fn offset(&self) -> DeviceIntPoint {
        match *self {
            ReadTarget::Default |
            ReadTarget::Texture { .. } |
            ReadTarget::External { .. } => {
                DeviceIntPoint::zero()
            }

            ReadTarget::NativeSurface { offset, .. } => {
                offset
            }
        }
    }
}

impl From<DrawTarget> for ReadTarget {
    fn from(t: DrawTarget) -> Self {
        match t {
            DrawTarget::Default { .. } => {
                ReadTarget::Default
            }
            DrawTarget::NativeSurface { external_fbo_id, offset, .. } => {
                ReadTarget::NativeSurface {
                    fbo_id: FBOId(external_fbo_id),
                    offset,
                }
            }
            DrawTarget::Texture { fbo_id, .. } => {
                ReadTarget::Texture { fbo_id }
            }
            DrawTarget::External { fbo, .. } => {
                ReadTarget::External { fbo }
            }
        }
    }
}

/// Parses the major, release, and patch versions from a GL_VERSION string on
/// Mali devices. For example, for the version string
/// "OpenGL ES 3.2 v1.r36p0-01eac0.28ab3a577f105e026887e2b4c93552fb" this
/// returns Some((1, 36, 0)). Returns None if the version cannot be parsed.
fn parse_mali_version(version_string: &str) -> Option<(u32, u32, u32)> {
    let (_prefix, version_string) = version_string.split_once("v")?;
    let (v_str, version_string) = version_string.split_once(".r")?;
    let v = v_str.parse().ok()?;

    let (r_str, version_string) = version_string.split_once("p")?;
    let r = r_str.parse().ok()?;

    // Not all devices have the trailing string following the "p" number.
    let (p_str, _) = version_string.split_once("-").unwrap_or((version_string, ""));
    let p = p_str.parse().ok()?;

    Some((v, r, p))
}

/// Returns whether this GPU belongs to the Mali Midgard family
fn is_mali_midgard(renderer_name: &str) -> bool {
    renderer_name.starts_with("Mali-T")
}

/// Returns whether this GPU belongs to the Mali Bifrost family
fn is_mali_bifrost(renderer_name: &str) -> bool {
    renderer_name == "Mali-G31"
        || renderer_name == "Mali-G51"
        || renderer_name == "Mali-G71"
        || renderer_name == "Mali-G52"
        || renderer_name == "Mali-G72"
        || renderer_name == "Mali-G76"
}

/// Returns whether this GPU belongs to the Mali Valhall family
fn is_mali_valhall(renderer_name: &str) -> bool {
    // As new Valhall GPUs may be released in the future we match all Mali-G models, apart from
    // Bifrost models (of which we don't expect any new ones to be released)
    renderer_name.starts_with("Mali-G") && !is_mali_bifrost(renderer_name)
}

impl Device {
    pub fn new(
        mut gl: Rc<dyn gl::Gl>,
        crash_annotator: Option<Box<dyn CrashAnnotator>>,
        resource_override_path: Option<PathBuf>,
        use_optimized_shaders: bool,
        upload_method: UploadMethod,
        batched_upload_threshold: i32,
        cached_programs: Option<Rc<ProgramCache>>,
        allow_texture_storage_support: bool,
        allow_texture_swizzling: bool,
        dump_shader_source: Option<String>,
        surface_origin_is_top_left: bool,
        panic_on_gl_error: bool,
    ) -> Device {
        let mut max_texture_size = [0];
        unsafe {
            gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
        }

        // We cap the max texture size at 16384. Some hardware report higher
        // capabilities but get very unstable with very large textures.
        // Bug 1702494 tracks re-evaluating this cap.
        let max_texture_size = max_texture_size[0].min(16384);

        let renderer_name = gl.get_string(gl::RENDERER);
        info!("Renderer: {}", renderer_name);
        let version_string = gl.get_string(gl::VERSION);
        info!("Version: {}", version_string);
        info!("Max texture size: {}", max_texture_size);

        let mut extension_count = [0];
        unsafe {
            gl.get_integer_v(gl::NUM_EXTENSIONS, &mut extension_count);
        }
        let extension_count = extension_count[0] as gl::GLuint;
        let mut extensions = Vec::new();
        for i in 0 .. extension_count {
            extensions.push(gl.get_string_i(gl::EXTENSIONS, i));
        }

        let is_xclipse = renderer_name.starts_with("ANGLE (Samsung Xclipse");

        // On debug builds, assert that each GL call is error-free. We don't do
        // this on release builds because the synchronous call can stall the
        // pipeline.
        // We block this on Mali Valhall GPUs as the extension's functions always return
        // GL_OUT_OF_MEMORY, causing us to panic in debug builds.
        // Blocked on Xclipse GPUs as glGetDebugMessageLog returns an incorrect count,
        // leading to an out-of-bounds index in gleam.
        let supports_khr_debug =
            supports_extension(&extensions, "GL_KHR_debug")
            && !is_mali_valhall(&renderer_name)
            && !is_xclipse;
        if panic_on_gl_error || cfg!(debug_assertions) {
            gl = gl::ErrorReactingGl::wrap(gl, move |gl, name, code| {
                if supports_khr_debug {
                    Self::log_driver_messages(gl);
                }
                error!("Caught GL error {:x} at {}", code, name);
                panic!("Caught GL error {:x} at {}", code, name);
            });
        }

        if supports_extension(&extensions, "GL_ANGLE_provoking_vertex") {
            gl.provoking_vertex_angle(gl::FIRST_VERTEX_CONVENTION);
        }

        let supports_texture_usage = supports_extension(&extensions, "GL_ANGLE_texture_usage");

        // Our common-case image data in Firefox is BGRA, so we make an effort
        // to use BGRA as the internal texture storage format to avoid the need
        // to swizzle during upload. Currently we only do this on GLES (and thus
        // for Windows, via ANGLE).
        //
        // On Mac, Apple docs [1] claim that BGRA is a more efficient internal
        // format, but they don't support it with glTextureStorage. As a workaround,
        // we pretend that it's RGBA8 for the purposes of texture transfers,
        // but swizzle R with B for the texture sampling.
        //
        // We also need our internal format types to be sized, since glTexStorage*
        // will reject non-sized internal format types.
        //
        // Unfortunately, with GL_EXT_texture_format_BGRA8888, BGRA8 is not a
        // valid internal format (for glTexImage* or glTexStorage*) unless
        // GL_EXT_texture_storage is also available [2][3], which is usually
        // not the case on GLES 3 as the latter's functionality has been
        // included by default but the former has not been updated.
        // The extension is available on ANGLE, but on Android this usually
        // means we must fall back to using unsized BGRA and glTexImage*.
        //
        // Overall, we have the following factors in play when choosing the formats:
        //   - with glTexStorage, the internal format needs to match the external format,
        //     or the driver would have to do the conversion, which is slow
        //   - on desktop GL, there is no BGRA internal format. However, initializing
        //     the textures with glTexImage as RGBA appears to use BGRA internally,
        //     preferring BGRA external data [4].
        //   - when glTexStorage + BGRA internal format is not supported,
        //     and the external data is BGRA, we have the following options:
        //       1. use glTexImage with RGBA internal format, this costs us VRAM for mipmaps
        //       2. use glTexStorage with RGBA internal format, this costs us the conversion by the driver
        //       3. pretend we are uploading RGBA and set up the swizzling of the texture unit - this costs us batch breaks
        //
        // [1] https://developer.apple.com/library/archive/documentation/
        //     GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/
        //     opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW22
        // [2] https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_format_BGRA8888.txt
        // [3] https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_storage.txt
        // [4] http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf

        // On the android emulator glTexImage fails to create textures larger than 3379.
        // So we must use glTexStorage instead. See bug 1591436.
        let is_emulator = renderer_name.starts_with("Android Emulator");
        let avoid_tex_image = is_emulator;
        let mut gl_version = [0; 2];
        unsafe {
            gl.get_integer_v(gl::MAJOR_VERSION, &mut gl_version[0..1]);
            gl.get_integer_v(gl::MINOR_VERSION, &mut gl_version[1..2]);
        }
        info!("GL context {:?} {}.{}", gl.get_type(), gl_version[0], gl_version[1]);

        // We block texture storage on mac because it doesn't support BGRA
        let supports_texture_storage = allow_texture_storage_support && !cfg!(target_os = "macos") &&
            match gl.get_type() {
                gl::GlType::Gl => supports_extension(&extensions, "GL_ARB_texture_storage"),
                gl::GlType::Gles => true,
            };

        // The GL_EXT_texture_format_BGRA8888 extension allows us to use BGRA as an internal format
        // with glTexImage on GLES. However, we can only use BGRA8 as an internal format for
        // glTexStorage when GL_EXT_texture_storage is also explicitly supported. This is because
        // glTexStorage was added in GLES 3, but GL_EXT_texture_format_BGRA8888 was written against
        // GLES 2 and GL_EXT_texture_storage.
        // To complicate things even further, some Intel devices claim to support both extensions
        // but in practice do not allow BGRA to be used with glTexStorage.
        let supports_gles_bgra = supports_extension(&extensions, "GL_EXT_texture_format_BGRA8888");
        let supports_texture_storage_with_gles_bgra = supports_gles_bgra
            && supports_extension(&extensions, "GL_EXT_texture_storage")
            && !renderer_name.starts_with("Intel(R) HD Graphics for BayTrail")
            && !renderer_name.starts_with("Intel(R) HD Graphics for Atom(TM) x5/x7");

        let supports_texture_swizzle = allow_texture_swizzling &&
            match gl.get_type() {
                // see https://www.g-truc.net/post-0734.html
                gl::GlType::Gl => gl_version >= [3, 3] ||
                    supports_extension(&extensions, "GL_ARB_texture_swizzle"),
                gl::GlType::Gles => true,
            };

        let (color_formats, bgra_formats, bgra_pixel_type, bgra8_sampling_swizzle, texture_storage_usage) = match gl.get_type() {
            // There is `glTexStorage`, use it and expect RGBA on the input.
            gl::GlType::Gl if supports_texture_storage && supports_texture_swizzle => (
                TextureFormatPair::from(ImageFormat::RGBA8),
                TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA },
                gl::UNSIGNED_BYTE,
                Swizzle::Bgra, // pretend it's RGBA, rely on swizzling
                TexStorageUsage::Always
            ),
            // There is no `glTexStorage`, upload as `glTexImage` with BGRA input.
            gl::GlType::Gl => (
                TextureFormatPair { internal: ImageFormat::BGRA8, external: ImageFormat::BGRA8 },
                TextureFormatPair { internal: gl::RGBA, external: gl::BGRA },
                gl::UNSIGNED_INT_8_8_8_8_REV,
                Swizzle::Rgba, // converted on uploads by the driver, no swizzling needed
                TexStorageUsage::Never
            ),
            // glTexStorage is always supported in GLES 3, but because the GL_EXT_texture_storage
            // extension is supported we can use glTexStorage with BGRA8 as the internal format.
            // Prefer BGRA textures over RGBA.
            gl::GlType::Gles if supports_texture_storage_with_gles_bgra => (
                TextureFormatPair::from(ImageFormat::BGRA8),
                TextureFormatPair { internal: gl::BGRA8_EXT, external: gl::BGRA_EXT },
                gl::UNSIGNED_BYTE,
                Swizzle::Rgba, // no conversion needed
                TexStorageUsage::Always,
            ),
            // BGRA is not supported as an internal format with glTexStorage, therefore we will
            // use RGBA textures instead and pretend BGRA data is RGBA when uploading.
            // The swizzling will happen at the texture unit.
            gl::GlType::Gles if supports_texture_swizzle => (
                TextureFormatPair::from(ImageFormat::RGBA8),
                TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA },
                gl::UNSIGNED_BYTE,
                Swizzle::Bgra, // pretend it's RGBA, rely on swizzling
                TexStorageUsage::Always,
            ),
            // BGRA is not supported as an internal format with glTexStorage, and we cannot use
            // swizzling either. Therefore prefer BGRA textures over RGBA, but use glTexImage
            // to initialize BGRA textures. glTexStorage can still be used for other formats.
            gl::GlType::Gles if supports_gles_bgra && !avoid_tex_image => (
                TextureFormatPair::from(ImageFormat::BGRA8),
                TextureFormatPair::from(gl::BGRA_EXT),
                gl::UNSIGNED_BYTE,
                Swizzle::Rgba, // no conversion needed
                TexStorageUsage::NonBGRA8,
            ),
            // Neither BGRA or swizzling are supported. GLES does not allow format conversion
            // during upload so we must use RGBA textures and pretend BGRA data is RGBA when
            // uploading. Images may be rendered incorrectly as a result.
            gl::GlType::Gles => {
                warn!("Neither BGRA or texture swizzling are supported. Images may be rendered incorrectly.");
                (
                    TextureFormatPair::from(ImageFormat::RGBA8),
                    TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA },
                    gl::UNSIGNED_BYTE,
                    Swizzle::Rgba,
                    TexStorageUsage::Always,
                )
            }
        };

        let is_software_webrender = renderer_name.starts_with("Software WebRender");
        let upload_method = if is_software_webrender {
            // Uploads in SWGL generally reduce to simple memory copies.
            UploadMethod::Immediate
        } else {
            upload_method
        };
        // Prefer 24-bit depth format. While 16-bit depth also works, it may exhaust depth ids easily.
        let depth_format = gl::DEPTH_COMPONENT24;

        info!("GL texture cache {:?}, bgra {:?} swizzle {:?}, texture storage {:?}, depth {:?}",
            color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage, depth_format);

        // On Mali-T devices glCopyImageSubData appears to stall the pipeline until any pending
        // renders to the source texture have completed. On Mali-G, it has been observed to
        // indefinitely hang in some circumstances. Using an alternative such as glBlitFramebuffer
        // is preferable on such devices, so pretend we don't support glCopyImageSubData.
        // See bugs 1669494 and 1677757.
        let supports_copy_image_sub_data = if renderer_name.starts_with("Mali") {
            false
        } else {
            supports_extension(&extensions, "GL_EXT_copy_image") ||
            supports_extension(&extensions, "GL_ARB_copy_image")
        };

        // We have seen crashes on x86 PowerVR Rogue G6430 devices during GPU cache
        // updates using the scatter shader. It seems likely that GL_EXT_color_buffer_float
        // is broken. See bug 1709408.
        let is_x86_powervr_rogue_g6430 = renderer_name.starts_with("PowerVR Rogue G6430")
            && cfg!(target_arch = "x86");
        let supports_color_buffer_float = match gl.get_type() {
            gl::GlType::Gl => true,
            gl::GlType::Gles if is_x86_powervr_rogue_g6430 => false,
            gl::GlType::Gles => supports_extension(&extensions, "GL_EXT_color_buffer_float"),
        };

        let is_adreno = renderer_name.starts_with("Adreno");

        // There appears to be a driver bug on older versions of the Adreno
        // driver which prevents usage of persistenly mapped buffers.
        // See bugs 1678585 and 1683936.
        // TODO: only disable feature for affected driver versions.
        let supports_buffer_storage = if is_adreno {
            false
        } else {
            supports_extension(&extensions, "GL_EXT_buffer_storage") ||
            supports_extension(&extensions, "GL_ARB_buffer_storage")
        };

        // KHR_blend_equation_advanced renders incorrectly on Adreno
        // devices. This has only been confirmed up to Adreno 5xx, and has been
        // fixed for Android 9, so this condition could be made more specific.
        let supports_advanced_blend_equation =
            supports_extension(&extensions, "GL_KHR_blend_equation_advanced") &&
            !is_adreno;

        let supports_dual_source_blending = match gl.get_type() {
            gl::GlType::Gl => supports_extension(&extensions,"GL_ARB_blend_func_extended") &&
                supports_extension(&extensions,"GL_ARB_explicit_attrib_location"),
            gl::GlType::Gles => supports_extension(&extensions,"GL_EXT_blend_func_extended"),
        };

        // Software webrender relies on the unoptimized shader source.
        let use_optimized_shaders = use_optimized_shaders && !is_software_webrender;

        // On the android emulator, and possibly some Mali devices, glShaderSource
        // can crash if the source strings are not null-terminated.
        // See bug 1591945 and bug 1799722.
        let requires_null_terminated_shader_source = is_emulator || renderer_name == "Mali-T628"
            || renderer_name == "Mali-T720" || renderer_name == "Mali-T760";

        // The android emulator gets confused if you don't explicitly unbind any texture
        // from GL_TEXTURE_EXTERNAL_OES before binding another to GL_TEXTURE_2D. See bug 1636085.
        let requires_texture_external_unbind = is_emulator;

        let is_macos = cfg!(target_os = "macos");
             //  && renderer_name.starts_with("AMD");
             //  (XXX: we apply this restriction to all GPUs to handle switching)

        let is_windows_angle = cfg!(target_os = "windows")
            && renderer_name.starts_with("ANGLE");
        let is_adreno_3xx = renderer_name.starts_with("Adreno (TM) 3");

        // Some GPUs require the stride of the data during texture uploads to be
        // aligned to certain requirements, either for correctness or performance
        // reasons.
        let required_pbo_stride = if is_adreno_3xx {
            // On Adreno 3xx, alignments of < 128 bytes can result in corrupted
            // glyphs. See bug 1696039.
            StrideAlignment::Bytes(NonZeroUsize::new(128).unwrap())
        } else if is_adreno {
            // On later Adreno devices it must be a multiple of 64 *pixels* to
            // hit the fast path, meaning value in bytes varies with the texture
            // format. This is purely an optimization.
            StrideAlignment::Pixels(NonZeroUsize::new(64).unwrap())
        } else if is_macos {
            // On AMD Mac, it must always be a multiple of 256 bytes.
            // We apply this restriction to all GPUs to handle switching
            StrideAlignment::Bytes(NonZeroUsize::new(256).unwrap())
        } else if is_windows_angle {
            // On ANGLE-on-D3D, PBO texture uploads get incorrectly truncated
            // if the stride is greater than the width * bpp.
            StrideAlignment::Bytes(NonZeroUsize::new(1).unwrap())
        } else {
            // Other platforms may have similar requirements and should be added
            // here. The default value should be 4 bytes.
            StrideAlignment::Bytes(NonZeroUsize::new(4).unwrap())
        };

        // On AMD Macs there is a driver bug which causes some texture uploads
        // from a non-zero offset within a PBO to fail. See bug 1603783.
        let supports_nonzero_pbo_offsets = !is_macos;

        // We have encountered several issues when only partially updating render targets on a
        // variety of Mali GPUs. As a precaution avoid doing so on all Midgard and Bifrost GPUs.
        // Valhall (eg Mali-Gx7 onwards) appears to be unnaffected. See bug 1691955, bug 1558374,
        // and bug 1663355.
        let supports_render_target_partial_update =
            !is_mali_midgard(&renderer_name) && !is_mali_bifrost(&renderer_name);

        let supports_shader_storage_object = match gl.get_type() {
            // see https://www.g-truc.net/post-0734.html
            gl::GlType::Gl => supports_extension(&extensions, "GL_ARB_shader_storage_buffer_object"),
            gl::GlType::Gles => gl_version >= [3, 1],
        };

        // SWGL uses swgl_clipMask() instead of implementing clip-masking in shaders.
        // This allows certain shaders to potentially bypass the more expensive alpha-
        // pass variants if they know the alpha-pass was only required to deal with
        // clip-masking.
        let uses_native_clip_mask = is_software_webrender;

        // SWGL uses swgl_antiAlias() instead of implementing anti-aliasing in shaders.
        // As above, this allows bypassing certain alpha-pass variants.
        let uses_native_antialiasing = is_software_webrender;

        // If running on android with a mesa driver (eg intel chromebooks), parse the mesa version.
        let mut android_mesa_version = None;
--> --------------------

--> maximum size reached

--> --------------------

[ 0.96Quellennavigators  ]