mirror of
https://github.com/servo/servo.git
synced 2025-06-04 07:35:36 +00:00
* Use 2024 style edition Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Reformat all code Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
3243 lines
126 KiB
Rust
3243 lines
126 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/. */
|
|
#![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 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 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 webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType};
|
|
|
|
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)),
|
|
)
|
|
};
|
|
sender.send(&pixels).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],
|
|
)
|
|
};
|
|
}
|
|
}
|