servo/components/canvas/webgl_thread.rs
2020-04-30 00:21:49 -04:00

3190 lines
124 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::webgl_limits::GLLimitsDetect;
use byteorder::{ByteOrder, NativeEndian, WriteBytesExt};
use canvas_traits::webgl;
use canvas_traits::webgl::ActiveAttribInfo;
use canvas_traits::webgl::ActiveUniformBlockInfo;
use canvas_traits::webgl::ActiveUniformInfo;
use canvas_traits::webgl::AlphaTreatment;
use canvas_traits::webgl::DOMToTextureCommand;
use canvas_traits::webgl::GLContextAttributes;
use canvas_traits::webgl::GLLimits;
use canvas_traits::webgl::GlType;
use canvas_traits::webgl::InternalFormatIntVec;
use canvas_traits::webgl::ProgramLinkInfo;
use canvas_traits::webgl::SwapChainId;
use canvas_traits::webgl::TexDataType;
use canvas_traits::webgl::TexFormat;
use canvas_traits::webgl::WebGLBufferId;
use canvas_traits::webgl::WebGLChan;
use canvas_traits::webgl::WebGLCommand;
use canvas_traits::webgl::WebGLCommandBacktrace;
use canvas_traits::webgl::WebGLContextId;
use canvas_traits::webgl::WebGLCreateContextResult;
use canvas_traits::webgl::WebGLFramebufferBindingRequest;
use canvas_traits::webgl::WebGLFramebufferId;
use canvas_traits::webgl::WebGLMsg;
use canvas_traits::webgl::WebGLMsgSender;
use canvas_traits::webgl::WebGLOpaqueFramebufferId;
use canvas_traits::webgl::WebGLProgramId;
use canvas_traits::webgl::WebGLQueryId;
use canvas_traits::webgl::WebGLReceiver;
use canvas_traits::webgl::WebGLRenderbufferId;
use canvas_traits::webgl::WebGLSLVersion;
use canvas_traits::webgl::WebGLSamplerId;
use canvas_traits::webgl::WebGLSender;
use canvas_traits::webgl::WebGLShaderId;
use canvas_traits::webgl::WebGLSyncId;
use canvas_traits::webgl::WebGLTextureId;
use canvas_traits::webgl::WebGLTransparentFramebufferId;
use canvas_traits::webgl::WebGLVersion;
use canvas_traits::webgl::WebGLVertexArrayId;
use canvas_traits::webgl::YAxisTreatment;
use euclid::default::Size2D;
use fnv::FnvHashMap;
use half::f16;
use pixels::{self, PixelFormat};
use sparkle::gl;
use sparkle::gl::GLint;
use sparkle::gl::GLuint;
use sparkle::gl::Gl;
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::slice;
use std::sync::{Arc, Mutex};
use std::thread;
use surfman;
use surfman::Adapter;
use surfman::Connection;
use surfman::Context;
use surfman::ContextAttributeFlags;
use surfman::ContextAttributes;
use surfman::Device;
use surfman::GLVersion;
use surfman::SurfaceAccess;
use surfman::SurfaceInfo;
use surfman::SurfaceType;
use surfman_chains::{SurfmanProvider, SwapChains};
use surfman_chains_api::SwapChainsAPI;
use webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType};
use webxr_api::SessionId;
use webxr_api::SwapChainId as WebXRSwapChainId;
#[cfg(feature = "xr-profile")]
fn to_ms(ns: u64) -> f64 {
ns as f64 / 1_000_000.
}
struct GLContextData {
ctx: Context,
gl: Rc<Gl>,
state: GLState,
attributes: GLContextAttributes,
}
#[derive(Debug)]
pub struct GLState {
webgl_version: WebGLVersion,
gl_version: GLVersion,
requested_flags: ContextAttributeFlags,
// This is the WebGL view of the color mask
// The GL view may be different: if the GL context supports alpha
// but the WebGL context doesn't, then color_write_mask.3 might be true
// but the GL color write mask is false.
color_write_mask: [bool; 4],
clear_color: (f32, f32, f32, f32),
scissor_test_enabled: bool,
// The WebGL view of the stencil write mask (see comment re `color_write_mask`)
stencil_write_mask: (u32, u32),
stencil_test_enabled: bool,
stencil_clear_value: i32,
// The WebGL view of the depth write mask (see comment re `color_write_mask`)
depth_write_mask: bool,
depth_test_enabled: bool,
depth_clear_value: f64,
// True when the default framebuffer is bound to DRAW_FRAMEBUFFER
drawing_to_default_framebuffer: bool,
default_vao: gl::GLuint,
}
impl GLState {
// Are we faking having no alpha / depth / stencil?
fn fake_no_alpha(&self) -> bool {
self.drawing_to_default_framebuffer &
!self.requested_flags.contains(ContextAttributeFlags::ALPHA)
}
fn fake_no_depth(&self) -> bool {
self.drawing_to_default_framebuffer &
!self.requested_flags.contains(ContextAttributeFlags::DEPTH)
}
fn fake_no_stencil(&self) -> bool {
self.drawing_to_default_framebuffer &
!self
.requested_flags
.contains(ContextAttributeFlags::STENCIL)
}
// We maintain invariants between the GLState object and the GL state.
fn restore_invariant(&self, gl: &Gl) {
self.restore_clear_color_invariant(gl);
self.restore_scissor_invariant(gl);
self.restore_alpha_invariant(gl);
self.restore_depth_invariant(gl);
self.restore_stencil_invariant(gl);
}
fn restore_clear_color_invariant(&self, gl: &Gl) {
let (r, g, b, a) = self.clear_color;
gl.clear_color(r, g, b, a);
}
fn restore_scissor_invariant(&self, gl: &Gl) {
if self.scissor_test_enabled {
gl.enable(gl::SCISSOR_TEST);
} else {
gl.disable(gl::SCISSOR_TEST);
}
}
fn restore_alpha_invariant(&self, gl: &Gl) {
let [r, g, b, a] = self.color_write_mask;
if self.fake_no_alpha() {
gl.color_mask(r, g, b, false);
} else {
gl.color_mask(r, g, b, a);
}
}
fn restore_depth_invariant(&self, gl: &Gl) {
if self.fake_no_depth() {
gl.depth_mask(false);
gl.disable(gl::DEPTH_TEST);
} else {
gl.depth_mask(self.depth_write_mask);
if self.depth_test_enabled {
gl.enable(gl::DEPTH_TEST);
} else {
gl.disable(gl::DEPTH_TEST);
}
}
}
fn restore_stencil_invariant(&self, gl: &Gl) {
if self.fake_no_stencil() {
gl.stencil_mask(0);
gl.disable(gl::STENCIL_TEST);
} else {
let (f, b) = self.stencil_write_mask;
gl.stencil_mask_separate(gl::FRONT, f);
gl.stencil_mask_separate(gl::BACK, b);
if self.stencil_test_enabled {
gl.enable(gl::STENCIL_TEST);
} else {
gl.disable(gl::STENCIL_TEST);
}
}
}
}
impl Default for GLState {
fn default() -> GLState {
GLState {
gl_version: GLVersion { major: 1, minor: 0 },
webgl_version: WebGLVersion::WebGL1,
requested_flags: ContextAttributeFlags::empty(),
color_write_mask: [true, true, true, true],
clear_color: (0., 0., 0., 0.),
scissor_test_enabled: false,
// Should these be 0xFFFF_FFFF?
stencil_write_mask: (0, 0),
stencil_test_enabled: false,
stencil_clear_value: 0,
depth_write_mask: true,
depth_test_enabled: false,
depth_clear_value: 1.,
default_vao: 0,
drawing_to_default_framebuffer: true,
}
}
}
/// A WebGLThread manages the life cycle and message multiplexing of
/// a set of WebGLContexts living in the same thread.
pub(crate) struct WebGLThread {
/// The GPU device.
device: Device,
/// Channel used to generate/update or delete `webrender_api::ImageKey`s.
webrender_api: webrender_api::RenderApi,
/// Map of live WebGLContexts.
contexts: FnvHashMap<WebGLContextId, GLContextData>,
/// Cached information for WebGLContexts.
cached_context_info: FnvHashMap<WebGLContextId, WebGLContextInfo>,
/// Current bound context.
bound_context_id: Option<WebGLContextId>,
/// Texture ids and sizes used in DOM to texture outputs.
dom_outputs: FnvHashMap<webrender_api::PipelineId, DOMToTextureData>,
/// List of registered webrender external images.
/// We use it to get an unique ID for new WebGLContexts.
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
/// The receiver that will be used for processing WebGL messages.
receiver: crossbeam_channel::Receiver<WebGLMsg>,
/// The receiver that should be used to send WebGL messages for processing.
sender: WebGLSender<WebGLMsg>,
/// The swap chains used by webrender
webrender_swap_chains: SwapChains<WebGLContextId, Device>,
/// The swap chains used by webxr
webxr_swap_chains: SwapChains<WebXRSwapChainId, Device>,
/// The set of all surface providers corresponding to WebXR sessions.
webxr_surface_providers: SurfaceProviders,
/// A channel to allow arbitrary threads to execute tasks that run in the WebGL thread.
runnable_receiver: crossbeam_channel::Receiver<WebGlRunnable>,
/// Whether this context is a GL or GLES context.
api_type: gl::GlType,
}
pub type WebGlExecutor = crossbeam_channel::Sender<WebGlRunnable>;
pub type WebGlRunnable = Box<dyn FnOnce(&Device) + Send>;
pub type SurfaceProviders = Arc<Mutex<HashMap<SessionId, SurfaceProvider>>>;
pub type SurfaceProvider = Box<dyn surfman_chains::SurfaceProvider<Device> + Send>;
/// The data required to initialize an instance of the WebGLThread type.
pub(crate) struct WebGLThreadInit {
pub webxr_surface_providers: SurfaceProviders,
pub webrender_api_sender: webrender_api::RenderApiSender,
pub external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
pub sender: WebGLSender<WebGLMsg>,
pub receiver: WebGLReceiver<WebGLMsg>,
pub webrender_swap_chains: SwapChains<WebGLContextId, Device>,
pub webxr_swap_chains: SwapChains<WebXRSwapChainId, Device>,
pub connection: Connection,
pub adapter: Adapter,
pub api_type: gl::GlType,
pub runnable_receiver: crossbeam_channel::Receiver<WebGlRunnable>,
}
// 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,
external_images,
sender,
receiver,
webrender_swap_chains,
webxr_swap_chains,
webxr_surface_providers,
connection,
adapter,
api_type,
runnable_receiver,
}: WebGLThreadInit,
) -> Self {
WebGLThread {
device: connection
.create_device(&adapter)
.expect("Couldn't open WebGL device!"),
webrender_api: webrender_api_sender.create_api(),
contexts: Default::default(),
cached_context_info: Default::default(),
bound_context_id: None,
dom_outputs: Default::default(),
external_images,
sender,
receiver: receiver.into_inner(),
webrender_swap_chains,
webxr_swap_chains,
webxr_surface_providers,
runnable_receiver,
api_type,
}
}
/// 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 thread".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());
loop {
crossbeam_channel::select! {
recv(self.receiver) -> msg => {
match msg {
Ok(msg) => {
let exit = self.handle_msg(msg, &webgl_chan);
if exit {
return;
}
}
_ => break,
}
}
recv(self.runnable_receiver) -> msg => {
if let Ok(msg) = msg {
msg(&self.device);
} else {
self.runnable_receiver = crossbeam_channel::never();
}
}
}
}
}
/// Handles a generic WebGLMsg message
fn handle_msg(&mut self, msg: WebGLMsg, webgl_chan: &WebGLChan) -> bool {
trace!("processing {:?}", msg);
match msg {
WebGLMsg::CreateContext(version, size, attributes, result_sender) => {
let result = self.create_webgl_context(version, size, attributes);
result_sender
.send(result.map(|(id, limits)| {
let image_key = self
.cached_context_info
.get_mut(&id)
.expect("Where's the cached context info?")
.image_key;
let data = Self::make_current_if_needed(
&self.device,
id,
&self.contexts,
&mut self.bound_context_id,
)
.expect("WebGLContext not found");
let glsl_version = Self::get_glsl_version(&*data.gl);
let api_type = match data.gl.get_type() {
gl::GlType::Gl => GlType::Gl,
gl::GlType::Gles => GlType::Gles,
};
// FIXME(nox): Should probably be done by surfman.
if api_type != GlType::Gles {
// Points sprites are enabled by default in OpenGL 3.2 core
// and in GLES. Rather than doing version detection, it does
// not hurt to enable them anyways.
data.gl.enable(gl::POINT_SPRITE);
let err = data.gl.get_error();
if err != 0 {
warn!("Error enabling GL point sprites: {}", err);
}
data.gl.enable(gl::PROGRAM_POINT_SIZE);
let err = data.gl.get_error();
if err != 0 {
warn!("Error enabling GL program point size: {}", err);
}
}
WebGLCreateContextResult {
sender: WebGLMsgSender::new(id, webgl_chan.clone()),
limits,
glsl_version,
api_type,
image_key,
}
}))
.unwrap();
},
WebGLMsg::ResizeContext(ctx_id, size, sender) => {
let _ = sender.send(self.resize_webgl_context(ctx_id, size));
},
WebGLMsg::RemoveContext(ctx_id) => {
self.remove_webgl_context(ctx_id);
},
WebGLMsg::WebGLCommand(ctx_id, command, backtrace) => {
self.handle_webgl_command(ctx_id, command, backtrace);
},
WebGLMsg::CreateWebXRSwapChain(ctx_id, size, sender, id) => {
let _ = sender.send(self.create_webxr_swap_chain(ctx_id, size, id));
},
WebGLMsg::SwapBuffers(swap_ids, sender, sent_time) => {
self.handle_swap_buffers(swap_ids, sender, sent_time);
},
WebGLMsg::DOMToTextureCommand(command) => {
self.handle_dom_to_texture(command);
},
WebGLMsg::Exit => {
return true;
},
}
false
}
/// Handles a 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 {
match command {
// We have to handle framebuffer binding differently, because `apply`
// assumes that the currently attached surface is the right one for binding
// the framebuffer, and since it doesn't get passed the swap buffers
// it casn't do that itself. At some point we could refactor apply so
// it takes a self parameter, at which point that won't be necessary.
WebGLCommand::BindFramebuffer(_, request) => {
WebGLImpl::attach_surface(
context_id,
&self.webrender_swap_chains,
&self.webxr_swap_chains,
request,
&mut data.ctx,
&mut self.device,
);
},
// Similarly, dropping a WebGL framebuffer needs access to the swap chains,
// in order to delete the entry.
WebGLCommand::DeleteFramebuffer(WebGLFramebufferId::Opaque(
WebGLOpaqueFramebufferId::WebXR(id),
)) => {
let _ = self
.webxr_swap_chains
.destroy(id, &mut self.device, &mut data.ctx);
},
_ => {},
}
WebGLImpl::apply(
&self.device,
&data.ctx,
&*data.gl,
&mut data.state,
&data.attributes,
command,
backtrace,
);
}
}
/// Creates a new WebGLContext
#[allow(unsafe_code)]
fn create_webgl_context(
&mut self,
webgl_version: WebGLVersion,
requested_size: Size2D<u32>,
attributes: GLContextAttributes,
) -> Result<(WebGLContextId, webgl::GLLimits), String> {
debug!(
"WebGLThread::create_webgl_context({:?}, {:?}, {:?})",
webgl_version, requested_size, attributes
);
// Creating a new GLContext may make the current bound context_id dirty.
// Clear it to ensure that make_current() is called in subsequent commands.
self.bound_context_id = None;
let requested_flags =
attributes.to_surfman_context_attribute_flags(webgl_version, self.api_type);
// Some GL implementations seem to only allow famebuffers
// to have alpha, depth and stencil if their creating context does.
// WebGL requires all contexts to be able to create framebuffers with
// alpha, depth and stencil. So we always create a context with them,
// and fake not having them if requested.
let flags = requested_flags |
ContextAttributeFlags::ALPHA |
ContextAttributeFlags::DEPTH |
ContextAttributeFlags::STENCIL;
let context_attributes = &ContextAttributes {
version: webgl_version.to_surfman_version(self.api_type),
flags: flags,
};
let context_descriptor = self
.device
.create_context_descriptor(&context_attributes)
.map_err(|err| format!("Failed to create context descriptor: {:?}", err))?;
let safe_size = Size2D::new(
requested_size.width.min(SAFE_VIEWPORT_DIMS[0]).max(1),
requested_size.height.min(SAFE_VIEWPORT_DIMS[1]).max(1),
);
let surface_type = SurfaceType::Generic {
size: safe_size.to_i32(),
};
let surface_access = self.surface_access();
let surface_provider = Box::new(SurfmanProvider::new(surface_access));
let mut ctx = self
.device
.create_context(&context_descriptor)
.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_provider)
.map_err(|err| format!("Failed to create swap chain: {:?}", err))?;
let swap_chain = self
.webrender_swap_chains
.get(id)
.expect("Failed to get the swap chain");
debug!(
"Created webgl context {:?}/{:?}",
id,
self.device.context_id(&ctx),
);
let gl = match self.api_type {
gl::GlType::Gl => Gl::gl_fns(gl::ffi_gl::Gl::load_with(|symbol_name| {
self.device.get_proc_address(&ctx, symbol_name)
})),
gl::GlType::Gles => Gl::gles_fns(gl::ffi_gles::Gles2::load_with(|symbol_name| {
self.device.get_proc_address(&ctx, symbol_name)
})),
};
let limits = GLLimits::detect(&*gl, webgl_version);
let size = clamp_viewport(&gl, requested_size);
if safe_size != size {
debug!("Resizing swap chain from {} to {}", safe_size, size);
swap_chain
.resize(&mut self.device, &mut ctx, size.to_i32())
.map_err(|err| format!("Failed to resize swap chain: {:?}", err))?;
}
let descriptor = self.device.context_descriptor(&ctx);
let descriptor_attributes = self.device.context_descriptor_attributes(&descriptor);
let gl_version = descriptor_attributes.version;
let has_alpha = requested_flags.contains(ContextAttributeFlags::ALPHA);
let texture_target = current_wr_texture_target(&self.device);
self.device.make_context_current(&ctx).unwrap();
let framebuffer = self
.device
.context_surface_info(&ctx)
.map_err(|err| format!("Failed to get context surface info: {:?}", err))?
.ok_or_else(|| format!("Failed to get context surface info"))?
.framebuffer_object;
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer);
gl.viewport(0, 0, size.width as i32, size.height as i32);
gl.scissor(0, 0, size.width as i32, size.height as i32);
gl.clear_color(0., 0., 0., !has_alpha as u32 as f32);
gl.clear_depth(1.);
gl.clear_stencil(0);
gl.clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
gl.clear_color(0., 0., 0., 0.);
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
let use_apple_vertex_array = WebGLImpl::needs_apple_vertex_arrays(gl_version);
let default_vao = if let Some(vao) =
WebGLImpl::create_vertex_array(&gl, use_apple_vertex_array, webgl_version)
{
let vao = vao.get();
WebGLImpl::bind_vertex_array(&gl, vao, use_apple_vertex_array, webgl_version);
vao
} else {
0
};
let state = GLState {
gl_version,
webgl_version,
requested_flags,
default_vao,
..Default::default()
};
debug!("Created state {:?}", state);
state.restore_invariant(&*gl);
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
self.contexts.insert(
id,
GLContextData {
ctx,
gl,
state,
attributes,
},
);
let image_key = Self::create_wr_external_image(
&self.webrender_api,
size.to_i32(),
has_alpha,
id,
texture_target,
);
self.cached_context_info
.insert(id, WebGLContextInfo { image_key });
Ok((id, limits))
}
/// Resizes a WebGLContext
fn resize_webgl_context(
&mut self,
context_id: WebGLContextId,
requested_size: Size2D<u32>,
) -> Result<(), String> {
let data = Self::make_current_if_needed_mut(
&self.device,
context_id,
&mut self.contexts,
&mut self.bound_context_id,
)
.expect("Missing WebGL context!");
let size = clamp_viewport(&data.gl, requested_size);
// Check to see if any of the current framebuffer bindings are the surface we're about to
// throw out. If so, we'll have to reset them after destroying the surface.
let framebuffer_rebinding_info =
FramebufferRebindingInfo::detect(&self.device, &data.ctx, &*data.gl);
// Resize the swap chains
if let Some(swap_chain) = self.webrender_swap_chains.get(context_id) {
let alpha = data
.state
.requested_flags
.contains(ContextAttributeFlags::ALPHA);
let clear_color = [0.0, 0.0, 0.0, !alpha as i32 as f32];
swap_chain
.resize(&mut self.device, &mut data.ctx, size.to_i32())
.map_err(|err| format!("Failed to resize swap chain: {:?}", err))?;
swap_chain
.clear_surface(&mut self.device, &mut data.ctx, &*data.gl, clear_color)
.map_err(|err| format!("Failed to clear resized swap chain: {:?}", err))?;
} else {
error!("Failed to find swap chain");
}
// Reset framebuffer bindings as appropriate.
framebuffer_rebinding_info.apply(&self.device, &data.ctx, &*data.gl);
// Update WR image if needed.
let info = self.cached_context_info.get_mut(&context_id).unwrap();
let has_alpha = data
.state
.requested_flags
.contains(ContextAttributeFlags::ALPHA);
let texture_target = current_wr_texture_target(&self.device);
Self::update_wr_external_image(
&self.webrender_api,
size.to_i32(),
has_alpha,
context_id,
info.image_key,
texture_target,
);
debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
Ok(())
}
/// Removes a WebGLContext and releases attached resources.
fn remove_webgl_context(&mut self, context_id: WebGLContextId) {
// Release webrender image keys.
if let Some(info) = self.cached_context_info.remove(&context_id) {
let mut txn = webrender_api::Transaction::new();
txn.delete_image(info.image_key);
self.webrender_api.update_resources(txn.resource_updates)
}
// 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,
);
// 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();
self.webxr_swap_chains
.destroy_all(&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,
swap_ids: Vec<SwapChainId>,
completed_sender: WebGLSender<u64>,
_sent_time: u64,
) {
#[cfg(feature = "xr-profile")]
let start_swap = time::precise_time_ns();
#[cfg(feature = "xr-profile")]
println!(
"WEBXR PROFILING [swap request]:\t{}ms",
to_ms(start_swap - _sent_time)
);
debug!("handle_swap_buffers()");
for swap_id in swap_ids {
let context_id = swap_id.context_id();
let data = Self::make_current_if_needed_mut(
&self.device,
context_id,
&mut self.contexts,
&mut self.bound_context_id,
)
.expect("Where's the GL data?");
// Ensure there are no pending GL errors from other parts of the pipeline.
debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
// Check to see if any of the current framebuffer bindings are the surface we're about
// to swap out. If so, we'll have to reset them after destroying the surface.
let framebuffer_rebinding_info =
FramebufferRebindingInfo::detect(&self.device, &data.ctx, &*data.gl);
debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
debug!("Getting swap chain for {:?}", swap_id);
let swap_chain = match swap_id {
SwapChainId::Context(id) => self.webrender_swap_chains.get(id),
SwapChainId::Framebuffer(_, WebGLOpaqueFramebufferId::WebXR(id)) => {
self.webxr_swap_chains.get(id)
},
}
.expect("Where's the swap chain?");
debug!("Swapping {:?}", swap_id);
swap_chain
.swap_buffers(&mut self.device, &mut data.ctx)
.unwrap();
debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
// TODO: if preserveDrawingBuffer is true, then blit the front buffer to the back buffer
// https://github.com/servo/servo/issues/24604
debug!("Clearing {:?}", swap_id);
let alpha = data
.state
.requested_flags
.contains(ContextAttributeFlags::ALPHA);
let clear_color = [0.0, 0.0, 0.0, !alpha as i32 as f32];
swap_chain
.clear_surface(&mut self.device, &mut data.ctx, &*data.gl, clear_color)
.unwrap();
debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
// Rebind framebuffers as appropriate.
debug!("Rebinding {:?}", swap_id);
framebuffer_rebinding_info.apply(&self.device, &data.ctx, &*data.gl);
debug_assert_eq!(data.gl.get_error(), gl::NO_ERROR);
let SurfaceInfo {
framebuffer_object,
id,
..
} = self
.device
.context_surface_info(&data.ctx)
.unwrap()
.unwrap();
debug!(
"... rebound framebuffer {}, new back buffer surface is {:?}",
framebuffer_object, id
);
}
#[allow(unused)]
let mut end_swap = 0;
#[cfg(feature = "xr-profile")]
{
end_swap = time::precise_time_ns();
println!(
"WEBXR PROFILING [swap buffer]:\t{}ms",
to_ms(end_swap - start_swap)
);
}
completed_sender.send(end_swap).unwrap();
}
/// Creates a new WebXR swap chain
#[allow(unsafe_code)]
fn create_webxr_swap_chain(
&mut self,
context_id: WebGLContextId,
size: Size2D<i32>,
session_id: SessionId,
) -> Option<WebXRSwapChainId> {
debug!("WebGLThread::create_webxr_swap_chain()");
let id = WebXRSwapChainId::new();
let surface_access = self.surface_access();
let data = Self::make_current_if_needed_mut(
&self.device,
context_id,
&mut self.contexts,
&mut self.bound_context_id,
)?;
let surface_provider = self
.webxr_surface_providers
.lock()
.unwrap()
.remove(&session_id)
.unwrap_or_else(|| Box::new(SurfmanProvider::new(surface_access)));
self.webxr_swap_chains
.create_detached_swap_chain(id, size, &mut self.device, &mut data.ctx, surface_provider)
.ok()?;
debug!("Created swap chain {:?}", id);
Some(id)
}
/// Which access mode to use
fn surface_access(&self) -> SurfaceAccess {
SurfaceAccess::GPUOnly
}
fn handle_dom_to_texture(&mut self, command: DOMToTextureCommand) {
match command {
DOMToTextureCommand::Attach(context_id, texture_id, document_id, pipeline_id, size) => {
let data = Self::make_current_if_needed(
&self.device,
context_id,
&self.contexts,
&mut self.bound_context_id,
)
.expect("WebGLContext not found in a WebGL DOMToTextureCommand::Attach command");
// Initialize the texture that WR will use for frame outputs.
data.gl.tex_image_2d(
gl::TEXTURE_2D,
0,
gl::RGBA as gl::GLint,
size.width,
size.height,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
None,
);
self.dom_outputs.insert(
pipeline_id,
DOMToTextureData {
context_id,
texture_id,
document_id,
size,
},
);
let mut txn = webrender_api::Transaction::new();
txn.enable_frame_output(pipeline_id, true);
self.webrender_api.send_transaction(document_id, txn);
},
DOMToTextureCommand::Lock(pipeline_id, gl_sync, sender) => {
let result = self.handle_dom_to_texture_lock(pipeline_id, gl_sync);
// Send the texture id and size to WR.
sender.send(result).unwrap();
},
DOMToTextureCommand::Detach(texture_id) => {
if let Some((pipeline_id, document_id)) = self
.dom_outputs
.iter()
.find(|&(_, v)| v.texture_id == texture_id)
.map(|(k, v)| (*k, v.document_id))
{
let mut txn = webrender_api::Transaction::new();
txn.enable_frame_output(pipeline_id, false);
self.webrender_api.send_transaction(document_id, txn);
self.dom_outputs.remove(&pipeline_id);
}
},
}
}
pub(crate) fn handle_dom_to_texture_lock(
&mut self,
pipeline_id: webrender_api::PipelineId,
gl_sync: usize,
) -> Option<(u32, Size2D<i32>)> {
let device = &self.device;
let contexts = &self.contexts;
let bound_context_id = &mut self.bound_context_id;
self.dom_outputs.get(&pipeline_id).and_then(|dom_data| {
let data = Self::make_current_if_needed(
device,
dom_data.context_id,
contexts,
bound_context_id,
);
data.and_then(|data| {
// The next glWaitSync call is used to synchronize the two flows of
// OpenGL commands (WR and WebGL) in order to avoid using semi-ready WR textures.
// glWaitSync doesn't block WebGL CPU thread.
data.gl
.wait_sync(gl_sync as gl::GLsync, 0, gl::TIMEOUT_IGNORED);
Some((dom_data.texture_id.get(), dom_data.size))
})
})
}
/// Gets a reference to a Context for a given WebGLContextId and makes it current if required.
fn make_current_if_needed<'a>(
device: &Device,
context_id: WebGLContextId,
contexts: &'a FnvHashMap<WebGLContextId, GLContextData>,
bound_id: &mut Option<WebGLContextId>,
) -> Option<&'a GLContextData> {
let data = contexts.get(&context_id);
if let Some(data) = data {
if Some(context_id) != *bound_id {
device.make_context_current(&data.ctx).unwrap();
*bound_id = Some(context_id);
}
}
data
}
/// Gets a mutable reference to a GLContextWrapper for a WebGLContextId and makes it current if required.
fn make_current_if_needed_mut<'a>(
device: &Device,
context_id: WebGLContextId,
contexts: &'a mut FnvHashMap<WebGLContextId, GLContextData>,
bound_id: &mut Option<WebGLContextId>,
) -> Option<&'a mut GLContextData> {
let data = contexts.get_mut(&context_id);
if let Some(ref data) = data {
if Some(context_id) != *bound_id {
device.make_context_current(&data.ctx).unwrap();
*bound_id = Some(context_id);
}
}
data
}
/// Creates a `webrender_api::ImageKey` that uses shared textures.
fn create_wr_external_image(
webrender_api: &webrender_api::RenderApi,
size: Size2D<i32>,
alpha: bool,
context_id: WebGLContextId,
target: webrender_api::TextureTarget,
) -> webrender_api::ImageKey {
let descriptor = Self::image_descriptor(size, alpha);
let data = Self::external_image_data(context_id, target);
let image_key = webrender_api.generate_image_key();
let mut txn = webrender_api::Transaction::new();
txn.add_image(image_key, descriptor, data, None);
webrender_api.update_resources(txn.resource_updates);
image_key
}
/// Updates a `webrender_api::ImageKey` that uses shared textures.
fn update_wr_external_image(
webrender_api: &webrender_api::RenderApi,
size: Size2D<i32>,
alpha: bool,
context_id: WebGLContextId,
image_key: webrender_api::ImageKey,
target: webrender_api::TextureTarget,
) {
let descriptor = Self::image_descriptor(size, alpha);
let data = Self::external_image_data(context_id, target);
let mut txn = webrender_api::Transaction::new();
txn.update_image(image_key, descriptor, data, &webrender_api::DirtyRect::All);
webrender_api.update_resources(txn.resource_updates);
}
/// Helper function to create a `webrender_api::ImageDescriptor`.
fn image_descriptor(size: Size2D<i32>, alpha: bool) -> webrender_api::ImageDescriptor {
let mut flags = webrender_api::ImageDescriptorFlags::empty();
flags.set(webrender_api::ImageDescriptorFlags::IS_OPAQUE, !alpha);
webrender_api::ImageDescriptor {
size: webrender_api::units::DeviceIntSize::new(size.width, size.height),
stride: None,
format: webrender_api::ImageFormat::BGRA8,
offset: 0,
flags,
}
}
/// Helper function to create a `webrender_api::ImageData::External` instance.
fn external_image_data(
context_id: WebGLContextId,
target: webrender_api::TextureTarget,
) -> webrender_api::ImageData {
let data = webrender_api::ExternalImageData {
id: webrender_api::ExternalImageId(context_id.0 as u64),
channel_index: 0,
image_type: webrender_api::ExternalImageType::TextureHandle(target),
};
webrender_api::ImageData::External(data)
}
/// Gets the GLSL Version supported by a GLContext.
fn get_glsl_version(gl: &Gl) -> WebGLSLVersion {
let version = gl.get_string(gl::SHADING_LANGUAGE_VERSION);
// Fomat used by SHADING_LANGUAGE_VERSION query : major.minor[.release] [vendor info]
let mut values = version.split(&['.', ' '][..]);
let major = values
.next()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(1);
let minor = values
.next()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(20);
WebGLSLVersion { major, minor }
}
}
impl Drop for WebGLThread {
fn drop(&mut self) {
// Call remove_context functions in order to correctly delete WebRender image keys.
let context_ids: Vec<WebGLContextId> = self.contexts.keys().map(|id| *id).collect();
for id in context_ids {
self.remove_webgl_context(id);
}
}
}
/// Helper struct to store cached WebGLContext information.
struct WebGLContextInfo {
/// Currently used WebRender image key.
image_key: webrender_api::ImageKey,
}
// TODO(pcwalton): Add `GL_TEXTURE_EXTERNAL_OES`?
fn current_wr_texture_target(device: &Device) -> webrender_api::TextureTarget {
match device.surface_gl_texture_target() {
gl::TEXTURE_RECTANGLE => webrender_api::TextureTarget::Rect,
_ => webrender_api::TextureTarget::Default,
}
}
/// Data about the linked DOM<->WebGLTexture elements.
struct DOMToTextureData {
context_id: WebGLContextId,
texture_id: WebGLTextureId,
document_id: webrender_api::DocumentId,
size: Size2D<i32>,
}
/// WebGL Commands Implementation
pub struct WebGLImpl;
impl WebGLImpl {
#[allow(unsafe_code)]
pub fn apply(
device: &Device,
ctx: &Context,
gl: &Gl,
state: &mut GLState,
attributes: &GLContextAttributes,
command: WebGLCommand,
_backtrace: WebGLCommandBacktrace,
) {
debug!("WebGLImpl::apply({:?})", command);
// Ensure there are no pending GL errors from other parts of the pipeline.
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
match command {
WebGLCommand::GetContextAttributes(ref sender) => sender.send(*attributes).unwrap(),
WebGLCommand::ActiveTexture(target) => gl.active_texture(target),
WebGLCommand::AttachShader(program_id, shader_id) => {
gl.attach_shader(program_id.get(), shader_id.get())
},
WebGLCommand::DetachShader(program_id, shader_id) => {
gl.detach_shader(program_id.get(), shader_id.get())
},
WebGLCommand::BindAttribLocation(program_id, index, ref name) => {
gl.bind_attrib_location(program_id.get(), index, &to_name_in_compiled_shader(name))
},
WebGLCommand::BlendColor(r, g, b, a) => gl.blend_color(r, g, b, a),
WebGLCommand::BlendEquation(mode) => gl.blend_equation(mode),
WebGLCommand::BlendEquationSeparate(mode_rgb, mode_alpha) => {
gl.blend_equation_separate(mode_rgb, mode_alpha)
},
WebGLCommand::BlendFunc(src, dest) => gl.blend_func(src, dest),
WebGLCommand::BlendFuncSeparate(src_rgb, dest_rgb, src_alpha, dest_alpha) => {
gl.blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha)
},
WebGLCommand::BufferData(buffer_type, ref receiver, usage) => {
gl::buffer_data(gl, buffer_type, &receiver.recv().unwrap(), usage)
},
WebGLCommand::BufferSubData(buffer_type, offset, ref receiver) => {
gl::buffer_sub_data(gl, buffer_type, offset, &receiver.recv().unwrap())
},
WebGLCommand::CopyBufferSubData(src, dst, src_offset, dst_offset, size) => {
gl.copy_buffer_sub_data(
src,
dst,
src_offset as isize,
dst_offset as isize,
size as isize,
);
},
WebGLCommand::GetBufferSubData(buffer_type, offset, length, ref sender) => {
let ptr = gl.map_buffer_range(
buffer_type,
offset as isize,
length as isize,
gl::MAP_READ_BIT,
);
let data: &[u8] = unsafe { slice::from_raw_parts(ptr as _, length) };
sender.send(data).unwrap();
gl.unmap_buffer(buffer_type);
},
WebGLCommand::Clear(mask) => {
gl.clear(mask);
},
WebGLCommand::ClearColor(r, g, b, a) => {
state.clear_color = (r, g, b, a);
gl.clear_color(r, g, b, a);
},
WebGLCommand::ClearDepth(depth) => {
let value = depth.max(0.).min(1.) as f64;
state.depth_clear_value = value;
gl.clear_depth(value)
},
WebGLCommand::ClearStencil(stencil) => {
state.stencil_clear_value = stencil;
gl.clear_stencil(stencil);
},
WebGLCommand::ColorMask(r, g, b, a) => {
state.color_write_mask = [r, g, b, a];
state.restore_alpha_invariant(gl);
},
WebGLCommand::CopyTexImage2D(
target,
level,
internal_format,
x,
y,
width,
height,
border,
) => gl.copy_tex_image_2d(target, level, internal_format, x, y, width, height, border),
WebGLCommand::CopyTexSubImage2D(
target,
level,
xoffset,
yoffset,
x,
y,
width,
height,
) => gl.copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height),
WebGLCommand::CullFace(mode) => gl.cull_face(mode),
WebGLCommand::DepthFunc(func) => gl.depth_func(func),
WebGLCommand::DepthMask(flag) => {
state.depth_write_mask = flag;
state.restore_depth_invariant(gl);
},
WebGLCommand::DepthRange(near, far) => {
gl.depth_range(near.max(0.).min(1.) as f64, far.max(0.).min(1.) as f64)
},
WebGLCommand::Disable(cap) => match cap {
gl::SCISSOR_TEST => {
state.scissor_test_enabled = false;
state.restore_scissor_invariant(gl);
},
gl::DEPTH_TEST => {
state.depth_test_enabled = false;
state.restore_depth_invariant(gl);
},
gl::STENCIL_TEST => {
state.stencil_test_enabled = false;
state.restore_stencil_invariant(gl);
},
_ => gl.disable(cap),
},
WebGLCommand::Enable(cap) => match cap {
gl::SCISSOR_TEST => {
state.scissor_test_enabled = true;
state.restore_scissor_invariant(gl);
},
gl::DEPTH_TEST => {
state.depth_test_enabled = true;
state.restore_depth_invariant(gl);
},
gl::STENCIL_TEST => {
state.stencil_test_enabled = true;
state.restore_stencil_invariant(gl);
},
_ => gl.enable(cap),
},
WebGLCommand::FramebufferRenderbuffer(target, attachment, renderbuffertarget, rb) => {
let attach = |attachment| {
gl.framebuffer_renderbuffer(
target,
attachment,
renderbuffertarget,
rb.map_or(0, WebGLRenderbufferId::get),
)
};
if attachment == gl::DEPTH_STENCIL_ATTACHMENT {
attach(gl::DEPTH_ATTACHMENT);
attach(gl::STENCIL_ATTACHMENT);
} else {
attach(attachment);
}
},
WebGLCommand::FramebufferTexture2D(target, attachment, textarget, texture, level) => {
let attach = |attachment| {
gl.framebuffer_texture_2d(
target,
attachment,
textarget,
texture.map_or(0, WebGLTextureId::get),
level,
)
};
if attachment == gl::DEPTH_STENCIL_ATTACHMENT {
attach(gl::DEPTH_ATTACHMENT);
attach(gl::STENCIL_ATTACHMENT);
} else {
attach(attachment)
}
},
WebGLCommand::FrontFace(mode) => gl.front_face(mode),
WebGLCommand::DisableVertexAttribArray(attrib_id) => {
gl.disable_vertex_attrib_array(attrib_id)
},
WebGLCommand::EnableVertexAttribArray(attrib_id) => {
gl.enable_vertex_attrib_array(attrib_id)
},
WebGLCommand::Hint(name, val) => gl.hint(name, val),
WebGLCommand::LineWidth(width) => {
gl.line_width(width);
// In OpenGL Core Profile >3.2, any non-1.0 value will generate INVALID_VALUE.
if width != 1.0 {
let _ = gl.get_error();
}
},
WebGLCommand::PixelStorei(name, val) => gl.pixel_store_i(name, val),
WebGLCommand::PolygonOffset(factor, units) => gl.polygon_offset(factor, units),
WebGLCommand::ReadPixels(rect, format, pixel_type, ref sender) => {
let pixels = gl.read_pixels(
rect.origin.x as i32,
rect.origin.y as i32,
rect.size.width as i32,
rect.size.height as i32,
format,
pixel_type,
);
sender.send(&pixels).unwrap();
},
WebGLCommand::ReadPixelsPP(rect, format, pixel_type, offset) => unsafe {
gl.read_pixels_into_pixel_pack_buffer(
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
format,
pixel_type,
offset,
);
},
WebGLCommand::RenderbufferStorage(target, format, width, height) => {
gl.renderbuffer_storage(target, format, width, height)
},
WebGLCommand::RenderbufferStorageMultisample(
target,
samples,
format,
width,
height,
) => gl.renderbuffer_storage_multisample(target, samples, format, width, height),
WebGLCommand::SampleCoverage(value, invert) => gl.sample_coverage(value, invert),
WebGLCommand::Scissor(x, y, width, height) => {
// FIXME(nox): Kinda unfortunate that some u32 values could
// end up as negative numbers here, but I don't even think
// that can happen in the real world.
gl.scissor(x, y, width as i32, height as i32);
},
WebGLCommand::StencilFunc(func, ref_, mask) => gl.stencil_func(func, ref_, mask),
WebGLCommand::StencilFuncSeparate(face, func, ref_, mask) => {
gl.stencil_func_separate(face, func, ref_, mask)
},
WebGLCommand::StencilMask(mask) => {
state.stencil_write_mask = (mask, mask);
state.restore_stencil_invariant(gl);
},
WebGLCommand::StencilMaskSeparate(face, mask) => {
if face == gl::FRONT {
state.stencil_write_mask.0 = mask;
} else {
state.stencil_write_mask.1 = mask;
}
state.restore_stencil_invariant(gl);
},
WebGLCommand::StencilOp(fail, zfail, zpass) => gl.stencil_op(fail, zfail, zpass),
WebGLCommand::StencilOpSeparate(face, fail, zfail, zpass) => {
gl.stencil_op_separate(face, fail, zfail, zpass)
},
WebGLCommand::GetRenderbufferParameter(target, pname, ref chan) => {
Self::get_renderbuffer_parameter(gl, target, pname, chan)
},
WebGLCommand::CreateTransformFeedback(ref sender) => {
let value = gl.gen_transform_feedbacks();
sender.send(value).unwrap()
},
WebGLCommand::DeleteTransformFeedback(id) => {
gl.delete_transform_feedbacks(id);
},
WebGLCommand::IsTransformFeedback(id, ref sender) => {
let value = gl.is_transform_feedback(id);
sender.send(value).unwrap()
},
WebGLCommand::BindTransformFeedback(target, id) => {
gl.bind_transform_feedback(target, id);
},
WebGLCommand::BeginTransformFeedback(mode) => {
gl.begin_transform_feedback(mode);
},
WebGLCommand::EndTransformFeedback() => {
gl.end_transform_feedback();
},
WebGLCommand::PauseTransformFeedback() => {
gl.pause_transform_feedback();
},
WebGLCommand::ResumeTransformFeedback() => {
gl.resume_transform_feedback();
},
WebGLCommand::GetTransformFeedbackVarying(program, index, ref sender) => {
let (size, ty, mut name) = gl.get_transform_feedback_varying(program.get(), index);
// We need to split, because the name starts with '_u' prefix.
name = name.split_off(2);
sender.send((size, ty, name)).unwrap();
},
WebGLCommand::TransformFeedbackVaryings(program, ref varyings, buffer_mode) => {
gl.transform_feedback_varyings(program.get(), varyings.as_slice(), buffer_mode);
},
WebGLCommand::GetFramebufferAttachmentParameter(
target,
attachment,
pname,
ref chan,
) => Self::get_framebuffer_attachment_parameter(gl, target, attachment, pname, chan),
WebGLCommand::GetShaderPrecisionFormat(shader_type, precision_type, ref chan) => {
Self::shader_precision_format(gl, shader_type, precision_type, chan)
},
WebGLCommand::GetExtensions(ref chan) => Self::get_extensions(gl, chan),
WebGLCommand::GetFragDataLocation(program_id, ref name, ref sender) => {
let location =
gl.get_frag_data_location(program_id.get(), &to_name_in_compiled_shader(name));
sender.send(location).unwrap();
},
WebGLCommand::GetUniformLocation(program_id, ref name, ref chan) => {
Self::uniform_location(gl, program_id, &name, chan)
},
WebGLCommand::GetShaderInfoLog(shader_id, ref chan) => {
Self::shader_info_log(gl, shader_id, chan)
},
WebGLCommand::GetProgramInfoLog(program_id, ref chan) => {
Self::program_info_log(gl, program_id, chan)
},
WebGLCommand::CompileShader(shader_id, ref source) => {
Self::compile_shader(gl, shader_id, &source)
},
WebGLCommand::CreateBuffer(ref chan) => Self::create_buffer(gl, chan),
WebGLCommand::CreateFramebuffer(ref chan) => Self::create_framebuffer(gl, chan),
WebGLCommand::CreateRenderbuffer(ref chan) => Self::create_renderbuffer(gl, chan),
WebGLCommand::CreateTexture(ref chan) => Self::create_texture(gl, chan),
WebGLCommand::CreateProgram(ref chan) => Self::create_program(gl, chan),
WebGLCommand::CreateShader(shader_type, ref chan) => {
Self::create_shader(gl, shader_type, chan)
},
WebGLCommand::DeleteBuffer(id) => gl.delete_buffers(&[id.get()]),
WebGLCommand::DeleteFramebuffer(WebGLFramebufferId::Transparent(id)) => {
gl.delete_framebuffers(&[id.get()])
},
WebGLCommand::DeleteFramebuffer(WebGLFramebufferId::Opaque(_)) => {},
WebGLCommand::DeleteRenderbuffer(id) => gl.delete_renderbuffers(&[id.get()]),
WebGLCommand::DeleteTexture(id) => gl.delete_textures(&[id.get()]),
WebGLCommand::DeleteProgram(id) => gl.delete_program(id.get()),
WebGLCommand::DeleteShader(id) => gl.delete_shader(id.get()),
WebGLCommand::BindBuffer(target, id) => {
gl.bind_buffer(target, id.map_or(0, WebGLBufferId::get))
},
WebGLCommand::BindFramebuffer(target, request) => {
Self::bind_framebuffer(gl, target, request, ctx, device, state)
},
WebGLCommand::BindRenderbuffer(target, id) => {
gl.bind_renderbuffer(target, id.map_or(0, WebGLRenderbufferId::get))
},
WebGLCommand::BindTexture(target, id) => {
gl.bind_texture(target, id.map_or(0, WebGLTextureId::get))
},
WebGLCommand::Uniform1f(uniform_id, v) => gl.uniform_1f(uniform_id, v),
WebGLCommand::Uniform1fv(uniform_id, ref v) => gl.uniform_1fv(uniform_id, v),
WebGLCommand::Uniform1i(uniform_id, v) => gl.uniform_1i(uniform_id, v),
WebGLCommand::Uniform1iv(uniform_id, ref v) => gl.uniform_1iv(uniform_id, v),
WebGLCommand::Uniform1ui(uniform_id, v) => gl.uniform_1ui(uniform_id, v),
WebGLCommand::Uniform1uiv(uniform_id, ref v) => gl.uniform_1uiv(uniform_id, v),
WebGLCommand::Uniform2f(uniform_id, x, y) => gl.uniform_2f(uniform_id, x, y),
WebGLCommand::Uniform2fv(uniform_id, ref v) => gl.uniform_2fv(uniform_id, v),
WebGLCommand::Uniform2i(uniform_id, x, y) => gl.uniform_2i(uniform_id, x, y),
WebGLCommand::Uniform2iv(uniform_id, ref v) => gl.uniform_2iv(uniform_id, v),
WebGLCommand::Uniform2ui(uniform_id, x, y) => gl.uniform_2ui(uniform_id, x, y),
WebGLCommand::Uniform2uiv(uniform_id, ref v) => gl.uniform_2uiv(uniform_id, v),
WebGLCommand::Uniform3f(uniform_id, x, y, z) => gl.uniform_3f(uniform_id, x, y, z),
WebGLCommand::Uniform3fv(uniform_id, ref v) => gl.uniform_3fv(uniform_id, v),
WebGLCommand::Uniform3i(uniform_id, x, y, z) => gl.uniform_3i(uniform_id, x, y, z),
WebGLCommand::Uniform3iv(uniform_id, ref v) => gl.uniform_3iv(uniform_id, v),
WebGLCommand::Uniform3ui(uniform_id, x, y, z) => gl.uniform_3ui(uniform_id, x, y, z),
WebGLCommand::Uniform3uiv(uniform_id, ref v) => gl.uniform_3uiv(uniform_id, v),
WebGLCommand::Uniform4f(uniform_id, x, y, z, w) => {
gl.uniform_4f(uniform_id, x, y, z, w)
},
WebGLCommand::Uniform4fv(uniform_id, ref v) => gl.uniform_4fv(uniform_id, v),
WebGLCommand::Uniform4i(uniform_id, x, y, z, w) => {
gl.uniform_4i(uniform_id, x, y, z, w)
},
WebGLCommand::Uniform4iv(uniform_id, ref v) => gl.uniform_4iv(uniform_id, v),
WebGLCommand::Uniform4ui(uniform_id, x, y, z, w) => {
gl.uniform_4ui(uniform_id, x, y, z, w)
},
WebGLCommand::Uniform4uiv(uniform_id, ref v) => gl.uniform_4uiv(uniform_id, v),
WebGLCommand::UniformMatrix2fv(uniform_id, ref v) => {
gl.uniform_matrix_2fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix3fv(uniform_id, ref v) => {
gl.uniform_matrix_3fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix4fv(uniform_id, ref v) => {
gl.uniform_matrix_4fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix3x2fv(uniform_id, ref v) => {
gl.uniform_matrix_3x2fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix4x2fv(uniform_id, ref v) => {
gl.uniform_matrix_4x2fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix2x3fv(uniform_id, ref v) => {
gl.uniform_matrix_2x3fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix4x3fv(uniform_id, ref v) => {
gl.uniform_matrix_4x3fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix2x4fv(uniform_id, ref v) => {
gl.uniform_matrix_2x4fv(uniform_id, false, v)
},
WebGLCommand::UniformMatrix3x4fv(uniform_id, ref v) => {
gl.uniform_matrix_3x4fv(uniform_id, false, v)
},
WebGLCommand::ValidateProgram(program_id) => gl.validate_program(program_id.get()),
WebGLCommand::VertexAttrib(attrib_id, x, y, z, w) => {
gl.vertex_attrib_4f(attrib_id, x, y, z, w)
},
WebGLCommand::VertexAttribI(attrib_id, x, y, z, w) => {
gl.vertex_attrib_4i(attrib_id, x, y, z, w)
},
WebGLCommand::VertexAttribU(attrib_id, x, y, z, w) => {
gl.vertex_attrib_4ui(attrib_id, x, y, z, w)
},
WebGLCommand::VertexAttribPointer2f(attrib_id, size, normalized, stride, offset) => {
gl.vertex_attrib_pointer_f32(attrib_id, size, normalized, stride, offset)
},
WebGLCommand::VertexAttribPointer(
attrib_id,
size,
data_type,
normalized,
stride,
offset,
) => gl.vertex_attrib_pointer(attrib_id, size, data_type, normalized, stride, offset),
WebGLCommand::SetViewport(x, y, width, height) => gl.viewport(x, y, width, height),
WebGLCommand::TexImage2D {
target,
level,
internal_format,
size,
format,
data_type,
effective_data_type,
unpacking_alignment,
alpha_treatment,
y_axis_treatment,
pixel_format,
ref data,
} => {
let pixels = prepare_pixels(
internal_format,
data_type,
size,
unpacking_alignment,
alpha_treatment,
y_axis_treatment,
pixel_format,
Cow::Borrowed(&*data),
);
gl.pixel_store_i(gl::UNPACK_ALIGNMENT, unpacking_alignment as i32);
gl.tex_image_2d(
target,
level as i32,
internal_format.as_gl_constant() as i32,
size.width as i32,
size.height as i32,
0,
format.as_gl_constant(),
effective_data_type,
Some(&pixels),
);
},
WebGLCommand::TexSubImage2D {
target,
level,
xoffset,
yoffset,
size,
format,
data_type,
effective_data_type,
unpacking_alignment,
alpha_treatment,
y_axis_treatment,
pixel_format,
ref data,
} => {
let pixels = prepare_pixels(
format,
data_type,
size,
unpacking_alignment,
alpha_treatment,
y_axis_treatment,
pixel_format,
Cow::Borrowed(&*data),
);
gl.pixel_store_i(gl::UNPACK_ALIGNMENT, unpacking_alignment as i32);
gl.tex_sub_image_2d(
target,
level as i32,
xoffset,
yoffset,
size.width as i32,
size.height as i32,
format.as_gl_constant(),
effective_data_type,
&pixels,
);
},
WebGLCommand::CompressedTexImage2D {
target,
level,
internal_format,
size,
ref data,
} => {
gl.compressed_tex_image_2d(
target,
level as i32,
internal_format,
size.width as i32,
size.height as i32,
0,
&*data,
);
},
WebGLCommand::CompressedTexSubImage2D {
target,
level,
xoffset,
yoffset,
size,
format,
ref data,
} => {
gl.compressed_tex_sub_image_2d(
target,
level as i32,
xoffset as i32,
yoffset as i32,
size.width as i32,
size.height as i32,
format,
&*data,
);
},
WebGLCommand::TexStorage2D(target, levels, internal_format, width, height) => gl
.tex_storage_2d(
target,
levels as i32,
internal_format.as_gl_constant(),
width as i32,
height as i32,
),
WebGLCommand::TexStorage3D(target, levels, internal_format, width, height, depth) => gl
.tex_storage_3d(
target,
levels as i32,
internal_format.as_gl_constant(),
width as i32,
height as i32,
depth as i32,
),
WebGLCommand::DrawingBufferWidth(ref sender) => {
let size = device
.context_surface_info(&ctx)
.unwrap()
.expect("Where's the front buffer?")
.size;
sender.send(size.width).unwrap()
},
WebGLCommand::DrawingBufferHeight(ref sender) => {
let size = device
.context_surface_info(&ctx)
.unwrap()
.expect("Where's the front buffer?")
.size;
sender.send(size.height).unwrap()
},
WebGLCommand::Finish(ref sender) => Self::finish(gl, sender),
WebGLCommand::Flush => gl.flush(),
WebGLCommand::GenerateMipmap(target) => gl.generate_mipmap(target),
WebGLCommand::CreateVertexArray(ref chan) => {
let use_apple_vertex_array = Self::needs_apple_vertex_arrays(state.gl_version);
let id = Self::create_vertex_array(gl, use_apple_vertex_array, state.webgl_version);
let _ = chan.send(id);
},
WebGLCommand::DeleteVertexArray(id) => {
let use_apple_vertex_array = Self::needs_apple_vertex_arrays(state.gl_version);
let id = id.get();
Self::delete_vertex_array(gl, id, use_apple_vertex_array, state.webgl_version);
},
WebGLCommand::BindVertexArray(id) => {
let id = id.map_or(state.default_vao, WebGLVertexArrayId::get);
let use_apple_vertex_array = Self::needs_apple_vertex_arrays(state.gl_version);
Self::bind_vertex_array(gl, id, use_apple_vertex_array, state.webgl_version);
},
WebGLCommand::GetParameterBool(param, ref sender) => {
let value = match param {
webgl::ParameterBool::DepthWritemask => state.depth_write_mask,
_ => unsafe {
let mut value = [0];
gl.get_boolean_v(param as u32, &mut value);
value[0] != 0
},
};
sender.send(value).unwrap()
},
WebGLCommand::FenceSync(ref sender) => {
let value = gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
sender
.send(unsafe { WebGLSyncId::new(value as u64) })
.unwrap();
},
WebGLCommand::IsSync(sync_id, ref sender) => {
let value = gl.is_sync(sync_id.get() as *const _);
sender.send(value).unwrap();
},
WebGLCommand::ClientWaitSync(sync_id, flags, timeout, ref sender) => {
let value = gl.client_wait_sync(sync_id.get() as *const _, flags, timeout as u64);
sender.send(value).unwrap();
},
WebGLCommand::WaitSync(sync_id, flags, timeout) => {
gl.wait_sync(sync_id.get() as *const _, flags, timeout as u64);
},
WebGLCommand::GetSyncParameter(sync_id, param, ref sender) => {
let value = gl.get_sync_iv(sync_id.get() as *const _, param);
sender.send(value[0] as u32).unwrap();
},
WebGLCommand::DeleteSync(sync_id) => {
gl.delete_sync(sync_id.get() as *const _);
},
WebGLCommand::GetParameterBool4(param, ref sender) => {
let value = match param {
webgl::ParameterBool4::ColorWritemask => state.color_write_mask,
};
sender.send(value).unwrap()
},
WebGLCommand::GetParameterInt(param, ref sender) => {
let value = match param {
webgl::ParameterInt::AlphaBits if state.fake_no_alpha() => 0,
webgl::ParameterInt::DepthBits if state.fake_no_depth() => 0,
webgl::ParameterInt::StencilBits if state.fake_no_stencil() => 0,
webgl::ParameterInt::StencilWritemask => state.stencil_write_mask.0 as i32,
webgl::ParameterInt::StencilBackWritemask => state.stencil_write_mask.1 as i32,
_ => unsafe {
let mut value = [0];
gl.get_integer_v(param as u32, &mut value);
value[0]
},
};
sender.send(value).unwrap()
},
WebGLCommand::GetParameterInt2(param, ref sender) => {
let mut value = [0; 2];
unsafe {
gl.get_integer_v(param as u32, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetParameterInt4(param, ref sender) => {
let mut value = [0; 4];
unsafe {
gl.get_integer_v(param as u32, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetParameterFloat(param, ref sender) => {
let mut value = [0.];
unsafe {
gl.get_float_v(param as u32, &mut value);
}
sender.send(value[0]).unwrap()
},
WebGLCommand::GetParameterFloat2(param, ref sender) => {
let mut value = [0.; 2];
unsafe {
gl.get_float_v(param as u32, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetParameterFloat4(param, ref sender) => {
let mut value = [0.; 4];
unsafe {
gl.get_float_v(param as u32, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetProgramValidateStatus(program, ref sender) => {
let mut value = [0];
unsafe {
gl.get_program_iv(program.get(), gl::VALIDATE_STATUS, &mut value);
}
sender.send(value[0] != 0).unwrap()
},
WebGLCommand::GetProgramActiveUniforms(program, ref sender) => {
let mut value = [0];
unsafe {
gl.get_program_iv(program.get(), gl::ACTIVE_UNIFORMS, &mut value);
}
sender.send(value[0]).unwrap()
},
WebGLCommand::GetCurrentVertexAttrib(index, ref sender) => {
let mut value = [0.; 4];
unsafe {
gl.get_vertex_attrib_fv(index, gl::CURRENT_VERTEX_ATTRIB, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetTexParameterFloat(target, param, ref sender) => {
sender
.send(gl.get_tex_parameter_fv(target, param as u32))
.unwrap();
},
WebGLCommand::GetTexParameterInt(target, param, ref sender) => {
sender
.send(gl.get_tex_parameter_iv(target, param as u32))
.unwrap();
},
WebGLCommand::GetInternalFormatIntVec(target, internal_format, param, ref sender) => {
match param {
InternalFormatIntVec::Samples => {
let mut count = [0; 1];
gl.get_internal_format_iv(
target,
internal_format,
gl::NUM_SAMPLE_COUNTS,
&mut count,
);
assert!(count[0] >= 0);
let mut values = vec![0; count[0] as usize];
gl.get_internal_format_iv(
target,
internal_format,
param as u32,
&mut values,
);
sender.send(values).unwrap()
},
}
},
WebGLCommand::TexParameteri(target, param, value) => {
gl.tex_parameter_i(target, param as u32, value)
},
WebGLCommand::TexParameterf(target, param, value) => {
gl.tex_parameter_f(target, param as u32, value)
},
WebGLCommand::LinkProgram(program_id, ref sender) => {
return sender.send(Self::link_program(gl, program_id)).unwrap();
},
WebGLCommand::UseProgram(program_id) => {
gl.use_program(program_id.map_or(0, |p| p.get()))
},
WebGLCommand::DrawArrays { mode, first, count } => gl.draw_arrays(mode, first, count),
WebGLCommand::DrawArraysInstanced {
mode,
first,
count,
primcount,
} => gl.draw_arrays_instanced(mode, first, count, primcount),
WebGLCommand::DrawElements {
mode,
count,
type_,
offset,
} => gl.draw_elements(mode, count, type_, offset),
WebGLCommand::DrawElementsInstanced {
mode,
count,
type_,
offset,
primcount,
} => gl.draw_elements_instanced(mode, count, type_, offset, primcount),
WebGLCommand::VertexAttribDivisor { index, divisor } => {
gl.vertex_attrib_divisor(index, divisor)
},
WebGLCommand::GetUniformBool(program_id, loc, ref sender) => {
let mut value = [0];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
sender.send(value[0] != 0).unwrap();
},
WebGLCommand::GetUniformBool2(program_id, loc, ref sender) => {
let mut value = [0; 2];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
let value = [value[0] != 0, value[1] != 0];
sender.send(value).unwrap();
},
WebGLCommand::GetUniformBool3(program_id, loc, ref sender) => {
let mut value = [0; 3];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
let value = [value[0] != 0, value[1] != 0, value[2] != 0];
sender.send(value).unwrap();
},
WebGLCommand::GetUniformBool4(program_id, loc, ref sender) => {
let mut value = [0; 4];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
let value = [value[0] != 0, value[1] != 0, value[2] != 0, value[3] != 0];
sender.send(value).unwrap();
},
WebGLCommand::GetUniformInt(program_id, loc, ref sender) => {
let mut value = [0];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
sender.send(value[0]).unwrap();
},
WebGLCommand::GetUniformInt2(program_id, loc, ref sender) => {
let mut value = [0; 2];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformInt3(program_id, loc, ref sender) => {
let mut value = [0; 3];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformInt4(program_id, loc, ref sender) => {
let mut value = [0; 4];
unsafe {
gl.get_uniform_iv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformUint(program_id, loc, ref sender) => {
let mut value = [0];
unsafe {
gl.get_uniform_uiv(program_id.get(), loc, &mut value);
}
sender.send(value[0]).unwrap();
},
WebGLCommand::GetUniformUint2(program_id, loc, ref sender) => {
let mut value = [0; 2];
unsafe {
gl.get_uniform_uiv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformUint3(program_id, loc, ref sender) => {
let mut value = [0; 3];
unsafe {
gl.get_uniform_uiv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformUint4(program_id, loc, ref sender) => {
let mut value = [0; 4];
unsafe {
gl.get_uniform_uiv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformFloat(program_id, loc, ref sender) => {
let mut value = [0.];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value[0]).unwrap();
},
WebGLCommand::GetUniformFloat2(program_id, loc, ref sender) => {
let mut value = [0.; 2];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformFloat3(program_id, loc, ref sender) => {
let mut value = [0.; 3];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformFloat4(program_id, loc, ref sender) => {
let mut value = [0.; 4];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformFloat9(program_id, loc, ref sender) => {
let mut value = [0.; 9];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformFloat16(program_id, loc, ref sender) => {
let mut value = [0.; 16];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap();
},
WebGLCommand::GetUniformFloat2x3(program_id, loc, ref sender) => {
let mut value = [0.; 2 * 3];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetUniformFloat2x4(program_id, loc, ref sender) => {
let mut value = [0.; 2 * 4];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetUniformFloat3x2(program_id, loc, ref sender) => {
let mut value = [0.; 3 * 2];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetUniformFloat3x4(program_id, loc, ref sender) => {
let mut value = [0.; 3 * 4];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetUniformFloat4x2(program_id, loc, ref sender) => {
let mut value = [0.; 4 * 2];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetUniformFloat4x3(program_id, loc, ref sender) => {
let mut value = [0.; 4 * 3];
unsafe {
gl.get_uniform_fv(program_id.get(), loc, &mut value);
}
sender.send(value).unwrap()
},
WebGLCommand::GetUniformBlockIndex(program_id, ref name, ref sender) => {
let name = to_name_in_compiled_shader(name);
let index = gl.get_uniform_block_index(program_id.get(), &name);
sender.send(index).unwrap();
},
WebGLCommand::GetUniformIndices(program_id, ref names, ref sender) => {
let names = names
.iter()
.map(|name| to_name_in_compiled_shader(name))
.collect::<Vec<_>>();
let name_strs = names.iter().map(|name| name.as_str()).collect::<Vec<_>>();
let indices = gl.get_uniform_indices(program_id.get(), &name_strs);
sender.send(indices).unwrap();
},
WebGLCommand::GetActiveUniforms(program_id, ref indices, pname, ref sender) => {
let results = gl.get_active_uniforms_iv(program_id.get(), indices, pname);
sender.send(results).unwrap();
},
WebGLCommand::GetActiveUniformBlockName(program_id, block_idx, ref sender) => {
let name = gl.get_active_uniform_block_name(program_id.get(), block_idx);
sender.send(name).unwrap();
},
WebGLCommand::GetActiveUniformBlockParameter(
program_id,
block_idx,
pname,
ref sender,
) => {
let results = gl.get_active_uniform_block_iv(program_id.get(), block_idx, pname);
sender.send(results).unwrap();
},
WebGLCommand::UniformBlockBinding(program_id, block_idx, block_binding) => {
gl.uniform_block_binding(program_id.get(), block_idx, block_binding)
},
WebGLCommand::InitializeFramebuffer {
color,
depth,
stencil,
} => Self::initialize_framebuffer(gl, state, color, depth, stencil),
WebGLCommand::BeginQuery(target, query_id) => {
gl.begin_query(target, query_id.get());
},
WebGLCommand::EndQuery(target) => {
gl.end_query(target);
},
WebGLCommand::DeleteQuery(query_id) => {
gl.delete_queries(&[query_id.get()]);
},
WebGLCommand::GenerateQuery(ref sender) => {
let id = gl.gen_queries(1)[0];
sender.send(unsafe { WebGLQueryId::new(id) }).unwrap()
},
WebGLCommand::GetQueryState(ref sender, query_id, pname) => {
let value = gl.get_query_object_uiv(query_id.get(), pname);
sender.send(value).unwrap()
},
WebGLCommand::GenerateSampler(ref sender) => {
let id = gl.gen_samplers(1)[0];
sender.send(unsafe { WebGLSamplerId::new(id) }).unwrap()
},
WebGLCommand::DeleteSampler(sampler_id) => {
gl.delete_samplers(&[sampler_id.get()]);
},
WebGLCommand::BindSampler(unit, sampler_id) => {
gl.bind_sampler(unit, sampler_id.get());
},
WebGLCommand::SetSamplerParameterInt(sampler_id, pname, value) => {
gl.sampler_parameter_i(sampler_id.get(), pname, value);
},
WebGLCommand::SetSamplerParameterFloat(sampler_id, pname, value) => {
gl.sampler_parameter_f(sampler_id.get(), pname, value);
},
WebGLCommand::GetSamplerParameterInt(sampler_id, pname, ref sender) => {
let value = gl.get_sampler_parameter_iv(sampler_id.get(), pname)[0];
sender.send(value).unwrap();
},
WebGLCommand::GetSamplerParameterFloat(sampler_id, pname, ref sender) => {
let value = gl.get_sampler_parameter_fv(sampler_id.get(), pname)[0];
sender.send(value).unwrap();
},
WebGLCommand::BindBufferBase(target, index, id) => {
// https://searchfox.org/mozilla-central/rev/13b081a62d3f3e3e3120f95564529257b0bf451c/dom/canvas/WebGLContextBuffers.cpp#208-210
// BindBufferBase/Range will fail (on some drivers) if the buffer name has
// never been bound. (GenBuffers makes a name, but BindBuffer initializes
// that name as a real buffer object)
let id = id.map_or(0, WebGLBufferId::get);
gl.bind_buffer(target, id);
gl.bind_buffer(target, 0);
gl.bind_buffer_base(target, index, id);
},
WebGLCommand::BindBufferRange(target, index, id, offset, size) => {
// https://searchfox.org/mozilla-central/rev/13b081a62d3f3e3e3120f95564529257b0bf451c/dom/canvas/WebGLContextBuffers.cpp#208-210
// BindBufferBase/Range will fail (on some drivers) if the buffer name has
// never been bound. (GenBuffers makes a name, but BindBuffer initializes
// that name as a real buffer object)
let id = id.map_or(0, WebGLBufferId::get);
gl.bind_buffer(target, id);
gl.bind_buffer(target, 0);
gl.bind_buffer_range(target, index, id, offset as isize, size as isize);
},
WebGLCommand::ClearBufferfv(buffer, draw_buffer, ref value) => {
gl.clear_buffer_fv(buffer, draw_buffer, value)
},
WebGLCommand::ClearBufferiv(buffer, draw_buffer, ref value) => {
gl.clear_buffer_iv(buffer, draw_buffer, value)
},
WebGLCommand::ClearBufferuiv(buffer, draw_buffer, ref value) => {
gl.clear_buffer_uiv(buffer, draw_buffer, value)
},
WebGLCommand::ClearBufferfi(buffer, draw_buffer, depth, stencil) => {
gl.clear_buffer_fi(buffer, draw_buffer, depth, stencil)
},
WebGLCommand::InvalidateFramebuffer(target, ref attachments) => {
gl.invalidate_framebuffer(target, attachments)
},
WebGLCommand::InvalidateSubFramebuffer(target, ref attachments, x, y, w, h) => {
gl.invalidate_sub_framebuffer(target, attachments, x, y, w, h)
},
WebGLCommand::FramebufferTextureLayer(target, attachment, tex_id, level, layer) => {
let tex_id = tex_id.map_or(0, WebGLTextureId::get);
let attach = |attachment| {
gl.framebuffer_texture_layer(target, attachment, tex_id, level, layer)
};
if attachment == gl::DEPTH_STENCIL_ATTACHMENT {
attach(gl::DEPTH_ATTACHMENT);
attach(gl::STENCIL_ATTACHMENT);
} else {
attach(attachment)
}
},
WebGLCommand::ReadBuffer(buffer) => gl.read_buffer(buffer),
WebGLCommand::DrawBuffers(ref buffers) => gl.draw_buffers(buffers),
}
// If debug asertions are enabled, then check the error state.
#[cfg(debug_assertions)]
{
let error = gl.get_error();
if error != gl::NO_ERROR {
error!("Last GL operation failed: {:?}", command);
if error == gl::INVALID_FRAMEBUFFER_OPERATION {
let mut framebuffer_bindings = [0];
unsafe {
gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut framebuffer_bindings);
}
debug!(
"(thread {:?}) Current draw framebuffer binding: {}",
::std::thread::current().id(),
framebuffer_bindings[0]
);
}
#[cfg(feature = "webgl_backtrace")]
{
error!("Backtrace from failed WebGL API:\n{}", _backtrace.backtrace);
if let Some(backtrace) = _backtrace.js_backtrace {
error!("JS backtrace from failed WebGL API:\n{}", backtrace);
}
}
panic!(
"Unexpected WebGL error: 0x{:x} ({}) [{:?}]",
error, error, command
);
}
}
}
fn initialize_framebuffer(gl: &Gl, state: &GLState, color: bool, depth: bool, stencil: bool) {
let bits = [
(color, gl::COLOR_BUFFER_BIT),
(depth, gl::DEPTH_BUFFER_BIT),
(stencil, gl::STENCIL_BUFFER_BIT),
]
.iter()
.fold(0, |bits, &(enabled, bit)| {
bits | if enabled { bit } else { 0 }
});
gl.disable(gl::SCISSOR_TEST);
gl.color_mask(true, true, true, true);
gl.clear_color(0., 0., 0., 0.);
gl.depth_mask(true);
gl.clear_depth(1.);
gl.stencil_mask_separate(gl::FRONT, 0xFFFFFFFF);
gl.stencil_mask_separate(gl::BACK, 0xFFFFFFFF);
gl.clear_stencil(0);
gl.clear(bits);
state.restore_invariant(gl);
}
#[allow(unsafe_code)]
fn link_program(gl: &Gl, program: WebGLProgramId) -> ProgramLinkInfo {
gl.link_program(program.get());
let mut linked = [0];
unsafe {
gl.get_program_iv(program.get(), gl::LINK_STATUS, &mut linked);
}
if linked[0] == 0 {
return ProgramLinkInfo {
linked: false,
active_attribs: vec![].into(),
active_uniforms: vec![].into(),
active_uniform_blocks: vec![].into(),
transform_feedback_length: Default::default(),
transform_feedback_mode: Default::default(),
};
}
let mut num_active_attribs = [0];
unsafe {
gl.get_program_iv(
program.get(),
gl::ACTIVE_ATTRIBUTES,
&mut num_active_attribs,
);
}
let active_attribs = (0..num_active_attribs[0] as u32)
.map(|i| {
// FIXME(nox): This allocates strings sometimes for nothing
// and the gleam method keeps getting ACTIVE_ATTRIBUTE_MAX_LENGTH.
let (size, type_, name) = gl.get_active_attrib(program.get(), i);
let location = if name.starts_with("gl_") {
-1
} else {
gl.get_attrib_location(program.get(), &name)
};
ActiveAttribInfo {
name: from_name_in_compiled_shader(&name),
size,
type_,
location,
}
})
.collect::<Vec<_>>()
.into();
let mut num_active_uniforms = [0];
unsafe {
gl.get_program_iv(program.get(), gl::ACTIVE_UNIFORMS, &mut num_active_uniforms);
}
let active_uniforms = (0..num_active_uniforms[0] as u32)
.map(|i| {
// FIXME(nox): This allocates strings sometimes for nothing
// and the gleam method keeps getting ACTIVE_UNIFORM_MAX_LENGTH.
let (size, type_, mut name) = gl.get_active_uniform(program.get(), i);
let is_array = name.ends_with("[0]");
if is_array {
// FIXME(nox): NLL
let len = name.len();
name.truncate(len - 3);
}
ActiveUniformInfo {
base_name: from_name_in_compiled_shader(&name).into(),
size: if is_array { Some(size) } else { None },
type_,
bind_index: None,
}
})
.collect::<Vec<_>>()
.into();
let mut num_active_uniform_blocks = [0];
unsafe {
gl.get_program_iv(
program.get(),
gl::ACTIVE_UNIFORM_BLOCKS,
&mut num_active_uniform_blocks,
);
}
let active_uniform_blocks = (0..num_active_uniform_blocks[0] as u32)
.map(|i| {
let name = gl.get_active_uniform_block_name(program.get(), i);
let size =
gl.get_active_uniform_block_iv(program.get(), i, gl::UNIFORM_BLOCK_DATA_SIZE)
[0];
ActiveUniformBlockInfo { name, size }
})
.collect::<Vec<_>>()
.into();
let mut transform_feedback_length = [0];
unsafe {
gl.get_program_iv(
program.get(),
gl::TRANSFORM_FEEDBACK_VARYINGS,
&mut transform_feedback_length,
);
}
let mut transform_feedback_mode = [0];
unsafe {
gl.get_program_iv(
program.get(),
gl::TRANSFORM_FEEDBACK_BUFFER_MODE,
&mut transform_feedback_mode,
);
}
ProgramLinkInfo {
linked: true,
active_attribs,
active_uniforms,
active_uniform_blocks,
transform_feedback_length: transform_feedback_length[0],
transform_feedback_mode: transform_feedback_mode[0],
}
}
fn finish(gl: &Gl, chan: &WebGLSender<()>) {
gl.finish();
chan.send(()).unwrap();
}
fn shader_precision_format(
gl: &Gl,
shader_type: u32,
precision_type: u32,
chan: &WebGLSender<(i32, i32, i32)>,
) {
let result = gl.get_shader_precision_format(shader_type, precision_type);
chan.send(result).unwrap();
}
// surfman creates a legacy OpenGL context on macOS when
// OpenGL 2 support is requested. Legacy contexts return GL errors for the vertex
// array object functions, but support a set of APPLE extension functions that
// provide VAO support instead.
fn needs_apple_vertex_arrays(gl_version: GLVersion) -> bool {
cfg!(target_os = "macos") && gl_version.major < 3
}
#[allow(unsafe_code)]
fn get_extensions(gl: &Gl, chan: &WebGLSender<String>) {
let mut ext_count = [0];
unsafe {
gl.get_integer_v(gl::NUM_EXTENSIONS, &mut ext_count);
}
// Fall back to the depricated extensions API if that fails
if gl.get_error() != gl::NO_ERROR {
chan.send(gl.get_string(gl::EXTENSIONS)).unwrap();
return;
}
let ext_count = ext_count[0] as usize;
let mut extensions = Vec::with_capacity(ext_count);
for idx in 0..ext_count {
extensions.push(gl.get_string_i(gl::EXTENSIONS, idx as u32))
}
let extensions = extensions.join(" ");
chan.send(extensions).unwrap();
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
fn get_framebuffer_attachment_parameter(
gl: &Gl,
target: u32,
attachment: u32,
pname: u32,
chan: &WebGLSender<i32>,
) {
let parameter = gl.get_framebuffer_attachment_parameter_iv(target, attachment, pname);
chan.send(parameter).unwrap();
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
fn get_renderbuffer_parameter(gl: &Gl, target: u32, pname: u32, chan: &WebGLSender<i32>) {
let parameter = gl.get_renderbuffer_parameter_iv(target, pname);
chan.send(parameter).unwrap();
}
fn uniform_location(gl: &Gl, program_id: WebGLProgramId, name: &str, chan: &WebGLSender<i32>) {
let location = gl.get_uniform_location(program_id.get(), &to_name_in_compiled_shader(name));
chan.send(location).unwrap();
}
fn shader_info_log(gl: &Gl, shader_id: WebGLShaderId, chan: &WebGLSender<String>) {
let log = gl.get_shader_info_log(shader_id.get());
chan.send(log).unwrap();
}
fn program_info_log(gl: &Gl, program_id: WebGLProgramId, chan: &WebGLSender<String>) {
let log = gl.get_program_info_log(program_id.get());
chan.send(log).unwrap();
}
#[allow(unsafe_code)]
fn create_buffer(gl: &Gl, chan: &WebGLSender<Option<WebGLBufferId>>) {
let buffer = gl.gen_buffers(1)[0];
let buffer = if buffer == 0 {
None
} else {
Some(unsafe { WebGLBufferId::new(buffer) })
};
chan.send(buffer).unwrap();
}
#[allow(unsafe_code)]
fn create_framebuffer(gl: &Gl, chan: &WebGLSender<Option<WebGLTransparentFramebufferId>>) {
let framebuffer = gl.gen_framebuffers(1)[0];
let framebuffer = if framebuffer == 0 {
None
} else {
Some(unsafe { WebGLTransparentFramebufferId::new(framebuffer) })
};
chan.send(framebuffer).unwrap();
}
#[allow(unsafe_code)]
fn create_renderbuffer(gl: &Gl, chan: &WebGLSender<Option<WebGLRenderbufferId>>) {
let renderbuffer = gl.gen_renderbuffers(1)[0];
let renderbuffer = if renderbuffer == 0 {
None
} else {
Some(unsafe { WebGLRenderbufferId::new(renderbuffer) })
};
chan.send(renderbuffer).unwrap();
}
#[allow(unsafe_code)]
fn create_texture(gl: &Gl, chan: &WebGLSender<Option<WebGLTextureId>>) {
let texture = gl.gen_textures(1)[0];
let texture = if texture == 0 {
None
} else {
Some(unsafe { WebGLTextureId::new(texture) })
};
chan.send(texture).unwrap();
}
#[allow(unsafe_code)]
fn create_program(gl: &Gl, chan: &WebGLSender<Option<WebGLProgramId>>) {
let program = gl.create_program();
let program = if program == 0 {
None
} else {
Some(unsafe { WebGLProgramId::new(program) })
};
chan.send(program).unwrap();
}
#[allow(unsafe_code)]
fn create_shader(gl: &Gl, shader_type: u32, chan: &WebGLSender<Option<WebGLShaderId>>) {
let shader = gl.create_shader(shader_type);
let shader = if shader == 0 {
None
} else {
Some(unsafe { WebGLShaderId::new(shader) })
};
chan.send(shader).unwrap();
}
#[allow(unsafe_code)]
fn create_vertex_array(
gl: &Gl,
use_apple_ext: bool,
version: WebGLVersion,
) -> Option<WebGLVertexArrayId> {
let vao = match gl {
Gl::Gl(ref gl) if use_apple_ext => {
let mut ids = vec![0];
unsafe {
gl.GenVertexArraysAPPLE(ids.len() as gl::GLsizei, ids.as_mut_ptr());
}
ids[0]
},
Gl::Gles(ref gles) if version == WebGLVersion::WebGL1 => {
let mut ids = vec![0];
unsafe { gles.GenVertexArraysOES(ids.len() as gl::GLsizei, ids.as_mut_ptr()) }
ids[0]
},
_ => gl.gen_vertex_arrays(1)[0],
};
if vao == 0 {
let code = gl.get_error();
warn!("Failed to create vertex array with error code {:x}", code);
None
} else {
Some(unsafe { WebGLVertexArrayId::new(vao) })
}
}
#[allow(unsafe_code)]
fn bind_vertex_array(gl: &Gl, vao: GLuint, use_apple_ext: bool, version: WebGLVersion) {
match gl {
Gl::Gl(ref gl) if use_apple_ext => unsafe {
gl.BindVertexArrayAPPLE(vao);
},
Gl::Gles(ref gles) if version == WebGLVersion::WebGL1 => unsafe {
gles.BindVertexArrayOES(vao);
},
_ => gl.bind_vertex_array(vao),
}
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
}
#[allow(unsafe_code)]
fn delete_vertex_array(gl: &Gl, vao: GLuint, use_apple_ext: bool, version: WebGLVersion) {
let vaos = [vao];
match gl {
Gl::Gl(ref gl) if use_apple_ext => unsafe {
gl.DeleteVertexArraysAPPLE(vaos.len() as gl::GLsizei, vaos.as_ptr());
},
Gl::Gles(ref gl) if version == WebGLVersion::WebGL1 => unsafe {
gl.DeleteVertexArraysOES(vaos.len() as gl::GLsizei, vaos.as_ptr());
},
_ => gl.delete_vertex_arrays(&vaos),
}
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
}
/// Updates the swap buffers if the context surface needs to be changed
fn attach_surface(
context_id: WebGLContextId,
webrender_swap_chains: &SwapChains<WebGLContextId, Device>,
webxr_swap_chains: &SwapChains<WebXRSwapChainId, Device>,
request: WebGLFramebufferBindingRequest,
ctx: &mut Context,
device: &mut Device,
) -> Option<()> {
debug!(
"WebGLImpl::attach_surface({:?} in {:?})",
request, context_id
);
let requested_framebuffer = match request {
WebGLFramebufferBindingRequest::Explicit(WebGLFramebufferId::Opaque(id)) => Some(id),
WebGLFramebufferBindingRequest::Explicit(WebGLFramebufferId::Transparent(_)) => {
return None
},
WebGLFramebufferBindingRequest::Default => None,
};
let attached_framebuffer = webxr_swap_chains
.iter(device, ctx)
.filter_map(|(id, swap_chain)| {
if swap_chain.is_attached() {
Some(id)
} else {
None
}
})
.map(WebGLOpaqueFramebufferId::WebXR)
.next();
if requested_framebuffer == attached_framebuffer {
return None;
}
let requested_swap_chain = match requested_framebuffer {
Some(WebGLOpaqueFramebufferId::WebXR(id)) => webxr_swap_chains.get(id)?,
None => webrender_swap_chains.get(context_id)?,
};
let current_swap_chain = match attached_framebuffer {
Some(WebGLOpaqueFramebufferId::WebXR(id)) => webxr_swap_chains.get(id)?,
None => webrender_swap_chains.get(context_id)?,
};
requested_swap_chain
.take_attachment_from(device, ctx, &current_swap_chain)
.unwrap();
Some(())
}
#[inline]
fn bind_framebuffer(
gl: &Gl,
target: u32,
request: WebGLFramebufferBindingRequest,
ctx: &Context,
device: &Device,
state: &mut GLState,
) {
let id = match request {
WebGLFramebufferBindingRequest::Explicit(WebGLFramebufferId::Transparent(id)) => {
id.get()
},
WebGLFramebufferBindingRequest::Explicit(WebGLFramebufferId::Opaque(_)) |
WebGLFramebufferBindingRequest::Default => {
device
.context_surface_info(ctx)
.unwrap()
.expect("No surface attached!")
.framebuffer_object
},
};
debug!("WebGLImpl::bind_framebuffer: {:?}", id);
gl.bind_framebuffer(target, id);
if (target == gl::FRAMEBUFFER) || (target == gl::DRAW_FRAMEBUFFER) {
state.drawing_to_default_framebuffer =
request == WebGLFramebufferBindingRequest::Default;
state.restore_invariant(gl);
}
}
#[inline]
fn compile_shader(gl: &Gl, shader_id: WebGLShaderId, source: &str) {
gl.shader_source(shader_id.get(), &[source.as_bytes()]);
gl.compile_shader(shader_id.get());
}
}
/// ANGLE adds a `_u` prefix to variable names:
///
/// https://chromium.googlesource.com/angle/angle/+/855d964bd0d05f6b2cb303f625506cf53d37e94f
///
/// To avoid hard-coding this we would need to use the `sh::GetAttributes` and `sh::GetUniforms`
/// API to look up the `x.name` and `x.mappedName` members.
const ANGLE_NAME_PREFIX: &'static str = "_u";
fn to_name_in_compiled_shader(s: &str) -> String {
map_dot_separated(s, |s, mapped| {
mapped.push_str(ANGLE_NAME_PREFIX);
mapped.push_str(s);
})
}
fn from_name_in_compiled_shader(s: &str) -> String {
map_dot_separated(s, |s, mapped| {
mapped.push_str(if s.starts_with(ANGLE_NAME_PREFIX) {
&s[ANGLE_NAME_PREFIX.len()..]
} else {
s
})
})
}
fn map_dot_separated<F: Fn(&str, &mut String)>(s: &str, f: F) -> String {
let mut iter = s.split('.');
let mut mapped = String::new();
f(iter.next().unwrap(), &mut mapped);
for s in iter {
mapped.push('.');
f(s, &mut mapped);
}
mapped
}
fn prepare_pixels(
internal_format: TexFormat,
data_type: TexDataType,
size: Size2D<u32>,
unpacking_alignment: u32,
alpha_treatment: Option<AlphaTreatment>,
y_axis_treatment: YAxisTreatment,
pixel_format: Option<PixelFormat>,
mut pixels: Cow<[u8]>,
) -> Cow<[u8]> {
match alpha_treatment {
Some(AlphaTreatment::Premultiply) => {
if let Some(pixel_format) = pixel_format {
match pixel_format {
PixelFormat::BGRA8 | PixelFormat::RGBA8 => {},
_ => unimplemented!("unsupported pixel format ({:?})", pixel_format),
}
premultiply_inplace(TexFormat::RGBA, TexDataType::UnsignedByte, pixels.to_mut());
} else {
premultiply_inplace(internal_format, data_type, pixels.to_mut());
}
},
Some(AlphaTreatment::Unmultiply) => {
assert!(pixel_format.is_some());
unmultiply_inplace(pixels.to_mut());
},
None => {},
}
if let Some(pixel_format) = pixel_format {
pixels = image_to_tex_image_data(
pixel_format,
internal_format,
data_type,
pixels.into_owned(),
)
.into();
}
if y_axis_treatment == YAxisTreatment::Flipped {
// FINISHME: Consider doing premultiply and flip in a single mutable Vec.
pixels = flip_pixels_y(
internal_format,
data_type,
size.width as usize,
size.height as usize,
unpacking_alignment as usize,
pixels.into_owned(),
)
.into();
}
pixels
}
/// Translates an image in rgba8 (red in the first byte) format to
/// the format that was requested of TexImage.
fn image_to_tex_image_data(
pixel_format: PixelFormat,
format: TexFormat,
data_type: TexDataType,
mut pixels: Vec<u8>,
) -> Vec<u8> {
// hint for vector allocation sizing.
let pixel_count = pixels.len() / 4;
match pixel_format {
PixelFormat::BGRA8 => pixels::rgba8_byte_swap_colors_inplace(&mut pixels),
PixelFormat::RGBA8 => {},
_ => unimplemented!("unsupported pixel format ({:?})", pixel_format),
}
match (format, data_type) {
(TexFormat::RGBA, TexDataType::UnsignedByte) |
(TexFormat::RGBA8, TexDataType::UnsignedByte) => pixels,
(TexFormat::RGB, TexDataType::UnsignedByte) |
(TexFormat::RGB8, TexDataType::UnsignedByte) => {
for i in 0..pixel_count {
let rgb = {
let rgb = &pixels[i * 4..i * 4 + 3];
[rgb[0], rgb[1], rgb[2]]
};
pixels[i * 3..i * 3 + 3].copy_from_slice(&rgb);
}
pixels.truncate(pixel_count * 3);
pixels
},
(TexFormat::Alpha, TexDataType::UnsignedByte) => {
for i in 0..pixel_count {
let p = pixels[i * 4 + 3];
pixels[i] = p;
}
pixels.truncate(pixel_count);
pixels
},
(TexFormat::Luminance, TexDataType::UnsignedByte) => {
for i in 0..pixel_count {
let p = pixels[i * 4];
pixels[i] = p;
}
pixels.truncate(pixel_count);
pixels
},
(TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
for i in 0..pixel_count {
let (lum, a) = {
let rgba = &pixels[i * 4..i * 4 + 4];
(rgba[0], rgba[3])
};
pixels[i * 2] = lum;
pixels[i * 2 + 1] = a;
}
pixels.truncate(pixel_count * 2);
pixels
},
(TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
for i in 0..pixel_count {
let p = {
let rgba = &pixels[i * 4..i * 4 + 4];
(rgba[0] as u16 & 0xf0) << 8 |
(rgba[1] as u16 & 0xf0) << 4 |
(rgba[2] as u16 & 0xf0) |
(rgba[3] as u16 & 0xf0) >> 4
};
NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
}
pixels.truncate(pixel_count * 2);
pixels
},
(TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
for i in 0..pixel_count {
let p = {
let rgba = &pixels[i * 4..i * 4 + 4];
(rgba[0] as u16 & 0xf8) << 8 |
(rgba[1] as u16 & 0xf8) << 3 |
(rgba[2] as u16 & 0xf8) >> 2 |
(rgba[3] as u16) >> 7
};
NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
}
pixels.truncate(pixel_count * 2);
pixels
},
(TexFormat::RGB, TexDataType::UnsignedShort565) => {
for i in 0..pixel_count {
let p = {
let rgb = &pixels[i * 4..i * 4 + 3];
(rgb[0] as u16 & 0xf8) << 8 |
(rgb[1] as u16 & 0xfc) << 3 |
(rgb[2] as u16 & 0xf8) >> 3
};
NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p);
}
pixels.truncate(pixel_count * 2);
pixels
},
(TexFormat::RGBA, TexDataType::Float) | (TexFormat::RGBA32f, TexDataType::Float) => {
let mut rgbaf32 = Vec::<u8>::with_capacity(pixel_count * 16);
for rgba8 in pixels.chunks(4) {
rgbaf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
}
rgbaf32
},
(TexFormat::RGB, TexDataType::Float) | (TexFormat::RGB32f, TexDataType::Float) => {
let mut rgbf32 = Vec::<u8>::with_capacity(pixel_count * 12);
for rgba8 in pixels.chunks(4) {
rgbf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
rgbf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
rgbf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
}
rgbf32
},
(TexFormat::Alpha, TexDataType::Float) | (TexFormat::Alpha32f, TexDataType::Float) => {
for rgba8 in pixels.chunks_mut(4) {
let p = rgba8[3] as f32;
NativeEndian::write_f32(rgba8, p);
}
pixels
},
(TexFormat::Luminance, TexDataType::Float) |
(TexFormat::Luminance32f, TexDataType::Float) => {
for rgba8 in pixels.chunks_mut(4) {
let p = rgba8[0] as f32;
NativeEndian::write_f32(rgba8, p);
}
pixels
},
(TexFormat::LuminanceAlpha, TexDataType::Float) |
(TexFormat::LuminanceAlpha32f, TexDataType::Float) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
data.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
data.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
}
data
},
(TexFormat::RGBA, TexDataType::HalfFloat) |
(TexFormat::RGBA16f, TexDataType::HalfFloat) => {
let mut rgbaf16 = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
rgbaf16
.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits())
.unwrap();
rgbaf16
.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits())
.unwrap();
rgbaf16
.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits())
.unwrap();
rgbaf16
.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_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).as_bits())
.unwrap();
rgbf16
.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits())
.unwrap();
rgbf16
.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_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).as_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).as_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).as_bits();
let a = f16::from_f32(rgba8[3] as f32).as_bits();
NativeEndian::write_u16(&mut rgba8[0..2], lum);
NativeEndian::write_u16(&mut rgba8[2..4], a);
}
pixels
},
// Validation should have ensured that we only hit the
// above cases, but we haven't turned the (format, type)
// into an enum yet so there's a default case here.
_ => unreachable!("Unsupported formats {:?} {:?}", format, data_type),
}
}
fn premultiply_inplace(format: TexFormat, data_type: TexDataType, pixels: &mut [u8]) {
match (format, data_type) {
(TexFormat::RGBA, TexDataType::UnsignedByte) => {
pixels::rgba8_premultiply_inplace(pixels);
},
(TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
for la in pixels.chunks_mut(2) {
la[0] = pixels::multiply_u8_color(la[0], la[1]);
}
},
(TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
for rgba in pixels.chunks_mut(2) {
if NativeEndian::read_u16(rgba) & 1 == 0 {
NativeEndian::write_u16(rgba, 0);
}
}
},
(TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
for rgba in pixels.chunks_mut(2) {
let pix = NativeEndian::read_u16(rgba);
let extend_to_8_bits = |val| (val | val << 4) as u8;
let r = extend_to_8_bits(pix >> 12 & 0x0f);
let g = extend_to_8_bits(pix >> 8 & 0x0f);
let b = extend_to_8_bits(pix >> 4 & 0x0f);
let a = extend_to_8_bits(pix & 0x0f);
NativeEndian::write_u16(
rgba,
((pixels::multiply_u8_color(r, a) & 0xf0) as u16) << 8 |
((pixels::multiply_u8_color(g, a) & 0xf0) as u16) << 4 |
((pixels::multiply_u8_color(b, a) & 0xf0) as u16) |
((a & 0x0f) as u16),
);
}
},
// Other formats don't have alpha, so return their data untouched.
_ => {},
}
}
fn unmultiply_inplace(pixels: &mut [u8]) {
for rgba in pixels.chunks_mut(4) {
let a = (rgba[3] as f32) / 255.0;
rgba[0] = (rgba[0] as f32 / a) as u8;
rgba[1] = (rgba[1] as f32 / a) as u8;
rgba[2] = (rgba[2] as f32 / a) as u8;
}
}
/// Flips the pixels in the Vec on the Y axis.
fn flip_pixels_y(
internal_format: TexFormat,
data_type: TexDataType,
width: usize,
height: usize,
unpacking_alignment: usize,
pixels: Vec<u8>,
) -> Vec<u8> {
let cpp = (data_type.element_size() * internal_format.components() /
data_type.components_per_element()) as usize;
let stride = (width * cpp + unpacking_alignment - 1) & !(unpacking_alignment - 1);
let mut flipped = Vec::<u8>::with_capacity(pixels.len());
for y in 0..height {
let flipped_y = height - 1 - y;
let start = flipped_y * stride;
flipped.extend_from_slice(&pixels[start..(start + width * cpp)]);
flipped.extend(vec![0u8; stride - width * cpp]);
}
flipped
}
// Clamp a size to the current GL context's max viewport
fn clamp_viewport(gl: &Gl, size: Size2D<u32>) -> Size2D<u32> {
let mut max_viewport = [i32::max_value(), i32::max_value()];
let mut max_renderbuffer = [i32::max_value()];
#[allow(unsafe_code)]
unsafe {
gl.get_integer_v(gl::MAX_VIEWPORT_DIMS, &mut max_viewport);
gl.get_integer_v(gl::MAX_RENDERBUFFER_SIZE, &mut max_renderbuffer);
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
}
Size2D::new(
size.width
.min(max_viewport[0] as u32)
.min(max_renderbuffer[0] as u32)
.max(1),
size.height
.min(max_viewport[1] as u32)
.min(max_renderbuffer[0] as u32)
.max(1),
)
}
trait ToSurfmanVersion {
fn to_surfman_version(self, api_type: gl::GlType) -> GLVersion;
}
impl ToSurfmanVersion for WebGLVersion {
fn to_surfman_version(self, api_type: gl::GlType) -> GLVersion {
if api_type == gl::GlType::Gles {
return GLVersion::new(3, 0);
}
match self {
// We make use of GL_PACK_PIXEL_BUFFER, which needs at least GL2.1
// We make use of compatibility mode, which needs at most GL3.0
WebGLVersion::WebGL1 => GLVersion::new(2, 1),
// The WebGL2 conformance tests use std140 layout, which needs at GL3.1
WebGLVersion::WebGL2 => GLVersion::new(3, 2),
}
}
}
trait SurfmanContextAttributeFlagsConvert {
fn to_surfman_context_attribute_flags(
&self,
webgl_version: WebGLVersion,
api_type: gl::GlType,
) -> ContextAttributeFlags;
}
impl SurfmanContextAttributeFlagsConvert for GLContextAttributes {
fn to_surfman_context_attribute_flags(
&self,
webgl_version: WebGLVersion,
api_type: gl::GlType,
) -> ContextAttributeFlags {
let mut flags = ContextAttributeFlags::empty();
flags.set(ContextAttributeFlags::ALPHA, self.alpha);
flags.set(ContextAttributeFlags::DEPTH, self.depth);
flags.set(ContextAttributeFlags::STENCIL, self.stencil);
if (webgl_version == WebGLVersion::WebGL1) && (api_type == gl::GlType::Gl) {
flags.set(ContextAttributeFlags::COMPATIBILITY_PROFILE, true);
}
flags
}
}
bitflags! {
struct FramebufferRebindingFlags: u8 {
const REBIND_READ_FRAMEBUFFER = 0x1;
const REBIND_DRAW_FRAMEBUFFER = 0x2;
}
}
struct FramebufferRebindingInfo {
flags: FramebufferRebindingFlags,
viewport: [GLint; 4],
}
impl FramebufferRebindingInfo {
#[allow(unsafe_code)]
fn detect(device: &Device, context: &Context, gl: &Gl) -> FramebufferRebindingInfo {
unsafe {
let (mut read_framebuffer, mut draw_framebuffer) = ([0], [0]);
gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut read_framebuffer);
gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut draw_framebuffer);
let context_surface_framebuffer = device
.context_surface_info(context)
.unwrap()
.unwrap()
.framebuffer_object;
let mut flags = FramebufferRebindingFlags::empty();
if context_surface_framebuffer == read_framebuffer[0] as GLuint {
flags.insert(FramebufferRebindingFlags::REBIND_READ_FRAMEBUFFER);
}
if context_surface_framebuffer == draw_framebuffer[0] as GLuint {
flags.insert(FramebufferRebindingFlags::REBIND_DRAW_FRAMEBUFFER);
}
let mut viewport = [0; 4];
gl.get_integer_v(gl::VIEWPORT, &mut viewport);
FramebufferRebindingInfo { flags, viewport }
}
}
fn apply(self, device: &Device, context: &Context, gl: &Gl) {
if self.flags.is_empty() {
return;
}
let context_surface_framebuffer = device
.context_surface_info(context)
.unwrap()
.unwrap()
.framebuffer_object;
if self
.flags
.contains(FramebufferRebindingFlags::REBIND_READ_FRAMEBUFFER)
{
gl.bind_framebuffer(gl::READ_FRAMEBUFFER, context_surface_framebuffer);
}
if self
.flags
.contains(FramebufferRebindingFlags::REBIND_DRAW_FRAMEBUFFER)
{
gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, context_surface_framebuffer);
}
gl.viewport(
self.viewport[0],
self.viewport[1],
self.viewport[2],
self.viewport[3],
);
}
}