Quelle gl.rs
Sprache: unbekannt
|
|
/* 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, ImageR endering};
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 + Default {
fn image_format() -> ImageFormat;
}
unsafe impl Texel for u8 {
fn image_format() -> ImageFormat { ImageFormat::R8 }
}
/// 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,
ImageBufferKind::TextureExternalBT709 => 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
)
}
}
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, Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
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_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(#[allow(dead_code)] 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,
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()?;
// Not all devices have the trailing string following the "p" number.
let (p_str, _) = version_string.split_once("-").unwrap_or((version_string, ""));
let p = p_str.parse().ok()?;
Some((v, r, p))
}
/// Returns whether this GPU belongs to the Mali Midgard family
fn is_mali_midgard(renderer_name: &str) -> bool {
renderer_name.starts_with("Mali-T")
}
/// Returns whether this GPU belongs to the Mali Bifrost family
fn is_mali_bifrost(renderer_name: &str) -> bool {
renderer_name == "Mali-G31"
|| renderer_name == "Mali-G51"
|| renderer_name == "Mali-G71"
|| renderer_name == "Mali-G52"
|| renderer_name == "Mali-G72"
|| renderer_name == "Mali-G76"
}
/// Returns whether this GPU belongs to the Mali Valhall family
fn is_mali_valhall(renderer_name: &str) -> bool {
// As new Valhall GPUs may be released in the future we match all Mali-G models, apart from
// Bifrost models (of which we don't expect any new ones to be released)
renderer_name.starts_with("Mali-G") && !is_mali_bifrost(renderer_name)
}
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));
}
let is_xclipse = renderer_name.starts_with("ANGLE (Samsung Xclipse");
// 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.
// We block this on Mali Valhall GPUs as the extension's functions always return
// GL_OUT_OF_MEMORY, causing us to panic in debug builds.
// Blocked on Xclipse GPUs as glGetDebugMessageLog returns an incorrect count,
// leading to an out-of-bounds index in gleam.
let supports_khr_debug =
supports_extension(&extensions, "GL_KHR_debug")
&& !is_mali_valhall(&renderer_name)
&& !is_xclipse;
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 =
!is_mali_midgard(&renderer_name) && !is_mali_bifrost(&renderer_name);
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;
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.9 Sekunden
(vorverarbeitet)
]
|
2026-04-06
|