/* 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 crate::webgl_limits::GLLimitsDetect;
use byteorder::{ByteOrder, NativeEndian, WriteBytesExt};
use canvas_traits::webgl;
use canvas_traits::webgl::webgl_channel;
use canvas_traits::webgl::ActiveAttribInfo;
use canvas_traits::webgl::ActiveUniformBlockInfo;
use canvas_traits::webgl::ActiveUniformInfo;
use canvas_traits::webgl::AlphaTreatment;
use canvas_traits::webgl::DOMToTextureCommand;
use canvas_traits::webgl::GLContextAttributes;
use canvas_traits::webgl::GLLimits;
use canvas_traits::webgl::GlType;
use canvas_traits::webgl::InternalFormatIntVec;
use canvas_traits::webgl::ProgramLinkInfo;
use canvas_traits::webgl::TexDataType;
use canvas_traits::webgl::TexFormat;
use canvas_traits::webgl::WebGLBufferId;
use canvas_traits::webgl::WebGLChan;
use canvas_traits::webgl::WebGLCommand;
use canvas_traits::webgl::WebGLCommandBacktrace;
use canvas_traits::webgl::WebGLContextId;
use canvas_traits::webgl::WebGLCreateContextResult;
use canvas_traits::webgl::WebGLFramebufferBindingRequest;
use canvas_traits::webgl::WebGLFramebufferId;
use canvas_traits::webgl::WebGLMsg;
use canvas_traits::webgl::WebGLMsgSender;
use canvas_traits::webgl::WebGLProgramId;
use canvas_traits::webgl::WebGLQueryId;
use canvas_traits::webgl::WebGLReceiver;
use canvas_traits::webgl::WebGLRenderbufferId;
use canvas_traits::webgl::WebGLSLVersion;
use canvas_traits::webgl::WebGLSamplerId;
use canvas_traits::webgl::WebGLSender;
use canvas_traits::webgl::WebGLShaderId;
use canvas_traits::webgl::WebGLSyncId;
use canvas_traits::webgl::WebGLTextureId;
use canvas_traits::webgl::WebGLVersion;
use canvas_traits::webgl::WebGLVertexArrayId;
use canvas_traits::webgl::WebXRCommand;
use canvas_traits::webgl::WebXRLayerManagerId;
use canvas_traits::webgl::YAxisTreatment;
use euclid::default::Size2D;
use fnv::FnvHashMap;
use half::f16;
use pixels::{self, PixelFormat};
use sparkle::gl;
use sparkle::gl::GLint;
use sparkle::gl::GLuint;
use sparkle::gl::Gl;
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::slice;
use std::sync::{Arc, Mutex};
use std::thread;
use surfman;
use surfman::Adapter;
use surfman::Connection;
use surfman::Context;
use surfman::ContextAttributeFlags;
use surfman::ContextAttributes;
use surfman::Device;
use surfman::GLVersion;
use surfman::SurfaceAccess;
use surfman::SurfaceInfo;
use surfman::SurfaceType;
use surfman_chains::SwapChains;
use surfman_chains_api::SwapChainsAPI;
use webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType};
use webxr::SurfmanGL as WebXRSurfman;
use webxr_api::ContextId as WebXRContextId;
use webxr_api::Error as WebXRError;
use webxr_api::GLContexts as WebXRContexts;
use webxr_api::GLTypes as WebXRTypes;
use webxr_api::LayerGrandManager as WebXRLayerGrandManager;
use webxr_api::LayerGrandManagerAPI as WebXRLayerGrandManagerAPI;
use webxr_api::LayerId as WebXRLayerId;
use webxr_api::LayerInit as WebXRLayerInit;
use webxr_api::LayerManager as WebXRLayerManager;
use webxr_api::LayerManagerAPI as WebXRLayerManagerAPI;
use webxr_api::LayerManagerFactory as WebXRLayerManagerFactory;
use webxr_api::SubImages as WebXRSubImages;

#[cfg(feature = "xr-profile")]
fn to_ms(ns: u64) -> f64 {
    ns as f64 / 1_000_000.
}

struct GLContextData {
    ctx: Context,
    gl: Rc<Gl>,
    state: GLState,
    attributes: GLContextAttributes,
}

#[derive(Debug)]
pub struct GLState {
    webgl_version: WebGLVersion,
    gl_version: GLVersion,
    requested_flags: ContextAttributeFlags,
    // This is the WebGL view of the color mask
    // The GL view may be different: if the GL context supports alpha
    // but the WebGL context doesn't, then color_write_mask.3 might be true
    // but the GL color write mask is false.
    color_write_mask: [bool; 4],
    clear_color: (f32, f32, f32, f32),
    scissor_test_enabled: bool,
    // The WebGL view of the stencil write mask (see comment re `color_write_mask`)
    stencil_write_mask: (u32, u32),
    stencil_test_enabled: bool,
    stencil_clear_value: i32,
    // The WebGL view of the depth write mask (see comment re `color_write_mask`)
    depth_write_mask: bool,
    depth_test_enabled: bool,
    depth_clear_value: f64,
    // True when the default framebuffer is bound to DRAW_FRAMEBUFFER
    drawing_to_default_framebuffer: bool,
    default_vao: gl::GLuint,
}

impl GLState {
    // Are we faking having no alpha / depth / stencil?
    fn fake_no_alpha(&self) -> bool {
        self.drawing_to_default_framebuffer &
            !self.requested_flags.contains(ContextAttributeFlags::ALPHA)
    }

    fn fake_no_depth(&self) -> bool {
        self.drawing_to_default_framebuffer &
            !self.requested_flags.contains(ContextAttributeFlags::DEPTH)
    }

    fn fake_no_stencil(&self) -> bool {
        self.drawing_to_default_framebuffer &
            !self
                .requested_flags
                .contains(ContextAttributeFlags::STENCIL)
    }

    // We maintain invariants between the GLState object and the GL state.
    fn restore_invariant(&self, gl: &Gl) {
        self.restore_clear_color_invariant(gl);
        self.restore_scissor_invariant(gl);
        self.restore_alpha_invariant(gl);
        self.restore_depth_invariant(gl);
        self.restore_stencil_invariant(gl);
    }

    fn restore_clear_color_invariant(&self, gl: &Gl) {
        let (r, g, b, a) = self.clear_color;
        gl.clear_color(r, g, b, a);
    }

    fn restore_scissor_invariant(&self, gl: &Gl) {
        if self.scissor_test_enabled {
            gl.enable(gl::SCISSOR_TEST);
        } else {
            gl.disable(gl::SCISSOR_TEST);
        }
    }

    fn restore_alpha_invariant(&self, gl: &Gl) {
        let [r, g, b, a] = self.color_write_mask;
        if self.fake_no_alpha() {
            gl.color_mask(r, g, b, false);
        } else {
            gl.color_mask(r, g, b, a);
        }
    }

    fn restore_depth_invariant(&self, gl: &Gl) {
        if self.fake_no_depth() {
            gl.depth_mask(false);
            gl.disable(gl::DEPTH_TEST);
        } else {
            gl.depth_mask(self.depth_write_mask);
            if self.depth_test_enabled {
                gl.enable(gl::DEPTH_TEST);
            } else {
                gl.disable(gl::DEPTH_TEST);
            }
        }
    }

    fn restore_stencil_invariant(&self, gl: &Gl) {
        if self.fake_no_stencil() {
            gl.stencil_mask(0);
            gl.disable(gl::STENCIL_TEST);
        } else {
            let (f, b) = self.stencil_write_mask;
            gl.stencil_mask_separate(gl::FRONT, f);
            gl.stencil_mask_separate(gl::BACK, b);
            if self.stencil_test_enabled {
                gl.enable(gl::STENCIL_TEST);
            } else {
                gl.disable(gl::STENCIL_TEST);
            }
        }
    }
}

impl Default for GLState {
    fn default() -> GLState {
        GLState {
            gl_version: GLVersion { major: 1, minor: 0 },
            webgl_version: WebGLVersion::WebGL1,
            requested_flags: ContextAttributeFlags::empty(),
            color_write_mask: [true, true, true, true],
            clear_color: (0., 0., 0., 0.),
            scissor_test_enabled: false,
            // Should these be 0xFFFF_FFFF?
            stencil_write_mask: (0, 0),
            stencil_test_enabled: false,
            stencil_clear_value: 0,
            depth_write_mask: true,
            depth_test_enabled: false,
            depth_clear_value: 1.,
            default_vao: 0,
            drawing_to_default_framebuffer: true,
        }
    }
}

/// A WebGLThread manages the life cycle and message multiplexing of
/// a set of WebGLContexts living in the same thread.
pub(crate) struct WebGLThread {
    /// The GPU device.
    device: Device,
    /// Channel used to generate/update or delete `webrender_api::ImageKey`s.
    webrender_api: webrender_api::RenderApi,
    webrender_doc: webrender_api::DocumentId,
    /// Map of live WebGLContexts.
    contexts: FnvHashMap<WebGLContextId, GLContextData>,
    /// Cached information for WebGLContexts.
    cached_context_info: FnvHashMap<WebGLContextId, WebGLContextInfo>,
    /// Current bound context.
    bound_context_id: Option<WebGLContextId>,
    /// Texture ids and sizes used in DOM to texture outputs.
    dom_outputs: FnvHashMap<webrender_api::PipelineId, DOMToTextureData>,
    /// List of registered webrender external images.
    /// We use it to get an unique ID for new WebGLContexts.
    external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
    /// The receiver that will be used for processing WebGL messages.
    receiver: crossbeam_channel::Receiver<WebGLMsg>,
    /// The receiver that should be used to send WebGL messages for processing.
    sender: WebGLSender<WebGLMsg>,
    /// The swap chains used by webrender
    webrender_swap_chains: SwapChains<WebGLContextId, Device>,
    /// Whether this context is a GL or GLES context.
    api_type: gl::GlType,
    /// The bridge to WebXR
    pub webxr_bridge: WebXRBridge,
}

/// The data required to initialize an instance of the WebGLThread type.
pub(crate) struct WebGLThreadInit {
    pub webrender_api_sender: webrender_api::RenderApiSender,
    pub webrender_doc: webrender_api::DocumentId,
    pub external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
    pub sender: WebGLSender<WebGLMsg>,
    pub receiver: WebGLReceiver<WebGLMsg>,
    pub webrender_swap_chains: SwapChains<WebGLContextId, Device>,
    pub connection: Connection,
    pub adapter: Adapter,
    pub api_type: gl::GlType,
    pub webxr_init: WebXRBridgeInit,
}

// A size at which it should be safe to create GL contexts
const SAFE_VIEWPORT_DIMS: [u32; 2] = [1024, 1024];

impl WebGLThread {
    /// Create a new instance of WebGLThread.
    pub(crate) fn new(
        WebGLThreadInit {
            webrender_api_sender,
            webrender_doc,
            external_images,
            sender,
            receiver,
            webrender_swap_chains,
            connection,
            adapter,
            api_type,
            webxr_init,
        }: WebGLThreadInit,
    ) -> Self {
        WebGLThread {
            device: connection
                .create_device(&adapter)
                .expect("Couldn't open WebGL device!"),
            webrender_api: webrender_api_sender.create_api(),
            webrender_doc,
            contexts: Default::default(),
            cached_context_info: Default::default(),
            bound_context_id: None,
            dom_outputs: Default::default(),
            external_images,
            sender,
            receiver: receiver.into_inner(),
            webrender_swap_chains,
            api_type,
            webxr_bridge: WebXRBridge::new(webxr_init),
        }
    }

    /// Perform all initialization required to run an instance of WebGLThread
    /// in parallel on its own dedicated thread.
    pub(crate) fn run_on_own_thread(init: WebGLThreadInit) {
        thread::Builder::new()
            .name("WebGL".to_owned())
            .spawn(move || {
                let mut data = WebGLThread::new(init);
                data.process();
            })
            .expect("Thread spawning failed");
    }

    fn process(&mut self) {
        let webgl_chan = WebGLChan(self.sender.clone());
        while let Ok(msg) = self.receiver.recv() {
            let exit = self.handle_msg(msg, &webgl_chan);
            if exit {
                // Call remove_context functions in order to correctly delete WebRender image keys.
                let context_ids: Vec<WebGLContextId> = self.contexts.keys().map(|id| *id).collect();
                for id in context_ids {
                    self.remove_webgl_context(id);
                }

                // Block on shutting-down WebRender.
                self.webrender_api.shut_down(true);
                return;
            }
        }
    }

    /// Handles a generic WebGLMsg message
    fn handle_msg(&mut self, msg: WebGLMsg, webgl_chan: &WebGLChan) -> bool {
        trace!("processing {:?}", msg);
        match msg {
            WebGLMsg::CreateContext(version, size, attributes, result_sender) => {
                let result = self.create_webgl_context(version, size, attributes);

                result_sender
                    .send(result.map(|(id, limits)| {
                        let image_key = self
                            .cached_context_info
                            .get_mut(&id)
                            .expect("Where's the cached context info?")
                            .image_key;

                        let data = Self::make_current_if_needed(
                            &self.device,
                            id,
                            &self.contexts,
                            &mut self.bound_context_id,
                        )
                        .expect("WebGLContext not found");
                        let glsl_version = Self::get_glsl_version(&*data.gl);
                        let api_type = match data.gl.get_type() {
                            gl::GlType::Gl => GlType::Gl,
                            gl::GlType::Gles => GlType::Gles,
                        };

                        // FIXME(nox): Should probably be done by surfman.
                        if api_type != GlType::Gles {
                            // Points sprites are enabled by default in OpenGL 3.2 core
                            // and in GLES. Rather than doing version detection, it does
                            // not hurt to enable them anyways.

                            data.gl.enable(gl::POINT_SPRITE);
                            let err = data.gl.get_error();
                            if err != 0 {
                                warn!("Error enabling GL point sprites: {}", err);
                            }

                            data.gl.enable(gl::PROGRAM_POINT_SIZE);
                            let err = data.gl.get_error();
                            if err != 0 {
                                warn!("Error enabling GL program point size: {}", err);
                            }
                        }

                        WebGLCreateContextResult {
                            sender: WebGLMsgSender::new(id, webgl_chan.clone()),
                            limits,
                            glsl_version,
                            api_type,
                            image_key,
                        }
                    }))
                    .unwrap();
            },
            WebGLMsg::ResizeContext(ctx_id, size, sender) => {
                let _ = sender.send(self.resize_webgl_context(ctx_id, size));
            },
            WebGLMsg::RemoveContext(ctx_id) => {
                self.remove_webgl_context(ctx_id);
            },
            WebGLMsg::WebGLCommand(ctx_id, command, backtrace) => {
                self.handle_webgl_command(ctx_id, command, backtrace);
            },
            WebGLMsg::WebXRCommand(command) => {
                self.handle_webxr_command(command);
            },
            WebGLMsg::SwapBuffers(swap_ids, sender, sent_time) => {
                self.handle_swap_buffers(swap_ids, sender, sent_time);
            },
            WebGLMsg::DOMToTextureCommand(command) => {
                self.handle_dom_to_texture(command);
            },
            WebGLMsg::Exit => {
                return true;
            },
        }

        false
    }

    /// Handles a WebXR message
    fn handle_webxr_command(&mut self, command: WebXRCommand) {
        trace!("processing {:?}", command);
        let mut contexts = WebXRBridgeContexts {
            contexts: &mut self.contexts,
            bound_context_id: &mut self.bound_context_id,
        };
        match command {
            WebXRCommand::CreateLayerManager(sender) => {
                let result = self
                    .webxr_bridge
                    .create_layer_manager(&mut self.device, &mut contexts);
                let _ = sender.send(result);
            },
            WebXRCommand::DestroyLayerManager(manager_id) => {
                self.webxr_bridge.destroy_layer_manager(manager_id);
            },
            WebXRCommand::CreateLayer(manager_id, context_id, layer_init, sender) => {
                let result = self.webxr_bridge.create_layer(
                    manager_id,
                    &mut self.device,
                    &mut contexts,
                    context_id,
                    layer_init,
                );
                let _ = sender.send(result);
            },
            WebXRCommand::DestroyLayer(manager_id, context_id, layer_id) => {
                self.webxr_bridge.destroy_layer(
                    manager_id,
                    &mut self.device,
                    &mut contexts,
                    context_id,
                    layer_id,
                );
            },
            WebXRCommand::BeginFrame(manager_id, layers, sender) => {
                let result = self.webxr_bridge.begin_frame(
                    manager_id,
                    &mut self.device,
                    &mut contexts,
                    &layers[..],
                );
                let _ = sender.send(result);
            },
            WebXRCommand::EndFrame(manager_id, layers, sender) => {
                let result = self.webxr_bridge.end_frame(
                    manager_id,
                    &mut self.device,
                    &mut contexts,
                    &layers[..],
                );
                let _ = sender.send(result);
            },
        }
    }

    /// Handles a WebGLCommand for a specific WebGLContext
    fn handle_webgl_command(
        &mut self,
        context_id: WebGLContextId,
        command: WebGLCommand,
        backtrace: WebGLCommandBacktrace,
    ) {
        if self.cached_context_info.get_mut(&context_id).is_none() {
            return;
        }
        let data = Self::make_current_if_needed_mut(
            &self.device,
            context_id,
            &mut self.contexts,
            &mut self.bound_context_id,
        );
        if let Some(data) = data {
            WebGLImpl::apply(
                &self.device,
                &data.ctx,
                &*data.gl,
                &mut data.state,
                &data.attributes,
                command,
                backtrace,
            );
        }
    }

    /// Creates a new WebGLContext
    #[allow(unsafe_code)]
    fn create_webgl_context(
        &mut self,
        webgl_version: WebGLVersion,
        requested_size: Size2D<u32>,
        attributes: GLContextAttributes,
    ) -> Result<(WebGLContextId, webgl::GLLimits), String> {
        debug!(
            "WebGLThread::create_webgl_context({:?}, {:?}, {:?})",
            webgl_version, requested_size, attributes
        );

        // Creating a new GLContext may make the current bound context_id dirty.
        // Clear it to ensure that  make_current() is called in subsequent commands.
        self.bound_context_id = None;

        let requested_flags =
            attributes.to_surfman_context_attribute_flags(webgl_version, self.api_type);
        // Some GL implementations seem to only allow famebuffers
        // to have alpha, depth and stencil if their creating context does.
        // WebGL requires all contexts to be able to create framebuffers with
        // alpha, depth and stencil. So we always create a context with them,
        // and fake not having them if requested.
        let flags = requested_flags |
            ContextAttributeFlags::ALPHA |
            ContextAttributeFlags::DEPTH |
            ContextAttributeFlags::STENCIL;
        let context_attributes = &ContextAttributes {
            version: webgl_version.to_surfman_version(self.api_type),
            flags: flags,
        };

        let context_descriptor = self
            .device
            .create_context_descriptor(&context_attributes)
            .map_err(|err| format!("Failed to create context descriptor: {:?}", err))?;

        let safe_size = Size2D::new(
            requested_size.width.min(SAFE_VIEWPORT_DIMS[0]).max(1),
            requested_size.height.min(SAFE_VIEWPORT_DIMS[1]).max(1),
        );
        let surface_type = SurfaceType::Generic {
            size: safe_size.to_i32(),
        };
        let surface_access = self.surface_access();

        let mut ctx = self
            .device
            .create_context(&context_descriptor, None)
            .map_err(|err| format!("Failed to create the GL context: {:?}", err))?;
        let surface = self
            .device
            .create_surface(&ctx, surface_access, surface_type)
            .map_err(|err| format!("Failed to create the initial surface: {:?}", err))?;
        self.device
            .bind_surface_to_context(&mut ctx, surface)
            .map_err(|err| format!("Failed to bind initial surface: {:?}", err))?;
        // https://github.com/pcwalton/surfman/issues/7
        self.device
            .make_context_current(&ctx)
            .map_err(|err| format!("Failed to make new context current: {:?}", err))?;

        let id = WebGLContextId(
            self.external_images
                .lock()
                .expect("Lock poisoned?")
                .next_id(WebrenderImageHandlerType::WebGL)
                .0,
        );

        self.webrender_swap_chains
            .create_attached_swap_chain(id, &mut self.device, &mut ctx, surface_access)
            .map_err(|err| format!("Failed to create swap chain: {:?}", err))?;

        let swap_chain = self
            .webrender_swap_chains
            .get(id)
            .expect("Failed to get the swap chain");

        debug!(
            "Created webgl context {:?}/{:?}",
            id,
            self.device.context_id(&ctx),
        );

        let gl = match self.api_type {
            gl::GlType::Gl => Gl::gl_fns(gl::ffi_gl::Gl::load_with(|symbol_name| {
                self.device.get_proc_address(&ctx, symbol_name)
            })),
            gl::GlType::Gles => Gl::gles_fns(gl::ffi_gles::Gles2::load_with(|symbol_name| {
                self.device.get_proc_address(&ctx, symbol_name)
            })),
        };

        let limits = GLLimits::detect(&*gl, webgl_version);

        let size = clamp_viewport(&gl, requested_size);
        if safe_size != size {
            debug!("Resizing swap chain from {:?} to {:?}", safe_size, size);
            swap_chain
                .resize(&mut self.device, &mut ctx, size.to_i32())
                .map_err(|err| format!("Failed to resize swap chain: {:?}", err))?;
        }

        let descriptor = self.device.context_descriptor(&ctx);
        let descriptor_attributes = self.device.context_descriptor_attributes(&descriptor);
        let gl_version = descriptor_attributes.version;
        let has_alpha = requested_flags.contains(ContextAttributeFlags::ALPHA);
        let texture_target = current_wr_texture_target(&self.device);

        self.device.make_context_current(&ctx).unwrap();
        let framebuffer = self
            .device
            .context_surface_info(&ctx)
            .map_err(|err| format!("Failed to get context surface info: {:?}", err))?
            .ok_or_else(|| format!("Failed to get context surface info"))?
            .framebuffer_object;

        gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer);
        gl.viewport(0, 0, size.width as i32, size.height as i32);
        gl.scissor(0, 0, size.width as i32, size.height as i32);
        gl.clear_color(0., 0., 0., !has_alpha as u32 as f32);
        gl.clear_depth(1.);
        gl.clear_stencil(0);
        gl.clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
        gl.clear_color(0., 0., 0., 0.);
        debug_assert_eq!(gl.get_error(), gl::NO_ERROR);

        let use_apple_vertex_array = WebGLImpl::needs_apple_vertex_arrays(gl_version);
        let default_vao = if let Some(vao) =
            WebGLImpl::create_vertex_array(&gl, use_apple_vertex_array, webgl_version)
        {
            let vao = vao.get();
            WebGLImpl::bind_vertex_array(&gl, vao, use_apple_vertex_array, webgl_version);
            vao
        } else {
            0
        };

        let state = GLState {
            gl_version,
            webgl_version,
            requested_flags,
            default_vao,
            ..Default::default()
        };
        debug!("Created state {:?}", state);

        state.restore_invariant(&*gl);
        debug_assert_eq!(gl.get_error(), gl::NO_ERROR);

        self.contexts.insert(
            id,
            GLContextData {
                ctx,
                gl,
                state,
                attributes,
            },
        );

        let image_key = Self::create_wr_external_image(
            &mut self.webrender_api,
            self.webrender_doc,
            size.to_i32(),
            has_alpha,
            id,
            texture_target,
        );

        self.cached_context_info
            .insert(id, WebGLContextInfo { image_key });

        Ok((id, limits))
    }

    /// Resizes a WebGLContext
    fn resize_webgl_context(
        &mut self,
        context_id: WebGLContextId,
        requested_size: Size2D<u32>,
    ) -> Result<(), String> {
        let data = Self::make_current_if_needed_mut(
            &self.device,
            context_id,
            &mut self.contexts,
            &mut self.bound_context_id,
        )
        .expect("Missing WebGL context!");

        let size = clamp_viewport(&data.gl, requested_size);

        // Check to see if any of the current framebuffer bindings are the surface we're about to
        // throw out. If so, we'll have to reset them after destroying the surface.
        let framebuffer_rebinding_info =
            FramebufferRebindingInfo::detect(&self.device, &data.ctx, &*data.gl);

        // Resize the swap chains
        if let Some(swap_chain) = self.webrender_swap_chains.get(context_id) {
            let alpha = data
                .state
                .requested_flags
                .contains(ContextAttributeFlags::ALPHA);
            let clear_color = [0.0, 0.0, 0.0, !alpha as i32 as f32];
            swap_chain
                .resize(&mut self.device, &mut data.ctx, size.to_i32())
                .map_err(|err| format!("Failed to resize swap chain: {:?}", err))?;
            swap_chain
                .clear_surface(&mut self.device, &mut data.ctx, &*data.gl, clear_color)
                .map_err(|err| format!("Failed to clear resized swap chain: {:?}", err))?;
        } else {
            error!("Failed to find swap chain");
        }

        // Reset framebuffer bindings as appropriate.
        framebuffer_rebinding_info.apply(&self.device, &data.ctx, &*data.gl);

        // Update WR image if needed.
        let info = self.cached_context_info.get_mut(&context_id).unwrap();
        let has_alpha = data
            .state
            .requested_flags
            .contains(ContextAttributeFlags::ALPHA);
        let texture_target = current_wr_texture_target(&self.device);
        Self::update_wr_external_image(
            &mut self.webrender_api,
            self.webrender_doc,
            size.to_i32(),
            has_alpha,
            context_id,
            info.image_key,
            texture_target,
        );

        debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);

        Ok(())
    }

    /// Removes a WebGLContext and releases attached resources.
    fn remove_webgl_context(&mut self, context_id: WebGLContextId) {
        // Release webrender image keys.
        if let Some(info) = self.cached_context_info.remove(&context_id) {
            let mut txn = webrender_api::Transaction::new();
            txn.delete_image(info.image_key);
            self.webrender_api.send_transaction(self.webrender_doc, txn)
        }

        // We need to make the context current so its resources can be disposed of.
        Self::make_current_if_needed(
            &self.device,
            context_id,
            &self.contexts,
            &mut self.bound_context_id,
        );

        // Destroy WebXR layers associated with this context
        let webxr_context_id = WebXRContextId::from(context_id);
        let mut webxr_contexts = WebXRBridgeContexts {
            contexts: &mut self.contexts,
            bound_context_id: &mut self.bound_context_id,
        };
        self.webxr_bridge.destroy_all_layers(
            &mut self.device,
            &mut webxr_contexts,
            webxr_context_id,
        );

        // Release GL context.
        let mut data = match self.contexts.remove(&context_id) {
            Some(data) => data,
            None => return,
        };

        // Destroy the swap chains
        self.webrender_swap_chains
            .destroy(context_id, &mut self.device, &mut data.ctx)
            .unwrap();

        // Destroy the context
        self.device.destroy_context(&mut data.ctx).unwrap();

        // Removing a GLContext may make the current bound context_id dirty.
        self.bound_context_id = None;
    }

    fn handle_swap_buffers(
        &mut self,
        context_ids: Vec<WebGLContextId>,
        completed_sender: WebGLSender<u64>,
        _sent_time: u64,
    ) {
        debug!("handle_swap_buffers()");
        for context_id in context_ids {
            let data = Self::make_current_if_needed_mut(
                &self.device,
                context_id,
                &mut self.contexts,
                &mut self.bound_context_id,
            )
            .expect("Where's the GL data?");

            // Ensure there are no pending GL errors from other parts of the pipeline.
            debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);

            // Check to see if any of the current framebuffer bindings are the surface we're about
            // to swap out. If so, we'll have to reset them after destroying the surface.
            let framebuffer_rebinding_info =
                FramebufferRebindingInfo::detect(&self.device, &data.ctx, &*data.gl);
            debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);

            debug!("Getting swap chain for {:?}", context_id);
            let swap_chain = self
                .webrender_swap_chains
                .get(context_id)
                .expect("Where's the swap chain?");

            debug!("Swapping {:?}", context_id);
            swap_chain
                .swap_buffers(
                    &mut self.device,
                    &mut data.ctx,
                    if data.attributes.preserve_drawing_buffer {
                        surfman_chains::PreserveBuffer::Yes(&*data.gl)
                    } else {
                        surfman_chains::PreserveBuffer::No
                    },
                )
                .unwrap();
            debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);

            if !data.attributes.preserve_drawing_buffer {
                debug!("Clearing {:?}", context_id);
                let alpha = data
                    .state
                    .requested_flags
                    .contains(ContextAttributeFlags::ALPHA);
                let clear_color = [0.0, 0.0, 0.0, !alpha as i32 as f32];
                swap_chain
                    .clear_surface(&mut self.device, &mut data.ctx, &*data.gl, clear_color)
                    .unwrap();
                debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
            }

            // Rebind framebuffers as appropriate.
            debug!("Rebinding {:?}", context_id);
            framebuffer_rebinding_info.apply(&self.device, &data.ctx, &*data.gl);
            debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);

            let SurfaceInfo {
                framebuffer_object,
                id,
                ..
            } = self
                .device
                .context_surface_info(&data.ctx)
                .unwrap()
                .unwrap();
            debug!(
                "... rebound framebuffer {}, new back buffer surface is {:?}",
                framebuffer_object, id
            );
        }

        #[allow(unused)]
        let mut end_swap = 0;
        #[cfg(feature = "xr-profile")]
        {
            end_swap = time::precise_time_ns();
            println!(
                "WEBXR PROFILING [swap buffer]:\t{}ms",
                to_ms(end_swap - start_swap)
            );
        }
        completed_sender.send(end_swap).unwrap();
    }

    /// Which access mode to use
    fn surface_access(&self) -> SurfaceAccess {
        SurfaceAccess::GPUOnly
    }

    fn handle_dom_to_texture(&mut self, command: DOMToTextureCommand) {
        match command {
            DOMToTextureCommand::Attach(context_id, texture_id, document_id, pipeline_id, size) => {
                let data = Self::make_current_if_needed(
                    &self.device,
                    context_id,
                    &self.contexts,
                    &mut self.bound_context_id,
                )
                .expect("WebGLContext not found in a WebGL DOMToTextureCommand::Attach command");
                // Initialize the texture that WR will use for frame outputs.
                data.gl.tex_image_2d(
                    gl::TEXTURE_2D,
                    0,
                    gl::RGBA as gl::GLint,
                    size.width,
                    size.height,
                    0,
                    gl::RGBA,
                    gl::UNSIGNED_BYTE,
                    gl::TexImageSource::Pixels(None),
                );
                self.dom_outputs.insert(
                    pipeline_id,
                    DOMToTextureData {
                        context_id,
                        texture_id,
                        document_id,
                        size,
                    },
                );
                let mut txn = webrender_api::Transaction::new();
                txn.enable_frame_output(pipeline_id, true);
                self.webrender_api.send_transaction(document_id, txn);
            },
            DOMToTextureCommand::Lock(pipeline_id, gl_sync, sender) => {
                let result = self.handle_dom_to_texture_lock(pipeline_id, gl_sync);
                // Send the texture id and size to WR.
                sender.send(result).unwrap();
            },
            DOMToTextureCommand::Detach(texture_id) => {
                if let Some((pipeline_id, document_id)) = self
                    .dom_outputs
                    .iter()
                    .find(|&(_, v)| v.texture_id == texture_id)
                    .map(|(k, v)| (*k, v.document_id))
                {
                    let mut txn = webrender_api::Transaction::new();
                    txn.enable_frame_output(pipeline_id, false);
                    self.webrender_api.send_transaction(document_id, txn);
                    self.dom_outputs.remove(&pipeline_id);
                }
            },
        }
    }

    pub(crate) fn handle_dom_to_texture_lock(
        &mut self,
        pipeline_id: webrender_api::PipelineId,
        gl_sync: usize,
    ) -> Option<(u32, Size2D<i32>)> {
        let device = &self.device;
        let contexts = &self.contexts;
        let bound_context_id = &mut self.bound_context_id;
        self.dom_outputs.get(&pipeline_id).and_then(|dom_data| {
            let data = Self::make_current_if_needed(
                device,
                dom_data.context_id,
                contexts,
                bound_context_id,
            );
            data.and_then(|data| {
                // The next glWaitSync call is used to synchronize the two flows of
                // OpenGL commands (WR and WebGL) in order to avoid using semi-ready WR textures.
                // glWaitSync doesn't block WebGL CPU thread.
                data.gl
                    .wait_sync(gl_sync as gl::GLsync, 0, gl::TIMEOUT_IGNORED);
                Some((dom_data.texture_id.get(), dom_data.size))
            })
        })
    }

    /// Gets a reference to a Context for a given WebGLContextId and makes it current if required.
    fn make_current_if_needed<'a>(
        device: &Device,
        context_id: WebGLContextId,
        contexts: &'a FnvHashMap<WebGLContextId, GLContextData>,
        bound_id: &mut Option<WebGLContextId>,
    ) -> Option<&'a GLContextData> {
        let data = contexts.get(&context_id);

        if let Some(data) = data {
            if Some(context_id) != *bound_id {
                device.make_context_current(&data.ctx).unwrap();
                *bound_id = Some(context_id);
            }
        }

        data
    }

    /// Gets a mutable reference to a GLContextWrapper for a WebGLContextId and makes it current if required.
    fn make_current_if_needed_mut<'a>(
        device: &Device,
        context_id: WebGLContextId,
        contexts: &'a mut FnvHashMap<WebGLContextId, GLContextData>,
        bound_id: &mut Option<WebGLContextId>,
    ) -> Option<&'a mut GLContextData> {
        let data = contexts.get_mut(&context_id);

        if let Some(ref data) = data {
            if Some(context_id) != *bound_id {
                device.make_context_current(&data.ctx).unwrap();
                *bound_id = Some(context_id);
            }
        }

        data
    }

    /// Creates a `webrender_api::ImageKey` that uses shared textures.
    fn create_wr_external_image(
        webrender_api: &mut webrender_api::RenderApi,
        webrender_doc: webrender_api::DocumentId,
        size: Size2D<i32>,
        alpha: bool,
        context_id: WebGLContextId,
        target: webrender_api::TextureTarget,
    ) -> webrender_api::ImageKey {
        let descriptor = Self::image_descriptor(size, alpha);
        let data = Self::external_image_data(context_id, target);

        let image_key = webrender_api.generate_image_key();
        let mut txn = webrender_api::Transaction::new();
        txn.add_image(image_key, descriptor, data, None);
        webrender_api.send_transaction(webrender_doc, txn);

        image_key
    }

    /// Updates a `webrender_api::ImageKey` that uses shared textures.
    fn update_wr_external_image(
        webrender_api: &mut webrender_api::RenderApi,
        webrender_doc: webrender_api::DocumentId,
        size: Size2D<i32>,
        alpha: bool,
        context_id: WebGLContextId,
        image_key: webrender_api::ImageKey,
        target: webrender_api::TextureTarget,
    ) {
        let descriptor = Self::image_descriptor(size, alpha);
        let data = Self::external_image_data(context_id, target);

        let mut txn = webrender_api::Transaction::new();
        txn.update_image(image_key, descriptor, data, &webrender_api::DirtyRect::All);
        webrender_api.send_transaction(webrender_doc, txn);
    }

    /// Helper function to create a `webrender_api::ImageDescriptor`.
    fn image_descriptor(size: Size2D<i32>, alpha: bool) -> webrender_api::ImageDescriptor {
        let mut flags = webrender_api::ImageDescriptorFlags::empty();
        flags.set(webrender_api::ImageDescriptorFlags::IS_OPAQUE, !alpha);
        webrender_api::ImageDescriptor {
            size: webrender_api::units::DeviceIntSize::new(size.width, size.height),
            stride: None,
            format: webrender_api::ImageFormat::BGRA8,
            offset: 0,
            flags,
        }
    }

    /// Helper function to create a `webrender_api::ImageData::External` instance.
    fn external_image_data(
        context_id: WebGLContextId,
        target: webrender_api::TextureTarget,
    ) -> webrender_api::ImageData {
        let data = webrender_api::ExternalImageData {
            id: webrender_api::ExternalImageId(context_id.0 as u64),
            channel_index: 0,
            image_type: webrender_api::ExternalImageType::TextureHandle(target),
        };
        webrender_api::ImageData::External(data)
    }

    /// Gets the GLSL Version supported by a GLContext.
    fn get_glsl_version(gl: &Gl) -> WebGLSLVersion {
        let version = gl.get_string(gl::SHADING_LANGUAGE_VERSION);
        // Fomat used by SHADING_LANGUAGE_VERSION query : major.minor[.release] [vendor info]
        let mut values = version.split(&['.', ' '][..]);
        let major = values
            .next()
            .and_then(|v| v.parse::<u32>().ok())
            .unwrap_or(1);
        let minor = values
            .next()
            .and_then(|v| v.parse::<u32>().ok())
            .unwrap_or(20);

        WebGLSLVersion { major, minor }
    }
}

/// Helper struct to store cached WebGLContext information.
struct WebGLContextInfo {
    /// Currently used WebRender image key.
    image_key: webrender_api::ImageKey,
}

// TODO(pcwalton): Add `GL_TEXTURE_EXTERNAL_OES`?
fn current_wr_texture_target(device: &Device) -> webrender_api::TextureTarget {
    match device.surface_gl_texture_target() {
        gl::TEXTURE_RECTANGLE => webrender_api::TextureTarget::Rect,
        _ => webrender_api::TextureTarget::Default,
    }
}

/// Data about the linked DOM<->WebGLTexture elements.
struct DOMToTextureData {
    context_id: WebGLContextId,
    texture_id: WebGLTextureId,
    document_id: webrender_api::DocumentId,
    size: Size2D<i32>,
}

/// WebGL Commands Implementation
pub struct WebGLImpl;

impl WebGLImpl {
    #[allow(unsafe_code)]
    pub fn apply(
        device: &Device,
        ctx: &Context,
        gl: &Gl,
        state: &mut GLState,
        attributes: &GLContextAttributes,
        command: WebGLCommand,
        _backtrace: WebGLCommandBacktrace,
    ) {
        debug!("WebGLImpl::apply({:?})", command);

        // Ensure there are no pending GL errors from other parts of the pipeline.
        debug_assert_eq!(gl.get_error(), gl::NO_ERROR);

        match command {
            WebGLCommand::GetContextAttributes(ref sender) => sender.send(*attributes).unwrap(),
            WebGLCommand::ActiveTexture(target) => gl.active_texture(target),
            WebGLCommand::AttachShader(program_id, shader_id) => {
                gl.attach_shader(program_id.get(), shader_id.get())
            },
            WebGLCommand::DetachShader(program_id, shader_id) => {
                gl.detach_shader(program_id.get(), shader_id.get())
            },
            WebGLCommand::BindAttribLocation(program_id, index, ref name) => {
                gl.bind_attrib_location(program_id.get(), index, &to_name_in_compiled_shader(name))
            },
            WebGLCommand::BlendColor(r, g, b, a) => gl.blend_color(r, g, b, a),
            WebGLCommand::BlendEquation(mode) => gl.blend_equation(mode),
            WebGLCommand::BlendEquationSeparate(mode_rgb, mode_alpha) => {
                gl.blend_equation_separate(mode_rgb, mode_alpha)
            },
            WebGLCommand::BlendFunc(src, dest) => gl.blend_func(src, dest),
            WebGLCommand::BlendFuncSeparate(src_rgb, dest_rgb, src_alpha, dest_alpha) => {
                gl.blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha)
            },
            WebGLCommand::BufferData(buffer_type, ref receiver, usage) => {
                gl::buffer_data(gl, buffer_type, &receiver.recv().unwrap(), usage)
            },
            WebGLCommand::BufferSubData(buffer_type, offset, ref receiver) => {
                gl::buffer_sub_data(gl, buffer_type, offset, &receiver.recv().unwrap())
            },
            WebGLCommand::CopyBufferSubData(src, dst, src_offset, dst_offset, size) => {
                gl.copy_buffer_sub_data(
                    src,
                    dst,
                    src_offset as isize,
                    dst_offset as isize,
                    size as isize,
                );
            },
            WebGLCommand::GetBufferSubData(buffer_type, offset, length, ref sender) => {
                let ptr = gl.map_buffer_range(
                    buffer_type,
                    offset as isize,
                    length as isize,
                    gl::MAP_READ_BIT,
                );
                let data: &[u8] = unsafe { slice::from_raw_parts(ptr as _, length) };
                sender.send(data).unwrap();
                gl.unmap_buffer(buffer_type);
            },
            WebGLCommand::Clear(mask) => {
                gl.clear(mask);
            },
            WebGLCommand::ClearColor(r, g, b, a) => {
                state.clear_color = (r, g, b, a);
                gl.clear_color(r, g, b, a);
            },
            WebGLCommand::ClearDepth(depth) => {
                let value = depth.max(0.).min(1.) as f64;
                state.depth_clear_value = value;
                gl.clear_depth(value)
            },
            WebGLCommand::ClearStencil(stencil) => {
                state.stencil_clear_value = stencil;
                gl.clear_stencil(stencil);
            },
            WebGLCommand::ColorMask(r, g, b, a) => {
                state.color_write_mask = [r, g, b, a];
                state.restore_alpha_invariant(gl);
            },
            WebGLCommand::CopyTexImage2D(
                target,
                level,
                internal_format,
                x,
                y,
                width,
                height,
                border,
            ) => gl.copy_tex_image_2d(target, level, internal_format, x, y, width, height, border),
            WebGLCommand::CopyTexSubImage2D(
                target,
                level,
                xoffset,
                yoffset,
                x,
                y,
                width,
                height,
            ) => gl.copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height),
            WebGLCommand::CullFace(mode) => gl.cull_face(mode),
            WebGLCommand::DepthFunc(func) => gl.depth_func(func),
            WebGLCommand::DepthMask(flag) => {
                state.depth_write_mask = flag;
                state.restore_depth_invariant(gl);
            },
            WebGLCommand::DepthRange(near, far) => {
                gl.depth_range(near.max(0.).min(1.) as f64, far.max(0.).min(1.) as f64)
            },
            WebGLCommand::Disable(cap) => match cap {
                gl::SCISSOR_TEST => {
                    state.scissor_test_enabled = false;
                    state.restore_scissor_invariant(gl);
                },
                gl::DEPTH_TEST => {
                    state.depth_test_enabled = false;
                    state.restore_depth_invariant(gl);
                },
                gl::STENCIL_TEST => {
                    state.stencil_test_enabled = false;
                    state.restore_stencil_invariant(gl);
                },
                _ => gl.disable(cap),
            },
            WebGLCommand::Enable(cap) => match cap {
                gl::SCISSOR_TEST => {
                    state.scissor_test_enabled = true;
                    state.restore_scissor_invariant(gl);
                },
                gl::DEPTH_TEST => {
                    state.depth_test_enabled = true;
                    state.restore_depth_invariant(gl);
                },
                gl::STENCIL_TEST => {
                    state.stencil_test_enabled = true;
                    state.restore_stencil_invariant(gl);
                },
                _ => gl.enable(cap),
            },
            WebGLCommand::FramebufferRenderbuffer(target, attachment, renderbuffertarget, rb) => {
                let attach = |attachment| {
                    gl.framebuffer_renderbuffer(
                        target,
                        attachment,
                        renderbuffertarget,
                        rb.map_or(0, WebGLRenderbufferId::get),
                    )
                };
                if attachment == gl::DEPTH_STENCIL_ATTACHMENT {
                    attach(gl::DEPTH_ATTACHMENT);
                    attach(gl::STENCIL_ATTACHMENT);
                } else {
                    attach(attachment);
                }
            },
            WebGLCommand::FramebufferTexture2D(target, attachment, textarget, texture, level) => {
                let attach = |attachment| {
                    gl.framebuffer_texture_2d(
                        target,
                        attachment,
                        textarget,
                        texture.map_or(0, WebGLTextureId::get),
                        level,
                    )
                };
                if attachment == gl::DEPTH_STENCIL_ATTACHMENT {
                    attach(gl::DEPTH_ATTACHMENT);
                    attach(gl::STENCIL_ATTACHMENT);
                } else {
                    attach(attachment)
                }
            },
            WebGLCommand::FrontFace(mode) => gl.front_face(mode),
            WebGLCommand::DisableVertexAttribArray(attrib_id) => {
                gl.disable_vertex_attrib_array(attrib_id)
            },
            WebGLCommand::EnableVertexAttribArray(attrib_id) => {
                gl.enable_vertex_attrib_array(attrib_id)
            },
            WebGLCommand::Hint(name, val) => gl.hint(name, val),
            WebGLCommand::LineWidth(width) => {
                gl.line_width(width);
                // In OpenGL Core Profile >3.2, any non-1.0 value will generate INVALID_VALUE.
                if width != 1.0 {
                    let _ = gl.get_error();
                }
            },
            WebGLCommand::PixelStorei(name, val) => gl.pixel_store_i(name, val),
            WebGLCommand::PolygonOffset(factor, units) => gl.polygon_offset(factor, units),
            WebGLCommand::ReadPixels(rect, format, pixel_type, ref sender) => {
                let pixels = gl.read_pixels(
                    rect.origin.x as i32,
                    rect.origin.y as i32,
                    rect.size.width as i32,
                    rect.size.height as i32,
                    format,
                    pixel_type,
                );
                sender.send(&pixels).unwrap();
            },
            WebGLCommand::ReadPixelsPP(rect, format, pixel_type, offset) => unsafe {
                gl.read_pixels_into_pixel_pack_buffer(
                    rect.origin.x,
                    rect.origin.y,
                    rect.size.width,
                    rect.size.height,
                    format,
                    pixel_type,
                    offset,
                );
            },
            WebGLCommand::RenderbufferStorage(target, format, width, height) => {
                gl.renderbuffer_storage(target, format, width, height)
            },
            WebGLCommand::RenderbufferStorageMultisample(
                target,
                samples,
                format,
                width,
                height,
            ) => gl.renderbuffer_storage_multisample(target, samples, format, width, height),
            WebGLCommand::SampleCoverage(value, invert) => gl.sample_coverage(value, invert),
            WebGLCommand::Scissor(x, y, width, height) => {
                // FIXME(nox): Kinda unfortunate that some u32 values could
                // end up as negative numbers here, but I don't even think
                // that can happen in the real world.
                gl.scissor(x, y, width as i32, height as i32);
            },
            WebGLCommand::StencilFunc(func, ref_, mask) => gl.stencil_func(func, ref_, mask),
            WebGLCommand::StencilFuncSeparate(face, func, ref_, mask) => {
                gl.stencil_func_separate(face, func, ref_, mask)
            },
            WebGLCommand::StencilMask(mask) => {
                state.stencil_write_mask = (mask, mask);
                state.restore_stencil_invariant(gl);
            },
            WebGLCommand::StencilMaskSeparate(face, mask) => {
                if face == gl::FRONT {
                    state.stencil_write_mask.0 = mask;
                } else {
                    state.stencil_write_mask.1 = mask;
                }
                state.restore_stencil_invariant(gl);
            },
            WebGLCommand::StencilOp(fail, zfail, zpass) => gl.stencil_op(fail, zfail, zpass),
            WebGLCommand::StencilOpSeparate(face, fail, zfail, zpass) => {
                gl.stencil_op_separate(face, fail, zfail, zpass)
            },
            WebGLCommand::GetRenderbufferParameter(target, pname, ref chan) => {
                Self::get_renderbuffer_parameter(gl, target, pname, chan)
            },
            WebGLCommand::CreateTransformFeedback(ref sender) => {
                let value = gl.gen_transform_feedbacks();
                sender.send(value).unwrap()
            },
            WebGLCommand::DeleteTransformFeedback(id) => {
                gl.delete_transform_feedbacks(id);
            },
            WebGLCommand::IsTransformFeedback(id, ref sender) => {
                let value = gl.is_transform_feedback(id);
                sender.send(value).unwrap()
            },
            WebGLCommand::BindTransformFeedback(target, id) => {
                gl.bind_transform_feedback(target, id);
            },
            WebGLCommand::BeginTransformFeedback(mode) => {
                gl.begin_transform_feedback(mode);
            },
            WebGLCommand::EndTransformFeedback() => {
                gl.end_transform_feedback();
            },
            WebGLCommand::PauseTransformFeedback() => {
                gl.pause_transform_feedback();
            },
            WebGLCommand::ResumeTransformFeedback() => {
                gl.resume_transform_feedback();
            },
            WebGLCommand::GetTransformFeedbackVarying(program, index, ref sender) => {
                let (size, ty, mut name) = gl.get_transform_feedback_varying(program.get(), index);
                // We need to split, because the name starts with '_u' prefix.
                name = name.split_off(2);
                sender.send((size, ty, name)).unwrap();
            },
            WebGLCommand::TransformFeedbackVaryings(program, ref varyings, buffer_mode) => {
                gl.transform_feedback_varyings(program.get(), varyings.as_slice(), buffer_mode);
            },
            WebGLCommand::GetFramebufferAttachmentParameter(
                target,
                attachment,
                pname,
                ref chan,
            ) => Self::get_framebuffer_attachment_parameter(gl, target, attachment, pname, chan),
            WebGLCommand::GetShaderPrecisionFormat(shader_type, precision_type, ref chan) => {
                Self::shader_precision_format(gl, shader_type, precision_type, chan)
            },
            WebGLCommand::GetExtensions(ref chan) => Self::get_extensions(gl, chan),
            WebGLCommand::GetFragDataLocation(program_id, ref name, ref sender) => {
                let location =
                    gl.get_frag_data_location(program_id.get(), &to_name_in_compiled_shader(name));
                sender.send(location).unwrap();
            },
            WebGLCommand::GetUniformLocation(program_id, ref name, ref chan) => {
                Self::uniform_location(gl, program_id, &name, chan)
            },
            WebGLCommand::GetShaderInfoLog(shader_id, ref chan) => {
                Self::shader_info_log(gl, shader_id, chan)
            },
            WebGLCommand::GetProgramInfoLog(program_id, ref chan) => {
                Self::program_info_log(gl, program_id, chan)
            },
            WebGLCommand::CompileShader(shader_id, ref source) => {
                Self::compile_shader(gl, shader_id, &source)
            },
            WebGLCommand::CreateBuffer(ref chan) => Self::create_buffer(gl, chan),
            WebGLCommand::CreateFramebuffer(ref chan) => Self::create_framebuffer(gl, chan),
            WebGLCommand::CreateRenderbuffer(ref chan) => Self::create_renderbuffer(gl, chan),
            WebGLCommand::CreateTexture(ref chan) => Self::create_texture(gl, chan),
            WebGLCommand::CreateProgram(ref chan) => Self::create_program(gl, chan),
            WebGLCommand::CreateShader(shader_type, ref chan) => {
                Self::create_shader(gl, shader_type, chan)
            },
            WebGLCommand::DeleteBuffer(id) => gl.delete_buffers(&[id.get()]),
            WebGLCommand::DeleteFramebuffer(id) => gl.delete_framebuffers(&[id.get()]),
            WebGLCommand::DeleteRenderbuffer(id) => gl.delete_renderbuffers(&[id.get()]),
            WebGLCommand::DeleteTexture(id) => gl.delete_textures(&[id.get()]),
            WebGLCommand::DeleteProgram(id) => gl.delete_program(id.get()),
            WebGLCommand::DeleteShader(id) => gl.delete_shader(id.get()),
            WebGLCommand::BindBuffer(target, id) => {
                gl.bind_buffer(target, id.map_or(0, WebGLBufferId::get))
            },
            WebGLCommand::BindFramebuffer(target, request) => {
                Self::bind_framebuffer(gl, target, request, ctx, device, state)
            },
            WebGLCommand::BindRenderbuffer(target, id) => {
                gl.bind_renderbuffer(target, id.map_or(0, WebGLRenderbufferId::get))
            },
            WebGLCommand::BindTexture(target, id) => {
                gl.bind_texture(target, id.map_or(0, WebGLTextureId::get))
            },
            WebGLCommand::Uniform1f(uniform_id, v) => gl.uniform_1f(uniform_id, v),
            WebGLCommand::Uniform1fv(uniform_id, ref v) => gl.uniform_1fv(uniform_id, v),
            WebGLCommand::Uniform1i(uniform_id, v) => gl.uniform_1i(uniform_id, v),
            WebGLCommand::Uniform1iv(uniform_id, ref v) => gl.uniform_1iv(uniform_id, v),
            WebGLCommand::Uniform1ui(uniform_id, v) => gl.uniform_1ui(uniform_id, v),
            WebGLCommand::Uniform1uiv(uniform_id, ref v) => gl.uniform_1uiv(uniform_id, v),
            WebGLCommand::Uniform2f(uniform_id, x, y) => gl.uniform_2f(uniform_id, x, y),
            WebGLCommand::Uniform2fv(uniform_id, ref v) => gl.uniform_2fv(uniform_id, v),
            WebGLCommand::Uniform2i(uniform_id, x, y) => gl.uniform_2i(uniform_id, x, y),
            WebGLCommand::Uniform2iv(uniform_id, ref v) => gl.uniform_2iv(uniform_id, v),
            WebGLCommand::Uniform2ui(uniform_id, x, y) => gl.uniform_2ui(uniform_id, x, y),
            WebGLCommand::Uniform2uiv(uniform_id, ref v) => gl.uniform_2uiv(uniform_id, v),
            WebGLCommand::Uniform3f(uniform_id, x, y, z) => gl.uniform_3f(uniform_id, x, y, z),
            WebGLCommand::Uniform3fv(uniform_id, ref v) => gl.uniform_3fv(uniform_id, v),
            WebGLCommand::Uniform3i(uniform_id, x, y, z) => gl.uniform_3i(uniform_id, x, y, z),
            WebGLCommand::Uniform3iv(uniform_id, ref v) => gl.uniform_3iv(uniform_id, v),
            WebGLCommand::Uniform3ui(uniform_id, x, y, z) => gl.uniform_3ui(uniform_id, x, y, z),
            WebGLCommand::Uniform3uiv(uniform_id, ref v) => gl.uniform_3uiv(uniform_id, v),
            WebGLCommand::Uniform4f(uniform_id, x, y, z, w) => {
                gl.uniform_4f(uniform_id, x, y, z, w)
            },
            WebGLCommand::Uniform4fv(uniform_id, ref v) => gl.uniform_4fv(uniform_id, v),
            WebGLCommand::Uniform4i(uniform_id, x, y, z, w) => {
                gl.uniform_4i(uniform_id, x, y, z, w)
            },
            WebGLCommand::Uniform4iv(uniform_id, ref v) => gl.uniform_4iv(uniform_id, v),
            WebGLCommand::Uniform4ui(uniform_id, x, y, z, w) => {
                gl.uniform_4ui(uniform_id, x, y, z, w)
            },
            WebGLCommand::Uniform4uiv(uniform_id, ref v) => gl.uniform_4uiv(uniform_id, v),
            WebGLCommand::UniformMatrix2fv(uniform_id, ref v) => {
                gl.uniform_matrix_2fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix3fv(uniform_id, ref v) => {
                gl.uniform_matrix_3fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix4fv(uniform_id, ref v) => {
                gl.uniform_matrix_4fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix3x2fv(uniform_id, ref v) => {
                gl.uniform_matrix_3x2fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix4x2fv(uniform_id, ref v) => {
                gl.uniform_matrix_4x2fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix2x3fv(uniform_id, ref v) => {
                gl.uniform_matrix_2x3fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix4x3fv(uniform_id, ref v) => {
                gl.uniform_matrix_4x3fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix2x4fv(uniform_id, ref v) => {
                gl.uniform_matrix_2x4fv(uniform_id, false, v)
            },
            WebGLCommand::UniformMatrix3x4fv(uniform_id, ref v) => {
                gl.uniform_matrix_3x4fv(uniform_id, false, v)
            },
            WebGLCommand::ValidateProgram(program_id) => gl.validate_program(program_id.get()),
            WebGLCommand::VertexAttrib(attrib_id, x, y, z, w) => {
                gl.vertex_attrib_4f(attrib_id, x, y, z, w)
            },
            WebGLCommand::VertexAttribI(attrib_id, x, y, z, w) => {
                gl.vertex_attrib_4i(attrib_id, x, y, z, w)
            },
            WebGLCommand::VertexAttribU(attrib_id, x, y, z, w) => {
                gl.vertex_attrib_4ui(attrib_id, x, y, z, w)
            },
            WebGLCommand::VertexAttribPointer2f(attrib_id, size, normalized, stride, offset) => {
                gl.vertex_attrib_pointer_f32(attrib_id, size, normalized, stride, offset)
            },
            WebGLCommand::VertexAttribPointer(
                attrib_id,
                size,
                data_type,
                normalized,
                stride,
                offset,
            ) => gl.vertex_attrib_pointer(attrib_id, size, data_type, normalized, stride, offset),
            WebGLCommand::SetViewport(x, y, width, height) => gl.viewport(x, y, width, height),
            WebGLCommand::TexImage2D {
                target,
                level,
                internal_format,
                size,
                format,
                data_type,
                effective_data_type,
                unpacking_alignment,
                alpha_treatment,
                y_axis_treatment,
                pixel_format,
                ref data,
            } => {
                let pixels = prepare_pixels(
                    internal_format,
                    data_type,
                    size,
                    unpacking_alignment,
                    alpha_treatment,
                    y_axis_treatment,
                    pixel_format,
                    Cow::Borrowed(&*data),
                );

                gl.pixel_store_i(gl::UNPACK_ALIGNMENT, unpacking_alignment as i32);
                gl.tex_image_2d(
                    target,
                    level as i32,
                    internal_format.as_gl_constant() as i32,
                    size.width as i32,
                    size.height as i32,
                    0,
                    format.as_gl_constant(),
                    effective_data_type,
                    gl::TexImageSource::Pixels(Some(&pixels)),
                );
            },
            WebGLCommand::TexImage2DPBO {
                target,
                level,
                internal_format,
                size,
                format,
                effective_data_type,
                unpacking_alignment,
                offset,
            } => {
                gl.pixel_store_i(gl::UNPACK_ALIGNMENT, unpacking_alignment as i32);

                gl.tex_image_2d(
                    target,
                    level as i32,
                    internal_format.as_gl_constant() as i32,
                    size.width as i32,
                    size.height as i32,
                    0,
                    format.as_gl_constant(),
                    effective_data_type,
                    gl::TexImageSource::BufferOffset(offset),
                );
            },
            WebGLCommand::TexSubImage2D {
                target,
                level,
                xoffset,
                yoffset,
                size,
                format,
                data_type,
                effective_data_type,
                unpacking_alignment,
                alpha_treatment,
                y_axis_treatment,
                pixel_format,
                ref data,
            } => {
                let pixels = prepare_pixels(
                    format,
                    data_type,
                    size,
                    unpacking_alignment,
                    alpha_treatment,
                    y_axis_treatment,
                    pixel_format,
                    Cow::Borrowed(&*data),
                );

                gl.pixel_store_i(gl::UNPACK_ALIGNMENT, unpacking_alignment as i32);
                gl.tex_sub_image_2d(
                    target,
                    level as i32,
                    xoffset,
                    yoffset,
                    size.width as i32,
                    size.height as i32,
                    format.as_gl_constant(),
                    effective_data_type,
                    &pixels,
                );
            },
            WebGLCommand::CompressedTexImage2D {
                target,
                level,
                internal_format,
                size,
                ref data,
            } => {
                gl.compressed_tex_image_2d(
                    target,
                    level as i32,
                    internal_format,
                    size.width as i32,
                    size.height as i32,
                    0,
                    &*data,
                );
            },
            WebGLCommand::CompressedTexSubImage2D {
                target,
                level,
                xoffset,
                yoffset,
                size,
                format,
                ref data,
            } => {
                gl.compressed_tex_sub_image_2d(
                    target,
                    level as i32,
                    xoffset as i32,
                    yoffset as i32,
                    size.width as i32,
                    size.height as i32,
                    format,
                    &*data,
                );
            },
            WebGLCommand::TexStorage2D(target, levels, internal_format, width, height) => gl
                .tex_storage_2d(
                    target,
                    levels as i32,
                    internal_format.as_gl_constant(),
                    width as i32,
                    height as i32,
                ),
            WebGLCommand::TexStorage3D(target, levels, internal_format, width, height, depth) => gl
                .tex_storage_3d(
                    target,
                    levels as i32,
                    internal_format.as_gl_constant(),
                    width as i32,
                    height as i32,
                    depth as i32,
                ),
            WebGLCommand::DrawingBufferWidth(ref sender) => {
                let size = device
                    .context_surface_info(&ctx)
                    .unwrap()
                    .expect("Where's the front buffer?")
                    .size;
                sender.send(size.width).unwrap()
            },
            WebGLCommand::DrawingBufferHeight(ref sender) => {
                let size = device
                    .context_surface_info(&ctx)
                    .unwrap()
                    .expect("Where's the front buffer?")
                    .size;
                sender.send(size.height).unwrap()
            },
            WebGLCommand::Finish(ref sender) => Self::finish(gl, sender),
            WebGLCommand::Flush => gl.flush(),
            WebGLCommand::GenerateMipmap(target) => gl.generate_mipmap(target),
            WebGLCommand::CreateVertexArray(ref chan) => {
                let use_apple_vertex_array = Self::needs_apple_vertex_arrays(state.gl_version);
                let id = Self::create_vertex_array(gl, use_apple_vertex_array, state.webgl_version);
                let _ = chan.send(id);
            },
            WebGLCommand::DeleteVertexArray(id) => {
                let use_apple_vertex_array = Self::needs_apple_vertex_arrays(state.gl_version);
                let id = id.get();
                Self::delete_vertex_array(gl, id, use_apple_vertex_array, state.webgl_version);
            },
            WebGLCommand::BindVertexArray(id) => {
                let id = id.map_or(state.default_vao, WebGLVertexArrayId::get);
                let use_apple_vertex_array = Self::needs_apple_vertex_arrays(state.gl_version);
                Self::bind_vertex_array(gl, id, use_apple_vertex_array, state.webgl_version);
            },
            WebGLCommand::GetParameterBool(param, ref sender) => {
                let value = match param {
                    webgl::ParameterBool::DepthWritemask => state.depth_write_mask,
                    _ => unsafe {
                        let mut value = [0];
                        gl.get_boolean_v(param as u32, &mut value);
                        value[0] != 0
                    },
                };
                sender.send(value).unwrap()
            },
            WebGLCommand::FenceSync(ref sender) => {
                let value = gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
                sender
                    .send(unsafe { WebGLSyncId::new(value as u64) })
                    .unwrap();
            },
            WebGLCommand::IsSync(sync_id, ref sender) => {
                let value = gl.is_sync(sync_id.get() as *const _);
                sender.send(value).unwrap();
            },
            WebGLCommand::ClientWaitSync(sync_id, flags, timeout, ref sender) => {
                let value = gl.client_wait_sync(sync_id.get() as *const _, flags, timeout as u64);
                sender.send(value).unwrap();
            },
            WebGLCommand::WaitSync(sync_id, flags, timeout) => {
                gl.wait_sync(sync_id.get() as *const _, flags, timeout as u64);
            },
            WebGLCommand::GetSyncParameter(sync_id, param, ref sender) => {
                let value = gl.get_sync_iv(sync_id.get() as *const _, param);
                sender.send(value[0] as u32).unwrap();
            },
            WebGLCommand::DeleteSync(sync_id) => {
                gl.delete_sync(sync_id.get() as *const _);
            },
            WebGLCommand::GetParameterBool4(param, ref sender) => {
                let value = match param {
                    webgl::ParameterBool4::ColorWritemask => state.color_write_mask,
                };
                sender.send(value).unwrap()
            },
            WebGLCommand::GetParameterInt(param, ref sender) => {
                let value = match param {
                    webgl::ParameterInt::AlphaBits if state.fake_no_alpha() => 0,
                    webgl::ParameterInt::DepthBits if state.fake_no_depth() => 0,
                    webgl::ParameterInt::StencilBits if state.fake_no_stencil() => 0,
                    webgl::ParameterInt::StencilWritemask => state.stencil_write_mask.0 as i32,
                    webgl::ParameterInt::StencilBackWritemask => state.stencil_write_mask.1 as i32,
                    _ => unsafe {
                        let mut value = [0];
                        gl.get_integer_v(param as u32, &mut value);
                        value[0]
                    },
                };
                sender.send(value).unwrap()
            },
            WebGLCommand::GetParameterInt2(param, ref sender) => {
                let mut value = [0; 2];
                unsafe {
                    gl.get_integer_v(param as u32, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetParameterInt4(param, ref sender) => {
                let mut value = [0; 4];
                unsafe {
                    gl.get_integer_v(param as u32, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetParameterFloat(param, ref sender) => {
                let mut value = [0.];
                unsafe {
                    gl.get_float_v(param as u32, &mut value);
                }
                sender.send(value[0]).unwrap()
            },
            WebGLCommand::GetParameterFloat2(param, ref sender) => {
                let mut value = [0.; 2];
                unsafe {
                    gl.get_float_v(param as u32, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetParameterFloat4(param, ref sender) => {
                let mut value = [0.; 4];
                unsafe {
                    gl.get_float_v(param as u32, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetProgramValidateStatus(program, ref sender) => {
                let mut value = [0];
                unsafe {
                    gl.get_program_iv(program.get(), gl::VALIDATE_STATUS, &mut value);
                }
                sender.send(value[0] != 0).unwrap()
            },
            WebGLCommand::GetProgramActiveUniforms(program, ref sender) => {
                let mut value = [0];
                unsafe {
                    gl.get_program_iv(program.get(), gl::ACTIVE_UNIFORMS, &mut value);
                }
                sender.send(value[0]).unwrap()
            },
            WebGLCommand::GetCurrentVertexAttrib(index, ref sender) => {
                let mut value = [0.; 4];
                unsafe {
                    gl.get_vertex_attrib_fv(index, gl::CURRENT_VERTEX_ATTRIB, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetTexParameterFloat(target, param, ref sender) => {
                sender
                    .send(gl.get_tex_parameter_fv(target, param as u32))
                    .unwrap();
            },
            WebGLCommand::GetTexParameterInt(target, param, ref sender) => {
                sender
                    .send(gl.get_tex_parameter_iv(target, param as u32))
                    .unwrap();
            },
            WebGLCommand::GetTexParameterBool(target, param, ref sender) => {
                sender
                    .send(gl.get_tex_parameter_iv(target, param as u32) != 0)
                    .unwrap();
            },
            WebGLCommand::GetInternalFormatIntVec(target, internal_format, param, ref sender) => {
                match param {
                    InternalFormatIntVec::Samples => {
                        let mut count = [0; 1];
                        gl.get_internal_format_iv(
                            target,
                            internal_format,
                            gl::NUM_SAMPLE_COUNTS,
                            &mut count,
                        );
                        assert!(count[0] >= 0);

                        let mut values = vec![0; count[0] as usize];
                        gl.get_internal_format_iv(
                            target,
                            internal_format,
                            param as u32,
                            &mut values,
                        );
                        sender.send(values).unwrap()
                    },
                }
            },
            WebGLCommand::TexParameteri(target, param, value) => {
                gl.tex_parameter_i(target, param as u32, value)
            },
            WebGLCommand::TexParameterf(target, param, value) => {
                gl.tex_parameter_f(target, param as u32, value)
            },
            WebGLCommand::LinkProgram(program_id, ref sender) => {
                return sender.send(Self::link_program(gl, program_id)).unwrap();
            },
            WebGLCommand::UseProgram(program_id) => {
                gl.use_program(program_id.map_or(0, |p| p.get()))
            },
            WebGLCommand::DrawArrays { mode, first, count } => gl.draw_arrays(mode, first, count),
            WebGLCommand::DrawArraysInstanced {
                mode,
                first,
                count,
                primcount,
            } => gl.draw_arrays_instanced(mode, first, count, primcount),
            WebGLCommand::DrawElements {
                mode,
                count,
                type_,
                offset,
            } => gl.draw_elements(mode, count, type_, offset),
            WebGLCommand::DrawElementsInstanced {
                mode,
                count,
                type_,
                offset,
                primcount,
            } => gl.draw_elements_instanced(mode, count, type_, offset, primcount),
            WebGLCommand::VertexAttribDivisor { index, divisor } => {
                gl.vertex_attrib_divisor(index, divisor)
            },
            WebGLCommand::GetUniformBool(program_id, loc, ref sender) => {
                let mut value = [0];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                sender.send(value[0] != 0).unwrap();
            },
            WebGLCommand::GetUniformBool2(program_id, loc, ref sender) => {
                let mut value = [0; 2];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                let value = [value[0] != 0, value[1] != 0];
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformBool3(program_id, loc, ref sender) => {
                let mut value = [0; 3];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                let value = [value[0] != 0, value[1] != 0, value[2] != 0];
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformBool4(program_id, loc, ref sender) => {
                let mut value = [0; 4];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                let value = [value[0] != 0, value[1] != 0, value[2] != 0, value[3] != 0];
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformInt(program_id, loc, ref sender) => {
                let mut value = [0];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                sender.send(value[0]).unwrap();
            },
            WebGLCommand::GetUniformInt2(program_id, loc, ref sender) => {
                let mut value = [0; 2];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformInt3(program_id, loc, ref sender) => {
                let mut value = [0; 3];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformInt4(program_id, loc, ref sender) => {
                let mut value = [0; 4];
                unsafe {
                    gl.get_uniform_iv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformUint(program_id, loc, ref sender) => {
                let mut value = [0];
                unsafe {
                    gl.get_uniform_uiv(program_id.get(), loc, &mut value);
                }
                sender.send(value[0]).unwrap();
            },
            WebGLCommand::GetUniformUint2(program_id, loc, ref sender) => {
                let mut value = [0; 2];
                unsafe {
                    gl.get_uniform_uiv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformUint3(program_id, loc, ref sender) => {
                let mut value = [0; 3];
                unsafe {
                    gl.get_uniform_uiv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformUint4(program_id, loc, ref sender) => {
                let mut value = [0; 4];
                unsafe {
                    gl.get_uniform_uiv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformFloat(program_id, loc, ref sender) => {
                let mut value = [0.];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value[0]).unwrap();
            },
            WebGLCommand::GetUniformFloat2(program_id, loc, ref sender) => {
                let mut value = [0.; 2];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformFloat3(program_id, loc, ref sender) => {
                let mut value = [0.; 3];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformFloat4(program_id, loc, ref sender) => {
                let mut value = [0.; 4];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformFloat9(program_id, loc, ref sender) => {
                let mut value = [0.; 9];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformFloat16(program_id, loc, ref sender) => {
                let mut value = [0.; 16];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap();
            },
            WebGLCommand::GetUniformFloat2x3(program_id, loc, ref sender) => {
                let mut value = [0.; 2 * 3];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetUniformFloat2x4(program_id, loc, ref sender) => {
                let mut value = [0.; 2 * 4];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetUniformFloat3x2(program_id, loc, ref sender) => {
                let mut value = [0.; 3 * 2];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetUniformFloat3x4(program_id, loc, ref sender) => {
                let mut value = [0.; 3 * 4];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetUniformFloat4x2(program_id, loc, ref sender) => {
                let mut value = [0.; 4 * 2];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetUniformFloat4x3(program_id, loc, ref sender) => {
                let mut value = [0.; 4 * 3];
                unsafe {
                    gl.get_uniform_fv(program_id.get(), loc, &mut value);
                }
                sender.send(value).unwrap()
            },
            WebGLCommand::GetUniformBlockIndex(program_id, ref name, ref sender) => {
                let name = to_name_in_compiled_shader(name);
                let index = gl.get_uniform_block_index(program_id.get(), &name);
                sender.send(index).unwrap();
            },
            WebGLCommand::GetUniformIndices(program_id, ref names, ref sender) => {
                let names = names
                    .iter()
                    .map(|name| to_name_in_compiled_shader(name))
                    .collect::<Vec<_>>();
                let name_strs = names.iter().map(|name| name.as_str()).collect::<Vec<_>>();
                let indices = gl.get_uniform_indices(program_id.get(), &name_strs);
                sender.send(indices).unwrap();
            },
            WebGLCommand::GetActiveUniforms(program_id, ref indices, pname, ref sender) => {
                let results = gl.get_active_uniforms_iv(program_id.get(), indices, pname);
                sender.send(results).unwrap();
            },
            WebGLCommand::GetActiveUniformBlockName(program_id, block_idx, ref sender) => {
                let name = gl.get_active_uniform_block_name(program_id.get(), block_idx);
                sender.send(name).unwrap();
            },
            WebGLCommand::GetActiveUniformBlockParameter(
                program_id,
                block_idx,
                pname,
                ref sender,
            ) => {
                let results = gl.get_active_uniform_block_iv(program_id.get(), block_idx, pname);
                sender.send(results).unwrap();
            },
            WebGLCommand::UniformBlockBinding(program_id, block_idx, block_binding) => {
                gl.uniform_block_binding(program_id.get(), block_idx, block_binding)
            },
            WebGLCommand::InitializeFramebuffer {
                color,
                depth,
                stencil,
            } => Self::initialize_framebuffer(gl, state, color, depth, stencil),
            WebGLCommand::BeginQuery(target, query_id) => {
                gl.begin_query(target, query_id.get());
            },
            WebGLCommand::EndQuery(target) => {
                gl.end_query(target);
            },
            WebGLCommand::DeleteQuery(query_id) => {
                gl.delete_queries(&[query_id.get()]);
            },
            WebGLCommand::GenerateQuery(ref sender) => {
                let id = gl.gen_queries(1)[0];
                sender.send(unsafe { WebGLQueryId::new(id) }).unwrap()
            },
            WebGLCommand::GetQueryState(ref sender, query_id, pname) => {
                let value = gl.get_query_object_uiv(query_id.get(), pname);
                sender.send(value).unwrap()
            },
            WebGLCommand::GenerateSampler(ref sender) => {
                let id = gl.gen_samplers(1)[0];
                sender.send(unsafe { WebGLSamplerId::new(id) }).unwrap()
            },
            WebGLCommand::DeleteSampler(sampler_id) => {
                gl.delete_samplers(&[sampler_id.get()]);
            },
            WebGLCommand::BindSampler(unit, sampler_id) => {
                gl.bind_sampler(unit, sampler_id.get());
            },
            WebGLCommand::SetSamplerParameterInt(sampler_id, pname, value) => {
                gl.sampler_parameter_i(sampler_id.get(), pname, value);
            },
            WebGLCommand::SetSamplerParameterFloat(sampler_id, pname, value) => {
                gl.sampler_parameter_f(sampler_id.get(), pname, value);
            },
            WebGLCommand::GetSamplerParameterInt(sampler_id, pname, ref sender) => {
                let value = gl.get_sampler_parameter_iv(sampler_id.get(), pname)[0];
                sender.send(value).unwrap();
            },
            WebGLCommand::GetSamplerParameterFloat(sampler_id, pname, ref sender) => {
                let value = gl.get_sampler_parameter_fv(sampler_id.get(), pname)[0];
                sender.send(value).unwrap();
            },
            WebGLCommand::BindBufferBase(target, index, id) => {
                // https://searchfox.org/mozilla-central/rev/13b081a62d3f3e3e3120f95564529257b0bf451c/dom/canvas/WebGLContextBuffers.cpp#208-210
                // BindBufferBase/Range will fail (on some drivers) if the buffer name has
                // never been bound. (GenBuffers makes a name, but BindBuffer initializes
                // that name as a real buffer object)
                let id = id.map_or(0, WebGLBufferId::get);
                gl.bind_buffer(target, id);
                gl.bind_buffer(target, 0);
                gl.bind_buffer_base(target, index, id);
            },
            WebGLCommand::BindBufferRange(target, index, id, offset, size) => {
                // https://searchfox.org/mozilla-central/rev/13b081a62d3f3e3e3120f95564529257b0bf451c/dom/canvas/WebGLContextBuffers.cpp#208-210
                // BindBufferBase/Range will fail (on some drivers) if the buffer name has
                // never been bound. (GenBuffers makes a name, but BindBuffer initializes
                // that name as a real buffer object)
                let id = id.map_or(0, WebGLBufferId::get);
                gl.bind_buffer(target, id);
                gl.bind_buffer(target, 0);
                gl.bind_buffer_range(target, index, id, offset as isize, size as isize);
            },
            WebGLCommand::ClearBufferfv(buffer, draw_buffer, ref value) => {
                gl.clear_buffer_fv(buffer, draw_buffer, value)
            },
            WebGLCommand::ClearBufferiv(buffer, draw_buffer, ref value) => {
                gl.clear_buffer_iv(buffer, draw_buffer, value)
            },
            WebGLCommand::ClearBufferuiv(buffer, draw_buffer, ref value) => {
                gl.clear_buffer_uiv(buffer, draw_buffer, value)
            },
            WebGLCommand::ClearBufferfi(buffer, draw_buffer, depth, stencil) => {
                gl.clear_buffer_fi(buffer, draw_buffer, depth, stencil)
            },
            WebGLCommand::InvalidateFramebuffer(target, ref attachments) => {
                gl.invalidate_framebuffer(target, attachments)
            },
            WebGLCommand::InvalidateSubFramebuffer(target, ref attachments, x, y, w, h) => {
                gl.invalidate_sub_framebuffer(target, attachments, x, y, w, h)
            },
            WebGLCommand::FramebufferTextureLayer(target, attachment, tex_id, level, layer) => {
                let tex_id = tex_id.map_or(0, WebGLTextureId::get);
                let attach = |attachment| {
                    gl.framebuffer_texture_layer(target, attachment, tex_id, level, layer)
                };

                if attachment == gl::DEPTH_STENCIL_ATTACHMENT {
                    attach(gl::DEPTH_ATTACHMENT);
                    attach(gl::STENCIL_ATTACHMENT);
                } else {
                    attach(attachment)
                }
            },
            WebGLCommand::ReadBuffer(buffer) => gl.read_buffer(buffer),
            WebGLCommand::DrawBuffers(ref buffers) => gl.draw_buffers(buffers),
        }

        // If debug asertions are enabled, then check the error state.
        #[cfg(debug_assertions)]
        {
            let error = gl.get_error();
            if error != gl::NO_ERROR {
                error!("Last GL operation failed: {:?}", command);
                if error == gl::INVALID_FRAMEBUFFER_OPERATION {
                    let mut framebuffer_bindings = [0];
                    unsafe {
                        gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut framebuffer_bindings);
                    }
                    debug!(
                        "(thread {:?}) Current draw framebuffer binding: {}",
                        ::std::thread::current().id(),
                        framebuffer_bindings[0]
                    );
                }
                #[cfg(feature = "webgl_backtrace")]
                {
                    error!("Backtrace from failed WebGL API:\n{}", _backtrace.backtrace);
                    if let Some(backtrace) = _backtrace.js_backtrace {
                        error!("JS backtrace from failed WebGL API:\n{}", backtrace);
                    }
                }
                panic!(
                    "Unexpected WebGL error: 0x{:x} ({}) [{:?}]",
                    error, error, command
                );
            }
        }
    }

    fn initialize_framebuffer(gl: &Gl, state: &GLState, color: bool, depth: bool, stencil: bool) {
        let bits = [
            (color, gl::COLOR_BUFFER_BIT),
            (depth, gl::DEPTH_BUFFER_BIT),
            (stencil, gl::STENCIL_BUFFER_BIT),
        ]
        .iter()
        .fold(0, |bits, &(enabled, bit)| {
            bits | if enabled { bit } else { 0 }
        });

        gl.disable(gl::SCISSOR_TEST);
        gl.color_mask(true, true, true, true);
        gl.clear_color(0., 0., 0., 0.);
        gl.depth_mask(true);
        gl.clear_depth(1.);
        gl.stencil_mask_separate(gl::FRONT, 0xFFFFFFFF);
        gl.stencil_mask_separate(gl::BACK, 0xFFFFFFFF);
        gl.clear_stencil(0);
        gl.clear(bits);

        state.restore_invariant(gl);
    }

    #[allow(unsafe_code)]
    fn link_program(gl: &Gl, program: WebGLProgramId) -> ProgramLinkInfo {
        gl.link_program(program.get());
        let mut linked = [0];
        unsafe {
            gl.get_program_iv(program.get(), gl::LINK_STATUS, &mut linked);
        }
        if linked[0] == 0 {
            return ProgramLinkInfo {
                linked: false,
                active_attribs: vec![].into(),
                active_uniforms: vec![].into(),
                active_uniform_blocks: vec![].into(),
                transform_feedback_length: Default::default(),
                transform_feedback_mode: Default::default(),
            };
        }
        let mut num_active_attribs = [0];
        unsafe {
            gl.get_program_iv(
                program.get(),
                gl::ACTIVE_ATTRIBUTES,
                &mut num_active_attribs,
            );
        }
        let active_attribs = (0..num_active_attribs[0] as u32)
            .map(|i| {
                // FIXME(nox): This allocates strings sometimes for nothing
                // and the gleam method keeps getting ACTIVE_ATTRIBUTE_MAX_LENGTH.
                let (size, type_, name) = gl.get_active_attrib(program.get(), i);
                let location = if name.starts_with("gl_") {
                    -1
                } else {
                    gl.get_attrib_location(program.get(), &name)
                };
                ActiveAttribInfo {
                    name: from_name_in_compiled_shader(&name),
                    size,
                    type_,
                    location,
                }
            })
            .collect::<Vec<_>>()
            .into();

        let mut num_active_uniforms = [0];
        unsafe {
            gl.get_program_iv(program.get(), gl::ACTIVE_UNIFORMS, &mut num_active_uniforms);
        }
        let active_uniforms = (0..num_active_uniforms[0] as u32)
            .map(|i| {
                // FIXME(nox): This allocates strings sometimes for nothing
                // and the gleam method keeps getting ACTIVE_UNIFORM_MAX_LENGTH.
                let (size, type_, mut name) = gl.get_active_uniform(program.get(), i);
                let is_array = name.ends_with("[0]");
                if is_array {
                    // FIXME(nox): NLL
                    let len = name.len();
                    name.truncate(len - 3);
                }
                ActiveUniformInfo {
                    base_name: from_name_in_compiled_shader(&name).into(),
                    size: if is_array { Some(size) } else { None },
                    type_,
                    bind_index: None,
                }
            })
            .collect::<Vec<_>>()
            .into();

        let mut num_active_uniform_blocks = [0];
        unsafe {
            gl.get_program_iv(
                program.get(),
                gl::ACTIVE_UNIFORM_BLOCKS,
                &mut num_active_uniform_blocks,
            );
        }
        let active_uniform_blocks = (0..num_active_uniform_blocks[0] as u32)
            .map(|i| {
                let name = gl.get_active_uniform_block_name(program.get(), i);
                let size =
                    gl.get_active_uniform_block_iv(program.get(), i, gl::UNIFORM_BLOCK_DATA_SIZE)
                        [0];
                ActiveUniformBlockInfo { name, size }
            })
            .collect::<Vec<_>>()
            .into();

        let mut transform_feedback_length = [0];
        unsafe {
            gl.get_program_iv(
                program.get(),
                gl::TRANSFORM_FEEDBACK_VARYINGS,
                &mut transform_feedback_length,
            );
        }
        let mut transform_feedback_mode = [0];
        unsafe {
            gl.get_program_iv(
                program.get(),
                gl::TRANSFORM_FEEDBACK_BUFFER_MODE,
                &mut transform_feedback_mode,
            );
        }

        ProgramLinkInfo {
            linked: true,
            active_attribs,
            active_uniforms,
            active_uniform_blocks,
            transform_feedback_length: transform_feedback_length[0],
            transform_feedback_mode: transform_feedback_mode[0],
        }
    }

    fn finish(gl: &Gl, chan: &WebGLSender<()>) {
        gl.finish();
        chan.send(()).unwrap();
    }

    fn shader_precision_format(
        gl: &Gl,
        shader_type: u32,
        precision_type: u32,
        chan: &WebGLSender<(i32, i32, i32)>,
    ) {
        let result = gl.get_shader_precision_format(shader_type, precision_type);
        chan.send(result).unwrap();
    }

    // surfman creates a legacy OpenGL context on macOS when
    // OpenGL 2 support is requested. Legacy contexts return GL errors for the vertex
    // array object functions, but support a set of APPLE extension functions that
    // provide VAO support instead.
    fn needs_apple_vertex_arrays(gl_version: GLVersion) -> bool {
        cfg!(target_os = "macos") && gl_version.major < 3
    }

    #[allow(unsafe_code)]
    fn get_extensions(gl: &Gl, chan: &WebGLSender<String>) {
        let mut ext_count = [0];
        unsafe {
            gl.get_integer_v(gl::NUM_EXTENSIONS, &mut ext_count);
        }
        // Fall back to the depricated extensions API if that fails
        if gl.get_error() != gl::NO_ERROR {
            chan.send(gl.get_string(gl::EXTENSIONS)).unwrap();
            return;
        }
        let ext_count = ext_count[0] as usize;
        let mut extensions = Vec::with_capacity(ext_count);
        for idx in 0..ext_count {
            extensions.push(gl.get_string_i(gl::EXTENSIONS, idx as u32))
        }
        let extensions = extensions.join(" ");
        chan.send(extensions).unwrap();
    }

    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn get_framebuffer_attachment_parameter(
        gl: &Gl,
        target: u32,
        attachment: u32,
        pname: u32,
        chan: &WebGLSender<i32>,
    ) {
        let parameter = gl.get_framebuffer_attachment_parameter_iv(target, attachment, pname);
        chan.send(parameter).unwrap();
    }

    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn get_renderbuffer_parameter(gl: &Gl, target: u32, pname: u32, chan: &WebGLSender<i32>) {
        let parameter = gl.get_renderbuffer_parameter_iv(target, pname);
        chan.send(parameter).unwrap();
    }

    fn uniform_location(gl: &Gl, program_id: WebGLProgramId, name: &str, chan: &WebGLSender<i32>) {
        let location = gl.get_uniform_location(program_id.get(), &to_name_in_compiled_shader(name));
        chan.send(location).unwrap();
    }

    fn shader_info_log(gl: &Gl, shader_id: WebGLShaderId, chan: &WebGLSender<String>) {
        let log = gl.get_shader_info_log(shader_id.get());
        chan.send(log).unwrap();
    }

    fn program_info_log(gl: &Gl, program_id: WebGLProgramId, chan: &WebGLSender<String>) {
        let log = gl.get_program_info_log(program_id.get());
        chan.send(log).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_buffer(gl: &Gl, chan: &WebGLSender<Option<WebGLBufferId>>) {
        let buffer = gl.gen_buffers(1)[0];
        let buffer = if buffer == 0 {
            None
        } else {
            Some(unsafe { WebGLBufferId::new(buffer) })
        };
        chan.send(buffer).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_framebuffer(gl: &Gl, chan: &WebGLSender<Option<WebGLFramebufferId>>) {
        let framebuffer = gl.gen_framebuffers(1)[0];
        let framebuffer = if framebuffer == 0 {
            None
        } else {
            Some(unsafe { WebGLFramebufferId::new(framebuffer) })
        };
        chan.send(framebuffer).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_renderbuffer(gl: &Gl, chan: &WebGLSender<Option<WebGLRenderbufferId>>) {
        let renderbuffer = gl.gen_renderbuffers(1)[0];
        let renderbuffer = if renderbuffer == 0 {
            None
        } else {
            Some(unsafe { WebGLRenderbufferId::new(renderbuffer) })
        };
        chan.send(renderbuffer).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_texture(gl: &Gl, chan: &WebGLSender<Option<WebGLTextureId>>) {
        let texture = gl.gen_textures(1)[0];
        let texture = if texture == 0 {
            None
        } else {
            Some(unsafe { WebGLTextureId::new(texture) })
        };
        chan.send(texture).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_program(gl: &Gl, chan: &WebGLSender<Option<WebGLProgramId>>) {
        let program = gl.create_program();
        let program = if program == 0 {
            None
        } else {
            Some(unsafe { WebGLProgramId::new(program) })
        };
        chan.send(program).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_shader(gl: &Gl, shader_type: u32, chan: &WebGLSender<Option<WebGLShaderId>>) {
        let shader = gl.create_shader(shader_type);
        let shader = if shader == 0 {
            None
        } else {
            Some(unsafe { WebGLShaderId::new(shader) })
        };
        chan.send(shader).unwrap();
    }

    #[allow(unsafe_code)]
    fn create_vertex_array(
        gl: &Gl,
        use_apple_ext: bool,
        version: WebGLVersion,
    ) -> Option<WebGLVertexArrayId> {
        let vao = match gl {
            Gl::Gl(ref gl) if use_apple_ext => {
                let mut ids = vec![0];
                unsafe {
                    gl.GenVertexArraysAPPLE(ids.len() as gl::GLsizei, ids.as_mut_ptr());
                }
                ids[0]
            },
            Gl::Gles(ref gles) if version == WebGLVersion::WebGL1 => {
                let mut ids = vec![0];
                unsafe { gles.GenVertexArraysOES(ids.len() as gl::GLsizei, ids.as_mut_ptr()) }
                ids[0]
            },
            _ => gl.gen_vertex_arrays(1)[0],
        };
        if vao == 0 {
            let code = gl.get_error();
            warn!("Failed to create vertex array with error code {:x}", code);
            None
        } else {
            Some(unsafe { WebGLVertexArrayId::new(vao) })
        }
    }

    #[allow(unsafe_code)]
    fn bind_vertex_array(gl: &Gl, vao: GLuint, use_apple_ext: bool, version: WebGLVersion) {
        match gl {
            Gl::Gl(ref gl) if use_apple_ext => unsafe {
                gl.BindVertexArrayAPPLE(vao);
            },
            Gl::Gles(ref gles) if version == WebGLVersion::WebGL1 => unsafe {
                gles.BindVertexArrayOES(vao);
            },
            _ => gl.bind_vertex_array(vao),
        }
        debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
    }

    #[allow(unsafe_code)]
    fn delete_vertex_array(gl: &Gl, vao: GLuint, use_apple_ext: bool, version: WebGLVersion) {
        let vaos = [vao];
        match gl {
            Gl::Gl(ref gl) if use_apple_ext => unsafe {
                gl.DeleteVertexArraysAPPLE(vaos.len() as gl::GLsizei, vaos.as_ptr());
            },
            Gl::Gles(ref gl) if version == WebGLVersion::WebGL1 => unsafe {
                gl.DeleteVertexArraysOES(vaos.len() as gl::GLsizei, vaos.as_ptr());
            },
            _ => gl.delete_vertex_arrays(&vaos),
        }
        debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
    }

    #[inline]
    fn bind_framebuffer(
        gl: &Gl,
        target: u32,
        request: WebGLFramebufferBindingRequest,
        ctx: &Context,
        device: &Device,
        state: &mut GLState,
    ) {
        let id = match request {
            WebGLFramebufferBindingRequest::Explicit(id) => id.get(),
            WebGLFramebufferBindingRequest::Default => {
                device
                    .context_surface_info(ctx)
                    .unwrap()
                    .expect("No surface attached!")
                    .framebuffer_object
            },
        };

        debug!("WebGLImpl::bind_framebuffer: {:?}", id);
        gl.bind_framebuffer(target, id);

        if (target == gl::FRAMEBUFFER) || (target == gl::DRAW_FRAMEBUFFER) {
            state.drawing_to_default_framebuffer =
                request == WebGLFramebufferBindingRequest::Default;
            state.restore_invariant(gl);
        }
    }

    #[inline]
    fn compile_shader(gl: &Gl, shader_id: WebGLShaderId, source: &str) {
        gl.shader_source(shader_id.get(), &[source.as_bytes()]);
        gl.compile_shader(shader_id.get());
    }
}

/// ANGLE adds a `_u` prefix to variable names:
///
/// https://chromium.googlesource.com/angle/angle/+/855d964bd0d05f6b2cb303f625506cf53d37e94f
///
/// To avoid hard-coding this we would need to use the `sh::GetAttributes` and `sh::GetUniforms`
/// API to look up the `x.name` and `x.mappedName` members.
const ANGLE_NAME_PREFIX: &'static str = "_u";

fn to_name_in_compiled_shader(s: &str) -> String {
    map_dot_separated(s, |s, mapped| {
        mapped.push_str(ANGLE_NAME_PREFIX);
        mapped.push_str(s);
    })
}

fn from_name_in_compiled_shader(s: &str) -> String {
    map_dot_separated(s, |s, mapped| {
        mapped.push_str(if s.starts_with(ANGLE_NAME_PREFIX) {
            &s[ANGLE_NAME_PREFIX.len()..]
        } else {
            s
        })
    })
}

fn map_dot_separated<F: Fn(&str, &mut String)>(s: &str, f: F) -> String {
    let mut iter = s.split('.');
    let mut mapped = String::new();
    f(iter.next().unwrap(), &mut mapped);
    for s in iter {
        mapped.push('.');
        f(s, &mut mapped);
    }
    mapped
}

fn prepare_pixels(
    internal_format: TexFormat,
    data_type: TexDataType,
    size: Size2D<u32>,
    unpacking_alignment: u32,
    alpha_treatment: Option<AlphaTreatment>,
    y_axis_treatment: YAxisTreatment,
    pixel_format: Option<PixelFormat>,
    mut pixels: Cow<[u8]>,
) -> Cow<[u8]> {
    match alpha_treatment {
        Some(AlphaTreatment::Premultiply) => {
            if let Some(pixel_format) = pixel_format {
                match pixel_format {
                    PixelFormat::BGRA8 | PixelFormat::RGBA8 => {},
                    _ => unimplemented!("unsupported pixel format ({:?})", pixel_format),
                }
                premultiply_inplace(TexFormat::RGBA, TexDataType::UnsignedByte, pixels.to_mut());
            } else {
                premultiply_inplace(internal_format, data_type, pixels.to_mut());
            }
        },
        Some(AlphaTreatment::Unmultiply) => {
            assert!(pixel_format.is_some());
            unmultiply_inplace(pixels.to_mut());
        },
        None => {},
    }

    if let Some(pixel_format) = pixel_format {
        pixels = image_to_tex_image_data(
            pixel_format,
            internal_format,
            data_type,
            pixels.into_owned(),
        )
        .into();
    }

    if y_axis_treatment == YAxisTreatment::Flipped {
        // FINISHME: Consider doing premultiply and flip in a single mutable Vec.
        pixels = flip_pixels_y(
            internal_format,
            data_type,
            size.width as usize,
            size.height as usize,
            unpacking_alignment as usize,
            pixels.into_owned(),
        )
        .into();
    }

    pixels
}

/// Translates an image in rgba8 (red in the first byte) format to
/// the format that was requested of TexImage.
fn image_to_tex_image_data(
    pixel_format: PixelFormat,
    format: TexFormat,
    data_type: TexDataType,
    mut pixels: Vec<u8>,
) -> Vec<u8> {
    // hint for vector allocation sizing.
    let pixel_count = pixels.len() / 4;

    match pixel_format {
        PixelFormat::BGRA8 => pixels::rgba8_byte_swap_colors_inplace(&mut pixels),
        PixelFormat::RGBA8 => {},
        _ => unimplemented!("unsupported pixel format ({:?})", pixel_format),
    }

    match (format, data_type) {
        (TexFormat::RGBA, TexDataType::UnsignedByte) |
        (TexFormat::RGBA8, TexDataType::UnsignedByte) => pixels,
        (TexFormat::RGB, TexDataType::UnsignedByte) |
        (TexFormat::RGB8, TexDataType::UnsignedByte) => {
            for i in 0..pixel_count {
                let rgb = {
                    let rgb = &pixels[i * 4..i * 4 + 3];
                    [rgb[0], rgb[1], rgb[2]]
                };
                pixels[i * 3..i * 3 + 3].copy_from_slice(&rgb);
            }
            pixels.truncate(pixel_count * 3);
            pixels
        },
        (TexFormat::Alpha, TexDataType::UnsignedByte) => {
            for i in 0..pixel_count {
                let p = pixels[i * 4 + 3];
                pixels[i] = p;
            }
            pixels.truncate(pixel_count);
            pixels
        },
        (TexFormat::Luminance, TexDataType::UnsignedByte) => {
            for i in 0..pixel_count {
                let p = pixels[i * 4];
                pixels[i] = p;
            }
            pixels.truncate(pixel_count);
            pixels
        },
        (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
            for i in 0..pixel_count {
                let (lum, a) = {
                    let rgba = &pixels[i * 4..i * 4 + 4];
                    (rgba[0], rgba[3])
                };
                pixels[i * 2] = lum;
                pixels[i * 2 + 1] = a;
            }
            pixels.truncate(pixel_count * 2);
            pixels
        },
        (TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
            for i in 0..pixel_count {
                let p = {
                    let rgba = &pixels[i * 4..i * 4 + 4];
                    (rgba[0] as u16 & 0xf0) << 8 |
                        (rgba[1] as u16 & 0xf0) << 4 |
                        (rgba[2] as u16 & 0xf0) |
                        (rgba[3] as u16 & 0xf0) >> 4
                };
                NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
            }
            pixels.truncate(pixel_count * 2);
            pixels
        },
        (TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
            for i in 0..pixel_count {
                let p = {
                    let rgba = &pixels[i * 4..i * 4 + 4];
                    (rgba[0] as u16 & 0xf8) << 8 |
                        (rgba[1] as u16 & 0xf8) << 3 |
                        (rgba[2] as u16 & 0xf8) >> 2 |
                        (rgba[3] as u16) >> 7
                };
                NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
            }
            pixels.truncate(pixel_count * 2);
            pixels
        },
        (TexFormat::RGB, TexDataType::UnsignedShort565) => {
            for i in 0..pixel_count {
                let p = {
                    let rgb = &pixels[i * 4..i * 4 + 3];
                    (rgb[0] as u16 & 0xf8) << 8 |
                        (rgb[1] as u16 & 0xfc) << 3 |
                        (rgb[2] as u16 & 0xf8) >> 3
                };
                NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
            }
            pixels.truncate(pixel_count * 2);
            pixels
        },
        (TexFormat::RGBA, TexDataType::Float) | (TexFormat::RGBA32f, TexDataType::Float) => {
            let mut rgbaf32 = Vec::<u8>::with_capacity(pixel_count * 16);
            for rgba8 in pixels.chunks(4) {
                rgbaf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
                rgbaf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
                rgbaf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
                rgbaf32.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
            }
            rgbaf32
        },

        (TexFormat::RGB, TexDataType::Float) | (TexFormat::RGB32f, TexDataType::Float) => {
            let mut rgbf32 = Vec::<u8>::with_capacity(pixel_count * 12);
            for rgba8 in pixels.chunks(4) {
                rgbf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
                rgbf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
                rgbf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
            }
            rgbf32
        },

        (TexFormat::Alpha, TexDataType::Float) | (TexFormat::Alpha32f, TexDataType::Float) => {
            for rgba8 in pixels.chunks_mut(4) {
                let p = rgba8[3] as f32;
                NativeEndian::write_f32(rgba8, p);
            }
            pixels
        },

        (TexFormat::Luminance, TexDataType::Float) |
        (TexFormat::Luminance32f, TexDataType::Float) => {
            for rgba8 in pixels.chunks_mut(4) {
                let p = rgba8[0] as f32;
                NativeEndian::write_f32(rgba8, p);
            }
            pixels
        },

        (TexFormat::LuminanceAlpha, TexDataType::Float) |
        (TexFormat::LuminanceAlpha32f, TexDataType::Float) => {
            let mut data = Vec::<u8>::with_capacity(pixel_count * 8);
            for rgba8 in pixels.chunks(4) {
                data.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
                data.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
            }
            data
        },

        (TexFormat::RGBA, TexDataType::HalfFloat) |
        (TexFormat::RGBA16f, TexDataType::HalfFloat) => {
            let mut rgbaf16 = Vec::<u8>::with_capacity(pixel_count * 8);
            for rgba8 in pixels.chunks(4) {
                rgbaf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).to_bits())
                    .unwrap();
                rgbaf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).to_bits())
                    .unwrap();
                rgbaf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).to_bits())
                    .unwrap();
                rgbaf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).to_bits())
                    .unwrap();
            }
            rgbaf16
        },

        (TexFormat::RGB, TexDataType::HalfFloat) | (TexFormat::RGB16f, TexDataType::HalfFloat) => {
            let mut rgbf16 = Vec::<u8>::with_capacity(pixel_count * 6);
            for rgba8 in pixels.chunks(4) {
                rgbf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).to_bits())
                    .unwrap();
                rgbf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).to_bits())
                    .unwrap();
                rgbf16
                    .write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).to_bits())
                    .unwrap();
            }
            rgbf16
        },
        (TexFormat::Alpha, TexDataType::HalfFloat) |
        (TexFormat::Alpha16f, TexDataType::HalfFloat) => {
            for i in 0..pixel_count {
                let p = f16::from_f32(pixels[i * 4 + 3] as f32).to_bits();
                NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
            }
            pixels.truncate(pixel_count * 2);
            pixels
        },
        (TexFormat::Luminance, TexDataType::HalfFloat) |
        (TexFormat::Luminance16f, TexDataType::HalfFloat) => {
            for i in 0..pixel_count {
                let p = f16::from_f32(pixels[i * 4] as f32).to_bits();
                NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
            }
            pixels.truncate(pixel_count * 2);
            pixels
        },
        (TexFormat::LuminanceAlpha, TexDataType::HalfFloat) |
        (TexFormat::LuminanceAlpha16f, TexDataType::HalfFloat) => {
            for rgba8 in pixels.chunks_mut(4) {
                let lum = f16::from_f32(rgba8[0] as f32).to_bits();
                let a = f16::from_f32(rgba8[3] as f32).to_bits();
                NativeEndian::write_u16(&mut rgba8[0..2], lum);
                NativeEndian::write_u16(&mut rgba8[2..4], a);
            }
            pixels
        },

        // Validation should have ensured that we only hit the
        // above cases, but we haven't turned the (format, type)
        // into an enum yet so there's a default case here.
        _ => unreachable!("Unsupported formats {:?} {:?}", format, data_type),
    }
}

fn premultiply_inplace(format: TexFormat, data_type: TexDataType, pixels: &mut [u8]) {
    match (format, data_type) {
        (TexFormat::RGBA, TexDataType::UnsignedByte) => {
            pixels::rgba8_premultiply_inplace(pixels);
        },
        (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
            for la in pixels.chunks_mut(2) {
                la[0] = pixels::multiply_u8_color(la[0], la[1]);
            }
        },
        (TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
            for rgba in pixels.chunks_mut(2) {
                if NativeEndian::read_u16(rgba) & 1 == 0 {
                    NativeEndian::write_u16(rgba, 0);
                }
            }
        },
        (TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
            for rgba in pixels.chunks_mut(2) {
                let pix = NativeEndian::read_u16(rgba);
                let extend_to_8_bits = |val| (val | val << 4) as u8;
                let r = extend_to_8_bits(pix >> 12 & 0x0f);
                let g = extend_to_8_bits(pix >> 8 & 0x0f);
                let b = extend_to_8_bits(pix >> 4 & 0x0f);
                let a = extend_to_8_bits(pix & 0x0f);
                NativeEndian::write_u16(
                    rgba,
                    ((pixels::multiply_u8_color(r, a) & 0xf0) as u16) << 8 |
                        ((pixels::multiply_u8_color(g, a) & 0xf0) as u16) << 4 |
                        ((pixels::multiply_u8_color(b, a) & 0xf0) as u16) |
                        ((a & 0x0f) as u16),
                );
            }
        },
        // Other formats don't have alpha, so return their data untouched.
        _ => {},
    }
}

fn unmultiply_inplace(pixels: &mut [u8]) {
    for rgba in pixels.chunks_mut(4) {
        let a = (rgba[3] as f32) / 255.0;
        rgba[0] = (rgba[0] as f32 / a) as u8;
        rgba[1] = (rgba[1] as f32 / a) as u8;
        rgba[2] = (rgba[2] as f32 / a) as u8;
    }
}

/// Flips the pixels in the Vec on the Y axis.
fn flip_pixels_y(
    internal_format: TexFormat,
    data_type: TexDataType,
    width: usize,
    height: usize,
    unpacking_alignment: usize,
    pixels: Vec<u8>,
) -> Vec<u8> {
    let cpp = (data_type.element_size() * internal_format.components() /
        data_type.components_per_element()) as usize;

    let stride = (width * cpp + unpacking_alignment - 1) & !(unpacking_alignment - 1);

    let mut flipped = Vec::<u8>::with_capacity(pixels.len());

    for y in 0..height {
        let flipped_y = height - 1 - y;
        let start = flipped_y * stride;

        flipped.extend_from_slice(&pixels[start..(start + width * cpp)]);
        flipped.extend(vec![0u8; stride - width * cpp]);
    }

    flipped
}

// Clamp a size to the current GL context's max viewport
fn clamp_viewport(gl: &Gl, size: Size2D<u32>) -> Size2D<u32> {
    let mut max_viewport = [i32::max_value(), i32::max_value()];
    let mut max_renderbuffer = [i32::max_value()];
    #[allow(unsafe_code)]
    unsafe {
        gl.get_integer_v(gl::MAX_VIEWPORT_DIMS, &mut max_viewport);
        gl.get_integer_v(gl::MAX_RENDERBUFFER_SIZE, &mut max_renderbuffer);
        debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
    }
    Size2D::new(
        size.width
            .min(max_viewport[0] as u32)
            .min(max_renderbuffer[0] as u32)
            .max(1),
        size.height
            .min(max_viewport[1] as u32)
            .min(max_renderbuffer[0] as u32)
            .max(1),
    )
}

trait ToSurfmanVersion {
    fn to_surfman_version(self, api_type: gl::GlType) -> GLVersion;
}

impl ToSurfmanVersion for WebGLVersion {
    fn to_surfman_version(self, api_type: gl::GlType) -> GLVersion {
        if api_type == gl::GlType::Gles {
            return GLVersion::new(3, 0);
        }
        match self {
            // We make use of GL_PACK_PIXEL_BUFFER, which needs at least GL2.1
            // We make use of compatibility mode, which needs at most GL3.0
            WebGLVersion::WebGL1 => GLVersion::new(2, 1),
            // The WebGL2 conformance tests use std140 layout, which needs at GL3.1
            WebGLVersion::WebGL2 => GLVersion::new(3, 2),
        }
    }
}

trait SurfmanContextAttributeFlagsConvert {
    fn to_surfman_context_attribute_flags(
        &self,
        webgl_version: WebGLVersion,
        api_type: gl::GlType,
    ) -> ContextAttributeFlags;
}

impl SurfmanContextAttributeFlagsConvert for GLContextAttributes {
    fn to_surfman_context_attribute_flags(
        &self,
        webgl_version: WebGLVersion,
        api_type: gl::GlType,
    ) -> ContextAttributeFlags {
        let mut flags = ContextAttributeFlags::empty();
        flags.set(ContextAttributeFlags::ALPHA, self.alpha);
        flags.set(ContextAttributeFlags::DEPTH, self.depth);
        flags.set(ContextAttributeFlags::STENCIL, self.stencil);
        if (webgl_version == WebGLVersion::WebGL1) && (api_type == gl::GlType::Gl) {
            flags.set(ContextAttributeFlags::COMPATIBILITY_PROFILE, true);
        }
        flags
    }
}

bitflags! {
    struct FramebufferRebindingFlags: u8 {
        const REBIND_READ_FRAMEBUFFER = 0x1;
        const REBIND_DRAW_FRAMEBUFFER = 0x2;
    }
}

struct FramebufferRebindingInfo {
    flags: FramebufferRebindingFlags,
    viewport: [GLint; 4],
}

impl FramebufferRebindingInfo {
    #[allow(unsafe_code)]
    fn detect(device: &Device, context: &Context, gl: &Gl) -> FramebufferRebindingInfo {
        unsafe {
            let (mut read_framebuffer, mut draw_framebuffer) = ([0], [0]);
            gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut read_framebuffer);
            gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut draw_framebuffer);

            let context_surface_framebuffer = device
                .context_surface_info(context)
                .unwrap()
                .unwrap()
                .framebuffer_object;

            let mut flags = FramebufferRebindingFlags::empty();
            if context_surface_framebuffer == read_framebuffer[0] as GLuint {
                flags.insert(FramebufferRebindingFlags::REBIND_READ_FRAMEBUFFER);
            }
            if context_surface_framebuffer == draw_framebuffer[0] as GLuint {
                flags.insert(FramebufferRebindingFlags::REBIND_DRAW_FRAMEBUFFER);
            }

            let mut viewport = [0; 4];
            gl.get_integer_v(gl::VIEWPORT, &mut viewport);

            FramebufferRebindingInfo { flags, viewport }
        }
    }

    fn apply(self, device: &Device, context: &Context, gl: &Gl) {
        if self.flags.is_empty() {
            return;
        }

        let context_surface_framebuffer = device
            .context_surface_info(context)
            .unwrap()
            .unwrap()
            .framebuffer_object;
        if self
            .flags
            .contains(FramebufferRebindingFlags::REBIND_READ_FRAMEBUFFER)
        {
            gl.bind_framebuffer(gl::READ_FRAMEBUFFER, context_surface_framebuffer);
        }
        if self
            .flags
            .contains(FramebufferRebindingFlags::REBIND_DRAW_FRAMEBUFFER)
        {
            gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, context_surface_framebuffer);
        }

        gl.viewport(
            self.viewport[0],
            self.viewport[1],
            self.viewport[2],
            self.viewport[3],
        );
    }
}

/// Bridge between WebGL and WebXR
pub(crate) struct WebXRBridge {
    factory_receiver: crossbeam_channel::Receiver<WebXRLayerManagerFactory<WebXRSurfman>>,
    managers: HashMap<WebXRLayerManagerId, Box<dyn WebXRLayerManagerAPI<WebXRSurfman>>>,
    next_manager_id: u32,
}

impl WebXRBridge {
    pub(crate) fn new(init: WebXRBridgeInit) -> WebXRBridge {
        let WebXRBridgeInit {
            factory_receiver, ..
        } = init;
        let managers = HashMap::new();
        let next_manager_id = 1;
        WebXRBridge {
            factory_receiver,
            managers,
            next_manager_id,
        }
    }
}

impl WebXRBridge {
    #[allow(unsafe_code)]
    fn create_layer_manager(
        &mut self,
        device: &mut Device,
        contexts: &mut dyn WebXRContexts<WebXRSurfman>,
    ) -> Result<WebXRLayerManagerId, WebXRError> {
        let factory = self
            .factory_receiver
            .recv()
            .map_err(|_| WebXRError::CommunicationError)?;
        let manager = factory.build(device, contexts)?;
        let manager_id = unsafe { WebXRLayerManagerId::new(self.next_manager_id) };
        self.next_manager_id = self.next_manager_id + 1;
        self.managers.insert(manager_id, manager);
        Ok(manager_id)
    }

    fn destroy_layer_manager(&mut self, manager_id: WebXRLayerManagerId) {
        self.managers.remove(&manager_id);
    }

    fn create_layer(
        &mut self,
        manager_id: WebXRLayerManagerId,
        device: &mut Device,
        contexts: &mut dyn WebXRContexts<WebXRSurfman>,
        context_id: WebXRContextId,
        layer_init: WebXRLayerInit,
    ) -> Result<WebXRLayerId, WebXRError> {
        let manager = self
            .managers
            .get_mut(&manager_id)
            .ok_or(WebXRError::NoMatchingDevice)?;
        manager.create_layer(device, contexts, context_id, layer_init)
    }

    fn destroy_layer(
        &mut self,
        manager_id: WebXRLayerManagerId,
        device: &mut Device,
        contexts: &mut dyn WebXRContexts<WebXRSurfman>,
        context_id: WebXRContextId,
        layer_id: WebXRLayerId,
    ) {
        if let Some(manager) = self.managers.get_mut(&manager_id) {
            manager.destroy_layer(device, contexts, context_id, layer_id);
        }
    }

    fn destroy_all_layers(
        &mut self,
        device: &mut Device,
        contexts: &mut dyn WebXRContexts<WebXRSurfman>,
        context_id: WebXRContextId,
    ) {
        for (_, manager) in &mut self.managers {
            for (other_id, layer_id) in manager.layers().to_vec() {
                if other_id == context_id {
                    manager.destroy_layer(device, contexts, context_id, layer_id);
                }
            }
        }
    }

    fn begin_frame(
        &mut self,
        manager_id: WebXRLayerManagerId,
        device: &mut Device,
        contexts: &mut dyn WebXRContexts<WebXRSurfman>,
        layers: &[(WebXRContextId, WebXRLayerId)],
    ) -> Result<Vec<WebXRSubImages>, WebXRError> {
        let manager = self
            .managers
            .get_mut(&manager_id)
            .ok_or(WebXRError::NoMatchingDevice)?;
        manager.begin_frame(device, contexts, layers)
    }

    fn end_frame(
        &mut self,
        manager_id: WebXRLayerManagerId,
        device: &mut Device,
        contexts: &mut dyn WebXRContexts<WebXRSurfman>,
        layers: &[(WebXRContextId, WebXRLayerId)],
    ) -> Result<(), WebXRError> {
        let manager = self
            .managers
            .get_mut(&manager_id)
            .ok_or(WebXRError::NoMatchingDevice)?;
        manager.end_frame(device, contexts, layers)
    }
}

pub(crate) struct WebXRBridgeInit {
    sender: WebGLSender<WebGLMsg>,
    factory_receiver: crossbeam_channel::Receiver<WebXRLayerManagerFactory<WebXRSurfman>>,
    factory_sender: crossbeam_channel::Sender<WebXRLayerManagerFactory<WebXRSurfman>>,
}

impl WebXRBridgeInit {
    pub(crate) fn new(sender: WebGLSender<WebGLMsg>) -> WebXRBridgeInit {
        let (factory_sender, factory_receiver) = crossbeam_channel::unbounded();
        WebXRBridgeInit {
            sender,
            factory_sender,
            factory_receiver,
        }
    }

    pub(crate) fn layer_grand_manager(&self) -> WebXRLayerGrandManager<WebXRSurfman> {
        WebXRLayerGrandManager::new(WebXRBridgeGrandManager {
            sender: self.sender.clone(),
            factory_sender: self.factory_sender.clone(),
        })
    }
}

struct WebXRBridgeGrandManager {
    sender: WebGLSender<WebGLMsg>,
    // WebXR layer manager factories use generic trait objects under the
    // hood, which aren't deserializable (even using typetag)
    // so we can't send them over the regular webgl channel.
    // Fortunately, the webgl thread runs in the same process as
    // the webxr threads, so we can use a crossbeam channel to send
    // factories.
    factory_sender: crossbeam_channel::Sender<WebXRLayerManagerFactory<WebXRSurfman>>,
}

impl WebXRLayerGrandManagerAPI<WebXRSurfman> for WebXRBridgeGrandManager {
    fn create_layer_manager(
        &self,
        factory: WebXRLayerManagerFactory<WebXRSurfman>,
    ) -> Result<WebXRLayerManager, WebXRError> {
        let (sender, receiver) = webgl_channel().map_err(|_| WebXRError::CommunicationError)?;
        let _ = self.factory_sender.send(factory);
        let _ = self
            .sender
            .send(WebGLMsg::WebXRCommand(WebXRCommand::CreateLayerManager(
                sender,
            )));
        let sender = self.sender.clone();
        let manager_id = receiver
            .recv()
            .map_err(|_| WebXRError::CommunicationError)??;
        let layers = Vec::new();
        Ok(WebXRLayerManager::new(WebXRBridgeManager {
            manager_id,
            sender,
            layers,
        }))
    }

    fn clone_layer_grand_manager(&self) -> WebXRLayerGrandManager<WebXRSurfman> {
        WebXRLayerGrandManager::new(WebXRBridgeGrandManager {
            sender: self.sender.clone(),
            factory_sender: self.factory_sender.clone(),
        })
    }
}

struct WebXRBridgeManager {
    sender: WebGLSender<WebGLMsg>,
    manager_id: WebXRLayerManagerId,
    layers: Vec<(WebXRContextId, WebXRLayerId)>,
}

impl<GL: WebXRTypes> WebXRLayerManagerAPI<GL> for WebXRBridgeManager {
    fn create_layer(
        &mut self,
        _: &mut GL::Device,
        _: &mut dyn WebXRContexts<GL>,
        context_id: WebXRContextId,
        init: WebXRLayerInit,
    ) -> Result<WebXRLayerId, WebXRError> {
        let (sender, receiver) = webgl_channel().map_err(|_| WebXRError::CommunicationError)?;
        let _ = self
            .sender
            .send(WebGLMsg::WebXRCommand(WebXRCommand::CreateLayer(
                self.manager_id,
                context_id,
                init,
                sender,
            )));
        let layer_id = receiver
            .recv()
            .map_err(|_| WebXRError::CommunicationError)??;
        self.layers.push((context_id, layer_id));
        Ok(layer_id)
    }

    fn destroy_layer(
        &mut self,
        _: &mut GL::Device,
        _: &mut dyn WebXRContexts<GL>,
        context_id: WebXRContextId,
        layer_id: WebXRLayerId,
    ) {
        self.layers.retain(|&ids| ids != (context_id, layer_id));
        let _ = self
            .sender
            .send(WebGLMsg::WebXRCommand(WebXRCommand::DestroyLayer(
                self.manager_id,
                context_id,
                layer_id,
            )));
    }

    fn layers(&self) -> &[(WebXRContextId, WebXRLayerId)] {
        &self.layers[..]
    }

    fn begin_frame(
        &mut self,
        _: &mut GL::Device,
        _: &mut dyn WebXRContexts<GL>,
        layers: &[(WebXRContextId, WebXRLayerId)],
    ) -> Result<Vec<WebXRSubImages>, WebXRError> {
        let (sender, receiver) = webgl_channel().map_err(|_| WebXRError::CommunicationError)?;
        let _ = self
            .sender
            .send(WebGLMsg::WebXRCommand(WebXRCommand::BeginFrame(
                self.manager_id,
                layers.to_vec(),
                sender,
            )));
        receiver
            .recv()
            .map_err(|_| WebXRError::CommunicationError)?
    }

    fn end_frame(
        &mut self,
        _: &mut GL::Device,
        _: &mut dyn WebXRContexts<GL>,
        layers: &[(WebXRContextId, WebXRLayerId)],
    ) -> Result<(), WebXRError> {
        let (sender, receiver) = webgl_channel().map_err(|_| WebXRError::CommunicationError)?;
        let _ = self
            .sender
            .send(WebGLMsg::WebXRCommand(WebXRCommand::EndFrame(
                self.manager_id,
                layers.to_vec(),
                sender,
            )));
        receiver
            .recv()
            .map_err(|_| WebXRError::CommunicationError)?
    }
}

impl Drop for WebXRBridgeManager {
    fn drop(&mut self) {
        let _ = self
            .sender
            .send(WebGLMsg::WebXRCommand(WebXRCommand::DestroyLayerManager(
                self.manager_id,
            )));
    }
}

struct WebXRBridgeContexts<'a> {
    contexts: &'a mut FnvHashMap<WebGLContextId, GLContextData>,
    bound_context_id: &'a mut Option<WebGLContextId>,
}

impl<'a> WebXRContexts<WebXRSurfman> for WebXRBridgeContexts<'a> {
    fn context(&mut self, device: &Device, context_id: WebXRContextId) -> Option<&mut Context> {
        let data = WebGLThread::make_current_if_needed_mut(
            device,
            WebGLContextId::from(context_id),
            &mut self.contexts,
            &mut self.bound_context_id,
        )?;
        Some(&mut data.ctx)
    }
    fn bindings(&mut self, device: &Device, context_id: WebXRContextId) -> Option<&Gl> {
        let data = WebGLThread::make_current_if_needed(
            device,
            WebGLContextId::from(context_id),
            &self.contexts,
            &mut self.bound_context_id,
        )?;
        Some(&data.gl)
    }
}