diff options
Diffstat (limited to 'components/webgl/webgl_thread.rs')
-rw-r--r-- | components/webgl/webgl_thread.rs | 3250 |
1 files changed, 3250 insertions, 0 deletions
diff --git a/components/webgl/webgl_thread.rs b/components/webgl/webgl_thread.rs new file mode 100644 index 00000000000..b1ac2b2d3c4 --- /dev/null +++ b/components/webgl/webgl_thread.rs @@ -0,0 +1,3250 @@ +/* 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/. */ +#![allow(unsafe_code)] +use std::borrow::Cow; +use std::num::NonZeroU32; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use std::{slice, thread}; + +use bitflags::bitflags; +use byteorder::{ByteOrder, NativeEndian, WriteBytesExt}; +use canvas_traits::webgl; +#[cfg(feature = "webxr")] +use canvas_traits::webgl::WebXRCommand; +use canvas_traits::webgl::{ + ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, AlphaTreatment, + GLContextAttributes, GLLimits, GlType, InternalFormatIntVec, ProgramLinkInfo, TexDataType, + TexFormat, WebGLBufferId, WebGLChan, WebGLCommand, WebGLCommandBacktrace, WebGLContextId, + WebGLCreateContextResult, WebGLFramebufferBindingRequest, WebGLFramebufferId, WebGLMsg, + WebGLMsgSender, WebGLProgramId, WebGLQueryId, WebGLReceiver, WebGLRenderbufferId, + WebGLSLVersion, WebGLSamplerId, WebGLSender, WebGLShaderId, WebGLSyncId, WebGLTextureId, + WebGLVersion, WebGLVertexArrayId, YAxisTreatment, +}; +use compositing_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType}; +use euclid::default::Size2D; +use fnv::FnvHashMap; +use glow::{ + self as gl, ActiveTransformFeedback, Context as Gl, HasContext, NativeTransformFeedback, + NativeUniformLocation, NativeVertexArray, PixelUnpackData, ShaderPrecisionFormat, + bytes_per_type, components_per_format, +}; +use half::f16; +use ipc_channel::ipc::IpcSharedMemory; +use log::{debug, error, trace, warn}; +use pixels::{self, PixelFormat, unmultiply_inplace}; +use surfman::chains::{PreserveBuffer, SwapChains, SwapChainsAPI}; +use surfman::{ + self, Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, + GLVersion, SurfaceAccess, SurfaceInfo, SurfaceType, +}; +use webrender::{RenderApi, RenderApiSender, Transaction}; +use webrender_api::units::DeviceIntSize; +use webrender_api::{ + DirtyRect, DocumentId, ExternalImageData, ExternalImageId, ExternalImageType, ImageBufferKind, + ImageData, ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey, +}; + +use crate::webgl_limits::GLLimitsDetect; +#[cfg(feature = "webxr")] +use crate::webxr::{WebXRBridge, WebXRBridgeContexts, WebXRBridgeInit}; + +type GLint = i32; + +fn native_uniform_location(location: i32) -> Option<NativeUniformLocation> { + location.try_into().ok().map(NativeUniformLocation) +} + +pub(crate) struct GLContextData { + pub(crate) ctx: Context, + pub(crate) gl: Rc<glow::Context>, + 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: Option<NativeVertexArray>, +} + +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; + unsafe { gl.clear_color(r, g, b, a) }; + } + + fn restore_scissor_invariant(&self, gl: &Gl) { + if self.scissor_test_enabled { + unsafe { gl.enable(gl::SCISSOR_TEST) }; + } else { + unsafe { 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() { + unsafe { gl.color_mask(r, g, b, false) }; + } else { + unsafe { gl.color_mask(r, g, b, a) }; + } + } + + fn restore_depth_invariant(&self, gl: &Gl) { + unsafe { + 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) { + unsafe { + 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: None, + 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 `ImageKey`s. + webrender_api: RenderApi, + webrender_doc: 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>, + /// 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: GlType, + #[cfg(feature = "webxr")] + /// 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: RenderApiSender, + pub webrender_doc: 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: GlType, + #[cfg(feature = "webxr")] + 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, + #[cfg(feature = "webxr")] + 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, + external_images, + sender, + receiver: receiver.into_inner(), + webrender_swap_chains, + api_type, + #[cfg(feature = "webxr")] + 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 { + break; + } + } + } + + /// 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 = if data.gl.version().is_embedded { + GlType::Gles + } else { + GlType::Gl + }; + + // 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. + + unsafe { + // XXX: Do we even need to this? + const GL_POINT_SPRITE: u32 = 0x8861; + 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) => { + #[cfg(feature = "webxr")] + self.handle_webxr_command(_command); + }, + WebGLMsg::SwapBuffers(swap_ids, sender, sent_time) => { + self.handle_swap_buffers(swap_ids, sender, sent_time); + }, + WebGLMsg::Exit(sender) => { + // Call remove_context functions in order to correctly delete WebRender image keys. + let context_ids: Vec<WebGLContextId> = self.contexts.keys().copied().collect(); + for id in context_ids { + self.remove_webgl_context(id); + } + + // Block on shutting-down WebRender. + self.webrender_api.shut_down(true); + if let Err(e) = sender.send(()) { + warn!("Failed to send response to WebGLMsg::Exit ({e})"); + } + return true; + }, + } + + false + } + + #[cfg(feature = "webxr")] + /// 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 + 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, + }; + + 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 = unsafe { + Rc::new(match self.api_type { + GlType::Gl => glow::Context::from_loader_function(|symbol_name| { + self.device.get_proc_address(&ctx, symbol_name) + }), + GlType::Gles => glow::Context::from_loader_function(|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 image_buffer_kind = current_wr_image_buffer_kind(&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(|| "Failed to get context surface info".to_string())? + .framebuffer_object; + + unsafe { + 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 default_vao = if let Some(vao) = WebGLImpl::create_vertex_array(&gl) { + WebGLImpl::bind_vertex_array(&gl, Some(vao.glow())); + Some(vao.glow()) + } else { + None + }; + + let state = GLState { + _gl_version: gl_version, + _webgl_version: webgl_version, + requested_flags, + default_vao, + ..Default::default() + }; + debug!("Created state {:?}", state); + + state.restore_invariant(&gl); + debug_assert_eq!(unsafe { 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, + image_buffer_kind, + ); + + 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); + debug_assert_eq!(unsafe { data.gl.get_error() }, gl::NO_ERROR); + + let has_alpha = data + .state + .requested_flags + .contains(ContextAttributeFlags::ALPHA); + self.update_wr_image_for_context(context_id, size.to_i32(), has_alpha); + + 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 = 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, + ); + + #[cfg(feature = "webxr")] + { + // Destroy WebXR layers associated with this context + let webxr_context_id = webxr_api::ContextId::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!(unsafe { 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!(unsafe { 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 { + PreserveBuffer::Yes(&data.gl) + } else { + PreserveBuffer::No + }, + ) + .unwrap(); + debug_assert_eq!(unsafe { 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!(unsafe { 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!(unsafe { data.gl.get_error() }, gl::NO_ERROR); + + let SurfaceInfo { + size, + framebuffer_object, + id, + .. + } = self + .device + .context_surface_info(&data.ctx) + .unwrap() + .unwrap(); + debug!( + "... rebound framebuffer {:?}, new back buffer surface is {:?}", + framebuffer_object, id + ); + + let has_alpha = data + .state + .requested_flags + .contains(ContextAttributeFlags::ALPHA); + self.update_wr_image_for_context(context_id, size, has_alpha); + } + + #[allow(unused)] + let mut end_swap = 0; + completed_sender.send(end_swap).unwrap(); + } + + /// Which access mode to use + fn surface_access(&self) -> SurfaceAccess { + SurfaceAccess::GPUOnly + } + + /// Gets a reference to a Context for a given WebGLContextId and makes it current if required. + pub(crate) 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. + pub(crate) 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 RenderApi, + webrender_doc: DocumentId, + size: Size2D<i32>, + alpha: bool, + context_id: WebGLContextId, + image_buffer_kind: ImageBufferKind, + ) -> ImageKey { + let descriptor = Self::image_descriptor(size, alpha); + let data = Self::external_image_data(context_id, image_buffer_kind); + + let image_key = webrender_api.generate_image_key(); + let mut txn = Transaction::new(); + txn.add_image(image_key, descriptor, data, None); + webrender_api.send_transaction(webrender_doc, txn); + + image_key + } + + /// Tell WebRender to invalidate any cached tiles for a given `WebGLContextId` + /// when the underlying surface has changed e.g due to resize or buffer swap + fn update_wr_image_for_context( + &mut self, + context_id: WebGLContextId, + size: Size2D<i32>, + has_alpha: bool, + ) { + let info = self.cached_context_info.get(&context_id).unwrap(); + let image_buffer_kind = current_wr_image_buffer_kind(&self.device); + + let descriptor = Self::image_descriptor(size, has_alpha); + let image_data = Self::external_image_data(context_id, image_buffer_kind); + + let mut txn = Transaction::new(); + txn.update_image(info.image_key, descriptor, image_data, &DirtyRect::All); + self.webrender_api.send_transaction(self.webrender_doc, txn); + } + + /// Helper function to create a `ImageDescriptor`. + fn image_descriptor(size: Size2D<i32>, alpha: bool) -> ImageDescriptor { + let mut flags = ImageDescriptorFlags::empty(); + flags.set(ImageDescriptorFlags::IS_OPAQUE, !alpha); + ImageDescriptor { + size: DeviceIntSize::new(size.width, size.height), + stride: None, + format: ImageFormat::BGRA8, + offset: 0, + flags, + } + } + + /// Helper function to create a `ImageData::External` instance. + fn external_image_data( + context_id: WebGLContextId, + image_buffer_kind: ImageBufferKind, + ) -> ImageData { + let data = ExternalImageData { + id: ExternalImageId(context_id.0), + channel_index: 0, + image_type: ExternalImageType::TextureHandle(image_buffer_kind), + normalized_uvs: false, + }; + ImageData::External(data) + } + + /// Gets the GLSL Version supported by a GLContext. + fn get_glsl_version(gl: &Gl) -> WebGLSLVersion { + let version = unsafe { gl.get_parameter_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: ImageKey, +} + +// TODO(pcwalton): Add `GL_TEXTURE_EXTERNAL_OES`? +fn current_wr_image_buffer_kind(device: &Device) -> ImageBufferKind { + match device.surface_gl_texture_target() { + gl::TEXTURE_RECTANGLE => ImageBufferKind::TextureRect, + _ => ImageBufferKind::Texture2D, + } +} + +/// WebGL Commands Implementation +pub struct WebGLImpl; + +impl WebGLImpl { + 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!(unsafe { gl.get_error() }, gl::NO_ERROR); + + match command { + WebGLCommand::GetContextAttributes(ref sender) => sender.send(*attributes).unwrap(), + WebGLCommand::ActiveTexture(target) => unsafe { gl.active_texture(target) }, + WebGLCommand::AttachShader(program_id, shader_id) => unsafe { + gl.attach_shader(program_id.glow(), shader_id.glow()) + }, + WebGLCommand::DetachShader(program_id, shader_id) => unsafe { + gl.detach_shader(program_id.glow(), shader_id.glow()) + }, + WebGLCommand::BindAttribLocation(program_id, index, ref name) => unsafe { + gl.bind_attrib_location(program_id.glow(), index, &to_name_in_compiled_shader(name)) + }, + WebGLCommand::BlendColor(r, g, b, a) => unsafe { gl.blend_color(r, g, b, a) }, + WebGLCommand::BlendEquation(mode) => unsafe { gl.blend_equation(mode) }, + WebGLCommand::BlendEquationSeparate(mode_rgb, mode_alpha) => unsafe { + gl.blend_equation_separate(mode_rgb, mode_alpha) + }, + WebGLCommand::BlendFunc(src, dest) => unsafe { gl.blend_func(src, dest) }, + WebGLCommand::BlendFuncSeparate(src_rgb, dest_rgb, src_alpha, dest_alpha) => unsafe { + gl.blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha) + }, + WebGLCommand::BufferData(buffer_type, ref receiver, usage) => unsafe { + gl.buffer_data_u8_slice(buffer_type, &receiver.recv().unwrap(), usage) + }, + WebGLCommand::BufferSubData(buffer_type, offset, ref receiver) => unsafe { + gl.buffer_sub_data_u8_slice(buffer_type, offset as i32, &receiver.recv().unwrap()) + }, + WebGLCommand::CopyBufferSubData(src, dst, src_offset, dst_offset, size) => { + unsafe { + gl.copy_buffer_sub_data( + src, + dst, + src_offset as i32, + dst_offset as i32, + size as i32, + ) + }; + }, + WebGLCommand::GetBufferSubData(buffer_type, offset, length, ref sender) => unsafe { + let ptr = gl.map_buffer_range( + buffer_type, + offset as i32, + length as i32, + gl::MAP_READ_BIT, + ); + let data: &[u8] = slice::from_raw_parts(ptr as _, length); + sender.send(data).unwrap(); + gl.unmap_buffer(buffer_type); + }, + WebGLCommand::Clear(mask) => { + unsafe { gl.clear(mask) }; + }, + WebGLCommand::ClearColor(r, g, b, a) => { + state.clear_color = (r, g, b, a); + unsafe { gl.clear_color(r, g, b, a) }; + }, + WebGLCommand::ClearDepth(depth) => { + let value = depth.clamp(0., 1.) as f64; + state.depth_clear_value = value; + unsafe { gl.clear_depth(value) } + }, + WebGLCommand::ClearStencil(stencil) => { + state.stencil_clear_value = stencil; + unsafe { 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, + ) => unsafe { + gl.copy_tex_image_2d(target, level, internal_format, x, y, width, height, border) + }, + WebGLCommand::CopyTexSubImage2D( + target, + level, + xoffset, + yoffset, + x, + y, + width, + height, + ) => unsafe { + gl.copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height) + }, + WebGLCommand::CullFace(mode) => unsafe { gl.cull_face(mode) }, + WebGLCommand::DepthFunc(func) => unsafe { gl.depth_func(func) }, + WebGLCommand::DepthMask(flag) => { + state.depth_write_mask = flag; + state.restore_depth_invariant(gl); + }, + WebGLCommand::DepthRange(near, far) => unsafe { + gl.depth_range(near.clamp(0., 1.) as f64, far.clamp(0., 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); + }, + _ => unsafe { 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); + }, + _ => unsafe { gl.enable(cap) }, + }, + WebGLCommand::FramebufferRenderbuffer(target, attachment, renderbuffertarget, rb) => { + let attach = |attachment| unsafe { + gl.framebuffer_renderbuffer( + target, + attachment, + renderbuffertarget, + rb.map(WebGLRenderbufferId::glow), + ) + }; + 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| unsafe { + gl.framebuffer_texture_2d( + target, + attachment, + textarget, + texture.map(WebGLTextureId::glow), + level, + ) + }; + if attachment == gl::DEPTH_STENCIL_ATTACHMENT { + attach(gl::DEPTH_ATTACHMENT); + attach(gl::STENCIL_ATTACHMENT); + } else { + attach(attachment) + } + }, + WebGLCommand::FrontFace(mode) => unsafe { gl.front_face(mode) }, + WebGLCommand::DisableVertexAttribArray(attrib_id) => unsafe { + gl.disable_vertex_attrib_array(attrib_id) + }, + WebGLCommand::EnableVertexAttribArray(attrib_id) => unsafe { + gl.enable_vertex_attrib_array(attrib_id) + }, + WebGLCommand::Hint(name, val) => unsafe { gl.hint(name, val) }, + WebGLCommand::LineWidth(width) => { + unsafe { gl.line_width(width) }; + // In OpenGL Core Profile >3.2, any non-1.0 value will generate INVALID_VALUE. + if width != 1.0 { + let _ = unsafe { gl.get_error() }; + } + }, + WebGLCommand::PixelStorei(name, val) => unsafe { gl.pixel_store_i32(name, val) }, + WebGLCommand::PolygonOffset(factor, units) => unsafe { + gl.polygon_offset(factor, units) + }, + WebGLCommand::ReadPixels(rect, format, pixel_type, ref sender) => { + let len = bytes_per_type(pixel_type) * + components_per_format(format) * + rect.size.area() as usize; + let mut pixels = vec![0; len]; + unsafe { + // We don't want any alignment padding on pixel rows. + gl.pixel_store_i32(glow::PACK_ALIGNMENT, 1); + 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, + glow::PixelPackData::Slice(Some(&mut pixels)), + ) + }; + let alpha_mode = match (attributes.alpha, attributes.premultiplied_alpha) { + (true, premultiplied) => snapshot::AlphaMode::Transparent { premultiplied }, + (false, _) => snapshot::AlphaMode::Opaque, + }; + sender + .send((IpcSharedMemory::from_bytes(&pixels), alpha_mode)) + .unwrap(); + }, + WebGLCommand::ReadPixelsPP(rect, format, pixel_type, offset) => unsafe { + gl.read_pixels( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + format, + pixel_type, + glow::PixelPackData::BufferOffset(offset as u32), + ); + }, + WebGLCommand::RenderbufferStorage(target, format, width, height) => unsafe { + gl.renderbuffer_storage(target, format, width, height) + }, + WebGLCommand::RenderbufferStorageMultisample( + target, + samples, + format, + width, + height, + ) => unsafe { + gl.renderbuffer_storage_multisample(target, samples, format, width, height) + }, + WebGLCommand::SampleCoverage(value, invert) => unsafe { + 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. + unsafe { gl.scissor(x, y, width as i32, height as i32) }; + }, + WebGLCommand::StencilFunc(func, ref_, mask) => unsafe { + gl.stencil_func(func, ref_, mask) + }, + WebGLCommand::StencilFuncSeparate(face, func, ref_, mask) => unsafe { + 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) => unsafe { + gl.stencil_op(fail, zfail, zpass) + }, + WebGLCommand::StencilOpSeparate(face, fail, zfail, zpass) => unsafe { + 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 = unsafe { gl.create_transform_feedback() }.ok(); + sender + .send(value.map(|ntf| ntf.0.get()).unwrap_or_default()) + .unwrap() + }, + WebGLCommand::DeleteTransformFeedback(id) => { + if let Some(tf) = NonZeroU32::new(id) { + unsafe { gl.delete_transform_feedback(NativeTransformFeedback(tf)) }; + } + }, + WebGLCommand::IsTransformFeedback(id, ref sender) => { + let value = NonZeroU32::new(id) + .map(|id| unsafe { gl.is_transform_feedback(NativeTransformFeedback(id)) }) + .unwrap_or_default(); + sender.send(value).unwrap() + }, + WebGLCommand::BindTransformFeedback(target, id) => { + unsafe { + gl.bind_transform_feedback( + target, + NonZeroU32::new(id).map(NativeTransformFeedback), + ) + }; + }, + WebGLCommand::BeginTransformFeedback(mode) => { + unsafe { gl.begin_transform_feedback(mode) }; + }, + WebGLCommand::EndTransformFeedback() => { + unsafe { gl.end_transform_feedback() }; + }, + WebGLCommand::PauseTransformFeedback() => { + unsafe { gl.pause_transform_feedback() }; + }, + WebGLCommand::ResumeTransformFeedback() => { + unsafe { gl.resume_transform_feedback() }; + }, + WebGLCommand::GetTransformFeedbackVarying(program, index, ref sender) => { + let ActiveTransformFeedback { size, tftype, name } = + unsafe { gl.get_transform_feedback_varying(program.glow(), index) }.unwrap(); + // We need to split, because the name starts with '_u' prefix. + let name = from_name_in_compiled_shader(&name); + sender.send((size, tftype, name)).unwrap(); + }, + WebGLCommand::TransformFeedbackVaryings(program, ref varyings, buffer_mode) => { + let varyings: Vec<String> = varyings + .iter() + .map(|varying| to_name_in_compiled_shader(varying)) + .collect(); + let varyings_refs: Vec<&str> = varyings.iter().map(String::as_ref).collect(); + unsafe { + gl.transform_feedback_varyings( + program.glow(), + varyings_refs.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 = unsafe { + gl.get_frag_data_location(program_id.glow(), &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) => unsafe { gl.delete_buffer(id.glow()) }, + WebGLCommand::DeleteFramebuffer(id) => unsafe { gl.delete_framebuffer(id.glow()) }, + WebGLCommand::DeleteRenderbuffer(id) => unsafe { gl.delete_renderbuffer(id.glow()) }, + WebGLCommand::DeleteTexture(id) => unsafe { gl.delete_texture(id.glow()) }, + WebGLCommand::DeleteProgram(id) => unsafe { gl.delete_program(id.glow()) }, + WebGLCommand::DeleteShader(id) => unsafe { gl.delete_shader(id.glow()) }, + WebGLCommand::BindBuffer(target, id) => unsafe { + gl.bind_buffer(target, id.map(WebGLBufferId::glow)) + }, + WebGLCommand::BindFramebuffer(target, request) => { + Self::bind_framebuffer(gl, target, request, ctx, device, state) + }, + WebGLCommand::BindRenderbuffer(target, id) => unsafe { + gl.bind_renderbuffer(target, id.map(WebGLRenderbufferId::glow)) + }, + WebGLCommand::BindTexture(target, id) => unsafe { + gl.bind_texture(target, id.map(WebGLTextureId::glow)) + }, + WebGLCommand::BlitFrameBuffer( + src_x0, + src_y0, + src_x1, + src_y1, + dst_x0, + dst_y0, + dst_x1, + dst_y1, + mask, + filter, + ) => unsafe { + gl.blit_framebuffer( + src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter, + ); + }, + WebGLCommand::Uniform1f(uniform_id, v) => unsafe { + gl.uniform_1_f32(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform1fv(uniform_id, ref v) => unsafe { + gl.uniform_1_f32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform1i(uniform_id, v) => unsafe { + gl.uniform_1_i32(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform1iv(uniform_id, ref v) => unsafe { + gl.uniform_1_i32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform1ui(uniform_id, v) => unsafe { + gl.uniform_1_u32(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform1uiv(uniform_id, ref v) => unsafe { + gl.uniform_1_u32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform2f(uniform_id, x, y) => unsafe { + gl.uniform_2_f32(native_uniform_location(uniform_id).as_ref(), x, y) + }, + WebGLCommand::Uniform2fv(uniform_id, ref v) => unsafe { + gl.uniform_2_f32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform2i(uniform_id, x, y) => unsafe { + gl.uniform_2_i32(native_uniform_location(uniform_id).as_ref(), x, y) + }, + WebGLCommand::Uniform2iv(uniform_id, ref v) => unsafe { + gl.uniform_2_i32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform2ui(uniform_id, x, y) => unsafe { + gl.uniform_2_u32(native_uniform_location(uniform_id).as_ref(), x, y) + }, + WebGLCommand::Uniform2uiv(uniform_id, ref v) => unsafe { + gl.uniform_2_u32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform3f(uniform_id, x, y, z) => unsafe { + gl.uniform_3_f32(native_uniform_location(uniform_id).as_ref(), x, y, z) + }, + WebGLCommand::Uniform3fv(uniform_id, ref v) => unsafe { + gl.uniform_3_f32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform3i(uniform_id, x, y, z) => unsafe { + gl.uniform_3_i32(native_uniform_location(uniform_id).as_ref(), x, y, z) + }, + WebGLCommand::Uniform3iv(uniform_id, ref v) => unsafe { + gl.uniform_3_i32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform3ui(uniform_id, x, y, z) => unsafe { + gl.uniform_3_u32(native_uniform_location(uniform_id).as_ref(), x, y, z) + }, + WebGLCommand::Uniform3uiv(uniform_id, ref v) => unsafe { + gl.uniform_3_u32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform4f(uniform_id, x, y, z, w) => unsafe { + gl.uniform_4_f32(native_uniform_location(uniform_id).as_ref(), x, y, z, w) + }, + WebGLCommand::Uniform4fv(uniform_id, ref v) => unsafe { + gl.uniform_4_f32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform4i(uniform_id, x, y, z, w) => unsafe { + gl.uniform_4_i32(native_uniform_location(uniform_id).as_ref(), x, y, z, w) + }, + WebGLCommand::Uniform4iv(uniform_id, ref v) => unsafe { + gl.uniform_4_i32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::Uniform4ui(uniform_id, x, y, z, w) => unsafe { + gl.uniform_4_u32(native_uniform_location(uniform_id).as_ref(), x, y, z, w) + }, + WebGLCommand::Uniform4uiv(uniform_id, ref v) => unsafe { + gl.uniform_4_u32_slice(native_uniform_location(uniform_id).as_ref(), v) + }, + WebGLCommand::UniformMatrix2fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_2_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix3fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_3_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix4fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_4_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix3x2fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_3x2_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix4x2fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_4x2_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix2x3fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_2x3_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix4x3fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_4x3_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix2x4fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_2x4_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::UniformMatrix3x4fv(uniform_id, ref v) => unsafe { + gl.uniform_matrix_3x4_f32_slice( + native_uniform_location(uniform_id).as_ref(), + false, + v, + ) + }, + WebGLCommand::ValidateProgram(program_id) => unsafe { + gl.validate_program(program_id.glow()) + }, + WebGLCommand::VertexAttrib(attrib_id, x, y, z, w) => unsafe { + gl.vertex_attrib_4_f32(attrib_id, x, y, z, w) + }, + WebGLCommand::VertexAttribI(attrib_id, x, y, z, w) => unsafe { + gl.vertex_attrib_4_i32(attrib_id, x, y, z, w) + }, + WebGLCommand::VertexAttribU(attrib_id, x, y, z, w) => unsafe { + gl.vertex_attrib_4_u32(attrib_id, x, y, z, w) + }, + WebGLCommand::VertexAttribPointer2f(attrib_id, size, normalized, stride, offset) => unsafe { + gl.vertex_attrib_pointer_f32( + attrib_id, + size, + gl::FLOAT, + normalized, + stride, + offset as _, + ) + }, + WebGLCommand::VertexAttribPointer( + attrib_id, + size, + data_type, + normalized, + stride, + offset, + ) => unsafe { + gl.vertex_attrib_pointer_f32( + attrib_id, + size, + data_type, + normalized, + stride, + offset as _, + ) + }, + WebGLCommand::SetViewport(x, y, width, height) => unsafe { + 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), + ); + + unsafe { + gl.pixel_store_i32(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, + PixelUnpackData::Slice(Some(&pixels)), + ); + } + }, + WebGLCommand::TexImage2DPBO { + target, + level, + internal_format, + size, + format, + effective_data_type, + unpacking_alignment, + offset, + } => unsafe { + gl.pixel_store_i32(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, + PixelUnpackData::BufferOffset(offset as u32), + ); + }, + 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), + ); + + unsafe { + gl.pixel_store_i32(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, + glow::PixelUnpackData::Slice(Some(&pixels)), + ); + } + }, + WebGLCommand::CompressedTexImage2D { + target, + level, + internal_format, + size, + ref data, + } => unsafe { + gl.compressed_tex_image_2d( + target, + level as i32, + internal_format as i32, + size.width as i32, + size.height as i32, + 0, + data.len() as i32, + data, + ) + }, + WebGLCommand::CompressedTexSubImage2D { + target, + level, + xoffset, + yoffset, + size, + format, + ref data, + } => { + unsafe { + gl.compressed_tex_sub_image_2d( + target, + level, + xoffset, + yoffset, + size.width as i32, + size.height as i32, + format, + glow::CompressedPixelUnpackData::Slice(data), + ) + }; + }, + WebGLCommand::TexStorage2D(target, levels, internal_format, width, height) => unsafe { + 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) => unsafe { + 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 => unsafe { gl.flush() }, + WebGLCommand::GenerateMipmap(target) => unsafe { gl.generate_mipmap(target) }, + WebGLCommand::CreateVertexArray(ref chan) => { + let id = Self::create_vertex_array(gl); + let _ = chan.send(id); + }, + WebGLCommand::DeleteVertexArray(id) => { + Self::delete_vertex_array(gl, id); + }, + WebGLCommand::BindVertexArray(id) => { + let id = id.map(WebGLVertexArrayId::glow).or(state.default_vao); + Self::bind_vertex_array(gl, id); + }, + WebGLCommand::GetParameterBool(param, ref sender) => { + let value = match param { + webgl::ParameterBool::DepthWritemask => state.depth_write_mask, + _ => unsafe { gl.get_parameter_bool(param as u32) }, + }; + sender.send(value).unwrap() + }, + WebGLCommand::FenceSync(ref sender) => { + let value = unsafe { gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0).unwrap() }; + sender.send(WebGLSyncId::from_glow(value)).unwrap(); + }, + WebGLCommand::IsSync(sync_id, ref sender) => { + let value = unsafe { gl.is_sync(sync_id.glow()) }; + sender.send(value).unwrap(); + }, + WebGLCommand::ClientWaitSync(sync_id, flags, timeout, ref sender) => { + let value = unsafe { gl.client_wait_sync(sync_id.glow(), flags, timeout as _) }; + sender.send(value).unwrap(); + }, + WebGLCommand::WaitSync(sync_id, flags, timeout) => { + unsafe { gl.wait_sync(sync_id.glow(), flags, timeout as u64) }; + }, + WebGLCommand::GetSyncParameter(sync_id, param, ref sender) => { + let value = unsafe { gl.get_sync_parameter_i32(sync_id.glow(), param) }; + sender.send(value as u32).unwrap(); + }, + WebGLCommand::DeleteSync(sync_id) => { + unsafe { gl.delete_sync(sync_id.glow()) }; + }, + 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 { gl.get_parameter_i32(param as u32) }, + }; + sender.send(value).unwrap() + }, + WebGLCommand::GetParameterInt2(param, ref sender) => { + let mut value = [0; 2]; + unsafe { + gl.get_parameter_i32_slice(param as u32, &mut value); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetParameterInt4(param, ref sender) => { + let mut value = [0; 4]; + unsafe { + gl.get_parameter_i32_slice(param as u32, &mut value); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetParameterFloat(param, ref sender) => { + let mut value = [0.]; + unsafe { + gl.get_parameter_f32_slice(param as u32, &mut value); + } + sender.send(value[0]).unwrap() + }, + WebGLCommand::GetParameterFloat2(param, ref sender) => { + let mut value = [0.; 2]; + unsafe { + gl.get_parameter_f32_slice(param as u32, &mut value); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetParameterFloat4(param, ref sender) => { + let mut value = [0.; 4]; + unsafe { + gl.get_parameter_f32_slice(param as u32, &mut value); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetProgramValidateStatus(program, ref sender) => sender + .send(unsafe { gl.get_program_validate_status(program.glow()) }) + .unwrap(), + WebGLCommand::GetProgramActiveUniforms(program, ref sender) => sender + .send(unsafe { gl.get_program_parameter_i32(program.glow(), gl::ACTIVE_UNIFORMS) }) + .unwrap(), + WebGLCommand::GetCurrentVertexAttrib(index, ref sender) => { + let mut value = [0.; 4]; + unsafe { + gl.get_vertex_attrib_parameter_f32_slice( + index, + gl::CURRENT_VERTEX_ATTRIB, + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetTexParameterFloat(target, param, ref sender) => { + sender + .send(unsafe { gl.get_tex_parameter_f32(target, param as u32) }) + .unwrap(); + }, + WebGLCommand::GetTexParameterInt(target, param, ref sender) => { + sender + .send(unsafe { gl.get_tex_parameter_i32(target, param as u32) }) + .unwrap(); + }, + WebGLCommand::GetTexParameterBool(target, param, ref sender) => { + sender + .send(unsafe { gl.get_tex_parameter_i32(target, param as u32) } != 0) + .unwrap(); + }, + WebGLCommand::GetInternalFormatIntVec(target, internal_format, param, ref sender) => { + match param { + InternalFormatIntVec::Samples => { + let mut count = [0; 1]; + unsafe { + gl.get_internal_format_i32_slice( + target, + internal_format, + gl::NUM_SAMPLE_COUNTS, + &mut count, + ) + }; + assert!(count[0] >= 0); + + let mut values = vec![0; count[0] as usize]; + unsafe { + gl.get_internal_format_i32_slice( + target, + internal_format, + param as u32, + &mut values, + ) + }; + sender.send(values).unwrap() + }, + } + }, + WebGLCommand::TexParameteri(target, param, value) => unsafe { + gl.tex_parameter_i32(target, param, value) + }, + WebGLCommand::TexParameterf(target, param, value) => unsafe { + gl.tex_parameter_f32(target, param, value) + }, + WebGLCommand::LinkProgram(program_id, ref sender) => { + return sender.send(Self::link_program(gl, program_id)).unwrap(); + }, + WebGLCommand::UseProgram(program_id) => unsafe { + gl.use_program(program_id.map(|p| p.glow())) + }, + WebGLCommand::DrawArrays { mode, first, count } => unsafe { + gl.draw_arrays(mode, first, count) + }, + WebGLCommand::DrawArraysInstanced { + mode, + first, + count, + primcount, + } => unsafe { gl.draw_arrays_instanced(mode, first, count, primcount) }, + WebGLCommand::DrawElements { + mode, + count, + type_, + offset, + } => unsafe { gl.draw_elements(mode, count, type_, offset as _) }, + WebGLCommand::DrawElementsInstanced { + mode, + count, + type_, + offset, + primcount, + } => unsafe { + gl.draw_elements_instanced(mode, count, type_, offset as i32, primcount) + }, + WebGLCommand::VertexAttribDivisor { index, divisor } => unsafe { + gl.vertex_attrib_divisor(index, divisor) + }, + WebGLCommand::GetUniformBool(program_id, loc, ref sender) => { + let mut value = [0]; + unsafe { + gl.get_uniform_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value[0] != 0).unwrap(); + }, + WebGLCommand::GetUniformBool2(program_id, loc, ref sender) => { + let mut value = [0; 2]; + unsafe { + gl.get_uniform_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &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_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &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_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &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_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value[0]).unwrap(); + }, + WebGLCommand::GetUniformInt2(program_id, loc, ref sender) => { + let mut value = [0; 2]; + unsafe { + gl.get_uniform_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformInt3(program_id, loc, ref sender) => { + let mut value = [0; 3]; + unsafe { + gl.get_uniform_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformInt4(program_id, loc, ref sender) => { + let mut value = [0; 4]; + unsafe { + gl.get_uniform_i32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformUint(program_id, loc, ref sender) => { + let mut value = [0]; + unsafe { + gl.get_uniform_u32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value[0]).unwrap(); + }, + WebGLCommand::GetUniformUint2(program_id, loc, ref sender) => { + let mut value = [0; 2]; + unsafe { + gl.get_uniform_u32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformUint3(program_id, loc, ref sender) => { + let mut value = [0; 3]; + unsafe { + gl.get_uniform_u32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformUint4(program_id, loc, ref sender) => { + let mut value = [0; 4]; + unsafe { + gl.get_uniform_u32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformFloat(program_id, loc, ref sender) => { + let mut value = [0.]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value[0]).unwrap(); + }, + WebGLCommand::GetUniformFloat2(program_id, loc, ref sender) => { + let mut value = [0.; 2]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformFloat3(program_id, loc, ref sender) => { + let mut value = [0.; 3]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformFloat4(program_id, loc, ref sender) => { + let mut value = [0.; 4]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformFloat9(program_id, loc, ref sender) => { + let mut value = [0.; 9]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformFloat16(program_id, loc, ref sender) => { + let mut value = [0.; 16]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap(); + }, + WebGLCommand::GetUniformFloat2x3(program_id, loc, ref sender) => { + let mut value = [0.; 2 * 3]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetUniformFloat2x4(program_id, loc, ref sender) => { + let mut value = [0.; 2 * 4]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetUniformFloat3x2(program_id, loc, ref sender) => { + let mut value = [0.; 3 * 2]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetUniformFloat3x4(program_id, loc, ref sender) => { + let mut value = [0.; 3 * 4]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetUniformFloat4x2(program_id, loc, ref sender) => { + let mut value = [0.; 4 * 2]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetUniformFloat4x3(program_id, loc, ref sender) => { + let mut value = [0.; 4 * 3]; + unsafe { + gl.get_uniform_f32( + program_id.glow(), + &NativeUniformLocation(loc as u32), + &mut value, + ); + } + sender.send(value).unwrap() + }, + WebGLCommand::GetUniformBlockIndex(program_id, ref name, ref sender) => { + let name = to_name_in_compiled_shader(name); + let index = unsafe { gl.get_uniform_block_index(program_id.glow(), &name) }; + // TODO(#34300): use Option<u32> + sender.send(index.unwrap_or(gl::INVALID_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 = unsafe { + gl.get_uniform_indices(program_id.glow(), &name_strs) + .iter() + .map(|index| index.unwrap_or(gl::INVALID_INDEX)) + .collect() + }; + sender.send(indices).unwrap(); + }, + WebGLCommand::GetActiveUniforms(program_id, ref indices, pname, ref sender) => { + let results = + unsafe { gl.get_active_uniforms_parameter(program_id.glow(), indices, pname) }; + sender.send(results).unwrap(); + }, + WebGLCommand::GetActiveUniformBlockName(program_id, block_idx, ref sender) => { + let name = + unsafe { gl.get_active_uniform_block_name(program_id.glow(), block_idx) }; + sender.send(name).unwrap(); + }, + WebGLCommand::GetActiveUniformBlockParameter( + program_id, + block_idx, + pname, + ref sender, + ) => { + let size = match pname { + gl::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES => unsafe { + gl.get_active_uniform_block_parameter_i32( + program_id.glow(), + block_idx, + gl::UNIFORM_BLOCK_ACTIVE_UNIFORMS, + ) as usize + }, + _ => 1, + }; + let mut result = vec![0; size]; + unsafe { + gl.get_active_uniform_block_parameter_i32_slice( + program_id.glow(), + block_idx, + pname, + &mut result, + ) + }; + sender.send(result).unwrap(); + }, + WebGLCommand::UniformBlockBinding(program_id, block_idx, block_binding) => unsafe { + gl.uniform_block_binding(program_id.glow(), block_idx, block_binding) + }, + WebGLCommand::InitializeFramebuffer { + color, + depth, + stencil, + } => Self::initialize_framebuffer(gl, state, color, depth, stencil), + WebGLCommand::BeginQuery(target, query_id) => { + unsafe { gl.begin_query(target, query_id.glow()) }; + }, + WebGLCommand::EndQuery(target) => { + unsafe { gl.end_query(target) }; + }, + WebGLCommand::DeleteQuery(query_id) => { + unsafe { gl.delete_query(query_id.glow()) }; + }, + WebGLCommand::GenerateQuery(ref sender) => { + // TODO(#34300): use Option<WebGLQueryId> + let id = unsafe { gl.create_query().unwrap() }; + sender.send(WebGLQueryId::from_glow(id)).unwrap() + }, + WebGLCommand::GetQueryState(ref sender, query_id, pname) => { + let value = unsafe { gl.get_query_parameter_u32(query_id.glow(), pname) }; + sender.send(value).unwrap() + }, + WebGLCommand::GenerateSampler(ref sender) => { + let id = unsafe { gl.create_sampler().unwrap() }; + sender.send(WebGLSamplerId::from_glow(id)).unwrap() + }, + WebGLCommand::DeleteSampler(sampler_id) => { + unsafe { gl.delete_sampler(sampler_id.glow()) }; + }, + WebGLCommand::BindSampler(unit, sampler_id) => { + unsafe { gl.bind_sampler(unit, Some(sampler_id.glow())) }; + }, + WebGLCommand::SetSamplerParameterInt(sampler_id, pname, value) => { + unsafe { gl.sampler_parameter_i32(sampler_id.glow(), pname, value) }; + }, + WebGLCommand::SetSamplerParameterFloat(sampler_id, pname, value) => { + unsafe { gl.sampler_parameter_f32(sampler_id.glow(), pname, value) }; + }, + WebGLCommand::GetSamplerParameterInt(sampler_id, pname, ref sender) => { + let value = unsafe { gl.get_sampler_parameter_i32(sampler_id.glow(), pname) }; + sender.send(value).unwrap(); + }, + WebGLCommand::GetSamplerParameterFloat(sampler_id, pname, ref sender) => { + let value = unsafe { gl.get_sampler_parameter_f32(sampler_id.glow(), pname) }; + 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(WebGLBufferId::glow); + unsafe { + gl.bind_buffer(target, id); + gl.bind_buffer(target, None); + 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(WebGLBufferId::glow); + unsafe { + gl.bind_buffer(target, id); + gl.bind_buffer(target, None); + gl.bind_buffer_range(target, index, id, offset as i32, size as i32); + } + }, + WebGLCommand::ClearBufferfv(buffer, draw_buffer, ref value) => unsafe { + gl.clear_buffer_f32_slice(buffer, draw_buffer as u32, value) + }, + WebGLCommand::ClearBufferiv(buffer, draw_buffer, ref value) => unsafe { + gl.clear_buffer_i32_slice(buffer, draw_buffer as u32, value) + }, + WebGLCommand::ClearBufferuiv(buffer, draw_buffer, ref value) => unsafe { + gl.clear_buffer_u32_slice(buffer, draw_buffer as u32, value) + }, + WebGLCommand::ClearBufferfi(buffer, draw_buffer, depth, stencil) => unsafe { + gl.clear_buffer_depth_stencil(buffer, draw_buffer as u32, depth, stencil) + }, + WebGLCommand::InvalidateFramebuffer(target, ref attachments) => unsafe { + gl.invalidate_framebuffer(target, attachments) + }, + WebGLCommand::InvalidateSubFramebuffer(target, ref attachments, x, y, w, h) => unsafe { + gl.invalidate_sub_framebuffer(target, attachments, x, y, w, h) + }, + WebGLCommand::FramebufferTextureLayer(target, attachment, tex_id, level, layer) => { + let tex_id = tex_id.map(WebGLTextureId::glow); + let attach = |attachment| unsafe { + 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) => unsafe { gl.read_buffer(buffer) }, + WebGLCommand::DrawBuffers(ref buffers) => unsafe { gl.draw_buffers(buffers) }, + } + + // If debug asertions are enabled, then check the error state. + #[cfg(debug_assertions)] + { + let error = unsafe { gl.get_error() }; + if error != gl::NO_ERROR { + error!("Last GL operation failed: {:?}", command); + if error == gl::INVALID_FRAMEBUFFER_OPERATION { + let framebuffer_bindings = + unsafe { gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING) }; + debug!( + "(thread {:?}) Current draw framebuffer binding: {:?}", + ::std::thread::current().id(), + framebuffer_bindings + ); + } + #[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); + } + } + // TODO(servo#30568) revert to panic!() once underlying bug is fixed + log::warn!( + "debug assertion failed! 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 } + }); + + unsafe { + 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); + } + + fn link_program(gl: &Gl, program: WebGLProgramId) -> ProgramLinkInfo { + unsafe { gl.link_program(program.glow()) }; + let linked = unsafe { gl.get_program_link_status(program.glow()) }; + if !linked { + 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 num_active_attribs = + unsafe { gl.get_program_parameter_i32(program.glow(), gl::ACTIVE_ATTRIBUTES) }; + let active_attribs = (0..num_active_attribs as u32) + .map(|i| { + let active_attribute = + unsafe { gl.get_active_attribute(program.glow(), i) }.unwrap(); + let name = &active_attribute.name; + let location = if name.starts_with("gl_") { + None + } else { + unsafe { gl.get_attrib_location(program.glow(), name) } + }; + ActiveAttribInfo { + name: from_name_in_compiled_shader(name), + size: active_attribute.size, + type_: active_attribute.atype, + location, + } + }) + .collect::<Vec<_>>() + .into(); + + let num_active_uniforms = + unsafe { gl.get_program_parameter_i32(program.glow(), gl::ACTIVE_UNIFORMS) }; + let active_uniforms = (0..num_active_uniforms as u32) + .map(|i| { + let active_uniform = unsafe { gl.get_active_uniform(program.glow(), i) }.unwrap(); + let is_array = active_uniform.name.ends_with("[0]"); + let active_uniform_name = active_uniform + .name + .strip_suffix("[0]") + .unwrap_or_else(|| &active_uniform.name); + ActiveUniformInfo { + base_name: from_name_in_compiled_shader(active_uniform_name).into(), + size: if is_array { + Some(active_uniform.size) + } else { + None + }, + type_: active_uniform.utype, + bind_index: None, + } + }) + .collect::<Vec<_>>() + .into(); + + let num_active_uniform_blocks = + unsafe { gl.get_program_parameter_i32(program.glow(), gl::ACTIVE_UNIFORM_BLOCKS) }; + let active_uniform_blocks = (0..num_active_uniform_blocks as u32) + .map(|i| { + let name = unsafe { gl.get_active_uniform_block_name(program.glow(), i) }; + let size = unsafe { + gl.get_active_uniform_block_parameter_i32( + program.glow(), + i, + gl::UNIFORM_BLOCK_DATA_SIZE, + ) + }; + ActiveUniformBlockInfo { name, size } + }) + .collect::<Vec<_>>() + .into(); + + let transform_feedback_length = unsafe { + gl.get_program_parameter_i32(program.glow(), gl::TRANSFORM_FEEDBACK_VARYINGS) + }; + let transform_feedback_mode = unsafe { + gl.get_program_parameter_i32(program.glow(), gl::TRANSFORM_FEEDBACK_BUFFER_MODE) + }; + + ProgramLinkInfo { + linked: true, + active_attribs, + active_uniforms, + active_uniform_blocks, + transform_feedback_length, + transform_feedback_mode, + } + } + + fn finish(gl: &Gl, chan: &WebGLSender<()>) { + unsafe { gl.finish() }; + chan.send(()).unwrap(); + } + + fn shader_precision_format( + gl: &Gl, + shader_type: u32, + precision_type: u32, + chan: &WebGLSender<(i32, i32, i32)>, + ) { + let ShaderPrecisionFormat { + range_min, + range_max, + precision, + } = unsafe { + gl.get_shader_precision_format(shader_type, precision_type) + .unwrap_or_else(|| { + ShaderPrecisionFormat::common_desktop_hardware( + precision_type, + gl.version().is_embedded, + ) + }) + }; + chan.send((range_min, range_max, precision)).unwrap(); + } + + fn get_extensions(gl: &Gl, chan: &WebGLSender<String>) { + let mut ext_count = [0]; + unsafe { + gl.get_parameter_i32_slice(gl::NUM_EXTENSIONS, &mut ext_count); + } + // Fall back to the depricated extensions API if that fails + if unsafe { gl.get_error() } != gl::NO_ERROR { + chan.send(unsafe { gl.get_parameter_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(unsafe { gl.get_parameter_indexed_string(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 = + unsafe { gl.get_framebuffer_attachment_parameter_i32(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 = unsafe { gl.get_renderbuffer_parameter_i32(target, pname) }; + chan.send(parameter).unwrap(); + } + + fn uniform_location(gl: &Gl, program_id: WebGLProgramId, name: &str, chan: &WebGLSender<i32>) { + let location = unsafe { + gl.get_uniform_location(program_id.glow(), &to_name_in_compiled_shader(name)) + }; + // (#34300): replace this with WebGLUniformId + chan.send(location.map(|l| l.0).unwrap_or_default() as i32) + .unwrap(); + } + + fn shader_info_log(gl: &Gl, shader_id: WebGLShaderId, chan: &WebGLSender<String>) { + let log = unsafe { gl.get_shader_info_log(shader_id.glow()) }; + chan.send(log).unwrap(); + } + + fn program_info_log(gl: &Gl, program_id: WebGLProgramId, chan: &WebGLSender<String>) { + let log = unsafe { gl.get_program_info_log(program_id.glow()) }; + chan.send(log).unwrap(); + } + + fn create_buffer(gl: &Gl, chan: &WebGLSender<Option<WebGLBufferId>>) { + let buffer = unsafe { gl.create_buffer() } + .ok() + .map(WebGLBufferId::from_glow); + chan.send(buffer).unwrap(); + } + + fn create_framebuffer(gl: &Gl, chan: &WebGLSender<Option<WebGLFramebufferId>>) { + let framebuffer = unsafe { gl.create_framebuffer() } + .ok() + .map(WebGLFramebufferId::from_glow); + chan.send(framebuffer).unwrap(); + } + + fn create_renderbuffer(gl: &Gl, chan: &WebGLSender<Option<WebGLRenderbufferId>>) { + let renderbuffer = unsafe { gl.create_renderbuffer() } + .ok() + .map(WebGLRenderbufferId::from_glow); + chan.send(renderbuffer).unwrap(); + } + + fn create_texture(gl: &Gl, chan: &WebGLSender<Option<WebGLTextureId>>) { + let texture = unsafe { gl.create_texture() } + .ok() + .map(WebGLTextureId::from_glow); + chan.send(texture).unwrap(); + } + + fn create_program(gl: &Gl, chan: &WebGLSender<Option<WebGLProgramId>>) { + let program = unsafe { gl.create_program() } + .ok() + .map(WebGLProgramId::from_glow); + chan.send(program).unwrap(); + } + + fn create_shader(gl: &Gl, shader_type: u32, chan: &WebGLSender<Option<WebGLShaderId>>) { + let shader = unsafe { gl.create_shader(shader_type) } + .ok() + .map(WebGLShaderId::from_glow); + chan.send(shader).unwrap(); + } + + fn create_vertex_array(gl: &Gl) -> Option<WebGLVertexArrayId> { + let vao = unsafe { gl.create_vertex_array() } + .ok() + .map(WebGLVertexArrayId::from_glow); + if vao.is_none() { + let code = unsafe { gl.get_error() }; + warn!("Failed to create vertex array with error code {:x}", code); + } + vao + } + + fn bind_vertex_array(gl: &Gl, vao: Option<NativeVertexArray>) { + unsafe { gl.bind_vertex_array(vao) } + debug_assert_eq!(unsafe { gl.get_error() }, gl::NO_ERROR); + } + + fn delete_vertex_array(gl: &Gl, vao: WebGLVertexArrayId) { + unsafe { gl.delete_vertex_array(vao.glow()) }; + debug_assert_eq!(unsafe { 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) => Some(id.glow()), + WebGLFramebufferBindingRequest::Default => { + device + .context_surface_info(ctx) + .unwrap() + .expect("No surface attached!") + .framebuffer_object + }, + }; + + debug!("WebGLImpl::bind_framebuffer: {:?}", id); + unsafe { 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) { + unsafe { + gl.shader_source(shader_id.glow(), source); + gl.compile_shader(shader_id.glow()); + } + } +} + +/// 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: &str = "_u"; + +/// Adds `_u` prefix to variable names +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); + }) +} + +/// Removes `_u` prefix from variable names +fn from_name_in_compiled_shader(s: &str) -> String { + map_dot_separated(s, |s, mapped| { + mapped.push_str(if let Some(stripped) = s.strip_prefix(ANGLE_NAME_PREFIX) { + stripped + } 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 +} + +#[allow(clippy::too_many_arguments)] +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::<false>(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. + _ => {}, + } +} + +/// 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, i32::MAX]; + let mut max_renderbuffer = [i32::MAX]; + + unsafe { + gl.get_parameter_i32_slice(gl::MAX_VIEWPORT_DIMS, &mut max_viewport); + gl.get_parameter_i32_slice(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: GlType) -> GLVersion; +} + +impl ToSurfmanVersion for WebGLVersion { + fn to_surfman_version(self, api_type: GlType) -> GLVersion { + if api_type == 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: GlType, + ) -> ContextAttributeFlags; +} + +impl SurfmanContextAttributeFlagsConvert for GLContextAttributes { + fn to_surfman_context_attribute_flags( + &self, + webgl_version: WebGLVersion, + api_type: 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 == 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 { + fn detect(device: &Device, context: &Context, gl: &Gl) -> FramebufferRebindingInfo { + unsafe { + let read_framebuffer = gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING); + let draw_framebuffer = gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING); + + let context_surface_framebuffer = device + .context_surface_info(context) + .unwrap() + .unwrap() + .framebuffer_object; + + let mut flags = FramebufferRebindingFlags::empty(); + if context_surface_framebuffer == read_framebuffer { + flags.insert(FramebufferRebindingFlags::REBIND_READ_FRAMEBUFFER); + } + if context_surface_framebuffer == draw_framebuffer { + flags.insert(FramebufferRebindingFlags::REBIND_DRAW_FRAMEBUFFER); + } + + let mut viewport = [0; 4]; + gl.get_parameter_i32_slice(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) + { + unsafe { gl.bind_framebuffer(gl::READ_FRAMEBUFFER, context_surface_framebuffer) }; + } + if self + .flags + .contains(FramebufferRebindingFlags::REBIND_DRAW_FRAMEBUFFER) + { + unsafe { gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, context_surface_framebuffer) }; + } + + unsafe { + gl.viewport( + self.viewport[0], + self.viewport[1], + self.viewport[2], + self.viewport[3], + ) + }; + } +} |