summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/device
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/device')
-rw-r--r--gfx/wr/webrender/src/device/gl.rs4814
-rw-r--r--gfx/wr/webrender/src/device/mod.rs9
-rw-r--r--gfx/wr/webrender/src/device/query_gl.rs318
3 files changed, 5141 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/device/gl.rs b/gfx/wr/webrender/src/device/gl.rs
new file mode 100644
index 0000000000..f240083bf7
--- /dev/null
+++ b/gfx/wr/webrender/src/device/gl.rs
@@ -0,0 +1,4814 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::super::shader_source::{OPTIMIZED_SHADERS, UNOPTIMIZED_SHADERS};
+use api::{ImageDescriptor, ImageFormat, Parameter, BoolParameter, IntParameter, ImageRendering};
+use api::{MixBlendMode, ImageBufferKind, VoidPtrToSizeFn};
+use api::{CrashAnnotator, CrashAnnotation, CrashAnnotatorGuard};
+use api::units::*;
+use euclid::default::Transform3D;
+use gleam::gl;
+use crate::render_api::MemoryReport;
+use crate::internal_types::{FastHashMap, RenderTargetInfo, Swizzle, SwizzleSettings};
+use crate::util::round_up_to_multiple;
+use crate::profiler;
+use log::Level;
+use smallvec::SmallVec;
+use std::{
+ borrow::Cow,
+ cell::{Cell, RefCell},
+ cmp,
+ collections::hash_map::Entry,
+ marker::PhantomData,
+ mem,
+ num::NonZeroUsize,
+ os::raw::c_void,
+ ops::Add,
+ path::PathBuf,
+ ptr,
+ rc::Rc,
+ slice,
+ sync::Arc,
+ thread,
+ time::Duration,
+};
+use webrender_build::shader::{
+ ProgramSourceDigest, ShaderKind, ShaderVersion, build_shader_main_string,
+ build_shader_prefix_string, do_build_shader_string, shader_source_from_file,
+};
+use malloc_size_of::MallocSizeOfOps;
+
+/// Sequence number for frames, as tracked by the device layer.
+#[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GpuFrameId(usize);
+
+impl GpuFrameId {
+ pub fn new(value: usize) -> Self {
+ GpuFrameId(value)
+ }
+}
+
+impl Add<usize> for GpuFrameId {
+ type Output = GpuFrameId;
+
+ fn add(self, other: usize) -> GpuFrameId {
+ GpuFrameId(self.0 + other)
+ }
+}
+
+pub struct TextureSlot(pub usize);
+
+// In some places we need to temporarily bind a texture to any slot.
+const DEFAULT_TEXTURE: TextureSlot = TextureSlot(0);
+
+#[repr(u32)]
+pub enum DepthFunction {
+ Always = gl::ALWAYS,
+ Less = gl::LESS,
+ LessEqual = gl::LEQUAL,
+}
+
+#[repr(u32)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum TextureFilter {
+ Nearest,
+ Linear,
+ Trilinear,
+}
+
+/// A structure defining a particular workflow of texture transfers.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct TextureFormatPair<T> {
+ /// Format the GPU natively stores texels in.
+ pub internal: T,
+ /// Format we expect the users to provide the texels in.
+ pub external: T,
+}
+
+impl<T: Copy> From<T> for TextureFormatPair<T> {
+ fn from(value: T) -> Self {
+ TextureFormatPair {
+ internal: value,
+ external: value,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum VertexAttributeKind {
+ F32,
+ U8Norm,
+ U16Norm,
+ I32,
+ U16,
+}
+
+#[derive(Debug)]
+pub struct VertexAttribute {
+ pub name: &'static str,
+ pub count: u32,
+ pub kind: VertexAttributeKind,
+}
+
+#[derive(Debug)]
+pub struct VertexDescriptor {
+ pub vertex_attributes: &'static [VertexAttribute],
+ pub instance_attributes: &'static [VertexAttribute],
+}
+
+enum FBOTarget {
+ Read,
+ Draw,
+}
+
+/// Method of uploading texel data from CPU to GPU.
+#[derive(Debug, Clone)]
+pub enum UploadMethod {
+ /// Just call `glTexSubImage` directly with the CPU data pointer
+ Immediate,
+ /// Accumulate the changes in PBO first before transferring to a texture.
+ PixelBuffer(VertexUsageHint),
+}
+
+/// Plain old data that can be used to initialize a texture.
+pub unsafe trait Texel: Copy {}
+unsafe impl Texel for u8 {}
+unsafe impl Texel for f32 {}
+
+/// Returns the size in bytes of a depth target with the given dimensions.
+fn depth_target_size_in_bytes(dimensions: &DeviceIntSize) -> usize {
+ // DEPTH24 textures generally reserve 3 bytes for depth and 1 byte
+ // for stencil, so we measure them as 32 bits.
+ let pixels = dimensions.width * dimensions.height;
+ (pixels as usize) * 4
+}
+
+pub fn get_gl_target(target: ImageBufferKind) -> gl::GLuint {
+ match target {
+ ImageBufferKind::Texture2D => gl::TEXTURE_2D,
+ ImageBufferKind::TextureRect => gl::TEXTURE_RECTANGLE,
+ ImageBufferKind::TextureExternal => gl::TEXTURE_EXTERNAL_OES,
+ }
+}
+
+pub fn from_gl_target(target: gl::GLuint) -> ImageBufferKind {
+ match target {
+ gl::TEXTURE_2D => ImageBufferKind::Texture2D,
+ gl::TEXTURE_RECTANGLE => ImageBufferKind::TextureRect,
+ gl::TEXTURE_EXTERNAL_OES => ImageBufferKind::TextureExternal,
+ _ => panic!("Unexpected target {:?}", target),
+ }
+}
+
+fn supports_extension(extensions: &[String], extension: &str) -> bool {
+ extensions.iter().any(|s| s == extension)
+}
+
+fn get_shader_version(gl: &dyn gl::Gl) -> ShaderVersion {
+ match gl.get_type() {
+ gl::GlType::Gl => ShaderVersion::Gl,
+ gl::GlType::Gles => ShaderVersion::Gles,
+ }
+}
+
+// Get an unoptimized shader string by name, from the built in resources or
+// an override path, if supplied.
+pub fn get_unoptimized_shader_source(shader_name: &str, base_path: Option<&PathBuf>) -> Cow<'static, str> {
+ if let Some(ref base) = base_path {
+ let shader_path = base.join(&format!("{}.glsl", shader_name));
+ Cow::Owned(shader_source_from_file(&shader_path))
+ } else {
+ Cow::Borrowed(
+ UNOPTIMIZED_SHADERS
+ .get(shader_name)
+ .expect("Shader not found")
+ .source
+ )
+ }
+}
+
+pub trait FileWatcherHandler: Send {
+ fn file_changed(&self, path: PathBuf);
+}
+
+impl VertexAttributeKind {
+ fn size_in_bytes(&self) -> u32 {
+ match *self {
+ VertexAttributeKind::F32 => 4,
+ VertexAttributeKind::U8Norm => 1,
+ VertexAttributeKind::U16Norm => 2,
+ VertexAttributeKind::I32 => 4,
+ VertexAttributeKind::U16 => 2,
+ }
+ }
+}
+
+impl VertexAttribute {
+ fn size_in_bytes(&self) -> u32 {
+ self.count * self.kind.size_in_bytes()
+ }
+
+ fn bind_to_vao(
+ &self,
+ attr_index: gl::GLuint,
+ divisor: gl::GLuint,
+ stride: gl::GLint,
+ offset: gl::GLuint,
+ gl: &dyn gl::Gl,
+ ) {
+ gl.enable_vertex_attrib_array(attr_index);
+ gl.vertex_attrib_divisor(attr_index, divisor);
+
+ match self.kind {
+ VertexAttributeKind::F32 => {
+ gl.vertex_attrib_pointer(
+ attr_index,
+ self.count as gl::GLint,
+ gl::FLOAT,
+ false,
+ stride,
+ offset,
+ );
+ }
+ VertexAttributeKind::U8Norm => {
+ gl.vertex_attrib_pointer(
+ attr_index,
+ self.count as gl::GLint,
+ gl::UNSIGNED_BYTE,
+ true,
+ stride,
+ offset,
+ );
+ }
+ VertexAttributeKind::U16Norm => {
+ gl.vertex_attrib_pointer(
+ attr_index,
+ self.count as gl::GLint,
+ gl::UNSIGNED_SHORT,
+ true,
+ stride,
+ offset,
+ );
+ }
+ VertexAttributeKind::I32 => {
+ gl.vertex_attrib_i_pointer(
+ attr_index,
+ self.count as gl::GLint,
+ gl::INT,
+ stride,
+ offset,
+ );
+ }
+ VertexAttributeKind::U16 => {
+ gl.vertex_attrib_i_pointer(
+ attr_index,
+ self.count as gl::GLint,
+ gl::UNSIGNED_SHORT,
+ stride,
+ offset,
+ );
+ }
+ }
+ }
+}
+
+impl VertexDescriptor {
+ fn instance_stride(&self) -> u32 {
+ self.instance_attributes
+ .iter()
+ .map(|attr| attr.size_in_bytes())
+ .sum()
+ }
+
+ fn bind_attributes(
+ attributes: &[VertexAttribute],
+ start_index: usize,
+ divisor: u32,
+ gl: &dyn gl::Gl,
+ vbo: VBOId,
+ ) {
+ vbo.bind(gl);
+
+ let stride: u32 = attributes
+ .iter()
+ .map(|attr| attr.size_in_bytes())
+ .sum();
+
+ let mut offset = 0;
+ for (i, attr) in attributes.iter().enumerate() {
+ let attr_index = (start_index + i) as gl::GLuint;
+ attr.bind_to_vao(attr_index, divisor, stride as _, offset, gl);
+ offset += attr.size_in_bytes();
+ }
+ }
+
+ fn bind(&self, gl: &dyn gl::Gl, main: VBOId, instance: VBOId, instance_divisor: u32) {
+ Self::bind_attributes(self.vertex_attributes, 0, 0, gl, main);
+
+ if !self.instance_attributes.is_empty() {
+ Self::bind_attributes(
+ self.instance_attributes,
+ self.vertex_attributes.len(),
+ instance_divisor,
+ gl,
+ instance,
+ );
+ }
+ }
+}
+
+impl VBOId {
+ fn bind(&self, gl: &dyn gl::Gl) {
+ gl.bind_buffer(gl::ARRAY_BUFFER, self.0);
+ }
+}
+
+impl IBOId {
+ fn bind(&self, gl: &dyn gl::Gl) {
+ gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.0);
+ }
+}
+
+impl FBOId {
+ fn bind(&self, gl: &dyn gl::Gl, target: FBOTarget) {
+ let target = match target {
+ FBOTarget::Read => gl::READ_FRAMEBUFFER,
+ FBOTarget::Draw => gl::DRAW_FRAMEBUFFER,
+ };
+ gl.bind_framebuffer(target, self.0);
+ }
+}
+
+pub struct Stream<'a> {
+ attributes: &'a [VertexAttribute],
+ vbo: VBOId,
+}
+
+pub struct VBO<V> {
+ id: gl::GLuint,
+ target: gl::GLenum,
+ allocated_count: usize,
+ marker: PhantomData<V>,
+}
+
+impl<V> VBO<V> {
+ pub fn allocated_count(&self) -> usize {
+ self.allocated_count
+ }
+
+ pub fn stream_with<'a>(&self, attributes: &'a [VertexAttribute]) -> Stream<'a> {
+ debug_assert_eq!(
+ mem::size_of::<V>(),
+ attributes.iter().map(|a| a.size_in_bytes() as usize).sum::<usize>()
+ );
+ Stream {
+ attributes,
+ vbo: VBOId(self.id),
+ }
+ }
+}
+
+impl<T> Drop for VBO<T> {
+ fn drop(&mut self) {
+ debug_assert!(thread::panicking() || self.id == 0);
+ }
+}
+
+#[cfg_attr(feature = "replay", derive(Clone))]
+#[derive(Debug)]
+pub struct ExternalTexture {
+ id: gl::GLuint,
+ target: gl::GLuint,
+ uv_rect: TexelRect,
+ image_rendering: ImageRendering,
+}
+
+impl ExternalTexture {
+ pub fn new(
+ id: u32,
+ target: ImageBufferKind,
+ uv_rect: TexelRect,
+ image_rendering: ImageRendering,
+ ) -> Self {
+ ExternalTexture {
+ id,
+ target: get_gl_target(target),
+ uv_rect,
+ image_rendering,
+ }
+ }
+
+ #[cfg(feature = "replay")]
+ pub fn internal_id(&self) -> gl::GLuint {
+ self.id
+ }
+
+ pub fn get_uv_rect(&self) -> TexelRect {
+ self.uv_rect
+ }
+}
+
+bitflags! {
+ #[derive(Default)]
+ pub struct TextureFlags: u32 {
+ /// This texture corresponds to one of the shared texture caches.
+ const IS_SHARED_TEXTURE_CACHE = 1 << 0;
+ }
+}
+
+/// WebRender interface to an OpenGL texture.
+///
+/// Because freeing a texture requires various device handles that are not
+/// reachable from this struct, manual destruction via `Device` is required.
+/// Our `Drop` implementation asserts that this has happened.
+#[derive(Debug)]
+pub struct Texture {
+ id: gl::GLuint,
+ target: gl::GLuint,
+ format: ImageFormat,
+ size: DeviceIntSize,
+ filter: TextureFilter,
+ flags: TextureFlags,
+ /// An internally mutable swizzling state that may change between batches.
+ active_swizzle: Cell<Swizzle>,
+ /// Framebuffer Object allowing this texture to be rendered to.
+ ///
+ /// Empty if this texture is not used as a render target or if a depth buffer is needed.
+ fbo: Option<FBOId>,
+ /// Same as the above, but with a depth buffer attached.
+ ///
+ /// FBOs are cheap to create but expensive to reconfigure (since doing so
+ /// invalidates framebuffer completeness caching). Moreover, rendering with
+ /// a depth buffer attached but the depth write+test disabled relies on the
+ /// driver to optimize it out of the rendering pass, which most drivers
+ /// probably do but, according to jgilbert, is best not to rely on.
+ ///
+ /// So we lazily generate a second list of FBOs with depth. This list is
+ /// empty if this texture is not used as a render target _or_ if it is, but
+ /// the depth buffer has never been requested.
+ ///
+ /// Note that we always fill fbo, and then lazily create fbo_with_depth
+ /// when needed. We could make both lazy (i.e. render targets would have one
+ /// or the other, but not both, unless they were actually used in both
+ /// configurations). But that would complicate a lot of logic in this module,
+ /// and FBOs are cheap enough to create.
+ fbo_with_depth: Option<FBOId>,
+ last_frame_used: GpuFrameId,
+}
+
+impl Texture {
+ pub fn get_dimensions(&self) -> DeviceIntSize {
+ self.size
+ }
+
+ pub fn get_format(&self) -> ImageFormat {
+ self.format
+ }
+
+ pub fn get_filter(&self) -> TextureFilter {
+ self.filter
+ }
+
+ pub fn get_target(&self) -> ImageBufferKind {
+ from_gl_target(self.target)
+ }
+
+ pub fn supports_depth(&self) -> bool {
+ self.fbo_with_depth.is_some()
+ }
+
+ pub fn last_frame_used(&self) -> GpuFrameId {
+ self.last_frame_used
+ }
+
+ pub fn used_in_frame(&self, frame_id: GpuFrameId) -> bool {
+ self.last_frame_used == frame_id
+ }
+
+ pub fn is_render_target(&self) -> bool {
+ self.fbo.is_some()
+ }
+
+ /// Returns true if this texture was used within `threshold` frames of
+ /// the current frame.
+ pub fn used_recently(&self, current_frame_id: GpuFrameId, threshold: usize) -> bool {
+ self.last_frame_used + threshold >= current_frame_id
+ }
+
+ /// Returns the flags for this texture.
+ pub fn flags(&self) -> &TextureFlags {
+ &self.flags
+ }
+
+ /// Returns a mutable borrow of the flags for this texture.
+ pub fn flags_mut(&mut self) -> &mut TextureFlags {
+ &mut self.flags
+ }
+
+ /// Returns the number of bytes (generally in GPU memory) that this texture
+ /// consumes.
+ pub fn size_in_bytes(&self) -> usize {
+ let bpp = self.format.bytes_per_pixel() as usize;
+ let w = self.size.width as usize;
+ let h = self.size.height as usize;
+ bpp * w * h
+ }
+
+ #[cfg(feature = "replay")]
+ pub fn into_external(mut self) -> ExternalTexture {
+ let ext = ExternalTexture {
+ id: self.id,
+ target: self.target,
+ // TODO(gw): Support custom UV rect for external textures during captures
+ uv_rect: TexelRect::new(
+ 0.0,
+ 0.0,
+ self.size.width as f32,
+ self.size.height as f32,
+ ),
+ image_rendering: ImageRendering::Auto,
+ };
+ self.id = 0; // don't complain, moved out
+ ext
+ }
+}
+
+impl Drop for Texture {
+ fn drop(&mut self) {
+ debug_assert!(thread::panicking() || self.id == 0);
+ }
+}
+
+pub struct Program {
+ id: gl::GLuint,
+ u_transform: gl::GLint,
+ u_mode: gl::GLint,
+ u_texture_size: gl::GLint,
+ source_info: ProgramSourceInfo,
+ is_initialized: bool,
+}
+
+impl Program {
+ pub fn is_initialized(&self) -> bool {
+ self.is_initialized
+ }
+}
+
+impl Drop for Program {
+ fn drop(&mut self) {
+ debug_assert!(
+ thread::panicking() || self.id == 0,
+ "renderer::deinit not called"
+ );
+ }
+}
+
+pub struct CustomVAO {
+ id: gl::GLuint,
+}
+
+impl Drop for CustomVAO {
+ fn drop(&mut self) {
+ debug_assert!(
+ thread::panicking() || self.id == 0,
+ "renderer::deinit not called"
+ );
+ }
+}
+
+pub struct VAO {
+ id: gl::GLuint,
+ ibo_id: IBOId,
+ main_vbo_id: VBOId,
+ instance_vbo_id: VBOId,
+ instance_stride: usize,
+ instance_divisor: u32,
+ owns_vertices_and_indices: bool,
+}
+
+impl Drop for VAO {
+ fn drop(&mut self) {
+ debug_assert!(
+ thread::panicking() || self.id == 0,
+ "renderer::deinit not called"
+ );
+ }
+}
+
+#[derive(Debug)]
+pub struct PBO {
+ id: gl::GLuint,
+ reserved_size: usize,
+}
+
+impl PBO {
+ pub fn get_reserved_size(&self) -> usize {
+ self.reserved_size
+ }
+}
+
+impl Drop for PBO {
+ fn drop(&mut self) {
+ debug_assert!(
+ thread::panicking() || self.id == 0,
+ "renderer::deinit not called or PBO not returned to pool"
+ );
+ }
+}
+
+pub struct BoundPBO<'a> {
+ device: &'a mut Device,
+ pub data: &'a [u8]
+}
+
+impl<'a> Drop for BoundPBO<'a> {
+ fn drop(&mut self) {
+ self.device.gl.unmap_buffer(gl::PIXEL_PACK_BUFFER);
+ self.device.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, 0);
+ }
+}
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct FBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct RBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct VBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+struct IBOId(gl::GLuint);
+
+#[derive(Clone, Debug)]
+enum ProgramSourceType {
+ Unoptimized,
+ Optimized(ShaderVersion),
+}
+
+#[derive(Clone, Debug)]
+pub struct ProgramSourceInfo {
+ base_filename: &'static str,
+ features: Vec<&'static str>,
+ full_name_cstr: Rc<std::ffi::CString>,
+ source_type: ProgramSourceType,
+ digest: ProgramSourceDigest,
+}
+
+impl ProgramSourceInfo {
+ fn new(
+ device: &Device,
+ name: &'static str,
+ features: &[&'static str],
+ ) -> Self {
+
+ // Compute the digest. Assuming the device has a `ProgramCache`, this
+ // will always be needed, whereas the source is rarely needed.
+
+ use std::collections::hash_map::DefaultHasher;
+ use std::hash::Hasher;
+
+ // Setup.
+ let mut hasher = DefaultHasher::new();
+ let gl_version = get_shader_version(&*device.gl());
+
+ // Hash the renderer name.
+ hasher.write(device.capabilities.renderer_name.as_bytes());
+
+ let full_name = Self::make_full_name(name, features);
+
+ let optimized_source = if device.use_optimized_shaders {
+ OPTIMIZED_SHADERS.get(&(gl_version, &full_name)).or_else(|| {
+ warn!("Missing optimized shader source for {}", &full_name);
+ None
+ })
+ } else {
+ None
+ };
+
+ let source_type = match optimized_source {
+ Some(source_and_digest) => {
+ // Optimized shader sources are used as-is, without any run-time processing.
+ // The vertex and fragment shaders are different, so must both be hashed.
+ // We use the hashes that were computed at build time, and verify it in debug builds.
+ if cfg!(debug_assertions) {
+ let mut h = DefaultHasher::new();
+ h.write(source_and_digest.vert_source.as_bytes());
+ h.write(source_and_digest.frag_source.as_bytes());
+ let d: ProgramSourceDigest = h.into();
+ let digest = d.to_string();
+ debug_assert_eq!(digest, source_and_digest.digest);
+ hasher.write(digest.as_bytes());
+ } else {
+ hasher.write(source_and_digest.digest.as_bytes());
+ }
+
+ ProgramSourceType::Optimized(gl_version)
+ }
+ None => {
+ // For non-optimized sources we compute the hash by walking the static strings
+ // in the same order as we would when concatenating the source, to avoid
+ // heap-allocating in the common case.
+ //
+ // Note that we cheat a bit to make the hashing more efficient. First, the only
+ // difference between the vertex and fragment shader is a single deterministic
+ // define, so we don't need to hash both. Second, we precompute the digest of the
+ // expanded source file at build time, and then just hash that digest here.
+ let override_path = device.resource_override_path.as_ref();
+ let source_and_digest = UNOPTIMIZED_SHADERS.get(&name).expect("Shader not found");
+
+ // Hash the prefix string.
+ build_shader_prefix_string(
+ gl_version,
+ &features,
+ ShaderKind::Vertex,
+ &name,
+ &mut |s| hasher.write(s.as_bytes()),
+ );
+
+ // Hash the shader file contents. We use a precomputed digest, and
+ // verify it in debug builds.
+ if override_path.is_some() || cfg!(debug_assertions) {
+ let mut h = DefaultHasher::new();
+ build_shader_main_string(
+ &name,
+ &|f| get_unoptimized_shader_source(f, override_path),
+ &mut |s| h.write(s.as_bytes())
+ );
+ let d: ProgramSourceDigest = h.into();
+ let digest = format!("{}", d);
+ debug_assert!(override_path.is_some() || digest == source_and_digest.digest);
+ hasher.write(digest.as_bytes());
+ } else {
+ hasher.write(source_and_digest.digest.as_bytes());
+ }
+
+ ProgramSourceType::Unoptimized
+ }
+ };
+
+ // Finish.
+ ProgramSourceInfo {
+ base_filename: name,
+ features: features.to_vec(),
+ full_name_cstr: Rc::new(std::ffi::CString::new(full_name).unwrap()),
+ source_type,
+ digest: hasher.into(),
+ }
+ }
+
+ fn compute_source(&self, device: &Device, kind: ShaderKind) -> String {
+ let full_name = self.full_name();
+ match self.source_type {
+ ProgramSourceType::Optimized(gl_version) => {
+ let shader = OPTIMIZED_SHADERS
+ .get(&(gl_version, &full_name))
+ .unwrap_or_else(|| panic!("Missing optimized shader source for {}", full_name));
+
+ match kind {
+ ShaderKind::Vertex => shader.vert_source.to_string(),
+ ShaderKind::Fragment => shader.frag_source.to_string(),
+ }
+ },
+ ProgramSourceType::Unoptimized => {
+ let mut src = String::new();
+ device.build_shader_string(
+ &self.features,
+ kind,
+ self.base_filename,
+ |s| src.push_str(s),
+ );
+ src
+ }
+ }
+ }
+
+ fn make_full_name(base_filename: &'static str, features: &[&'static str]) -> String {
+ if features.is_empty() {
+ base_filename.to_string()
+ } else {
+ format!("{}_{}", base_filename, features.join("_"))
+ }
+ }
+
+ fn full_name(&self) -> String {
+ Self::make_full_name(self.base_filename, &self.features)
+ }
+}
+
+#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
+pub struct ProgramBinary {
+ bytes: Vec<u8>,
+ format: gl::GLenum,
+ source_digest: ProgramSourceDigest,
+}
+
+impl ProgramBinary {
+ fn new(bytes: Vec<u8>,
+ format: gl::GLenum,
+ source_digest: ProgramSourceDigest) -> Self {
+ ProgramBinary {
+ bytes,
+ format,
+ source_digest,
+ }
+ }
+
+ /// Returns a reference to the source digest hash.
+ pub fn source_digest(&self) -> &ProgramSourceDigest {
+ &self.source_digest
+ }
+}
+
+/// The interfaces that an application can implement to handle ProgramCache update
+pub trait ProgramCacheObserver {
+ fn save_shaders_to_disk(&self, entries: Vec<Arc<ProgramBinary>>);
+ fn set_startup_shaders(&self, entries: Vec<Arc<ProgramBinary>>);
+ fn try_load_shader_from_disk(&self, digest: &ProgramSourceDigest, program_cache: &Rc<ProgramCache>);
+ fn notify_program_binary_failed(&self, program_binary: &Arc<ProgramBinary>);
+}
+
+struct ProgramCacheEntry {
+ /// The binary.
+ binary: Arc<ProgramBinary>,
+ /// True if the binary has been linked, i.e. used for rendering.
+ linked: bool,
+}
+
+pub struct ProgramCache {
+ entries: RefCell<FastHashMap<ProgramSourceDigest, ProgramCacheEntry>>,
+
+ /// Optional trait object that allows the client
+ /// application to handle ProgramCache updating
+ program_cache_handler: Option<Box<dyn ProgramCacheObserver>>,
+
+ /// Programs that have not yet been cached to disk (by program_cache_handler)
+ pending_entries: RefCell<Vec<Arc<ProgramBinary>>>,
+}
+
+impl ProgramCache {
+ pub fn new(program_cache_observer: Option<Box<dyn ProgramCacheObserver>>) -> Rc<Self> {
+ Rc::new(
+ ProgramCache {
+ entries: RefCell::new(FastHashMap::default()),
+ program_cache_handler: program_cache_observer,
+ pending_entries: RefCell::new(Vec::default()),
+ }
+ )
+ }
+
+ /// Save any new program binaries to the disk cache, and if startup has
+ /// just completed then write the list of shaders to load on next startup.
+ fn update_disk_cache(&self, startup_complete: bool) {
+ if let Some(ref handler) = self.program_cache_handler {
+ if !self.pending_entries.borrow().is_empty() {
+ let pending_entries = self.pending_entries.replace(Vec::default());
+ handler.save_shaders_to_disk(pending_entries);
+ }
+
+ if startup_complete {
+ let startup_shaders = self.entries.borrow().values()
+ .filter(|e| e.linked).map(|e| e.binary.clone())
+ .collect::<Vec<_>>();
+ handler.set_startup_shaders(startup_shaders);
+ }
+ }
+ }
+
+ /// Add a new ProgramBinary to the cache.
+ /// This function is typically used after compiling and linking a new program.
+ /// The binary will be saved to disk the next time update_disk_cache() is called.
+ fn add_new_program_binary(&self, program_binary: Arc<ProgramBinary>) {
+ self.pending_entries.borrow_mut().push(program_binary.clone());
+
+ let digest = program_binary.source_digest.clone();
+ let entry = ProgramCacheEntry {
+ binary: program_binary,
+ linked: true,
+ };
+ self.entries.borrow_mut().insert(digest, entry);
+ }
+
+ /// Load ProgramBinary to ProgramCache.
+ /// The function is typically used to load ProgramBinary from disk.
+ #[cfg(feature = "serialize_program")]
+ pub fn load_program_binary(&self, program_binary: Arc<ProgramBinary>) {
+ let digest = program_binary.source_digest.clone();
+ let entry = ProgramCacheEntry {
+ binary: program_binary,
+ linked: false,
+ };
+ self.entries.borrow_mut().insert(digest, entry);
+ }
+
+ /// Returns the number of bytes allocated for shaders in the cache.
+ pub fn report_memory(&self, op: VoidPtrToSizeFn) -> usize {
+ self.entries.borrow().values()
+ .map(|e| unsafe { op(e.binary.bytes.as_ptr() as *const c_void ) })
+ .sum()
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum VertexUsageHint {
+ Static,
+ Dynamic,
+ Stream,
+}
+
+impl VertexUsageHint {
+ fn to_gl(&self) -> gl::GLuint {
+ match *self {
+ VertexUsageHint::Static => gl::STATIC_DRAW,
+ VertexUsageHint::Dynamic => gl::DYNAMIC_DRAW,
+ VertexUsageHint::Stream => gl::STREAM_DRAW,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct UniformLocation(gl::GLint);
+
+impl UniformLocation {
+ pub const INVALID: Self = UniformLocation(-1);
+}
+
+#[derive(Debug)]
+pub struct Capabilities {
+ /// Whether multisampled render targets are supported.
+ pub supports_multisampling: bool,
+ /// Whether the function `glCopyImageSubData` is available.
+ pub supports_copy_image_sub_data: bool,
+ /// Whether the RGBAF32 textures can be bound to framebuffers.
+ pub supports_color_buffer_float: bool,
+ /// Whether the device supports persistently mapped buffers, via glBufferStorage.
+ pub supports_buffer_storage: bool,
+ /// Whether advanced blend equations are supported.
+ pub supports_advanced_blend_equation: bool,
+ /// Whether dual-source blending is supported.
+ pub supports_dual_source_blending: bool,
+ /// Whether KHR_debug is supported for getting debug messages from
+ /// the driver.
+ pub supports_khr_debug: bool,
+ /// Whether we can configure texture units to do swizzling on sampling.
+ pub supports_texture_swizzle: bool,
+ /// Whether the driver supports uploading to textures from a non-zero
+ /// offset within a PBO.
+ pub supports_nonzero_pbo_offsets: bool,
+ /// Whether the driver supports specifying the texture usage up front.
+ pub supports_texture_usage: bool,
+ /// Whether offscreen render targets can be partially updated.
+ pub supports_render_target_partial_update: bool,
+ /// Whether we can use SSBOs.
+ pub supports_shader_storage_object: bool,
+ /// Whether to enforce that texture uploads be batched regardless of what
+ /// the pref says.
+ pub requires_batched_texture_uploads: Option<bool>,
+ /// Whether we are able to ue glClear to clear regions of an alpha render target.
+ /// If false, we must use a shader to clear instead.
+ pub supports_alpha_target_clears: bool,
+ /// Whether we must perform a full unscissored glClear on alpha targets
+ /// prior to rendering.
+ pub requires_alpha_target_full_clear: bool,
+ /// Whether clearing a render target (immediately after binding it) is faster using a scissor
+ /// rect to clear just the required area, or clearing the entire target without a scissor rect.
+ pub prefers_clear_scissor: bool,
+ /// Whether the driver can correctly invalidate render targets. This can be
+ /// a worthwhile optimization, but is buggy on some devices.
+ pub supports_render_target_invalidate: bool,
+ /// Whether the driver can reliably upload data to R8 format textures.
+ pub supports_r8_texture_upload: bool,
+ /// Whether the extension QCOM_tiled_rendering is supported.
+ pub supports_qcom_tiled_rendering: bool,
+ /// Whether clip-masking is supported natively by the GL implementation
+ /// rather than emulated in shaders.
+ pub uses_native_clip_mask: bool,
+ /// Whether anti-aliasing is supported natively by the GL implementation
+ /// rather than emulated in shaders.
+ pub uses_native_antialiasing: bool,
+ /// Whether the extension GL_OES_EGL_image_external_essl3 is supported. If true, external
+ /// textures can be used as normal. If false, external textures can only be rendered with
+ /// certain shaders, and must first be copied in to regular textures for others.
+ pub supports_image_external_essl3: bool,
+ /// Whether the VAO must be rebound after an attached VBO has been orphaned.
+ pub requires_vao_rebind_after_orphaning: bool,
+ /// The name of the renderer, as reported by GL
+ pub renderer_name: String,
+}
+
+#[derive(Clone, Debug)]
+pub enum ShaderError {
+ Compilation(String, String), // name, error message
+ Link(String, String), // name, error message
+}
+
+/// A refcounted depth target, which may be shared by multiple textures across
+/// the device.
+struct SharedDepthTarget {
+ /// The Render Buffer Object representing the depth target.
+ rbo_id: RBOId,
+ /// Reference count. When this drops to zero, the RBO is deleted.
+ refcount: usize,
+}
+
+#[cfg(debug_assertions)]
+impl Drop for SharedDepthTarget {
+ fn drop(&mut self) {
+ debug_assert!(thread::panicking() || self.refcount == 0);
+ }
+}
+
+/// Describes for which texture formats to use the glTexStorage*
+/// family of functions.
+#[derive(PartialEq, Debug)]
+enum TexStorageUsage {
+ Never,
+ NonBGRA8,
+ Always,
+}
+
+/// Describes a required alignment for a stride,
+/// which can either be represented in bytes or pixels.
+#[derive(Copy, Clone, Debug)]
+pub enum StrideAlignment {
+ Bytes(NonZeroUsize),
+ Pixels(NonZeroUsize),
+}
+
+impl StrideAlignment {
+ pub fn num_bytes(&self, format: ImageFormat) -> NonZeroUsize {
+ match *self {
+ Self::Bytes(bytes) => bytes,
+ Self::Pixels(pixels) => {
+ assert!(format.bytes_per_pixel() > 0);
+ NonZeroUsize::new(pixels.get() * format.bytes_per_pixel() as usize).unwrap()
+ }
+ }
+ }
+}
+
+// We get 24 bits of Z value - use up 22 bits of it to give us
+// 4 bits to account for GPU issues. This seems to manifest on
+// some GPUs under certain perspectives due to z interpolation
+// precision problems.
+const RESERVE_DEPTH_BITS: i32 = 2;
+
+pub struct Device {
+ gl: Rc<dyn gl::Gl>,
+
+ /// If non-None, |gl| points to a profiling wrapper, and this points to the
+ /// underling Gl instance.
+ base_gl: Option<Rc<dyn gl::Gl>>,
+
+ // device state
+ bound_textures: [gl::GLuint; 16],
+ bound_program: gl::GLuint,
+ bound_program_name: Rc<std::ffi::CString>,
+ bound_vao: gl::GLuint,
+ bound_read_fbo: (FBOId, DeviceIntPoint),
+ bound_draw_fbo: FBOId,
+ program_mode_id: UniformLocation,
+ default_read_fbo: FBOId,
+ default_draw_fbo: FBOId,
+
+ /// Track depth state for assertions. Note that the default FBO has depth,
+ /// so this defaults to true.
+ depth_available: bool,
+
+ upload_method: UploadMethod,
+ use_batched_texture_uploads: bool,
+ /// Whether to use draw calls instead of regular blitting commands.
+ ///
+ /// Note: this currently only applies to the batched texture uploads
+ /// path.
+ use_draw_calls_for_texture_copy: bool,
+ /// Number of pixels below which we prefer batched uploads.
+ batched_upload_threshold: i32,
+
+ // HW or API capabilities
+ capabilities: Capabilities,
+
+ color_formats: TextureFormatPair<ImageFormat>,
+ bgra_formats: TextureFormatPair<gl::GLuint>,
+ bgra_pixel_type: gl::GLuint,
+ swizzle_settings: SwizzleSettings,
+ depth_format: gl::GLuint,
+
+ /// Map from texture dimensions to shared depth buffers for render targets.
+ ///
+ /// Render targets often have the same width/height, so we can save memory
+ /// by sharing these across targets.
+ depth_targets: FastHashMap<DeviceIntSize, SharedDepthTarget>,
+
+ // debug
+ inside_frame: bool,
+ crash_annotator: Option<Box<dyn CrashAnnotator>>,
+ annotate_draw_call_crashes: bool,
+
+ // resources
+ resource_override_path: Option<PathBuf>,
+
+ /// Whether to use shaders that have been optimized at build time.
+ use_optimized_shaders: bool,
+
+ max_texture_size: i32,
+ cached_programs: Option<Rc<ProgramCache>>,
+
+ // Frame counter. This is used to map between CPU
+ // frames and GPU frames.
+ frame_id: GpuFrameId,
+
+ /// When to use glTexStorage*. We prefer this over glTexImage* because it
+ /// guarantees that mipmaps won't be generated (which they otherwise are on
+ /// some drivers, particularly ANGLE). However, it is not always supported
+ /// at all, or for BGRA8 format. If it's not supported for the required
+ /// format, we fall back to glTexImage*.
+ texture_storage_usage: TexStorageUsage,
+
+ /// Required stride alignment for pixel transfers. This may be required for
+ /// correctness reasons due to driver bugs, or for performance reasons to
+ /// ensure we remain on the fast-path for transfers.
+ required_pbo_stride: StrideAlignment,
+
+ /// Whether we must ensure the source strings passed to glShaderSource()
+ /// are null-terminated, to work around driver bugs.
+ requires_null_terminated_shader_source: bool,
+
+ /// Whether we must unbind any texture from GL_TEXTURE_EXTERNAL_OES before
+ /// binding to GL_TEXTURE_2D, to work around an android emulator bug.
+ requires_texture_external_unbind: bool,
+
+ ///
+ is_software_webrender: bool,
+
+ // GL extensions
+ extensions: Vec<String>,
+
+ /// Dumps the source of the shader with the given name
+ dump_shader_source: Option<String>,
+
+ surface_origin_is_top_left: bool,
+
+ /// A debug boolean for tracking if the shader program has been set after
+ /// a blend mode change.
+ ///
+ /// This is needed for compatibility with next-gen
+ /// GPU APIs that switch states using "pipeline object" that bundles
+ /// together the blending state with the shader.
+ ///
+ /// Having the constraint of always binding the shader last would allow
+ /// us to have the "pipeline object" bound at that time. Without this
+ /// constraint, we'd either have to eagerly bind the "pipeline object"
+ /// on changing either the shader or the blend more, or lazily bind it
+ /// at draw call time, neither of which is desirable.
+ #[cfg(debug_assertions)]
+ shader_is_ready: bool,
+
+ // count created/deleted textures to report in the profiler.
+ pub textures_created: u32,
+ pub textures_deleted: u32,
+}
+
+/// Contains the parameters necessary to bind a draw target.
+#[derive(Clone, Copy, Debug)]
+pub enum DrawTarget {
+ /// Use the device's default draw target, with the provided dimensions,
+ /// which are used to set the viewport.
+ Default {
+ /// Target rectangle to draw.
+ rect: FramebufferIntRect,
+ /// Total size of the target.
+ total_size: FramebufferIntSize,
+ surface_origin_is_top_left: bool,
+ },
+ /// Use the provided texture.
+ Texture {
+ /// Size of the texture in pixels
+ dimensions: DeviceIntSize,
+ /// Whether to draw with the texture's associated depth target
+ with_depth: bool,
+ /// FBO that corresponds to the selected layer / depth mode
+ fbo_id: FBOId,
+ /// Native GL texture ID
+ id: gl::GLuint,
+ /// Native GL texture target
+ target: gl::GLuint,
+ },
+ /// Use an FBO attached to an external texture.
+ External {
+ fbo: FBOId,
+ size: FramebufferIntSize,
+ },
+ /// An OS compositor surface
+ NativeSurface {
+ offset: DeviceIntPoint,
+ external_fbo_id: u32,
+ dimensions: DeviceIntSize,
+ },
+}
+
+impl DrawTarget {
+ pub fn new_default(size: DeviceIntSize, surface_origin_is_top_left: bool) -> Self {
+ let total_size = device_size_as_framebuffer_size(size);
+ DrawTarget::Default {
+ rect: total_size.into(),
+ total_size,
+ surface_origin_is_top_left,
+ }
+ }
+
+ /// Returns true if this draw target corresponds to the default framebuffer.
+ pub fn is_default(&self) -> bool {
+ match *self {
+ DrawTarget::Default {..} => true,
+ _ => false,
+ }
+ }
+
+ pub fn from_texture(
+ texture: &Texture,
+ with_depth: bool,
+ ) -> Self {
+ let fbo_id = if with_depth {
+ texture.fbo_with_depth.unwrap()
+ } else {
+ texture.fbo.unwrap()
+ };
+
+ DrawTarget::Texture {
+ dimensions: texture.get_dimensions(),
+ fbo_id,
+ with_depth,
+ id: texture.id,
+ target: texture.target,
+ }
+ }
+
+ /// Returns the dimensions of this draw-target.
+ pub fn dimensions(&self) -> DeviceIntSize {
+ match *self {
+ DrawTarget::Default { total_size, .. } => total_size.cast_unit(),
+ DrawTarget::Texture { dimensions, .. } => dimensions,
+ DrawTarget::External { size, .. } => size.cast_unit(),
+ DrawTarget::NativeSurface { dimensions, .. } => dimensions,
+ }
+ }
+
+ pub fn offset(&self) -> DeviceIntPoint {
+ match *self {
+ DrawTarget::Default { .. } |
+ DrawTarget::Texture { .. } |
+ DrawTarget::External { .. } => {
+ DeviceIntPoint::zero()
+ }
+ DrawTarget::NativeSurface { offset, .. } => offset,
+ }
+ }
+
+ pub fn to_framebuffer_rect(&self, device_rect: DeviceIntRect) -> FramebufferIntRect {
+ let mut fb_rect = device_rect_as_framebuffer_rect(&device_rect);
+ match *self {
+ DrawTarget::Default { ref rect, surface_origin_is_top_left, .. } => {
+ // perform a Y-flip here
+ if !surface_origin_is_top_left {
+ let w = fb_rect.width();
+ let h = fb_rect.height();
+ fb_rect.min.x = fb_rect.min.x + rect.min.x;
+ fb_rect.min.y = rect.max.y - fb_rect.max.y;
+ fb_rect.max.x = fb_rect.min.x + w;
+ fb_rect.max.y = fb_rect.min.y + h;
+ }
+ }
+ DrawTarget::Texture { .. } | DrawTarget::External { .. } | DrawTarget::NativeSurface { .. } => (),
+ }
+ fb_rect
+ }
+
+ pub fn surface_origin_is_top_left(&self) -> bool {
+ match *self {
+ DrawTarget::Default { surface_origin_is_top_left, .. } => surface_origin_is_top_left,
+ DrawTarget::Texture { .. } | DrawTarget::External { .. } | DrawTarget::NativeSurface { .. } => true,
+ }
+ }
+
+ /// Given a scissor rect, convert it to the right coordinate space
+ /// depending on the draw target kind. If no scissor rect was supplied,
+ /// returns a scissor rect that encloses the entire render target.
+ pub fn build_scissor_rect(
+ &self,
+ scissor_rect: Option<DeviceIntRect>,
+ ) -> FramebufferIntRect {
+ let dimensions = self.dimensions();
+
+ match scissor_rect {
+ Some(scissor_rect) => match *self {
+ DrawTarget::Default { ref rect, .. } => {
+ self.to_framebuffer_rect(scissor_rect)
+ .intersection(rect)
+ .unwrap_or_else(FramebufferIntRect::zero)
+ }
+ DrawTarget::NativeSurface { offset, .. } => {
+ device_rect_as_framebuffer_rect(&scissor_rect.translate(offset.to_vector()))
+ }
+ DrawTarget::Texture { .. } | DrawTarget::External { .. } => {
+ device_rect_as_framebuffer_rect(&scissor_rect)
+ }
+ }
+ None => {
+ FramebufferIntRect::from_size(
+ device_size_as_framebuffer_size(dimensions),
+ )
+ }
+ }
+ }
+}
+
+/// Contains the parameters necessary to bind a texture-backed read target.
+#[derive(Clone, Copy, Debug)]
+pub enum ReadTarget {
+ /// Use the device's default draw target.
+ Default,
+ /// Use the provided texture,
+ Texture {
+ /// ID of the FBO to read from.
+ fbo_id: FBOId,
+ },
+ /// Use an FBO attached to an external texture.
+ External {
+ fbo: FBOId,
+ },
+ /// An FBO bound to a native (OS compositor) surface
+ NativeSurface {
+ fbo_id: FBOId,
+ offset: DeviceIntPoint,
+ },
+}
+
+impl ReadTarget {
+ pub fn from_texture(
+ texture: &Texture,
+ ) -> Self {
+ ReadTarget::Texture {
+ fbo_id: texture.fbo.unwrap(),
+ }
+ }
+
+ fn offset(&self) -> DeviceIntPoint {
+ match *self {
+ ReadTarget::Default |
+ ReadTarget::Texture { .. } |
+ ReadTarget::External { .. } => {
+ DeviceIntPoint::zero()
+ }
+
+ ReadTarget::NativeSurface { offset, .. } => {
+ offset
+ }
+ }
+ }
+}
+
+impl From<DrawTarget> for ReadTarget {
+ fn from(t: DrawTarget) -> Self {
+ match t {
+ DrawTarget::Default { .. } => {
+ ReadTarget::Default
+ }
+ DrawTarget::NativeSurface { external_fbo_id, offset, .. } => {
+ ReadTarget::NativeSurface {
+ fbo_id: FBOId(external_fbo_id),
+ offset,
+ }
+ }
+ DrawTarget::Texture { fbo_id, .. } => {
+ ReadTarget::Texture { fbo_id }
+ }
+ DrawTarget::External { fbo, .. } => {
+ ReadTarget::External { fbo }
+ }
+ }
+ }
+}
+
+/// Parses the major, release, and patch versions from a GL_VERSION string on
+/// Mali devices. For example, for the version string
+/// "OpenGL ES 3.2 v1.r36p0-01eac0.28ab3a577f105e026887e2b4c93552fb" this
+/// returns Some((1, 36, 0)). Returns None if the version cannot be parsed.
+fn parse_mali_version(version_string: &str) -> Option<(u32, u32, u32)> {
+ let (_prefix, version_string) = version_string.split_once("v")?;
+ let (v_str, version_string) = version_string.split_once(".r")?;
+ let v = v_str.parse().ok()?;
+
+ let (r_str, version_string) = version_string.split_once("p")?;
+ let r = r_str.parse().ok()?;
+
+ let (p_str, _) = version_string.split_once("-")?;
+ let p = p_str.parse().ok()?;
+
+ Some((v, r, p))
+}
+
+impl Device {
+ pub fn new(
+ mut gl: Rc<dyn gl::Gl>,
+ crash_annotator: Option<Box<dyn CrashAnnotator>>,
+ resource_override_path: Option<PathBuf>,
+ use_optimized_shaders: bool,
+ upload_method: UploadMethod,
+ batched_upload_threshold: i32,
+ cached_programs: Option<Rc<ProgramCache>>,
+ allow_texture_storage_support: bool,
+ allow_texture_swizzling: bool,
+ dump_shader_source: Option<String>,
+ surface_origin_is_top_left: bool,
+ panic_on_gl_error: bool,
+ ) -> Device {
+ let mut max_texture_size = [0];
+ unsafe {
+ gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
+ }
+
+ // We cap the max texture size at 16384. Some hardware report higher
+ // capabilities but get very unstable with very large textures.
+ // Bug 1702494 tracks re-evaluating this cap.
+ let max_texture_size = max_texture_size[0].min(16384);
+
+ let renderer_name = gl.get_string(gl::RENDERER);
+ info!("Renderer: {}", renderer_name);
+ let version_string = gl.get_string(gl::VERSION);
+ info!("Version: {}", version_string);
+ info!("Max texture size: {}", max_texture_size);
+
+ let mut extension_count = [0];
+ unsafe {
+ gl.get_integer_v(gl::NUM_EXTENSIONS, &mut extension_count);
+ }
+ let extension_count = extension_count[0] as gl::GLuint;
+ let mut extensions = Vec::new();
+ for i in 0 .. extension_count {
+ extensions.push(gl.get_string_i(gl::EXTENSIONS, i));
+ }
+
+ // On debug builds, assert that each GL call is error-free. We don't do
+ // this on release builds because the synchronous call can stall the
+ // pipeline.
+ let supports_khr_debug = supports_extension(&extensions, "GL_KHR_debug");
+ if panic_on_gl_error || cfg!(debug_assertions) {
+ gl = gl::ErrorReactingGl::wrap(gl, move |gl, name, code| {
+ if supports_khr_debug {
+ Self::log_driver_messages(gl);
+ }
+ error!("Caught GL error {:x} at {}", code, name);
+ panic!("Caught GL error {:x} at {}", code, name);
+ });
+ }
+
+ if supports_extension(&extensions, "GL_ANGLE_provoking_vertex") {
+ gl.provoking_vertex_angle(gl::FIRST_VERTEX_CONVENTION);
+ }
+
+ let supports_texture_usage = supports_extension(&extensions, "GL_ANGLE_texture_usage");
+
+ // Our common-case image data in Firefox is BGRA, so we make an effort
+ // to use BGRA as the internal texture storage format to avoid the need
+ // to swizzle during upload. Currently we only do this on GLES (and thus
+ // for Windows, via ANGLE).
+ //
+ // On Mac, Apple docs [1] claim that BGRA is a more efficient internal
+ // format, but they don't support it with glTextureStorage. As a workaround,
+ // we pretend that it's RGBA8 for the purposes of texture transfers,
+ // but swizzle R with B for the texture sampling.
+ //
+ // We also need our internal format types to be sized, since glTexStorage*
+ // will reject non-sized internal format types.
+ //
+ // Unfortunately, with GL_EXT_texture_format_BGRA8888, BGRA8 is not a
+ // valid internal format (for glTexImage* or glTexStorage*) unless
+ // GL_EXT_texture_storage is also available [2][3], which is usually
+ // not the case on GLES 3 as the latter's functionality has been
+ // included by default but the former has not been updated.
+ // The extension is available on ANGLE, but on Android this usually
+ // means we must fall back to using unsized BGRA and glTexImage*.
+ //
+ // Overall, we have the following factors in play when choosing the formats:
+ // - with glTexStorage, the internal format needs to match the external format,
+ // or the driver would have to do the conversion, which is slow
+ // - on desktop GL, there is no BGRA internal format. However, initializing
+ // the textures with glTexImage as RGBA appears to use BGRA internally,
+ // preferring BGRA external data [4].
+ // - when glTexStorage + BGRA internal format is not supported,
+ // and the external data is BGRA, we have the following options:
+ // 1. use glTexImage with RGBA internal format, this costs us VRAM for mipmaps
+ // 2. use glTexStorage with RGBA internal format, this costs us the conversion by the driver
+ // 3. pretend we are uploading RGBA and set up the swizzling of the texture unit - this costs us batch breaks
+ //
+ // [1] https://developer.apple.com/library/archive/documentation/
+ // GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/
+ // opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW22
+ // [2] https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_format_BGRA8888.txt
+ // [3] https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_storage.txt
+ // [4] http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf
+
+ // On the android emulator glTexImage fails to create textures larger than 3379.
+ // So we must use glTexStorage instead. See bug 1591436.
+ let is_emulator = renderer_name.starts_with("Android Emulator");
+ let avoid_tex_image = is_emulator;
+ let mut gl_version = [0; 2];
+ unsafe {
+ gl.get_integer_v(gl::MAJOR_VERSION, &mut gl_version[0..1]);
+ gl.get_integer_v(gl::MINOR_VERSION, &mut gl_version[1..2]);
+ }
+ info!("GL context {:?} {}.{}", gl.get_type(), gl_version[0], gl_version[1]);
+
+ // We block texture storage on mac because it doesn't support BGRA
+ let supports_texture_storage = allow_texture_storage_support && !cfg!(target_os = "macos") &&
+ match gl.get_type() {
+ gl::GlType::Gl => supports_extension(&extensions, "GL_ARB_texture_storage"),
+ gl::GlType::Gles => true,
+ };
+
+ // The GL_EXT_texture_format_BGRA8888 extension allows us to use BGRA as an internal format
+ // with glTexImage on GLES. However, we can only use BGRA8 as an internal format for
+ // glTexStorage when GL_EXT_texture_storage is also explicitly supported. This is because
+ // glTexStorage was added in GLES 3, but GL_EXT_texture_format_BGRA8888 was written against
+ // GLES 2 and GL_EXT_texture_storage.
+ // To complicate things even further, some Intel devices claim to support both extensions
+ // but in practice do not allow BGRA to be used with glTexStorage.
+ let supports_gles_bgra = supports_extension(&extensions, "GL_EXT_texture_format_BGRA8888");
+ let supports_texture_storage_with_gles_bgra = supports_gles_bgra
+ && supports_extension(&extensions, "GL_EXT_texture_storage")
+ && !renderer_name.starts_with("Intel(R) HD Graphics for BayTrail")
+ && !renderer_name.starts_with("Intel(R) HD Graphics for Atom(TM) x5/x7");
+
+ let supports_texture_swizzle = allow_texture_swizzling &&
+ match gl.get_type() {
+ // see https://www.g-truc.net/post-0734.html
+ gl::GlType::Gl => gl_version >= [3, 3] ||
+ supports_extension(&extensions, "GL_ARB_texture_swizzle"),
+ gl::GlType::Gles => true,
+ };
+
+ let (color_formats, bgra_formats, bgra_pixel_type, bgra8_sampling_swizzle, texture_storage_usage) = match gl.get_type() {
+ // There is `glTexStorage`, use it and expect RGBA on the input.
+ gl::GlType::Gl if supports_texture_storage && supports_texture_swizzle => (
+ TextureFormatPair::from(ImageFormat::RGBA8),
+ TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA },
+ gl::UNSIGNED_BYTE,
+ Swizzle::Bgra, // pretend it's RGBA, rely on swizzling
+ TexStorageUsage::Always
+ ),
+ // There is no `glTexStorage`, upload as `glTexImage` with BGRA input.
+ gl::GlType::Gl => (
+ TextureFormatPair { internal: ImageFormat::BGRA8, external: ImageFormat::BGRA8 },
+ TextureFormatPair { internal: gl::RGBA, external: gl::BGRA },
+ gl::UNSIGNED_INT_8_8_8_8_REV,
+ Swizzle::Rgba, // converted on uploads by the driver, no swizzling needed
+ TexStorageUsage::Never
+ ),
+ // glTexStorage is always supported in GLES 3, but because the GL_EXT_texture_storage
+ // extension is supported we can use glTexStorage with BGRA8 as the internal format.
+ // Prefer BGRA textures over RGBA.
+ gl::GlType::Gles if supports_texture_storage_with_gles_bgra => (
+ TextureFormatPair::from(ImageFormat::BGRA8),
+ TextureFormatPair { internal: gl::BGRA8_EXT, external: gl::BGRA_EXT },
+ gl::UNSIGNED_BYTE,
+ Swizzle::Rgba, // no conversion needed
+ TexStorageUsage::Always,
+ ),
+ // BGRA is not supported as an internal format with glTexStorage, therefore we will
+ // use RGBA textures instead and pretend BGRA data is RGBA when uploading.
+ // The swizzling will happen at the texture unit.
+ gl::GlType::Gles if supports_texture_swizzle => (
+ TextureFormatPair::from(ImageFormat::RGBA8),
+ TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA },
+ gl::UNSIGNED_BYTE,
+ Swizzle::Bgra, // pretend it's RGBA, rely on swizzling
+ TexStorageUsage::Always,
+ ),
+ // BGRA is not supported as an internal format with glTexStorage, and we cannot use
+ // swizzling either. Therefore prefer BGRA textures over RGBA, but use glTexImage
+ // to initialize BGRA textures. glTexStorage can still be used for other formats.
+ gl::GlType::Gles if supports_gles_bgra && !avoid_tex_image => (
+ TextureFormatPair::from(ImageFormat::BGRA8),
+ TextureFormatPair::from(gl::BGRA_EXT),
+ gl::UNSIGNED_BYTE,
+ Swizzle::Rgba, // no conversion needed
+ TexStorageUsage::NonBGRA8,
+ ),
+ // Neither BGRA or swizzling are supported. GLES does not allow format conversion
+ // during upload so we must use RGBA textures and pretend BGRA data is RGBA when
+ // uploading. Images may be rendered incorrectly as a result.
+ gl::GlType::Gles => {
+ warn!("Neither BGRA or texture swizzling are supported. Images may be rendered incorrectly.");
+ (
+ TextureFormatPair::from(ImageFormat::RGBA8),
+ TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA },
+ gl::UNSIGNED_BYTE,
+ Swizzle::Rgba,
+ TexStorageUsage::Always,
+ )
+ }
+ };
+
+ let is_software_webrender = renderer_name.starts_with("Software WebRender");
+ let upload_method = if is_software_webrender {
+ // Uploads in SWGL generally reduce to simple memory copies.
+ UploadMethod::Immediate
+ } else {
+ upload_method
+ };
+ // Prefer 24-bit depth format. While 16-bit depth also works, it may exhaust depth ids easily.
+ let depth_format = gl::DEPTH_COMPONENT24;
+
+ info!("GL texture cache {:?}, bgra {:?} swizzle {:?}, texture storage {:?}, depth {:?}",
+ color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage, depth_format);
+
+ // On Mali-T devices glCopyImageSubData appears to stall the pipeline until any pending
+ // renders to the source texture have completed. On Mali-G, it has been observed to
+ // indefinitely hang in some circumstances. Using an alternative such as glBlitFramebuffer
+ // is preferable on such devices, so pretend we don't support glCopyImageSubData.
+ // See bugs 1669494 and 1677757.
+ let supports_copy_image_sub_data = if renderer_name.starts_with("Mali") {
+ false
+ } else {
+ supports_extension(&extensions, "GL_EXT_copy_image") ||
+ supports_extension(&extensions, "GL_ARB_copy_image")
+ };
+
+ // We have seen crashes on x86 PowerVR Rogue G6430 devices during GPU cache
+ // updates using the scatter shader. It seems likely that GL_EXT_color_buffer_float
+ // is broken. See bug 1709408.
+ let is_x86_powervr_rogue_g6430 = renderer_name.starts_with("PowerVR Rogue G6430")
+ && cfg!(target_arch = "x86");
+ let supports_color_buffer_float = match gl.get_type() {
+ gl::GlType::Gl => true,
+ gl::GlType::Gles if is_x86_powervr_rogue_g6430 => false,
+ gl::GlType::Gles => supports_extension(&extensions, "GL_EXT_color_buffer_float"),
+ };
+
+ let is_adreno = renderer_name.starts_with("Adreno");
+
+ // There appears to be a driver bug on older versions of the Adreno
+ // driver which prevents usage of persistenly mapped buffers.
+ // See bugs 1678585 and 1683936.
+ // TODO: only disable feature for affected driver versions.
+ let supports_buffer_storage = if is_adreno {
+ false
+ } else {
+ supports_extension(&extensions, "GL_EXT_buffer_storage") ||
+ supports_extension(&extensions, "GL_ARB_buffer_storage")
+ };
+
+ // KHR_blend_equation_advanced renders incorrectly on Adreno
+ // devices. This has only been confirmed up to Adreno 5xx, and has been
+ // fixed for Android 9, so this condition could be made more specific.
+ let supports_advanced_blend_equation =
+ supports_extension(&extensions, "GL_KHR_blend_equation_advanced") &&
+ !is_adreno;
+
+ let supports_dual_source_blending = match gl.get_type() {
+ gl::GlType::Gl => supports_extension(&extensions,"GL_ARB_blend_func_extended") &&
+ supports_extension(&extensions,"GL_ARB_explicit_attrib_location"),
+ gl::GlType::Gles => supports_extension(&extensions,"GL_EXT_blend_func_extended"),
+ };
+
+ // Software webrender relies on the unoptimized shader source.
+ let use_optimized_shaders = use_optimized_shaders && !is_software_webrender;
+
+ // On the android emulator, and possibly some Mali devices, glShaderSource
+ // can crash if the source strings are not null-terminated.
+ // See bug 1591945 and bug 1799722.
+ let requires_null_terminated_shader_source = is_emulator || renderer_name == "Mali-T628"
+ || renderer_name == "Mali-T720" || renderer_name == "Mali-T760";
+
+ // The android emulator gets confused if you don't explicitly unbind any texture
+ // from GL_TEXTURE_EXTERNAL_OES before binding another to GL_TEXTURE_2D. See bug 1636085.
+ let requires_texture_external_unbind = is_emulator;
+
+ let is_macos = cfg!(target_os = "macos");
+ // && renderer_name.starts_with("AMD");
+ // (XXX: we apply this restriction to all GPUs to handle switching)
+
+ let is_windows_angle = cfg!(target_os = "windows")
+ && renderer_name.starts_with("ANGLE");
+ let is_adreno_3xx = renderer_name.starts_with("Adreno (TM) 3");
+
+ // Some GPUs require the stride of the data during texture uploads to be
+ // aligned to certain requirements, either for correctness or performance
+ // reasons.
+ let required_pbo_stride = if is_adreno_3xx {
+ // On Adreno 3xx, alignments of < 128 bytes can result in corrupted
+ // glyphs. See bug 1696039.
+ StrideAlignment::Bytes(NonZeroUsize::new(128).unwrap())
+ } else if is_adreno {
+ // On later Adreno devices it must be a multiple of 64 *pixels* to
+ // hit the fast path, meaning value in bytes varies with the texture
+ // format. This is purely an optimization.
+ StrideAlignment::Pixels(NonZeroUsize::new(64).unwrap())
+ } else if is_macos {
+ // On AMD Mac, it must always be a multiple of 256 bytes.
+ // We apply this restriction to all GPUs to handle switching
+ StrideAlignment::Bytes(NonZeroUsize::new(256).unwrap())
+ } else if is_windows_angle {
+ // On ANGLE-on-D3D, PBO texture uploads get incorrectly truncated
+ // if the stride is greater than the width * bpp.
+ StrideAlignment::Bytes(NonZeroUsize::new(1).unwrap())
+ } else {
+ // Other platforms may have similar requirements and should be added
+ // here. The default value should be 4 bytes.
+ StrideAlignment::Bytes(NonZeroUsize::new(4).unwrap())
+ };
+
+ // On AMD Macs there is a driver bug which causes some texture uploads
+ // from a non-zero offset within a PBO to fail. See bug 1603783.
+ let supports_nonzero_pbo_offsets = !is_macos;
+
+ // We have encountered several issues when only partially updating render targets on a
+ // variety of Mali GPUs. As a precaution avoid doing so on all Midgard and Bifrost GPUs.
+ // Valhall (eg Mali-Gx7 onwards) appears to be unnaffected. See bug 1691955, bug 1558374,
+ // and bug 1663355.
+ let supports_render_target_partial_update = !(renderer_name.starts_with("Mali-T")
+ || renderer_name == "Mali-G31"
+ || renderer_name == "Mali-G51"
+ || renderer_name == "Mali-G71"
+ || renderer_name == "Mali-G52"
+ || renderer_name == "Mali-G72"
+ || renderer_name == "Mali-G76");
+
+ let supports_shader_storage_object = match gl.get_type() {
+ // see https://www.g-truc.net/post-0734.html
+ gl::GlType::Gl => supports_extension(&extensions, "GL_ARB_shader_storage_buffer_object"),
+ gl::GlType::Gles => gl_version >= [3, 1],
+ };
+
+ // SWGL uses swgl_clipMask() instead of implementing clip-masking in shaders.
+ // This allows certain shaders to potentially bypass the more expensive alpha-
+ // pass variants if they know the alpha-pass was only required to deal with
+ // clip-masking.
+ let uses_native_clip_mask = is_software_webrender;
+
+ // SWGL uses swgl_antiAlias() instead of implementing anti-aliasing in shaders.
+ // As above, this allows bypassing certain alpha-pass variants.
+ let uses_native_antialiasing = is_software_webrender;
+
+ // If running on android with a mesa driver (eg intel chromebooks), parse the mesa version.
+ let mut android_mesa_version = None;
+ if cfg!(target_os = "android") && renderer_name.starts_with("Mesa") {
+ if let Some((_, mesa_version)) = version_string.split_once("Mesa ") {
+ if let Some((major_str, _)) = mesa_version.split_once(".") {
+ if let Ok(major) = major_str.parse::<i32>() {
+ android_mesa_version = Some(major);
+ }
+ }
+ }
+ }
+
+ // If the device supports OES_EGL_image_external_essl3 we can use it to render
+ // external images. If not, we must use the ESSL 1.0 OES_EGL_image_external
+ // extension instead.
+ // Mesa versions prior to 20.0 do not implement textureSize(samplerExternalOES),
+ // so we must use the fallback path.
+ let supports_image_external_essl3 = match android_mesa_version {
+ Some(major) if major < 20 => false,
+ _ => supports_extension(&extensions, "GL_OES_EGL_image_external_essl3"),
+ };
+
+ let mut requires_batched_texture_uploads = None;
+ if is_software_webrender {
+ // No benefit to batching texture uploads with swgl.
+ requires_batched_texture_uploads = Some(false);
+ } else if renderer_name.starts_with("Mali-G") {
+ // On Mali-Gxx the driver really struggles with many small texture uploads,
+ // and handles fewer, larger uploads better.
+ requires_batched_texture_uploads = Some(true);
+ }
+
+ // On Mali-Txxx devices we have observed crashes during draw calls when rendering
+ // to an alpha target immediately after using glClear to clear regions of it.
+ // Using a shader to clear the regions avoids the crash. See bug 1638593.
+ let supports_alpha_target_clears = !renderer_name.starts_with("Mali-T");
+
+ // On Adreno 4xx devices with older drivers we have seen render tasks to alpha targets have
+ // no effect unless the target is fully cleared prior to rendering. See bug 1714227.
+ let is_adreno_4xx = renderer_name.starts_with("Adreno (TM) 4");
+ let requires_alpha_target_full_clear = is_adreno_4xx;
+
+ // Testing on Intel and nVidia GPUs, as well as software webrender, showed large performance
+ // wins applying a scissor rect when clearing render targets. Assume this is the best
+ // default. On mobile GPUs, however, it can be much more efficient to clear the entire
+ // render target. For now, enable the scissor everywhere except Android hardware
+ // webrender. We can tweak this further if needs be.
+ let prefers_clear_scissor = !cfg!(target_os = "android") || is_software_webrender;
+
+ let mut supports_render_target_invalidate = true;
+
+ // On PowerVR Rogue devices we have seen that invalidating render targets after we are done
+ // with them can incorrectly cause pending renders to be written to different targets
+ // instead. See bug 1719345.
+ let is_powervr_rogue = renderer_name.starts_with("PowerVR Rogue");
+ if is_powervr_rogue {
+ supports_render_target_invalidate = false;
+ }
+
+ // On Mali Valhall devices with a driver version v1.r36p0 we have seen that invalidating
+ // render targets can result in image corruption, perhaps due to subsequent reuses of the
+ // render target not correctly reinitializing them to a valid state. See bug 1787520.
+ if renderer_name.starts_with("Mali-G77")
+ || renderer_name.starts_with("Mali-G78")
+ || renderer_name.starts_with("Mali-G710")
+ {
+ match parse_mali_version(&version_string) {
+ Some(version) if version >= (1, 36, 0) => supports_render_target_invalidate = false,
+ _ => {}
+ }
+ }
+
+ // On Linux we we have seen uploads to R8 format textures result in
+ // corruption on some AMD cards.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1687554#c13
+ let supports_r8_texture_upload = if cfg!(target_os = "linux")
+ && renderer_name.starts_with("AMD Radeon RX")
+ {
+ false
+ } else {
+ true
+ };
+
+ // We have encountered rendering errors on a variety of Adreno GPUs specifically on driver
+ // version V@0490, so block this extension on that driver version.
+ let supports_qcom_tiled_rendering =
+ supports_extension(&extensions, "GL_QCOM_tiled_rendering")
+ && !(is_adreno && version_string.contains("V@0490"));
+
+ // On some Adreno 3xx devices the vertex array object must be unbound and rebound after
+ // an attached buffer has been orphaned.
+ let requires_vao_rebind_after_orphaning = is_adreno_3xx;
+
+ Device {
+ gl,
+ base_gl: None,
+ crash_annotator,
+ annotate_draw_call_crashes: false,
+ resource_override_path,
+ use_optimized_shaders,
+ upload_method,
+ use_batched_texture_uploads: requires_batched_texture_uploads.unwrap_or(false),
+ use_draw_calls_for_texture_copy: false,
+ batched_upload_threshold,
+
+ inside_frame: false,
+
+ capabilities: Capabilities {
+ supports_multisampling: false, //TODO
+ supports_copy_image_sub_data,
+ supports_color_buffer_float,
+ supports_buffer_storage,
+ supports_advanced_blend_equation,
+ supports_dual_source_blending,
+ supports_khr_debug,
+ supports_texture_swizzle,
+ supports_nonzero_pbo_offsets,
+ supports_texture_usage,
+ supports_render_target_partial_update,
+ supports_shader_storage_object,
+ requires_batched_texture_uploads,
+ supports_alpha_target_clears,
+ requires_alpha_target_full_clear,
+ prefers_clear_scissor,
+ supports_render_target_invalidate,
+ supports_r8_texture_upload,
+ supports_qcom_tiled_rendering,
+ uses_native_clip_mask,
+ uses_native_antialiasing,
+ supports_image_external_essl3,
+ requires_vao_rebind_after_orphaning,
+ renderer_name,
+ },
+
+ color_formats,
+ bgra_formats,
+ bgra_pixel_type,
+ swizzle_settings: SwizzleSettings {
+ bgra8_sampling_swizzle,
+ },
+ depth_format,
+
+ depth_targets: FastHashMap::default(),
+
+ bound_textures: [0; 16],
+ bound_program: 0,
+ bound_program_name: Rc::new(std::ffi::CString::new("").unwrap()),
+ bound_vao: 0,
+ bound_read_fbo: (FBOId(0), DeviceIntPoint::zero()),
+ bound_draw_fbo: FBOId(0),
+ program_mode_id: UniformLocation::INVALID,
+ default_read_fbo: FBOId(0),
+ default_draw_fbo: FBOId(0),
+
+ depth_available: true,
+
+ max_texture_size,
+ cached_programs,
+ frame_id: GpuFrameId(0),
+ extensions,
+ texture_storage_usage,
+ requires_null_terminated_shader_source,
+ requires_texture_external_unbind,
+ is_software_webrender,
+ required_pbo_stride,
+ dump_shader_source,
+ surface_origin_is_top_left,
+
+ #[cfg(debug_assertions)]
+ shader_is_ready: false,
+
+ textures_created: 0,
+ textures_deleted: 0,
+ }
+ }
+
+ pub fn gl(&self) -> &dyn gl::Gl {
+ &*self.gl
+ }
+
+ pub fn rc_gl(&self) -> &Rc<dyn gl::Gl> {
+ &self.gl
+ }
+
+ pub fn set_parameter(&mut self, param: &Parameter) {
+ match param {
+ Parameter::Bool(BoolParameter::PboUploads, enabled) => {
+ if !self.is_software_webrender {
+ self.upload_method = if *enabled {
+ UploadMethod::PixelBuffer(crate::ONE_TIME_USAGE_HINT)
+ } else {
+ UploadMethod::Immediate
+ };
+ }
+ }
+ Parameter::Bool(BoolParameter::BatchedUploads, enabled) => {
+ self.use_batched_texture_uploads = *enabled;
+ }
+ Parameter::Bool(BoolParameter::DrawCallsForTextureCopy, enabled) => {
+ if self.capabilities.requires_batched_texture_uploads.is_none() {
+ self.use_draw_calls_for_texture_copy = *enabled;
+ }
+ }
+ Parameter::Int(IntParameter::BatchedUploadThreshold, threshold) => {
+ self.batched_upload_threshold = *threshold;
+ }
+ _ => {}
+ }
+ }
+
+ /// Ensures that the maximum texture size is less than or equal to the
+ /// provided value. If the provided value is less than the value supported
+ /// by the driver, the latter is used.
+ pub fn clamp_max_texture_size(&mut self, size: i32) {
+ self.max_texture_size = self.max_texture_size.min(size);
+ }
+
+ /// Returns the limit on texture dimensions (width or height).
+ pub fn max_texture_size(&self) -> i32 {
+ self.max_texture_size
+ }
+
+ pub fn surface_origin_is_top_left(&self) -> bool {
+ self.surface_origin_is_top_left
+ }
+
+ pub fn get_capabilities(&self) -> &Capabilities {
+ &self.capabilities
+ }
+
+ pub fn preferred_color_formats(&self) -> TextureFormatPair<ImageFormat> {
+ self.color_formats.clone()
+ }
+
+ pub fn swizzle_settings(&self) -> Option<SwizzleSettings> {
+ if self.capabilities.supports_texture_swizzle {
+ Some(self.swizzle_settings)
+ } else {
+ None
+ }
+ }
+
+ pub fn depth_bits(&self) -> i32 {
+ match self.depth_format {
+ gl::DEPTH_COMPONENT16 => 16,
+ gl::DEPTH_COMPONENT24 => 24,
+ _ => panic!("Unknown depth format {:?}", self.depth_format),
+ }
+ }
+
+ // See gpu_types.rs where we declare the number of possible documents and
+ // number of items per document. This should match up with that.
+ pub fn max_depth_ids(&self) -> i32 {
+ return 1 << (self.depth_bits() - RESERVE_DEPTH_BITS);
+ }
+
+ pub fn ortho_near_plane(&self) -> f32 {
+ return -self.max_depth_ids() as f32;
+ }
+
+ pub fn ortho_far_plane(&self) -> f32 {
+ return (self.max_depth_ids() - 1) as f32;
+ }
+
+ pub fn required_pbo_stride(&self) -> StrideAlignment {
+ self.required_pbo_stride
+ }
+
+ pub fn upload_method(&self) -> &UploadMethod {
+ &self.upload_method
+ }
+
+ pub fn use_batched_texture_uploads(&self) -> bool {
+ self.use_batched_texture_uploads
+ }
+
+ pub fn use_draw_calls_for_texture_copy(&self) -> bool {
+ self.use_draw_calls_for_texture_copy
+ }
+
+ pub fn batched_upload_threshold(&self) -> i32 {
+ self.batched_upload_threshold
+ }
+
+ pub fn reset_state(&mut self) {
+ for i in 0 .. self.bound_textures.len() {
+ self.bound_textures[i] = 0;
+ self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
+ self.gl.bind_texture(gl::TEXTURE_2D, 0);
+ }
+
+ self.bound_vao = 0;
+ self.gl.bind_vertex_array(0);
+
+ self.bound_read_fbo = (self.default_read_fbo, DeviceIntPoint::zero());
+ self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.default_read_fbo.0);
+
+ self.bound_draw_fbo = self.default_draw_fbo;
+ self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.bound_draw_fbo.0);
+ }
+
+ #[cfg(debug_assertions)]
+ fn print_shader_errors(source: &str, log: &str) {
+ // hacky way to extract the offending lines
+ if !log.starts_with("0:") && !log.starts_with("0(") {
+ return;
+ }
+ let end_pos = match log[2..].chars().position(|c| !c.is_digit(10)) {
+ Some(pos) => 2 + pos,
+ None => return,
+ };
+ let base_line_number = match log[2 .. end_pos].parse::<usize>() {
+ Ok(number) if number >= 2 => number - 2,
+ _ => return,
+ };
+ for (line, prefix) in source.lines().skip(base_line_number).zip(&["|",">","|"]) {
+ error!("{}\t{}", prefix, line);
+ }
+ }
+
+ pub fn compile_shader(
+ &self,
+ name: &str,
+ shader_type: gl::GLenum,
+ source: &String,
+ ) -> Result<gl::GLuint, ShaderError> {
+ debug!("compile {}", name);
+ let id = self.gl.create_shader(shader_type);
+
+ let mut new_source = Cow::from(source.as_str());
+ // Ensure the source strings we pass to glShaderSource are
+ // null-terminated on buggy platforms.
+ if self.requires_null_terminated_shader_source {
+ new_source.to_mut().push('\0');
+ }
+
+ self.gl.shader_source(id, &[new_source.as_bytes()]);
+ self.gl.compile_shader(id);
+ let log = self.gl.get_shader_info_log(id);
+ let mut status = [0];
+ unsafe {
+ self.gl.get_shader_iv(id, gl::COMPILE_STATUS, &mut status);
+ }
+ if status[0] == 0 {
+ let type_str = match shader_type {
+ gl::VERTEX_SHADER => "vertex",
+ gl::FRAGMENT_SHADER => "fragment",
+ _ => panic!("Unexpected shader type {:x}", shader_type),
+ };
+ error!("Failed to compile {} shader: {}\n{}", type_str, name, log);
+ #[cfg(debug_assertions)]
+ Self::print_shader_errors(source, &log);
+ Err(ShaderError::Compilation(name.to_string(), log))
+ } else {
+ if !log.is_empty() {
+ warn!("Warnings detected on shader: {}\n{}", name, log);
+ }
+ Ok(id)
+ }
+ }
+
+ pub fn begin_frame(&mut self) -> GpuFrameId {
+ debug_assert!(!self.inside_frame);
+ self.inside_frame = true;
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = false;
+ }
+
+ self.textures_created = 0;
+ self.textures_deleted = 0;
+
+ // If our profiler state has changed, apply or remove the profiling
+ // wrapper from our GL context.
+ let being_profiled = profiler::thread_is_being_profiled();
+ let using_wrapper = self.base_gl.is_some();
+
+ // We can usually unwind driver stacks on x86 so we don't need to manually instrument
+ // gl calls there. Timestamps can be pretty expensive on Windows (2us each and perhaps
+ // an opportunity to be descheduled?) which makes the profiles gathered with this
+ // turned on less useful so only profile on ARM.
+ if cfg!(any(target_arch = "arm", target_arch = "aarch64"))
+ && being_profiled
+ && !using_wrapper
+ {
+ fn note(name: &str, duration: Duration) {
+ profiler::add_text_marker("OpenGL Calls", name, duration);
+ }
+ let threshold = Duration::from_millis(1);
+ let wrapped = gl::ProfilingGl::wrap(self.gl.clone(), threshold, note);
+ let base = mem::replace(&mut self.gl, wrapped);
+ self.base_gl = Some(base);
+ } else if !being_profiled && using_wrapper {
+ self.gl = self.base_gl.take().unwrap();
+ }
+
+ // Retrieve the currently set FBO.
+ let mut default_read_fbo = [0];
+ unsafe {
+ self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut default_read_fbo);
+ }
+ self.default_read_fbo = FBOId(default_read_fbo[0] as gl::GLuint);
+ let mut default_draw_fbo = [0];
+ unsafe {
+ self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut default_draw_fbo);
+ }
+ self.default_draw_fbo = FBOId(default_draw_fbo[0] as gl::GLuint);
+
+ // Shader state
+ self.bound_program = 0;
+ self.program_mode_id = UniformLocation::INVALID;
+ self.gl.use_program(0);
+
+ // Reset common state
+ self.reset_state();
+
+ // Pixel op state
+ self.gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1);
+ self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+
+ // Default is sampler 0, always
+ self.gl.active_texture(gl::TEXTURE0);
+
+ self.frame_id
+ }
+
+ fn bind_texture_impl(
+ &mut self,
+ slot: TextureSlot,
+ id: gl::GLuint,
+ target: gl::GLenum,
+ set_swizzle: Option<Swizzle>,
+ image_rendering: Option<ImageRendering>,
+ ) {
+ debug_assert!(self.inside_frame);
+
+ if self.bound_textures[slot.0] != id || set_swizzle.is_some() || image_rendering.is_some() {
+ self.gl.active_texture(gl::TEXTURE0 + slot.0 as gl::GLuint);
+ // The android emulator gets confused if you don't explicitly unbind any texture
+ // from GL_TEXTURE_EXTERNAL_OES before binding to GL_TEXTURE_2D. See bug 1636085.
+ if target == gl::TEXTURE_2D && self.requires_texture_external_unbind {
+ self.gl.bind_texture(gl::TEXTURE_EXTERNAL_OES, 0);
+ }
+ self.gl.bind_texture(target, id);
+ if let Some(swizzle) = set_swizzle {
+ if self.capabilities.supports_texture_swizzle {
+ let components = match swizzle {
+ Swizzle::Rgba => [gl::RED, gl::GREEN, gl::BLUE, gl::ALPHA],
+ Swizzle::Bgra => [gl::BLUE, gl::GREEN, gl::RED, gl::ALPHA],
+ };
+ self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_R, components[0] as i32);
+ self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_G, components[1] as i32);
+ self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_B, components[2] as i32);
+ self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_A, components[3] as i32);
+ } else {
+ debug_assert_eq!(swizzle, Swizzle::default());
+ }
+ }
+ if let Some(image_rendering) = image_rendering {
+ let filter = match image_rendering {
+ ImageRendering::Auto | ImageRendering::CrispEdges => gl::LINEAR,
+ ImageRendering::Pixelated => gl::NEAREST,
+ };
+ self.gl.tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, filter as i32);
+ self.gl.tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, filter as i32);
+ }
+ self.gl.active_texture(gl::TEXTURE0);
+ self.bound_textures[slot.0] = id;
+ }
+ }
+
+ pub fn bind_texture<S>(&mut self, slot: S, texture: &Texture, swizzle: Swizzle)
+ where
+ S: Into<TextureSlot>,
+ {
+ let old_swizzle = texture.active_swizzle.replace(swizzle);
+ let set_swizzle = if old_swizzle != swizzle {
+ Some(swizzle)
+ } else {
+ None
+ };
+ self.bind_texture_impl(slot.into(), texture.id, texture.target, set_swizzle, None);
+ }
+
+ pub fn bind_external_texture<S>(&mut self, slot: S, external_texture: &ExternalTexture)
+ where
+ S: Into<TextureSlot>,
+ {
+ self.bind_texture_impl(
+ slot.into(),
+ external_texture.id,
+ external_texture.target,
+ None,
+ Some(external_texture.image_rendering),
+ );
+ }
+
+ pub fn bind_read_target_impl(
+ &mut self,
+ fbo_id: FBOId,
+ offset: DeviceIntPoint,
+ ) {
+ debug_assert!(self.inside_frame);
+
+ if self.bound_read_fbo != (fbo_id, offset) {
+ fbo_id.bind(self.gl(), FBOTarget::Read);
+ }
+
+ self.bound_read_fbo = (fbo_id, offset);
+ }
+
+ pub fn bind_read_target(&mut self, target: ReadTarget) {
+ let fbo_id = match target {
+ ReadTarget::Default => self.default_read_fbo,
+ ReadTarget::Texture { fbo_id } => fbo_id,
+ ReadTarget::External { fbo } => fbo,
+ ReadTarget::NativeSurface { fbo_id, .. } => fbo_id,
+ };
+
+ self.bind_read_target_impl(fbo_id, target.offset())
+ }
+
+ fn bind_draw_target_impl(&mut self, fbo_id: FBOId) {
+ debug_assert!(self.inside_frame);
+
+ if self.bound_draw_fbo != fbo_id {
+ self.bound_draw_fbo = fbo_id;
+ fbo_id.bind(self.gl(), FBOTarget::Draw);
+ }
+ }
+
+ pub fn reset_read_target(&mut self) {
+ let fbo = self.default_read_fbo;
+ self.bind_read_target_impl(fbo, DeviceIntPoint::zero());
+ }
+
+
+ pub fn reset_draw_target(&mut self) {
+ let fbo = self.default_draw_fbo;
+ self.bind_draw_target_impl(fbo);
+ self.depth_available = true;
+ }
+
+ pub fn bind_draw_target(
+ &mut self,
+ target: DrawTarget,
+ ) {
+ let (fbo_id, rect, depth_available) = match target {
+ DrawTarget::Default { rect, .. } => {
+ (self.default_draw_fbo, rect, false)
+ }
+ DrawTarget::Texture { dimensions, fbo_id, with_depth, .. } => {
+ let rect = FramebufferIntRect::from_size(
+ device_size_as_framebuffer_size(dimensions),
+ );
+ (fbo_id, rect, with_depth)
+ },
+ DrawTarget::External { fbo, size } => {
+ (fbo, size.into(), false)
+ }
+ DrawTarget::NativeSurface { external_fbo_id, offset, dimensions, .. } => {
+ (
+ FBOId(external_fbo_id),
+ device_rect_as_framebuffer_rect(&DeviceIntRect::from_origin_and_size(offset, dimensions)),
+ true
+ )
+ }
+ };
+
+ self.depth_available = depth_available;
+ self.bind_draw_target_impl(fbo_id);
+ self.gl.viewport(
+ rect.min.x,
+ rect.min.y,
+ rect.width(),
+ rect.height(),
+ );
+ }
+
+ /// Creates an unbound FBO object. Additional attachment API calls are
+ /// required to make it complete.
+ pub fn create_fbo(&mut self) -> FBOId {
+ FBOId(self.gl.gen_framebuffers(1)[0])
+ }
+
+ /// Creates an FBO with the given texture bound as the color attachment.
+ pub fn create_fbo_for_external_texture(&mut self, texture_id: u32) -> FBOId {
+ let fbo = self.create_fbo();
+ fbo.bind(self.gl(), FBOTarget::Draw);
+ self.gl.framebuffer_texture_2d(
+ gl::DRAW_FRAMEBUFFER,
+ gl::COLOR_ATTACHMENT0,
+ gl::TEXTURE_2D,
+ texture_id,
+ 0,
+ );
+ debug_assert_eq!(
+ self.gl.check_frame_buffer_status(gl::DRAW_FRAMEBUFFER),
+ gl::FRAMEBUFFER_COMPLETE,
+ "Incomplete framebuffer",
+ );
+ self.bound_draw_fbo.bind(self.gl(), FBOTarget::Draw);
+ fbo
+ }
+
+ pub fn delete_fbo(&mut self, fbo: FBOId) {
+ self.gl.delete_framebuffers(&[fbo.0]);
+ }
+
+ pub fn bind_external_draw_target(&mut self, fbo_id: FBOId) {
+ debug_assert!(self.inside_frame);
+
+ if self.bound_draw_fbo != fbo_id {
+ self.bound_draw_fbo = fbo_id;
+ fbo_id.bind(self.gl(), FBOTarget::Draw);
+ }
+ }
+
+ /// Link a program, attaching the supplied vertex format.
+ ///
+ /// If `create_program()` finds a binary shader on disk, it will kick
+ /// off linking immediately, which some drivers (notably ANGLE) run
+ /// in parallel on background threads. As such, this function should
+ /// ideally be run sometime later, to give the driver time to do that
+ /// before blocking due to an API call accessing the shader.
+ ///
+ /// This generally means that the first run of the application will have
+ /// to do a bunch of blocking work to compile the shader from source, but
+ /// subsequent runs should load quickly.
+ pub fn link_program(
+ &mut self,
+ program: &mut Program,
+ descriptor: &VertexDescriptor,
+ ) -> Result<(), ShaderError> {
+ profile_scope!("compile shader");
+
+ let _guard = CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::CompileShader,
+ &program.source_info.full_name_cstr
+ );
+
+ assert!(!program.is_initialized());
+ let mut build_program = true;
+ let info = &program.source_info;
+
+ // See if we hit the binary shader cache
+ if let Some(ref cached_programs) = self.cached_programs {
+ // If the shader is not in the cache, attempt to load it from disk
+ if cached_programs.entries.borrow().get(&program.source_info.digest).is_none() {
+ if let Some(ref handler) = cached_programs.program_cache_handler {
+ handler.try_load_shader_from_disk(&program.source_info.digest, cached_programs);
+ if let Some(entry) = cached_programs.entries.borrow().get(&program.source_info.digest) {
+ self.gl.program_binary(program.id, entry.binary.format, &entry.binary.bytes);
+ }
+ }
+ }
+
+ if let Some(entry) = cached_programs.entries.borrow_mut().get_mut(&info.digest) {
+ let mut link_status = [0];
+ unsafe {
+ self.gl.get_program_iv(program.id, gl::LINK_STATUS, &mut link_status);
+ }
+ if link_status[0] == 0 {
+ let error_log = self.gl.get_program_info_log(program.id);
+ error!(
+ "Failed to load a program object with a program binary: {} renderer {}\n{}",
+ &info.base_filename,
+ self.capabilities.renderer_name,
+ error_log
+ );
+ if let Some(ref program_cache_handler) = cached_programs.program_cache_handler {
+ program_cache_handler.notify_program_binary_failed(&entry.binary);
+ }
+ } else {
+ entry.linked = true;
+ build_program = false;
+ }
+ }
+ }
+
+ // If not, we need to do a normal compile + link pass.
+ if build_program {
+ // Compile the vertex shader
+ let vs_source = info.compute_source(self, ShaderKind::Vertex);
+ let vs_id = match self.compile_shader(&info.full_name(), gl::VERTEX_SHADER, &vs_source) {
+ Ok(vs_id) => vs_id,
+ Err(err) => return Err(err),
+ };
+
+ // Compile the fragment shader
+ let fs_source = info.compute_source(self, ShaderKind::Fragment);
+ let fs_id =
+ match self.compile_shader(&info.full_name(), gl::FRAGMENT_SHADER, &fs_source) {
+ Ok(fs_id) => fs_id,
+ Err(err) => {
+ self.gl.delete_shader(vs_id);
+ return Err(err);
+ }
+ };
+
+ // Check if shader source should be dumped
+ if Some(info.base_filename) == self.dump_shader_source.as_ref().map(String::as_ref) {
+ let path = std::path::Path::new(info.base_filename);
+ std::fs::write(path.with_extension("vert"), vs_source).unwrap();
+ std::fs::write(path.with_extension("frag"), fs_source).unwrap();
+ }
+
+ // Attach shaders
+ self.gl.attach_shader(program.id, vs_id);
+ self.gl.attach_shader(program.id, fs_id);
+
+ // Bind vertex attributes
+ for (i, attr) in descriptor
+ .vertex_attributes
+ .iter()
+ .chain(descriptor.instance_attributes.iter())
+ .enumerate()
+ {
+ self.gl
+ .bind_attrib_location(program.id, i as gl::GLuint, attr.name);
+ }
+
+ if self.cached_programs.is_some() {
+ self.gl.program_parameter_i(program.id, gl::PROGRAM_BINARY_RETRIEVABLE_HINT, gl::TRUE as gl::GLint);
+ }
+
+ // Link!
+ self.gl.link_program(program.id);
+
+ // GL recommends detaching and deleting shaders once the link
+ // is complete (whether successful or not). This allows the driver
+ // to free any memory associated with the parsing and compilation.
+ self.gl.detach_shader(program.id, vs_id);
+ self.gl.detach_shader(program.id, fs_id);
+ self.gl.delete_shader(vs_id);
+ self.gl.delete_shader(fs_id);
+
+ let mut link_status = [0];
+ unsafe {
+ self.gl.get_program_iv(program.id, gl::LINK_STATUS, &mut link_status);
+ }
+ if link_status[0] == 0 {
+ let error_log = self.gl.get_program_info_log(program.id);
+ error!(
+ "Failed to link shader program: {}\n{}",
+ &info.base_filename,
+ error_log
+ );
+ self.gl.delete_program(program.id);
+ return Err(ShaderError::Link(info.base_filename.to_owned(), error_log));
+ }
+
+ if let Some(ref cached_programs) = self.cached_programs {
+ if !cached_programs.entries.borrow().contains_key(&info.digest) {
+ let (buffer, format) = self.gl.get_program_binary(program.id);
+ if buffer.len() > 0 {
+ let binary = Arc::new(ProgramBinary::new(buffer, format, info.digest.clone()));
+ cached_programs.add_new_program_binary(binary);
+ }
+ }
+ }
+ }
+
+ // If we get here, the link succeeded, so get the uniforms.
+ program.is_initialized = true;
+ program.u_transform = self.gl.get_uniform_location(program.id, "uTransform");
+ program.u_mode = self.gl.get_uniform_location(program.id, "uMode");
+ program.u_texture_size = self.gl.get_uniform_location(program.id, "uTextureSize");
+
+ Ok(())
+ }
+
+ pub fn bind_program(&mut self, program: &Program) -> bool {
+ debug_assert!(self.inside_frame);
+ debug_assert!(program.is_initialized());
+ if !program.is_initialized() {
+ return false;
+ }
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = true;
+ }
+
+ if self.bound_program != program.id {
+ self.gl.use_program(program.id);
+ self.bound_program = program.id;
+ self.bound_program_name = program.source_info.full_name_cstr.clone();
+ self.program_mode_id = UniformLocation(program.u_mode);
+ }
+ true
+ }
+
+ pub fn create_texture(
+ &mut self,
+ target: ImageBufferKind,
+ format: ImageFormat,
+ mut width: i32,
+ mut height: i32,
+ filter: TextureFilter,
+ render_target: Option<RenderTargetInfo>,
+ ) -> Texture {
+ debug_assert!(self.inside_frame);
+
+ if width > self.max_texture_size || height > self.max_texture_size {
+ error!("Attempting to allocate a texture of size {}x{} above the limit, trimming", width, height);
+ width = width.min(self.max_texture_size);
+ height = height.min(self.max_texture_size);
+ }
+
+ // Set up the texture book-keeping.
+ let mut texture = Texture {
+ id: self.gl.gen_textures(1)[0],
+ target: get_gl_target(target),
+ size: DeviceIntSize::new(width, height),
+ format,
+ filter,
+ active_swizzle: Cell::default(),
+ fbo: None,
+ fbo_with_depth: None,
+ last_frame_used: self.frame_id,
+ flags: TextureFlags::default(),
+ };
+ self.bind_texture(DEFAULT_TEXTURE, &texture, Swizzle::default());
+ self.set_texture_parameters(texture.target, filter);
+
+ if self.capabilities.supports_texture_usage && render_target.is_some() {
+ self.gl.tex_parameter_i(texture.target, gl::TEXTURE_USAGE_ANGLE, gl::FRAMEBUFFER_ATTACHMENT_ANGLE as gl::GLint);
+ }
+
+ // Allocate storage.
+ let desc = self.gl_describe_format(texture.format);
+
+ // Firefox doesn't use mipmaps, but Servo uses them for standalone image
+ // textures images larger than 512 pixels. This is the only case where
+ // we set the filter to trilinear.
+ let mipmap_levels = if texture.filter == TextureFilter::Trilinear {
+ let max_dimension = cmp::max(width, height);
+ ((max_dimension) as f64).log2() as gl::GLint + 1
+ } else {
+ 1
+ };
+
+ // We never want to upload texture data at the same time as allocating the texture.
+ self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+
+ // Use glTexStorage where available, since it avoids allocating
+ // unnecessary mipmap storage and generally improves performance with
+ // stronger invariants.
+ let use_texture_storage = match self.texture_storage_usage {
+ TexStorageUsage::Always => true,
+ TexStorageUsage::NonBGRA8 => texture.format != ImageFormat::BGRA8,
+ TexStorageUsage::Never => false,
+ };
+ if use_texture_storage {
+ self.gl.tex_storage_2d(
+ texture.target,
+ mipmap_levels,
+ desc.internal,
+ texture.size.width as gl::GLint,
+ texture.size.height as gl::GLint,
+ );
+ } else {
+ self.gl.tex_image_2d(
+ texture.target,
+ 0,
+ desc.internal as gl::GLint,
+ texture.size.width as gl::GLint,
+ texture.size.height as gl::GLint,
+ 0,
+ desc.external,
+ desc.pixel_type,
+ None,
+ );
+ }
+
+ // Set up FBOs, if required.
+ if let Some(rt_info) = render_target {
+ self.init_fbos(&mut texture, false);
+ if rt_info.has_depth {
+ self.init_fbos(&mut texture, true);
+ }
+ }
+
+ self.textures_created += 1;
+
+ texture
+ }
+
+ fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
+ let mag_filter = match filter {
+ TextureFilter::Nearest => gl::NEAREST,
+ TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
+ };
+
+ let min_filter = match filter {
+ TextureFilter::Nearest => gl::NEAREST,
+ TextureFilter::Linear => gl::LINEAR,
+ TextureFilter::Trilinear => gl::LINEAR_MIPMAP_LINEAR,
+ };
+
+ self.gl
+ .tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, mag_filter as gl::GLint);
+ self.gl
+ .tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, min_filter as gl::GLint);
+
+ self.gl
+ .tex_parameter_i(target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
+ self.gl
+ .tex_parameter_i(target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
+ }
+
+ /// Copies the entire contents of one texture to another. The dest texture must be at least
+ /// as large as the source texture in each dimension. No scaling is performed, so if the dest
+ /// texture is larger than the source texture then some of its pixels will not be written to.
+ pub fn copy_entire_texture(
+ &mut self,
+ dst: &mut Texture,
+ src: &Texture,
+ ) {
+ debug_assert!(self.inside_frame);
+ debug_assert!(dst.size.width >= src.size.width);
+ debug_assert!(dst.size.height >= src.size.height);
+
+ self.copy_texture_sub_region(
+ src,
+ 0,
+ 0,
+ dst,
+ 0,
+ 0,
+ src.size.width as _,
+ src.size.height as _,
+ );
+ }
+
+ /// Copies the specified subregion from src_texture to dest_texture.
+ pub fn copy_texture_sub_region(
+ &mut self,
+ src_texture: &Texture,
+ src_x: usize,
+ src_y: usize,
+ dest_texture: &Texture,
+ dest_x: usize,
+ dest_y: usize,
+ width: usize,
+ height: usize,
+ ) {
+ if self.capabilities.supports_copy_image_sub_data {
+ assert_ne!(
+ src_texture.id, dest_texture.id,
+ "glCopyImageSubData's behaviour is undefined if src and dst images are identical and the rectangles overlap."
+ );
+ unsafe {
+ self.gl.copy_image_sub_data(
+ src_texture.id,
+ src_texture.target,
+ 0,
+ src_x as _,
+ src_y as _,
+ 0,
+ dest_texture.id,
+ dest_texture.target,
+ 0,
+ dest_x as _,
+ dest_y as _,
+ 0,
+ width as _,
+ height as _,
+ 1,
+ );
+ }
+ } else {
+ let src_offset = FramebufferIntPoint::new(src_x as i32, src_y as i32);
+ let dest_offset = FramebufferIntPoint::new(dest_x as i32, dest_y as i32);
+ let size = FramebufferIntSize::new(width as i32, height as i32);
+
+ self.blit_render_target(
+ ReadTarget::from_texture(src_texture),
+ FramebufferIntRect::from_origin_and_size(src_offset, size),
+ DrawTarget::from_texture(dest_texture, false),
+ FramebufferIntRect::from_origin_and_size(dest_offset, size),
+ // In most cases the filter shouldn't matter, as there is no scaling involved
+ // in the blit. We were previously using Linear, but this caused issues when
+ // blitting RGBAF32 textures on Mali, so use Nearest to be safe.
+ TextureFilter::Nearest,
+ );
+ }
+ }
+
+ /// Notifies the device that the contents of a render target are no longer
+ /// needed.
+ pub fn invalidate_render_target(&mut self, texture: &Texture) {
+ if self.capabilities.supports_render_target_invalidate {
+ let (fbo, attachments) = if texture.supports_depth() {
+ (&texture.fbo_with_depth,
+ &[gl::COLOR_ATTACHMENT0, gl::DEPTH_ATTACHMENT] as &[gl::GLenum])
+ } else {
+ (&texture.fbo, &[gl::COLOR_ATTACHMENT0] as &[gl::GLenum])
+ };
+
+ if let Some(fbo_id) = fbo {
+ let original_bound_fbo = self.bound_draw_fbo;
+ // Note: The invalidate extension may not be supported, in which
+ // case this is a no-op. That's ok though, because it's just a
+ // hint.
+ self.bind_external_draw_target(*fbo_id);
+ self.gl.invalidate_framebuffer(gl::FRAMEBUFFER, attachments);
+ self.bind_external_draw_target(original_bound_fbo);
+ }
+ }
+ }
+
+ /// Notifies the device that the contents of the current framebuffer's depth
+ /// attachment is no longer needed. Unlike invalidate_render_target, this can
+ /// be called even when the contents of the colour attachment is still required.
+ /// This should be called before unbinding the framebuffer at the end of a pass,
+ /// to allow tiled GPUs to avoid writing the contents back to memory.
+ pub fn invalidate_depth_target(&mut self) {
+ assert!(self.depth_available);
+ let attachments = if self.bound_draw_fbo == self.default_draw_fbo {
+ &[gl::DEPTH] as &[gl::GLenum]
+ } else {
+ &[gl::DEPTH_ATTACHMENT] as &[gl::GLenum]
+ };
+ self.gl.invalidate_framebuffer(gl::DRAW_FRAMEBUFFER, attachments);
+ }
+
+ /// Notifies the device that a render target is about to be reused.
+ ///
+ /// This method adds or removes a depth target as necessary.
+ pub fn reuse_render_target<T: Texel>(
+ &mut self,
+ texture: &mut Texture,
+ rt_info: RenderTargetInfo,
+ ) {
+ texture.last_frame_used = self.frame_id;
+
+ // Add depth support if needed.
+ if rt_info.has_depth && !texture.supports_depth() {
+ self.init_fbos(texture, true);
+ }
+ }
+
+ fn init_fbos(&mut self, texture: &mut Texture, with_depth: bool) {
+ let (fbo, depth_rb) = if with_depth {
+ let depth_target = self.acquire_depth_target(texture.get_dimensions());
+ (&mut texture.fbo_with_depth, Some(depth_target))
+ } else {
+ (&mut texture.fbo, None)
+ };
+
+ // Generate the FBOs.
+ assert!(fbo.is_none());
+ let fbo_id = FBOId(*self.gl.gen_framebuffers(1).first().unwrap());
+ *fbo = Some(fbo_id);
+
+ // Bind the FBOs.
+ let original_bound_fbo = self.bound_draw_fbo;
+
+ self.bind_external_draw_target(fbo_id);
+
+ self.gl.framebuffer_texture_2d(
+ gl::DRAW_FRAMEBUFFER,
+ gl::COLOR_ATTACHMENT0,
+ texture.target,
+ texture.id,
+ 0,
+ );
+
+ if let Some(depth_rb) = depth_rb {
+ self.gl.framebuffer_renderbuffer(
+ gl::DRAW_FRAMEBUFFER,
+ gl::DEPTH_ATTACHMENT,
+ gl::RENDERBUFFER,
+ depth_rb.0,
+ );
+ }
+
+ debug_assert_eq!(
+ self.gl.check_frame_buffer_status(gl::DRAW_FRAMEBUFFER),
+ gl::FRAMEBUFFER_COMPLETE,
+ "Incomplete framebuffer",
+ );
+
+ self.bind_external_draw_target(original_bound_fbo);
+ }
+
+ fn acquire_depth_target(&mut self, dimensions: DeviceIntSize) -> RBOId {
+ let gl = &self.gl;
+ let depth_format = self.depth_format;
+ let target = self.depth_targets.entry(dimensions).or_insert_with(|| {
+ let renderbuffer_ids = gl.gen_renderbuffers(1);
+ let depth_rb = renderbuffer_ids[0];
+ gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
+ gl.renderbuffer_storage(
+ gl::RENDERBUFFER,
+ depth_format,
+ dimensions.width as _,
+ dimensions.height as _,
+ );
+ SharedDepthTarget {
+ rbo_id: RBOId(depth_rb),
+ refcount: 0,
+ }
+ });
+ target.refcount += 1;
+ target.rbo_id
+ }
+
+ fn release_depth_target(&mut self, dimensions: DeviceIntSize) {
+ let mut entry = match self.depth_targets.entry(dimensions) {
+ Entry::Occupied(x) => x,
+ Entry::Vacant(..) => panic!("Releasing unknown depth target"),
+ };
+ debug_assert!(entry.get().refcount != 0);
+ entry.get_mut().refcount -= 1;
+ if entry.get().refcount == 0 {
+ let (_, target) = entry.remove_entry();
+ self.gl.delete_renderbuffers(&[target.rbo_id.0]);
+ }
+ }
+
+ /// Perform a blit between self.bound_read_fbo and self.bound_draw_fbo.
+ fn blit_render_target_impl(
+ &mut self,
+ src_rect: FramebufferIntRect,
+ dest_rect: FramebufferIntRect,
+ filter: TextureFilter,
+ ) {
+ debug_assert!(self.inside_frame);
+
+ let filter = match filter {
+ TextureFilter::Nearest => gl::NEAREST,
+ TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
+ };
+
+ let src_x0 = src_rect.min.x + self.bound_read_fbo.1.x;
+ let src_y0 = src_rect.min.y + self.bound_read_fbo.1.y;
+
+ self.gl.blit_framebuffer(
+ src_x0,
+ src_y0,
+ src_x0 + src_rect.width(),
+ src_y0 + src_rect.height(),
+ dest_rect.min.x,
+ dest_rect.min.y,
+ dest_rect.max.x,
+ dest_rect.max.y,
+ gl::COLOR_BUFFER_BIT,
+ filter,
+ );
+ }
+
+ /// Perform a blit between src_target and dest_target.
+ /// This will overwrite self.bound_read_fbo and self.bound_draw_fbo.
+ pub fn blit_render_target(
+ &mut self,
+ src_target: ReadTarget,
+ src_rect: FramebufferIntRect,
+ dest_target: DrawTarget,
+ dest_rect: FramebufferIntRect,
+ filter: TextureFilter,
+ ) {
+ debug_assert!(self.inside_frame);
+
+ self.bind_read_target(src_target);
+
+ self.bind_draw_target(dest_target);
+
+ self.blit_render_target_impl(src_rect, dest_rect, filter);
+ }
+
+ /// Performs a blit while flipping vertically. Useful for blitting textures
+ /// (which use origin-bottom-left) to the main framebuffer (which uses
+ /// origin-top-left).
+ pub fn blit_render_target_invert_y(
+ &mut self,
+ src_target: ReadTarget,
+ src_rect: FramebufferIntRect,
+ dest_target: DrawTarget,
+ dest_rect: FramebufferIntRect,
+ ) {
+ debug_assert!(self.inside_frame);
+
+ let mut inverted_dest_rect = dest_rect;
+ inverted_dest_rect.min.y = dest_rect.max.y;
+ inverted_dest_rect.max.y = dest_rect.min.y;
+
+ self.blit_render_target(
+ src_target,
+ src_rect,
+ dest_target,
+ inverted_dest_rect,
+ TextureFilter::Linear,
+ );
+ }
+
+ pub fn delete_texture(&mut self, mut texture: Texture) {
+ debug_assert!(self.inside_frame);
+ let had_depth = texture.supports_depth();
+ if let Some(fbo) = texture.fbo {
+ self.gl.delete_framebuffers(&[fbo.0]);
+ texture.fbo = None;
+ }
+ if let Some(fbo) = texture.fbo_with_depth {
+ self.gl.delete_framebuffers(&[fbo.0]);
+ texture.fbo_with_depth = None;
+ }
+
+ if had_depth {
+ self.release_depth_target(texture.get_dimensions());
+ }
+
+ self.gl.delete_textures(&[texture.id]);
+
+ for bound_texture in &mut self.bound_textures {
+ if *bound_texture == texture.id {
+ *bound_texture = 0;
+ }
+ }
+
+ self.textures_deleted += 1;
+
+ // Disarm the assert in Texture::drop().
+ texture.id = 0;
+ }
+
+ #[cfg(feature = "replay")]
+ pub fn delete_external_texture(&mut self, mut external: ExternalTexture) {
+ self.gl.delete_textures(&[external.id]);
+ external.id = 0;
+ }
+
+ pub fn delete_program(&mut self, mut program: Program) {
+ self.gl.delete_program(program.id);
+ program.id = 0;
+ }
+
+ /// Create a shader program and link it immediately.
+ pub fn create_program_linked(
+ &mut self,
+ base_filename: &'static str,
+ features: &[&'static str],
+ descriptor: &VertexDescriptor,
+ ) -> Result<Program, ShaderError> {
+ let mut program = self.create_program(base_filename, features)?;
+ self.link_program(&mut program, descriptor)?;
+ Ok(program)
+ }
+
+ /// Create a shader program. This does minimal amount of work to start
+ /// loading a binary shader. If a binary shader is found, we invoke
+ /// glProgramBinary, which, at least on ANGLE, will load and link the
+ /// binary on a background thread. This can speed things up later when
+ /// we invoke `link_program()`.
+ pub fn create_program(
+ &mut self,
+ base_filename: &'static str,
+ features: &[&'static str],
+ ) -> Result<Program, ShaderError> {
+ debug_assert!(self.inside_frame);
+
+ let source_info = ProgramSourceInfo::new(self, base_filename, features);
+
+ // Create program
+ let pid = self.gl.create_program();
+
+ // Attempt to load a cached binary if possible.
+ if let Some(ref cached_programs) = self.cached_programs {
+ if let Some(entry) = cached_programs.entries.borrow().get(&source_info.digest) {
+ self.gl.program_binary(pid, entry.binary.format, &entry.binary.bytes);
+ }
+ }
+
+ // Use 0 for the uniforms as they are initialized by link_program.
+ let program = Program {
+ id: pid,
+ u_transform: 0,
+ u_mode: 0,
+ u_texture_size: 0,
+ source_info,
+ is_initialized: false,
+ };
+
+ Ok(program)
+ }
+
+ fn build_shader_string<F: FnMut(&str)>(
+ &self,
+ features: &[&'static str],
+ kind: ShaderKind,
+ base_filename: &str,
+ output: F,
+ ) {
+ do_build_shader_string(
+ get_shader_version(&*self.gl),
+ features,
+ kind,
+ base_filename,
+ &|f| get_unoptimized_shader_source(f, self.resource_override_path.as_ref()),
+ output,
+ )
+ }
+
+ pub fn bind_shader_samplers<S>(&mut self, program: &Program, bindings: &[(&'static str, S)])
+ where
+ S: Into<TextureSlot> + Copy,
+ {
+ // bind_program() must be called before calling bind_shader_samplers
+ assert_eq!(self.bound_program, program.id);
+
+ for binding in bindings {
+ let u_location = self.gl.get_uniform_location(program.id, binding.0);
+ if u_location != -1 {
+ self.bind_program(program);
+ self.gl
+ .uniform_1i(u_location, binding.1.into().0 as gl::GLint);
+ }
+ }
+ }
+
+ pub fn get_uniform_location(&self, program: &Program, name: &str) -> UniformLocation {
+ UniformLocation(self.gl.get_uniform_location(program.id, name))
+ }
+
+ pub fn set_uniforms(
+ &self,
+ program: &Program,
+ transform: &Transform3D<f32>,
+ ) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ self.gl
+ .uniform_matrix_4fv(program.u_transform, false, &transform.to_array());
+ }
+
+ pub fn switch_mode(&self, mode: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ self.gl.uniform_1i(self.program_mode_id.0, mode);
+ }
+
+ /// Sets the uTextureSize uniform. Most shaders do not require this to be called
+ /// as they use the textureSize GLSL function instead.
+ pub fn set_shader_texture_size(
+ &self,
+ program: &Program,
+ texture_size: DeviceSize,
+ ) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ if program.u_texture_size != -1 {
+ self.gl.uniform_2f(program.u_texture_size, texture_size.width, texture_size.height);
+ }
+ }
+
+ pub fn create_pbo(&mut self) -> PBO {
+ let id = self.gl.gen_buffers(1)[0];
+ PBO {
+ id,
+ reserved_size: 0,
+ }
+ }
+
+ pub fn create_pbo_with_size(&mut self, size: usize) -> PBO {
+ let mut pbo = self.create_pbo();
+
+ self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, pbo.id);
+ self.gl.pixel_store_i(gl::PACK_ALIGNMENT, 1);
+ self.gl.buffer_data_untyped(
+ gl::PIXEL_PACK_BUFFER,
+ size as _,
+ ptr::null(),
+ gl::STREAM_READ,
+ );
+ self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+
+ pbo.reserved_size = size;
+ pbo
+ }
+
+ pub fn read_pixels_into_pbo(
+ &mut self,
+ read_target: ReadTarget,
+ rect: DeviceIntRect,
+ format: ImageFormat,
+ pbo: &PBO,
+ ) {
+ let byte_size = rect.area() as usize * format.bytes_per_pixel() as usize;
+
+ assert!(byte_size <= pbo.reserved_size);
+
+ self.bind_read_target(read_target);
+
+ self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, pbo.id);
+ self.gl.pixel_store_i(gl::PACK_ALIGNMENT, 1);
+
+ let gl_format = self.gl_describe_format(format);
+
+ unsafe {
+ self.gl.read_pixels_into_pbo(
+ rect.min.x as _,
+ rect.min.y as _,
+ rect.width() as _,
+ rect.height() as _,
+ gl_format.read,
+ gl_format.pixel_type,
+ );
+ }
+
+ self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, 0);
+ }
+
+ pub fn map_pbo_for_readback<'a>(&'a mut self, pbo: &'a PBO) -> Option<BoundPBO<'a>> {
+ self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, pbo.id);
+
+ let buf_ptr = match self.gl.get_type() {
+ gl::GlType::Gl => {
+ self.gl.map_buffer(gl::PIXEL_PACK_BUFFER, gl::READ_ONLY)
+ }
+
+ gl::GlType::Gles => {
+ self.gl.map_buffer_range(
+ gl::PIXEL_PACK_BUFFER,
+ 0,
+ pbo.reserved_size as _,
+ gl::MAP_READ_BIT)
+ }
+ };
+
+ if buf_ptr.is_null() {
+ return None;
+ }
+
+ let buffer = unsafe { slice::from_raw_parts(buf_ptr as *const u8, pbo.reserved_size) };
+
+ Some(BoundPBO {
+ device: self,
+ data: buffer,
+ })
+ }
+
+ pub fn delete_pbo(&mut self, mut pbo: PBO) {
+ self.gl.delete_buffers(&[pbo.id]);
+ pbo.id = 0;
+ pbo.reserved_size = 0
+ }
+
+ /// Returns the size and stride in bytes required to upload an area of pixels
+ /// of the specified size, to a texture of the specified format.
+ pub fn required_upload_size_and_stride(&self, size: DeviceIntSize, format: ImageFormat) -> (usize, usize) {
+ assert!(size.width >= 0);
+ assert!(size.height >= 0);
+
+ let bytes_pp = format.bytes_per_pixel() as usize;
+ let width_bytes = size.width as usize * bytes_pp;
+
+ let dst_stride = round_up_to_multiple(width_bytes, self.required_pbo_stride.num_bytes(format));
+
+ // The size of the chunk should only need to be (height - 1) * dst_stride + width_bytes,
+ // however, the android emulator will error unless it is height * dst_stride.
+ // See bug 1587047 for details.
+ // Using the full final row also ensures that the offset of the next chunk is
+ // optimally aligned.
+ let dst_size = dst_stride * size.height as usize;
+
+ (dst_size, dst_stride)
+ }
+
+ /// Returns a `TextureUploader` which can be used to upload texture data to `texture`.
+ /// Once uploads have been performed the uploader must be flushed with `TextureUploader::flush()`.
+ pub fn upload_texture<'a>(
+ &mut self,
+ pbo_pool: &'a mut UploadPBOPool,
+ ) -> TextureUploader<'a> {
+ debug_assert!(self.inside_frame);
+
+ pbo_pool.begin_frame(self);
+
+ TextureUploader {
+ buffers: Vec::new(),
+ pbo_pool,
+ }
+ }
+
+ /// Performs an immediate (non-PBO) texture upload.
+ pub fn upload_texture_immediate<T: Texel>(
+ &mut self,
+ texture: &Texture,
+ pixels: &[T]
+ ) {
+ self.bind_texture(DEFAULT_TEXTURE, texture, Swizzle::default());
+ let desc = self.gl_describe_format(texture.format);
+ self.gl.tex_sub_image_2d(
+ texture.target,
+ 0,
+ 0,
+ 0,
+ texture.size.width as gl::GLint,
+ texture.size.height as gl::GLint,
+ desc.external,
+ desc.pixel_type,
+ texels_to_u8_slice(pixels),
+ );
+ }
+
+ pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec<u8> {
+ let desc = self.gl_describe_format(img_desc.format);
+ self.gl.read_pixels(
+ 0, 0,
+ img_desc.size.width as i32,
+ img_desc.size.height as i32,
+ desc.read,
+ desc.pixel_type,
+ )
+ }
+
+ /// Read rectangle of pixels into the specified output slice.
+ pub fn read_pixels_into(
+ &mut self,
+ rect: FramebufferIntRect,
+ format: ImageFormat,
+ output: &mut [u8],
+ ) {
+ let bytes_per_pixel = format.bytes_per_pixel();
+ let desc = self.gl_describe_format(format);
+ let size_in_bytes = (bytes_per_pixel * rect.area()) as usize;
+ assert_eq!(output.len(), size_in_bytes);
+
+ self.gl.flush();
+ self.gl.read_pixels_into_buffer(
+ rect.min.x as _,
+ rect.min.y as _,
+ rect.width() as _,
+ rect.height() as _,
+ desc.read,
+ desc.pixel_type,
+ output,
+ );
+ }
+
+ /// Get texels of a texture into the specified output slice.
+ pub fn get_tex_image_into(
+ &mut self,
+ texture: &Texture,
+ format: ImageFormat,
+ output: &mut [u8],
+ ) {
+ self.bind_texture(DEFAULT_TEXTURE, texture, Swizzle::default());
+ let desc = self.gl_describe_format(format);
+ self.gl.get_tex_image_into_buffer(
+ texture.target,
+ 0,
+ desc.external,
+ desc.pixel_type,
+ output,
+ );
+ }
+
+ /// Attaches the provided texture to the current Read FBO binding.
+ fn attach_read_texture_raw(&mut self, texture_id: gl::GLuint, target: gl::GLuint) {
+ self.gl.framebuffer_texture_2d(
+ gl::READ_FRAMEBUFFER,
+ gl::COLOR_ATTACHMENT0,
+ target,
+ texture_id,
+ 0,
+ )
+ }
+
+ pub fn attach_read_texture_external(
+ &mut self, texture_id: gl::GLuint, target: ImageBufferKind
+ ) {
+ self.attach_read_texture_raw(texture_id, get_gl_target(target))
+ }
+
+ pub fn attach_read_texture(&mut self, texture: &Texture) {
+ self.attach_read_texture_raw(texture.id, texture.target)
+ }
+
+ fn bind_vao_impl(&mut self, id: gl::GLuint) {
+ debug_assert!(self.inside_frame);
+
+ if self.bound_vao != id {
+ self.bound_vao = id;
+ self.gl.bind_vertex_array(id);
+ }
+ }
+
+ pub fn bind_vao(&mut self, vao: &VAO) {
+ self.bind_vao_impl(vao.id)
+ }
+
+ pub fn bind_custom_vao(&mut self, vao: &CustomVAO) {
+ self.bind_vao_impl(vao.id)
+ }
+
+ fn create_vao_with_vbos(
+ &mut self,
+ descriptor: &VertexDescriptor,
+ main_vbo_id: VBOId,
+ instance_vbo_id: VBOId,
+ instance_divisor: u32,
+ ibo_id: IBOId,
+ owns_vertices_and_indices: bool,
+ ) -> VAO {
+ let instance_stride = descriptor.instance_stride() as usize;
+ let vao_id = self.gl.gen_vertex_arrays(1)[0];
+
+ self.bind_vao_impl(vao_id);
+
+ descriptor.bind(self.gl(), main_vbo_id, instance_vbo_id, instance_divisor);
+ ibo_id.bind(self.gl()); // force it to be a part of VAO
+
+ VAO {
+ id: vao_id,
+ ibo_id,
+ main_vbo_id,
+ instance_vbo_id,
+ instance_stride,
+ instance_divisor,
+ owns_vertices_and_indices,
+ }
+ }
+
+ pub fn create_custom_vao(
+ &mut self,
+ streams: &[Stream],
+ ) -> CustomVAO {
+ debug_assert!(self.inside_frame);
+
+ let vao_id = self.gl.gen_vertex_arrays(1)[0];
+ self.bind_vao_impl(vao_id);
+
+ let mut attrib_index = 0;
+ for stream in streams {
+ VertexDescriptor::bind_attributes(
+ stream.attributes,
+ attrib_index,
+ 0,
+ self.gl(),
+ stream.vbo,
+ );
+ attrib_index += stream.attributes.len();
+ }
+
+ CustomVAO {
+ id: vao_id,
+ }
+ }
+
+ pub fn delete_custom_vao(&mut self, mut vao: CustomVAO) {
+ self.gl.delete_vertex_arrays(&[vao.id]);
+ vao.id = 0;
+ }
+
+ pub fn create_vbo<T>(&mut self) -> VBO<T> {
+ let ids = self.gl.gen_buffers(1);
+ VBO {
+ id: ids[0],
+ target: gl::ARRAY_BUFFER,
+ allocated_count: 0,
+ marker: PhantomData,
+ }
+ }
+
+ pub fn delete_vbo<T>(&mut self, mut vbo: VBO<T>) {
+ self.gl.delete_buffers(&[vbo.id]);
+ vbo.id = 0;
+ }
+
+ pub fn create_vao(&mut self, descriptor: &VertexDescriptor, instance_divisor: u32) -> VAO {
+ debug_assert!(self.inside_frame);
+
+ let buffer_ids = self.gl.gen_buffers(3);
+ let ibo_id = IBOId(buffer_ids[0]);
+ let main_vbo_id = VBOId(buffer_ids[1]);
+ let intance_vbo_id = VBOId(buffer_ids[2]);
+
+ self.create_vao_with_vbos(descriptor, main_vbo_id, intance_vbo_id, instance_divisor, ibo_id, true)
+ }
+
+ pub fn delete_vao(&mut self, mut vao: VAO) {
+ self.gl.delete_vertex_arrays(&[vao.id]);
+ vao.id = 0;
+
+ if vao.owns_vertices_and_indices {
+ self.gl.delete_buffers(&[vao.ibo_id.0]);
+ self.gl.delete_buffers(&[vao.main_vbo_id.0]);
+ }
+
+ self.gl.delete_buffers(&[vao.instance_vbo_id.0])
+ }
+
+ pub fn allocate_vbo<V>(
+ &mut self,
+ vbo: &mut VBO<V>,
+ count: usize,
+ usage_hint: VertexUsageHint,
+ ) {
+ debug_assert!(self.inside_frame);
+ vbo.allocated_count = count;
+
+ self.gl.bind_buffer(vbo.target, vbo.id);
+ self.gl.buffer_data_untyped(
+ vbo.target,
+ (count * mem::size_of::<V>()) as _,
+ ptr::null(),
+ usage_hint.to_gl(),
+ );
+ }
+
+ pub fn fill_vbo<V>(
+ &mut self,
+ vbo: &VBO<V>,
+ data: &[V],
+ offset: usize,
+ ) {
+ debug_assert!(self.inside_frame);
+ assert!(offset + data.len() <= vbo.allocated_count);
+ let stride = mem::size_of::<V>();
+
+ self.gl.bind_buffer(vbo.target, vbo.id);
+ self.gl.buffer_sub_data_untyped(
+ vbo.target,
+ (offset * stride) as _,
+ (data.len() * stride) as _,
+ data.as_ptr() as _,
+ );
+ }
+
+ fn update_vbo_data<V>(
+ &mut self,
+ vbo: VBOId,
+ vertices: &[V],
+ usage_hint: VertexUsageHint,
+ ) {
+ debug_assert!(self.inside_frame);
+
+ vbo.bind(self.gl());
+ gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, vertices, usage_hint.to_gl());
+ }
+
+ pub fn create_vao_with_new_instances(
+ &mut self,
+ descriptor: &VertexDescriptor,
+ base_vao: &VAO,
+ ) -> VAO {
+ debug_assert!(self.inside_frame);
+
+ let buffer_ids = self.gl.gen_buffers(1);
+ let intance_vbo_id = VBOId(buffer_ids[0]);
+
+ self.create_vao_with_vbos(
+ descriptor,
+ base_vao.main_vbo_id,
+ intance_vbo_id,
+ base_vao.instance_divisor,
+ base_vao.ibo_id,
+ false,
+ )
+ }
+
+ pub fn update_vao_main_vertices<V>(
+ &mut self,
+ vao: &VAO,
+ vertices: &[V],
+ usage_hint: VertexUsageHint,
+ ) {
+ debug_assert_eq!(self.bound_vao, vao.id);
+ self.update_vbo_data(vao.main_vbo_id, vertices, usage_hint)
+ }
+
+ pub fn update_vao_instances<V: Clone>(
+ &mut self,
+ vao: &VAO,
+ instances: &[V],
+ usage_hint: VertexUsageHint,
+ // if `Some(count)`, each instance is repeated `count` times
+ repeat: Option<NonZeroUsize>,
+ ) {
+ debug_assert_eq!(self.bound_vao, vao.id);
+ debug_assert_eq!(vao.instance_stride as usize, mem::size_of::<V>());
+
+ match repeat {
+ Some(count) => {
+ let target = gl::ARRAY_BUFFER;
+ self.gl.bind_buffer(target, vao.instance_vbo_id.0);
+ let size = instances.len() * count.get() * mem::size_of::<V>();
+ self.gl.buffer_data_untyped(
+ target,
+ size as _,
+ ptr::null(),
+ usage_hint.to_gl(),
+ );
+
+ let ptr = match self.gl.get_type() {
+ gl::GlType::Gl => {
+ self.gl.map_buffer(target, gl::WRITE_ONLY)
+ }
+ gl::GlType::Gles => {
+ self.gl.map_buffer_range(target, 0, size as _, gl::MAP_WRITE_BIT)
+ }
+ };
+ assert!(!ptr.is_null());
+
+ let buffer_slice = unsafe {
+ slice::from_raw_parts_mut(ptr as *mut V, instances.len() * count.get())
+ };
+ for (quad, instance) in buffer_slice.chunks_mut(4).zip(instances) {
+ quad[0] = instance.clone();
+ quad[1] = instance.clone();
+ quad[2] = instance.clone();
+ quad[3] = instance.clone();
+ }
+ self.gl.unmap_buffer(target);
+ }
+ None => {
+ self.update_vbo_data(vao.instance_vbo_id, instances, usage_hint);
+ }
+ }
+
+ // On some devices the VAO must be manually unbound and rebound after an attached buffer has
+ // been orphaned. Failure to do so appeared to result in the orphaned buffer's contents
+ // being used for the subsequent draw call, rather than the new buffer's contents.
+ if self.capabilities.requires_vao_rebind_after_orphaning {
+ self.bind_vao_impl(0);
+ self.bind_vao_impl(vao.id);
+ }
+ }
+
+ pub fn update_vao_indices<I>(&mut self, vao: &VAO, indices: &[I], usage_hint: VertexUsageHint) {
+ debug_assert!(self.inside_frame);
+ debug_assert_eq!(self.bound_vao, vao.id);
+
+ vao.ibo_id.bind(self.gl());
+ gl::buffer_data(
+ self.gl(),
+ gl::ELEMENT_ARRAY_BUFFER,
+ indices,
+ usage_hint.to_gl(),
+ );
+ }
+
+ pub fn draw_triangles_u16(&mut self, first_vertex: i32, index_count: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ let _guard = if self.annotate_draw_call_crashes {
+ Some(CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::DrawShader,
+ &self.bound_program_name,
+ ))
+ } else {
+ None
+ };
+
+ self.gl.draw_elements(
+ gl::TRIANGLES,
+ index_count,
+ gl::UNSIGNED_SHORT,
+ first_vertex as u32 * 2,
+ );
+ }
+
+ pub fn draw_triangles_u32(&mut self, first_vertex: i32, index_count: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ let _guard = if self.annotate_draw_call_crashes {
+ Some(CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::DrawShader,
+ &self.bound_program_name,
+ ))
+ } else {
+ None
+ };
+
+ self.gl.draw_elements(
+ gl::TRIANGLES,
+ index_count,
+ gl::UNSIGNED_INT,
+ first_vertex as u32 * 4,
+ );
+ }
+
+ pub fn draw_nonindexed_points(&mut self, first_vertex: i32, vertex_count: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ let _guard = if self.annotate_draw_call_crashes {
+ Some(CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::DrawShader,
+ &self.bound_program_name,
+ ))
+ } else {
+ None
+ };
+
+ self.gl.draw_arrays(gl::POINTS, first_vertex, vertex_count);
+ }
+
+ pub fn draw_nonindexed_lines(&mut self, first_vertex: i32, vertex_count: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ let _guard = if self.annotate_draw_call_crashes {
+ Some(CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::DrawShader,
+ &self.bound_program_name,
+ ))
+ } else {
+ None
+ };
+
+ self.gl.draw_arrays(gl::LINES, first_vertex, vertex_count);
+ }
+
+ pub fn draw_indexed_triangles(&mut self, index_count: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ let _guard = if self.annotate_draw_call_crashes {
+ Some(CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::DrawShader,
+ &self.bound_program_name,
+ ))
+ } else {
+ None
+ };
+
+ self.gl.draw_elements(
+ gl::TRIANGLES,
+ index_count,
+ gl::UNSIGNED_SHORT,
+ 0,
+ );
+ }
+
+ pub fn draw_indexed_triangles_instanced_u16(&mut self, index_count: i32, instance_count: i32) {
+ debug_assert!(self.inside_frame);
+ #[cfg(debug_assertions)]
+ debug_assert!(self.shader_is_ready);
+
+ let _guard = if self.annotate_draw_call_crashes {
+ Some(CrashAnnotatorGuard::new(
+ &self.crash_annotator,
+ CrashAnnotation::DrawShader,
+ &self.bound_program_name,
+ ))
+ } else {
+ None
+ };
+
+ self.gl.draw_elements_instanced(
+ gl::TRIANGLES,
+ index_count,
+ gl::UNSIGNED_SHORT,
+ 0,
+ instance_count,
+ );
+ }
+
+ pub fn end_frame(&mut self) {
+ self.reset_draw_target();
+ self.reset_read_target();
+
+ debug_assert!(self.inside_frame);
+ self.inside_frame = false;
+
+ self.gl.bind_texture(gl::TEXTURE_2D, 0);
+ self.gl.use_program(0);
+
+ for i in 0 .. self.bound_textures.len() {
+ self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
+ self.gl.bind_texture(gl::TEXTURE_2D, 0);
+ }
+
+ self.gl.active_texture(gl::TEXTURE0);
+
+ self.frame_id.0 += 1;
+
+ // Save any shaders compiled this frame to disk.
+ // If this is the tenth frame then treat startup as complete, meaning the
+ // current set of in-use shaders are the ones to load on the next startup.
+ if let Some(ref cache) = self.cached_programs {
+ cache.update_disk_cache(self.frame_id.0 == 10);
+ }
+ }
+
+ pub fn clear_target(
+ &self,
+ color: Option<[f32; 4]>,
+ depth: Option<f32>,
+ rect: Option<FramebufferIntRect>,
+ ) {
+ let mut clear_bits = 0;
+
+ if let Some(color) = color {
+ self.gl.clear_color(color[0], color[1], color[2], color[3]);
+ clear_bits |= gl::COLOR_BUFFER_BIT;
+ }
+
+ if let Some(depth) = depth {
+ if cfg!(debug_assertions) {
+ let mut mask = [0];
+ unsafe {
+ self.gl.get_boolean_v(gl::DEPTH_WRITEMASK, &mut mask);
+ }
+ assert_ne!(mask[0], 0);
+ }
+ self.gl.clear_depth(depth as f64);
+ clear_bits |= gl::DEPTH_BUFFER_BIT;
+ }
+
+ if clear_bits != 0 {
+ match rect {
+ Some(rect) => {
+ self.gl.enable(gl::SCISSOR_TEST);
+ self.gl.scissor(
+ rect.min.x,
+ rect.min.y,
+ rect.width(),
+ rect.height(),
+ );
+ self.gl.clear(clear_bits);
+ self.gl.disable(gl::SCISSOR_TEST);
+ }
+ None => {
+ self.gl.clear(clear_bits);
+ }
+ }
+ }
+ }
+
+ pub fn enable_depth(&self, depth_func: DepthFunction) {
+ assert!(self.depth_available, "Enabling depth test without depth target");
+ self.gl.enable(gl::DEPTH_TEST);
+ self.gl.depth_func(depth_func as gl::GLuint);
+ }
+
+ pub fn disable_depth(&self) {
+ self.gl.disable(gl::DEPTH_TEST);
+ }
+
+ pub fn enable_depth_write(&self) {
+ assert!(self.depth_available, "Enabling depth write without depth target");
+ self.gl.depth_mask(true);
+ }
+
+ pub fn disable_depth_write(&self) {
+ self.gl.depth_mask(false);
+ }
+
+ pub fn disable_stencil(&self) {
+ self.gl.disable(gl::STENCIL_TEST);
+ }
+
+ pub fn set_scissor_rect(&self, rect: FramebufferIntRect) {
+ self.gl.scissor(
+ rect.min.x,
+ rect.min.y,
+ rect.width(),
+ rect.height(),
+ );
+ }
+
+ pub fn enable_scissor(&self) {
+ self.gl.enable(gl::SCISSOR_TEST);
+ }
+
+ pub fn disable_scissor(&self) {
+ self.gl.disable(gl::SCISSOR_TEST);
+ }
+
+ pub fn enable_color_write(&self) {
+ self.gl.color_mask(true, true, true, true);
+ }
+
+ pub fn disable_color_write(&self) {
+ self.gl.color_mask(false, false, false, false);
+ }
+
+ pub fn set_blend(&mut self, enable: bool) {
+ if enable {
+ self.gl.enable(gl::BLEND);
+ } else {
+ self.gl.disable(gl::BLEND);
+ }
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = false;
+ }
+ }
+
+ fn set_blend_factors(
+ &mut self,
+ color: (gl::GLenum, gl::GLenum),
+ alpha: (gl::GLenum, gl::GLenum),
+ ) {
+ self.gl.blend_equation(gl::FUNC_ADD);
+ if color == alpha {
+ self.gl.blend_func(color.0, color.1);
+ } else {
+ self.gl.blend_func_separate(color.0, color.1, alpha.0, alpha.1);
+ }
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = false;
+ }
+ }
+
+ pub fn set_blend_mode_alpha(&mut self) {
+ self.set_blend_factors(
+ (gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+
+ pub fn set_blend_mode_premultiplied_alpha(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+
+ pub fn set_blend_mode_premultiplied_dest_out(&mut self) {
+ self.set_blend_factors(
+ (gl::ZERO, gl::ONE_MINUS_SRC_ALPHA),
+ (gl::ZERO, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+
+ pub fn set_blend_mode_multiply(&mut self) {
+ self.set_blend_factors(
+ (gl::ZERO, gl::SRC_COLOR),
+ (gl::ZERO, gl::SRC_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_subpixel_pass0(&mut self) {
+ self.set_blend_factors(
+ (gl::ZERO, gl::ONE_MINUS_SRC_COLOR),
+ (gl::ZERO, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_subpixel_pass1(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE),
+ (gl::ONE, gl::ONE),
+ );
+ }
+ pub fn set_blend_mode_subpixel_with_bg_color_pass0(&mut self) {
+ self.set_blend_factors(
+ (gl::ZERO, gl::ONE_MINUS_SRC_COLOR),
+ (gl::ZERO, gl::ONE),
+ );
+ }
+ pub fn set_blend_mode_subpixel_with_bg_color_pass1(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE_MINUS_DST_ALPHA, gl::ONE),
+ (gl::ZERO, gl::ONE),
+ );
+ }
+ pub fn set_blend_mode_subpixel_with_bg_color_pass2(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_subpixel_dual_source(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE_MINUS_SRC1_COLOR),
+ (gl::ONE, gl::ONE_MINUS_SRC1_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_multiply_dual_source(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE_MINUS_DST_ALPHA, gl::ONE_MINUS_SRC1_COLOR),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_screen(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE_MINUS_SRC_COLOR),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_plus_lighter(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE),
+ (gl::ONE, gl::ONE),
+ );
+ }
+ pub fn set_blend_mode_exclusion(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE_MINUS_DST_COLOR, gl::ONE_MINUS_SRC_COLOR),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+ pub fn set_blend_mode_show_overdraw(&mut self) {
+ self.set_blend_factors(
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ (gl::ONE, gl::ONE_MINUS_SRC_ALPHA),
+ );
+ }
+
+ pub fn set_blend_mode_max(&mut self) {
+ self.gl
+ .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
+ self.gl.blend_equation_separate(gl::MAX, gl::FUNC_ADD);
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = false;
+ }
+ }
+ pub fn set_blend_mode_min(&mut self) {
+ self.gl
+ .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
+ self.gl.blend_equation_separate(gl::MIN, gl::FUNC_ADD);
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = false;
+ }
+ }
+ pub fn set_blend_mode_advanced(&mut self, mode: MixBlendMode) {
+ self.gl.blend_equation(match mode {
+ MixBlendMode::Normal => {
+ // blend factor only make sense for the normal mode
+ self.gl.blend_func_separate(gl::ZERO, gl::SRC_COLOR, gl::ZERO, gl::SRC_ALPHA);
+ gl::FUNC_ADD
+ },
+ MixBlendMode::PlusLighter => {
+ return self.set_blend_mode_plus_lighter();
+ },
+ MixBlendMode::Multiply => gl::MULTIPLY_KHR,
+ MixBlendMode::Screen => gl::SCREEN_KHR,
+ MixBlendMode::Overlay => gl::OVERLAY_KHR,
+ MixBlendMode::Darken => gl::DARKEN_KHR,
+ MixBlendMode::Lighten => gl::LIGHTEN_KHR,
+ MixBlendMode::ColorDodge => gl::COLORDODGE_KHR,
+ MixBlendMode::ColorBurn => gl::COLORBURN_KHR,
+ MixBlendMode::HardLight => gl::HARDLIGHT_KHR,
+ MixBlendMode::SoftLight => gl::SOFTLIGHT_KHR,
+ MixBlendMode::Difference => gl::DIFFERENCE_KHR,
+ MixBlendMode::Exclusion => gl::EXCLUSION_KHR,
+ MixBlendMode::Hue => gl::HSL_HUE_KHR,
+ MixBlendMode::Saturation => gl::HSL_SATURATION_KHR,
+ MixBlendMode::Color => gl::HSL_COLOR_KHR,
+ MixBlendMode::Luminosity => gl::HSL_LUMINOSITY_KHR,
+ });
+ #[cfg(debug_assertions)]
+ {
+ self.shader_is_ready = false;
+ }
+ }
+
+ pub fn supports_extension(&self, extension: &str) -> bool {
+ supports_extension(&self.extensions, extension)
+ }
+
+ pub fn echo_driver_messages(&self) {
+ if self.capabilities.supports_khr_debug {
+ Device::log_driver_messages(self.gl());
+ }
+ }
+
+ fn log_driver_messages(gl: &dyn gl::Gl) {
+ for msg in gl.get_debug_messages() {
+ let level = match msg.severity {
+ gl::DEBUG_SEVERITY_HIGH => Level::Error,
+ gl::DEBUG_SEVERITY_MEDIUM => Level::Warn,
+ gl::DEBUG_SEVERITY_LOW => Level::Info,
+ gl::DEBUG_SEVERITY_NOTIFICATION => Level::Debug,
+ _ => Level::Trace,
+ };
+ let ty = match msg.ty {
+ gl::DEBUG_TYPE_ERROR => "error",
+ gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "deprecated",
+ gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "undefined",
+ gl::DEBUG_TYPE_PORTABILITY => "portability",
+ gl::DEBUG_TYPE_PERFORMANCE => "perf",
+ gl::DEBUG_TYPE_MARKER => "marker",
+ gl::DEBUG_TYPE_PUSH_GROUP => "group push",
+ gl::DEBUG_TYPE_POP_GROUP => "group pop",
+ gl::DEBUG_TYPE_OTHER => "other",
+ _ => "?",
+ };
+ log!(level, "({}) {}", ty, msg.message);
+ }
+ }
+
+ pub fn gl_describe_format(&self, format: ImageFormat) -> FormatDesc {
+ match format {
+ ImageFormat::R8 => FormatDesc {
+ internal: gl::R8,
+ external: gl::RED,
+ read: gl::RED,
+ pixel_type: gl::UNSIGNED_BYTE,
+ },
+ ImageFormat::R16 => FormatDesc {
+ internal: gl::R16,
+ external: gl::RED,
+ read: gl::RED,
+ pixel_type: gl::UNSIGNED_SHORT,
+ },
+ ImageFormat::BGRA8 => {
+ FormatDesc {
+ internal: self.bgra_formats.internal,
+ external: self.bgra_formats.external,
+ read: gl::BGRA,
+ pixel_type: self.bgra_pixel_type,
+ }
+ },
+ ImageFormat::RGBA8 => {
+ FormatDesc {
+ internal: gl::RGBA8,
+ external: gl::RGBA,
+ read: gl::RGBA,
+ pixel_type: gl::UNSIGNED_BYTE,
+ }
+ },
+ ImageFormat::RGBAF32 => FormatDesc {
+ internal: gl::RGBA32F,
+ external: gl::RGBA,
+ read: gl::RGBA,
+ pixel_type: gl::FLOAT,
+ },
+ ImageFormat::RGBAI32 => FormatDesc {
+ internal: gl::RGBA32I,
+ external: gl::RGBA_INTEGER,
+ read: gl::RGBA_INTEGER,
+ pixel_type: gl::INT,
+ },
+ ImageFormat::RG8 => FormatDesc {
+ internal: gl::RG8,
+ external: gl::RG,
+ read: gl::RG,
+ pixel_type: gl::UNSIGNED_BYTE,
+ },
+ ImageFormat::RG16 => FormatDesc {
+ internal: gl::RG16,
+ external: gl::RG,
+ read: gl::RG,
+ pixel_type: gl::UNSIGNED_SHORT,
+ },
+ }
+ }
+
+ /// Generates a memory report for the resources managed by the device layer.
+ pub fn report_memory(&self, size_op_funs: &MallocSizeOfOps, swgl: *mut c_void) -> MemoryReport {
+ let mut report = MemoryReport::default();
+ report.depth_target_textures += self.depth_targets_memory();
+
+ #[cfg(feature = "sw_compositor")]
+ if !swgl.is_null() {
+ report.swgl += swgl::Context::from(swgl).report_memory(size_op_funs.size_of_op);
+ }
+ // unconditionally use swgl stuff
+ let _ = size_op_funs;
+ let _ = swgl;
+ report
+ }
+
+ pub fn depth_targets_memory(&self) -> usize {
+ let mut total = 0;
+ for dim in self.depth_targets.keys() {
+ total += depth_target_size_in_bytes(dim);
+ }
+
+ total
+ }
+}
+
+pub struct FormatDesc {
+ /// Format the texel data is internally stored in within a texture.
+ pub internal: gl::GLenum,
+ /// Format that we expect the data to be provided when filling the texture.
+ pub external: gl::GLuint,
+ /// Format to read the texels as, so that they can be uploaded as `external`
+ /// later on.
+ pub read: gl::GLuint,
+ /// Associated pixel type.
+ pub pixel_type: gl::GLuint,
+}
+
+#[derive(Debug)]
+struct UploadChunk<'a> {
+ rect: DeviceIntRect,
+ stride: Option<i32>,
+ offset: usize,
+ format_override: Option<ImageFormat>,
+ texture: &'a Texture,
+}
+
+#[derive(Debug)]
+struct PixelBuffer<'a> {
+ size_used: usize,
+ // small vector avoids heap allocation for a single chunk
+ chunks: SmallVec<[UploadChunk<'a>; 1]>,
+ inner: UploadPBO,
+ mapping: &'a mut [mem::MaybeUninit<u8>],
+}
+
+impl<'a> PixelBuffer<'a> {
+ fn new(
+ pbo: UploadPBO,
+ ) -> Self {
+ let mapping = unsafe {
+ slice::from_raw_parts_mut(pbo.mapping.get_ptr().as_ptr(), pbo.pbo.reserved_size)
+ };
+ Self {
+ size_used: 0,
+ chunks: SmallVec::new(),
+ inner: pbo,
+ mapping,
+ }
+ }
+
+ fn flush_chunks(&mut self, device: &mut Device) {
+ for chunk in self.chunks.drain(..) {
+ TextureUploader::update_impl(device, chunk);
+ }
+ }
+}
+
+impl<'a> Drop for PixelBuffer<'a> {
+ fn drop(&mut self) {
+ assert_eq!(self.chunks.len(), 0, "PixelBuffer must be flushed before dropping.");
+ }
+}
+
+#[derive(Debug)]
+enum PBOMapping {
+ Unmapped,
+ Transient(ptr::NonNull<mem::MaybeUninit<u8>>),
+ Persistent(ptr::NonNull<mem::MaybeUninit<u8>>),
+}
+
+impl PBOMapping {
+ fn get_ptr(&self) -> ptr::NonNull<mem::MaybeUninit<u8>> {
+ match self {
+ PBOMapping::Unmapped => unreachable!("Cannot get pointer to unmapped PBO."),
+ PBOMapping::Transient(ptr) => *ptr,
+ PBOMapping::Persistent(ptr) => *ptr,
+ }
+ }
+}
+
+/// A PBO for uploading texture data, managed by UploadPBOPool.
+#[derive(Debug)]
+struct UploadPBO {
+ pbo: PBO,
+ mapping: PBOMapping,
+ can_recycle: bool,
+}
+
+impl UploadPBO {
+ fn empty() -> Self {
+ Self {
+ pbo: PBO {
+ id: 0,
+ reserved_size: 0,
+ },
+ mapping: PBOMapping::Unmapped,
+ can_recycle: false,
+ }
+ }
+}
+
+/// Allocates and recycles PBOs used for uploading texture data.
+/// Tries to allocate and recycle PBOs of a fixed size, but will make exceptions when
+/// a larger buffer is required or to work around driver bugs.
+pub struct UploadPBOPool {
+ /// Usage hint to provide to the driver for optimizations.
+ usage_hint: VertexUsageHint,
+ /// The preferred size, in bytes, of the buffers to allocate.
+ default_size: usize,
+ /// List of allocated PBOs ready to be re-used.
+ available_buffers: Vec<UploadPBO>,
+ /// PBOs which have been returned during the current frame,
+ /// and do not yet have an associated sync object.
+ returned_buffers: Vec<UploadPBO>,
+ /// PBOs which are waiting until their sync object is signalled,
+ /// indicating they can are ready to be re-used.
+ waiting_buffers: Vec<(gl::GLsync, Vec<UploadPBO>)>,
+ /// PBOs which have been orphaned.
+ /// We can recycle their IDs but must reallocate their storage.
+ orphaned_buffers: Vec<PBO>,
+}
+
+impl UploadPBOPool {
+ pub fn new(device: &mut Device, default_size: usize) -> Self {
+ let usage_hint = match device.upload_method {
+ UploadMethod::Immediate => VertexUsageHint::Stream,
+ UploadMethod::PixelBuffer(usage_hint) => usage_hint,
+ };
+ Self {
+ usage_hint,
+ default_size,
+ available_buffers: Vec::new(),
+ returned_buffers: Vec::new(),
+ waiting_buffers: Vec::new(),
+ orphaned_buffers: Vec::new(),
+ }
+ }
+
+ /// To be called at the beginning of a series of uploads.
+ /// Moves any buffers which are now ready to be used from the waiting list to the ready list.
+ pub fn begin_frame(&mut self, device: &mut Device) {
+ // Iterate through the waiting buffers and check if each fence has been signalled.
+ // If a fence is signalled, move its corresponding buffers to the available list.
+ // On error, delete the buffers. Stop when we find the first non-signalled fence,
+ // and clean up the signalled fences.
+ let mut first_not_signalled = self.waiting_buffers.len();
+ for (i, (sync, buffers)) in self.waiting_buffers.iter_mut().enumerate() {
+ match device.gl.client_wait_sync(*sync, 0, 0) {
+ gl::TIMEOUT_EXPIRED => {
+ first_not_signalled = i;
+ break;
+ },
+ gl::ALREADY_SIGNALED | gl::CONDITION_SATISFIED => {
+ self.available_buffers.extend(buffers.drain(..));
+ }
+ gl::WAIT_FAILED | _ => {
+ warn!("glClientWaitSync error in UploadPBOPool::begin_frame()");
+ for buffer in buffers.drain(..) {
+ device.delete_pbo(buffer.pbo);
+ }
+ }
+ }
+ }
+
+ // Delete signalled fences, and remove their now-empty Vecs from waiting_buffers.
+ for (sync, _) in self.waiting_buffers.drain(0..first_not_signalled) {
+ device.gl.delete_sync(sync);
+ }
+ }
+
+ // To be called at the end of a series of uploads.
+ // Creates a sync object, and adds the buffers returned during this frame to waiting_buffers.
+ pub fn end_frame(&mut self, device: &mut Device) {
+ if !self.returned_buffers.is_empty() {
+ let sync = device.gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
+ if !sync.is_null() {
+ self.waiting_buffers.push((sync, mem::replace(&mut self.returned_buffers, Vec::new())))
+ } else {
+ warn!("glFenceSync error in UploadPBOPool::end_frame()");
+
+ for buffer in self.returned_buffers.drain(..) {
+ device.delete_pbo(buffer.pbo);
+ }
+ }
+ }
+ }
+
+ /// Obtain a PBO, either by reusing an existing PBO or allocating a new one.
+ /// min_size specifies the minimum required size of the PBO. The returned PBO
+ /// may be larger than required.
+ fn get_pbo(&mut self, device: &mut Device, min_size: usize) -> Result<UploadPBO, String> {
+
+ // If min_size is smaller than our default size, then use the default size.
+ // The exception to this is when due to driver bugs we cannot upload from
+ // offsets other than zero within a PBO. In this case, there is no point in
+ // allocating buffers larger than required, as they cannot be shared.
+ let (can_recycle, size) = if min_size <= self.default_size && device.capabilities.supports_nonzero_pbo_offsets {
+ (true, self.default_size)
+ } else {
+ (false, min_size)
+ };
+
+ // Try to recycle an already allocated PBO.
+ if can_recycle {
+ if let Some(mut buffer) = self.available_buffers.pop() {
+ assert_eq!(buffer.pbo.reserved_size, size);
+ assert!(buffer.can_recycle);
+
+ device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, buffer.pbo.id);
+
+ match buffer.mapping {
+ PBOMapping::Unmapped => {
+ // If buffer was unmapped then transiently map it.
+ let ptr = device.gl.map_buffer_range(
+ gl::PIXEL_UNPACK_BUFFER,
+ 0,
+ buffer.pbo.reserved_size as _,
+ gl::MAP_WRITE_BIT | gl::MAP_UNSYNCHRONIZED_BIT,
+ ) as *mut _;
+
+ let ptr = ptr::NonNull::new(ptr).ok_or_else(
+ || format!("Failed to transiently map PBO of size {} bytes", buffer.pbo.reserved_size)
+ )?;
+
+ buffer.mapping = PBOMapping::Transient(ptr);
+ }
+ PBOMapping::Transient(_) => {
+ unreachable!("Transiently mapped UploadPBO must be unmapped before returning to pool.");
+ }
+ PBOMapping::Persistent(_) => {
+ }
+ }
+
+ return Ok(buffer);
+ }
+ }
+
+ // Try to recycle a PBO ID (but not its allocation) from a previously allocated PBO.
+ // If there are none available, create a new PBO.
+ let mut pbo = match self.orphaned_buffers.pop() {
+ Some(pbo) => pbo,
+ None => device.create_pbo(),
+ };
+
+ assert_eq!(pbo.reserved_size, 0);
+ pbo.reserved_size = size;
+
+ device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo.id);
+ let mapping = if device.capabilities.supports_buffer_storage && can_recycle {
+ device.gl.buffer_storage(
+ gl::PIXEL_UNPACK_BUFFER,
+ pbo.reserved_size as _,
+ ptr::null(),
+ gl::MAP_WRITE_BIT | gl::MAP_PERSISTENT_BIT,
+ );
+ let ptr = device.gl.map_buffer_range(
+ gl::PIXEL_UNPACK_BUFFER,
+ 0,
+ pbo.reserved_size as _,
+ // GL_MAP_COHERENT_BIT doesn't seem to work on Adreno, so use glFlushMappedBufferRange.
+ // kvark notes that coherent memory can be faster on some platforms, such as nvidia,
+ // so in the future we could choose which to use at run time.
+ gl::MAP_WRITE_BIT | gl::MAP_PERSISTENT_BIT | gl::MAP_FLUSH_EXPLICIT_BIT,
+ ) as *mut _;
+
+ let ptr = ptr::NonNull::new(ptr).ok_or_else(
+ || format!("Failed to transiently map PBO of size {} bytes", pbo.reserved_size)
+ )?;
+
+ PBOMapping::Persistent(ptr)
+ } else {
+ device.gl.buffer_data_untyped(
+ gl::PIXEL_UNPACK_BUFFER,
+ pbo.reserved_size as _,
+ ptr::null(),
+ self.usage_hint.to_gl(),
+ );
+ let ptr = device.gl.map_buffer_range(
+ gl::PIXEL_UNPACK_BUFFER,
+ 0,
+ pbo.reserved_size as _,
+ // Unlike the above code path, where we are re-mapping a buffer that has previously been unmapped,
+ // this buffer has just been created there is no need for GL_MAP_UNSYNCHRONIZED_BIT.
+ gl::MAP_WRITE_BIT,
+ ) as *mut _;
+
+ let ptr = ptr::NonNull::new(ptr).ok_or_else(
+ || format!("Failed to transiently map PBO of size {} bytes", pbo.reserved_size)
+ )?;
+
+ PBOMapping::Transient(ptr)
+ };
+
+ Ok(UploadPBO { pbo, mapping, can_recycle })
+ }
+
+ /// Returns a PBO to the pool. If the PBO is recyclable it is placed in the waiting list.
+ /// Otherwise we orphan the allocation immediately, and will subsequently reuse just the ID.
+ fn return_pbo(&mut self, device: &mut Device, mut buffer: UploadPBO) {
+ assert!(
+ !matches!(buffer.mapping, PBOMapping::Transient(_)),
+ "Transiently mapped UploadPBO must be unmapped before returning to pool.",
+ );
+
+ if buffer.can_recycle {
+ self.returned_buffers.push(buffer);
+ } else {
+ device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, buffer.pbo.id);
+ device.gl.buffer_data_untyped(
+ gl::PIXEL_UNPACK_BUFFER,
+ 0,
+ ptr::null(),
+ gl::STREAM_DRAW,
+ );
+ buffer.pbo.reserved_size = 0;
+ self.orphaned_buffers.push(buffer.pbo);
+ }
+
+ device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ /// Frees all allocated buffers in response to a memory pressure event.
+ pub fn on_memory_pressure(&mut self, device: &mut Device) {
+ for buffer in self.available_buffers.drain(..) {
+ device.delete_pbo(buffer.pbo);
+ }
+ for buffer in self.returned_buffers.drain(..) {
+ device.delete_pbo(buffer.pbo)
+ }
+ for (sync, buffers) in self.waiting_buffers.drain(..) {
+ device.gl.delete_sync(sync);
+ for buffer in buffers {
+ device.delete_pbo(buffer.pbo)
+ }
+ }
+ // There is no need to delete orphaned PBOs on memory pressure.
+ }
+
+ /// Generates a memory report.
+ pub fn report_memory(&self) -> MemoryReport {
+ let mut report = MemoryReport::default();
+ for buffer in &self.available_buffers {
+ report.texture_upload_pbos += buffer.pbo.reserved_size;
+ }
+ for buffer in &self.returned_buffers {
+ report.texture_upload_pbos += buffer.pbo.reserved_size;
+ }
+ for (_, buffers) in &self.waiting_buffers {
+ for buffer in buffers {
+ report.texture_upload_pbos += buffer.pbo.reserved_size;
+ }
+ }
+ report
+ }
+
+ pub fn deinit(&mut self, device: &mut Device) {
+ for buffer in self.available_buffers.drain(..) {
+ device.delete_pbo(buffer.pbo);
+ }
+ for buffer in self.returned_buffers.drain(..) {
+ device.delete_pbo(buffer.pbo)
+ }
+ for (sync, buffers) in self.waiting_buffers.drain(..) {
+ device.gl.delete_sync(sync);
+ for buffer in buffers {
+ device.delete_pbo(buffer.pbo)
+ }
+ }
+ for pbo in self.orphaned_buffers.drain(..) {
+ device.delete_pbo(pbo);
+ }
+ }
+}
+
+/// Used to perform a series of texture uploads.
+/// Create using Device::upload_texture(). Perform a series of uploads using either
+/// upload(), or stage() and upload_staged(), then call flush().
+pub struct TextureUploader<'a> {
+ /// A list of buffers containing uploads that need to be flushed.
+ buffers: Vec<PixelBuffer<'a>>,
+ /// Pool used to obtain PBOs to fill with texture data.
+ pub pbo_pool: &'a mut UploadPBOPool,
+}
+
+impl<'a> Drop for TextureUploader<'a> {
+ fn drop(&mut self) {
+ assert!(
+ thread::panicking() || self.buffers.is_empty(),
+ "TextureUploader must be flushed before it is dropped."
+ );
+ }
+}
+
+/// A buffer used to manually stage data to be uploaded to a texture.
+/// Created by calling TextureUploader::stage(), the data can then be written to via get_mapping().
+#[derive(Debug)]
+pub struct UploadStagingBuffer<'a> {
+ /// The PixelBuffer containing this upload.
+ buffer: PixelBuffer<'a>,
+ /// The offset of this upload within the PixelBuffer.
+ offset: usize,
+ /// The size of this upload.
+ size: usize,
+ /// The stride of the data within the buffer.
+ stride: usize,
+}
+
+impl<'a> UploadStagingBuffer<'a> {
+ /// Returns the required stride of the data to be written to the buffer.
+ pub fn get_stride(&self) -> usize {
+ self.stride
+ }
+
+ /// Returns a mapping of the data in the buffer, to be written to.
+ pub fn get_mapping(&mut self) -> &mut [mem::MaybeUninit<u8>] {
+ &mut self.buffer.mapping[self.offset..self.offset + self.size]
+ }
+}
+
+impl<'a> TextureUploader<'a> {
+ /// Returns an UploadStagingBuffer which can be used to manually stage data to be uploaded.
+ /// Once the data has been staged, it can be uploaded with upload_staged().
+ pub fn stage(
+ &mut self,
+ device: &mut Device,
+ format: ImageFormat,
+ size: DeviceIntSize,
+ ) -> Result<UploadStagingBuffer<'a>, String> {
+ assert!(matches!(device.upload_method, UploadMethod::PixelBuffer(_)), "Texture uploads should only be staged when using pixel buffers.");
+
+ // for optimal PBO texture uploads the offset and stride of the data in
+ // the buffer may have to be a multiple of a certain value.
+ let (dst_size, dst_stride) = device.required_upload_size_and_stride(
+ size,
+ format,
+ );
+
+ // Find a pixel buffer with enough space remaining, creating a new one if required.
+ let buffer_index = self.buffers.iter().position(|buffer| {
+ buffer.size_used + dst_size <= buffer.inner.pbo.reserved_size
+ });
+ let buffer = match buffer_index {
+ Some(i) => self.buffers.swap_remove(i),
+ None => PixelBuffer::new(self.pbo_pool.get_pbo(device, dst_size)?),
+ };
+
+ if !device.capabilities.supports_nonzero_pbo_offsets {
+ assert_eq!(buffer.size_used, 0, "PBO uploads from non-zero offset are not supported.");
+ }
+ assert!(buffer.size_used + dst_size <= buffer.inner.pbo.reserved_size, "PixelBuffer is too small");
+
+ let offset = buffer.size_used;
+
+ Ok(UploadStagingBuffer {
+ buffer,
+ offset,
+ size: dst_size,
+ stride: dst_stride,
+ })
+ }
+
+ /// Uploads manually staged texture data to the specified texture.
+ pub fn upload_staged(
+ &mut self,
+ device: &mut Device,
+ texture: &'a Texture,
+ rect: DeviceIntRect,
+ format_override: Option<ImageFormat>,
+ mut staging_buffer: UploadStagingBuffer<'a>,
+ ) -> usize {
+ let size = staging_buffer.size;
+
+ staging_buffer.buffer.chunks.push(UploadChunk {
+ rect,
+ stride: Some(staging_buffer.stride as i32),
+ offset: staging_buffer.offset,
+ format_override,
+ texture,
+ });
+ staging_buffer.buffer.size_used += staging_buffer.size;
+
+ // Flush the buffer if it is full, otherwise return it to the uploader for further use.
+ if staging_buffer.buffer.size_used < staging_buffer.buffer.inner.pbo.reserved_size {
+ self.buffers.push(staging_buffer.buffer);
+ } else {
+ Self::flush_buffer(device, self.pbo_pool, staging_buffer.buffer);
+ }
+
+ size
+ }
+
+ /// Uploads texture data to the specified texture.
+ pub fn upload<T>(
+ &mut self,
+ device: &mut Device,
+ texture: &'a Texture,
+ mut rect: DeviceIntRect,
+ stride: Option<i32>,
+ format_override: Option<ImageFormat>,
+ data: *const T,
+ len: usize,
+ ) -> usize {
+ // Textures dimensions may have been clamped by the hardware. Crop the
+ // upload region to match.
+ let cropped = rect.intersection(
+ &DeviceIntRect::from_size(texture.get_dimensions())
+ );
+ if cfg!(debug_assertions) && cropped.map_or(true, |r| r != rect) {
+ warn!("Cropping texture upload {:?} to {:?}", rect, cropped);
+ }
+ rect = match cropped {
+ None => return 0,
+ Some(r) => r,
+ };
+
+ let bytes_pp = texture.format.bytes_per_pixel() as usize;
+ let width_bytes = rect.width() as usize * bytes_pp;
+
+ let src_stride = stride.map_or(width_bytes, |stride| {
+ assert!(stride >= 0);
+ stride as usize
+ });
+ let src_size = (rect.height() as usize - 1) * src_stride + width_bytes;
+ assert!(src_size <= len * mem::size_of::<T>());
+
+ match device.upload_method {
+ UploadMethod::Immediate => {
+ if cfg!(debug_assertions) {
+ let mut bound_buffer = [0];
+ unsafe {
+ device.gl.get_integer_v(gl::PIXEL_UNPACK_BUFFER_BINDING, &mut bound_buffer);
+ }
+ assert_eq!(bound_buffer[0], 0, "GL_PIXEL_UNPACK_BUFFER must not be bound for immediate uploads.");
+ }
+
+ Self::update_impl(device, UploadChunk {
+ rect,
+ stride: Some(src_stride as i32),
+ offset: data as _,
+ format_override,
+ texture,
+ });
+
+ width_bytes * rect.height() as usize
+ }
+ UploadMethod::PixelBuffer(_) => {
+ let mut staging_buffer = match self.stage(device, texture.format, rect.size()) {
+ Ok(staging_buffer) => staging_buffer,
+ Err(_) => return 0,
+ };
+ let dst_stride = staging_buffer.get_stride();
+
+ unsafe {
+ let src: &[mem::MaybeUninit<u8>] = slice::from_raw_parts(data as *const _, src_size);
+
+ if src_stride == dst_stride {
+ // the stride is already optimal, so simply copy
+ // the data as-is in to the buffer
+ staging_buffer.get_mapping()[..src_size].copy_from_slice(src);
+ } else {
+ // copy the data line-by-line in to the buffer so
+ // that it has an optimal stride
+ for y in 0..rect.height() as usize {
+ let src_start = y * src_stride;
+ let src_end = src_start + width_bytes;
+ let dst_start = y * staging_buffer.get_stride();
+ let dst_end = dst_start + width_bytes;
+
+ staging_buffer.get_mapping()[dst_start..dst_end].copy_from_slice(&src[src_start..src_end])
+ }
+ }
+ }
+
+ self.upload_staged(device, texture, rect, format_override, staging_buffer)
+ }
+ }
+ }
+
+ fn flush_buffer(device: &mut Device, pbo_pool: &mut UploadPBOPool, mut buffer: PixelBuffer) {
+ device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, buffer.inner.pbo.id);
+ match buffer.inner.mapping {
+ PBOMapping::Unmapped => unreachable!("UploadPBO should be mapped at this stage."),
+ PBOMapping::Transient(_) => {
+ device.gl.unmap_buffer(gl::PIXEL_UNPACK_BUFFER);
+ buffer.inner.mapping = PBOMapping::Unmapped;
+ }
+ PBOMapping::Persistent(_) => {
+ device.gl.flush_mapped_buffer_range(gl::PIXEL_UNPACK_BUFFER, 0, buffer.size_used as _);
+ }
+ }
+ buffer.flush_chunks(device);
+ let pbo = mem::replace(&mut buffer.inner, UploadPBO::empty());
+ pbo_pool.return_pbo(device, pbo);
+ }
+
+ /// Flushes all pending texture uploads. Must be called after all
+ /// required upload() or upload_staged() calls have been made.
+ pub fn flush(mut self, device: &mut Device) {
+ for buffer in self.buffers.drain(..) {
+ Self::flush_buffer(device, self.pbo_pool, buffer);
+ }
+
+ device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ fn update_impl(device: &mut Device, chunk: UploadChunk) {
+ device.bind_texture(DEFAULT_TEXTURE, chunk.texture, Swizzle::default());
+
+ let format = chunk.format_override.unwrap_or(chunk.texture.format);
+ let (gl_format, bpp, data_type) = match format {
+ ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
+ ImageFormat::R16 => (gl::RED, 2, gl::UNSIGNED_SHORT),
+ ImageFormat::BGRA8 => (device.bgra_formats.external, 4, device.bgra_pixel_type),
+ ImageFormat::RGBA8 => (gl::RGBA, 4, gl::UNSIGNED_BYTE),
+ ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
+ ImageFormat::RG16 => (gl::RG, 4, gl::UNSIGNED_SHORT),
+ ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
+ ImageFormat::RGBAI32 => (gl::RGBA_INTEGER, 16, gl::INT),
+ };
+
+ let row_length = match chunk.stride {
+ Some(value) => value / bpp,
+ None => chunk.texture.size.width,
+ };
+
+ if chunk.stride.is_some() {
+ device.gl.pixel_store_i(
+ gl::UNPACK_ROW_LENGTH,
+ row_length as _,
+ );
+ }
+
+ let pos = chunk.rect.min;
+ let size = chunk.rect.size();
+
+ match chunk.texture.target {
+ gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
+ device.gl.tex_sub_image_2d_pbo(
+ chunk.texture.target,
+ 0,
+ pos.x as _,
+ pos.y as _,
+ size.width as _,
+ size.height as _,
+ gl_format,
+ data_type,
+ chunk.offset,
+ );
+ }
+ _ => panic!("BUG: Unexpected texture target!"),
+ }
+
+ // If using tri-linear filtering, build the mip-map chain for this texture.
+ if chunk.texture.filter == TextureFilter::Trilinear {
+ device.gl.generate_mipmap(chunk.texture.target);
+ }
+
+ // Reset row length to 0, otherwise the stride would apply to all texture uploads.
+ if chunk.stride.is_some() {
+ device.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _);
+ }
+ }
+}
+
+fn texels_to_u8_slice<T: Texel>(texels: &[T]) -> &[u8] {
+ unsafe {
+ slice::from_raw_parts(texels.as_ptr() as *const u8, texels.len() * mem::size_of::<T>())
+ }
+}
diff --git a/gfx/wr/webrender/src/device/mod.rs b/gfx/wr/webrender/src/device/mod.rs
new file mode 100644
index 0000000000..21684dea3e
--- /dev/null
+++ b/gfx/wr/webrender/src/device/mod.rs
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod gl;
+pub mod query_gl;
+
+pub use self::gl::*;
+pub use self::query_gl as query;
diff --git a/gfx/wr/webrender/src/device/query_gl.rs b/gfx/wr/webrender/src/device/query_gl.rs
new file mode 100644
index 0000000000..c7fd9a9070
--- /dev/null
+++ b/gfx/wr/webrender/src/device/query_gl.rs
@@ -0,0 +1,318 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use gleam::gl;
+use std::mem;
+use std::rc::Rc;
+
+use crate::device::GpuFrameId;
+use crate::profiler::GpuProfileTag;
+
+#[derive(Copy, Clone, Debug)]
+pub enum GpuDebugMethod {
+ None,
+ MarkerEXT,
+ KHR,
+}
+
+#[derive(Debug, Clone)]
+pub struct GpuTimer {
+ pub tag: GpuProfileTag,
+ pub time_ns: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct GpuSampler {
+ pub tag: GpuProfileTag,
+ pub count: u64,
+}
+
+pub struct QuerySet<T> {
+ set: Vec<gl::GLuint>,
+ data: Vec<T>,
+ pending: gl::GLuint,
+}
+
+impl<T> QuerySet<T> {
+ fn new() -> Self {
+ QuerySet {
+ set: Vec::new(),
+ data: Vec::new(),
+ pending: 0,
+ }
+ }
+
+ fn reset(&mut self) {
+ self.data.clear();
+ self.pending = 0;
+ }
+
+ fn add(&mut self, value: T) -> Option<gl::GLuint> {
+ assert_eq!(self.pending, 0);
+ self.set.get(self.data.len()).cloned().map(|query_id| {
+ self.data.push(value);
+ self.pending = query_id;
+ query_id
+ })
+ }
+
+ fn take<F: Fn(&mut T, gl::GLuint)>(&mut self, fun: F) -> Vec<T> {
+ let mut data = mem::replace(&mut self.data, Vec::new());
+ for (value, &query) in data.iter_mut().zip(self.set.iter()) {
+ fun(value, query)
+ }
+ data
+ }
+}
+
+pub struct GpuFrameProfile {
+ gl: Rc<dyn gl::Gl>,
+ timers: QuerySet<GpuTimer>,
+ samplers: QuerySet<GpuSampler>,
+ frame_id: GpuFrameId,
+ inside_frame: bool,
+ debug_method: GpuDebugMethod,
+}
+
+impl GpuFrameProfile {
+ fn new(gl: Rc<dyn gl::Gl>, debug_method: GpuDebugMethod) -> Self {
+ GpuFrameProfile {
+ gl,
+ timers: QuerySet::new(),
+ samplers: QuerySet::new(),
+ frame_id: GpuFrameId::new(0),
+ inside_frame: false,
+ debug_method
+ }
+ }
+
+ fn enable_timers(&mut self, count: i32) {
+ self.timers.set = self.gl.gen_queries(count);
+ }
+
+ fn disable_timers(&mut self) {
+ if !self.timers.set.is_empty() {
+ self.gl.delete_queries(&self.timers.set);
+ }
+ self.timers.set = Vec::new();
+ }
+
+ fn enable_samplers(&mut self, count: i32) {
+ self.samplers.set = self.gl.gen_queries(count);
+ }
+
+ fn disable_samplers(&mut self) {
+ if !self.samplers.set.is_empty() {
+ self.gl.delete_queries(&self.samplers.set);
+ }
+ self.samplers.set = Vec::new();
+ }
+
+ fn begin_frame(&mut self, frame_id: GpuFrameId) {
+ self.frame_id = frame_id;
+ self.timers.reset();
+ self.samplers.reset();
+ self.inside_frame = true;
+ }
+
+ fn end_frame(&mut self) {
+ self.finish_timer();
+ self.finish_sampler();
+ self.inside_frame = false;
+ }
+
+ fn finish_timer(&mut self) {
+ debug_assert!(self.inside_frame);
+ if self.timers.pending != 0 {
+ self.gl.end_query(gl::TIME_ELAPSED);
+ self.timers.pending = 0;
+ }
+ }
+
+ fn finish_sampler(&mut self) {
+ debug_assert!(self.inside_frame);
+ if self.samplers.pending != 0 {
+ self.gl.end_query(gl::SAMPLES_PASSED);
+ self.samplers.pending = 0;
+ }
+ }
+
+ fn start_timer(&mut self, tag: GpuProfileTag) -> GpuTimeQuery {
+ self.finish_timer();
+
+ let marker = GpuMarker::new(&self.gl, tag.label, self.debug_method);
+
+ if let Some(query) = self.timers.add(GpuTimer { tag, time_ns: 0 }) {
+ self.gl.begin_query(gl::TIME_ELAPSED, query);
+ }
+
+ GpuTimeQuery(marker)
+ }
+
+ fn start_sampler(&mut self, tag: GpuProfileTag) -> GpuSampleQuery {
+ self.finish_sampler();
+
+ if let Some(query) = self.samplers.add(GpuSampler { tag, count: 0 }) {
+ self.gl.begin_query(gl::SAMPLES_PASSED, query);
+ }
+
+ GpuSampleQuery
+ }
+
+ fn build_samples(&mut self) -> (GpuFrameId, Vec<GpuTimer>, Vec<GpuSampler>) {
+ debug_assert!(!self.inside_frame);
+ let gl = &self.gl;
+
+ (
+ self.frame_id,
+ self.timers.take(|timer, query| {
+ timer.time_ns = gl.get_query_object_ui64v(query, gl::QUERY_RESULT)
+ }),
+ self.samplers.take(|sampler, query| {
+ sampler.count = gl.get_query_object_ui64v(query, gl::QUERY_RESULT)
+ }),
+ )
+ }
+}
+
+impl Drop for GpuFrameProfile {
+ fn drop(&mut self) {
+ self.disable_timers();
+ self.disable_samplers();
+ }
+}
+
+const NUM_PROFILE_FRAMES: usize = 4;
+
+pub struct GpuProfiler {
+ gl: Rc<dyn gl::Gl>,
+ frames: [GpuFrameProfile; NUM_PROFILE_FRAMES],
+ next_frame: usize,
+ debug_method: GpuDebugMethod
+}
+
+impl GpuProfiler {
+ pub fn new(gl: Rc<dyn gl::Gl>, debug_method: GpuDebugMethod) -> Self {
+ let f = || GpuFrameProfile::new(Rc::clone(&gl), debug_method);
+
+ let frames = [f(), f(), f(), f()];
+ GpuProfiler {
+ gl,
+ next_frame: 0,
+ frames,
+ debug_method
+ }
+ }
+
+ pub fn enable_timers(&mut self) {
+ const MAX_TIMERS_PER_FRAME: i32 = 256;
+
+ for frame in &mut self.frames {
+ frame.enable_timers(MAX_TIMERS_PER_FRAME);
+ }
+ }
+
+ pub fn disable_timers(&mut self) {
+ for frame in &mut self.frames {
+ frame.disable_timers();
+ }
+ }
+
+ pub fn enable_samplers(&mut self) {
+ const MAX_SAMPLERS_PER_FRAME: i32 = 16;
+ if cfg!(target_os = "macos") {
+ warn!("Expect macOS driver bugs related to sample queries")
+ }
+
+ for frame in &mut self.frames {
+ frame.enable_samplers(MAX_SAMPLERS_PER_FRAME);
+ }
+ }
+
+ pub fn disable_samplers(&mut self) {
+ for frame in &mut self.frames {
+ frame.disable_samplers();
+ }
+ }
+
+ pub fn build_samples(&mut self) -> (GpuFrameId, Vec<GpuTimer>, Vec<GpuSampler>) {
+ self.frames[self.next_frame].build_samples()
+ }
+
+ pub fn begin_frame(&mut self, frame_id: GpuFrameId) {
+ self.frames[self.next_frame].begin_frame(frame_id);
+ }
+
+ pub fn end_frame(&mut self) {
+ self.frames[self.next_frame].end_frame();
+ self.next_frame = (self.next_frame + 1) % self.frames.len();
+ }
+
+ pub fn start_timer(&mut self, tag: GpuProfileTag) -> GpuTimeQuery {
+ self.frames[self.next_frame].start_timer(tag)
+ }
+
+ pub fn start_sampler(&mut self, tag: GpuProfileTag) -> GpuSampleQuery {
+ self.frames[self.next_frame].start_sampler(tag)
+ }
+
+ pub fn finish_sampler(&mut self, _sampler: GpuSampleQuery) {
+ self.frames[self.next_frame].finish_sampler()
+ }
+
+ pub fn start_marker(&mut self, label: &str) -> GpuMarker {
+ GpuMarker::new(&self.gl, label, self.debug_method)
+ }
+
+ pub fn place_marker(&mut self, label: &str) {
+ GpuMarker::fire(&self.gl, label, self.debug_method)
+ }
+}
+
+#[must_use]
+pub struct GpuMarker {
+ gl: Option<(Rc<dyn gl::Gl>, GpuDebugMethod)>,
+}
+
+impl GpuMarker {
+ fn new(gl: &Rc<dyn gl::Gl>, message: &str, debug_method: GpuDebugMethod) -> Self {
+ let gl = match debug_method {
+ GpuDebugMethod::KHR => {
+ gl.push_debug_group_khr(gl::DEBUG_SOURCE_APPLICATION, 0, message);
+ Some((Rc::clone(gl), debug_method))
+ },
+ GpuDebugMethod::MarkerEXT => {
+ gl.push_group_marker_ext(message);
+ Some((Rc::clone(gl), debug_method))
+ },
+ GpuDebugMethod::None => None,
+ };
+ GpuMarker { gl }
+ }
+
+ fn fire(gl: &Rc<dyn gl::Gl>, message: &str, debug_method: GpuDebugMethod) {
+ match debug_method {
+ GpuDebugMethod::KHR => gl.debug_message_insert_khr(gl::DEBUG_SOURCE_APPLICATION, gl::DEBUG_TYPE_MARKER, 0, gl::DEBUG_SEVERITY_NOTIFICATION, message),
+ GpuDebugMethod::MarkerEXT => gl.insert_event_marker_ext(message),
+ GpuDebugMethod::None => {}
+ };
+ }
+}
+
+impl Drop for GpuMarker {
+ fn drop(&mut self) {
+ if let Some((ref gl, debug_method)) = self.gl {
+ match debug_method {
+ GpuDebugMethod::KHR => gl.pop_debug_group_khr(),
+ GpuDebugMethod::MarkerEXT => gl.pop_group_marker_ext(),
+ GpuDebugMethod::None => {}
+ };
+ }
+ }
+}
+
+#[must_use]
+pub struct GpuTimeQuery(GpuMarker);
+#[must_use]
+pub struct GpuSampleQuery;