🔒 Repository is read-only – file editing is disabled.

PaganDE/smithay-0.7.0/src/backend/renderer/gles/mod.rs main

3241 linii Raw ← Powrót
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241
//! Implementation of the rendering traits using OpenGL ES 2

use cgmath::{prelude::*, Matrix3, Vector2};
use core::slice;
use std::{
    collections::HashMap,
    ffi::{CStr, CString},
    fmt,
    marker::PhantomData,
    mem,
    os::raw::c_char,
    ptr,
    rc::Rc,
    sync::{
        atomic::{AtomicBool, AtomicPtr, Ordering},
        mpsc::{channel, Receiver, Sender},
        Arc, Mutex, RwLock, RwLockWriteGuard,
    },
};
use tracing::{debug, error, info, info_span, instrument, span, span::EnteredSpan, trace, warn, Level};

pub mod element;
mod error;
pub mod format;
mod shaders;
mod texture;
mod uniform;
mod version;

pub use error::*;
use format::*;
pub use shaders::*;
pub use texture::*;
pub use uniform::*;

use self::version::GlVersion;

use super::{
    sync::SyncPoint, Bind, Blit, BlitFrame, Color32F, ContextId, DebugFlags, ExportMem, Frame, ImportDma,
    ImportMem, Offscreen, Renderer, RendererSuper, Texture, TextureFilter, TextureMapping,
};
use crate::{
    backend::{
        allocator::{
            dmabuf::{Dmabuf, WeakDmabuf},
            format::{get_bpp, get_opaque, has_alpha, FormatSet},
            Buffer, Format, Fourcc,
        },
        egl::{
            fence::EGLFence,
            ffi::egl::{self as ffi_egl, types::EGLImage},
            EGLContext, EGLSurface, MakeCurrentError,
        },
    },
    utils::{Buffer as BufferCoord, Physical, Rectangle, Size, Transform},
};

#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
use super::ImportEgl;
#[cfg(feature = "wayland_frontend")]
use super::{ImportDmaWl, ImportMemWl};
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
use crate::backend::egl::{display::EGLBufferReader, Format as EGLFormat};
#[cfg(feature = "wayland_frontend")]
use crate::wayland::shm::shm_format_to_fourcc;
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::wl_buffer;

#[allow(clippy::all, missing_docs, missing_debug_implementations)]
pub mod ffi {
    include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}

enum CleanupResource {
    Texture(ffi::types::GLuint),
    FramebufferObject(ffi::types::GLuint),
    RenderbufferObject(ffi::types::GLuint),
    EGLImage(EGLImage),
    Mapping(ffi::types::GLuint, *const std::ffi::c_void),
    Program(ffi::types::GLuint),
    Sync(ffi::types::GLsync),
}
unsafe impl Send for CleanupResource {}

#[derive(Debug, Clone)]
struct GlesBuffer {
    dmabuf: WeakDmabuf,
    image: EGLImage,
    rbo: ffi::types::GLuint,
    fbo: ffi::types::GLuint,
}

/// Offscreen render surface
///
/// Usually more performant than using a texture as a framebuffer.
/// Can be read out, but not used like a texture otherwise.
#[derive(Debug, Clone)]
pub struct GlesRenderbuffer(Rc<GlesRenderbufferInternal>);

#[derive(Debug)]
struct GlesRenderbufferInternal {
    rbo: ffi::types::GLuint,
    format: ffi::types::GLenum,
    has_alpha: bool,
    size: Size<i32, BufferCoord>,
    destruction_callback_sender: Sender<CleanupResource>,
}

impl GlesRenderbuffer {
    /// Size of the renderbuffer
    pub fn size(&self) -> Size<i32, BufferCoord> {
        self.0.size
    }

    /// Internal format of the renderbuffer
    pub fn format(&self) -> Option<Fourcc> {
        let fmt = gl_internal_format_to_fourcc(self.0.format);
        if self.0.has_alpha {
            fmt
        } else {
            fmt.and_then(get_opaque)
        }
    }
}

impl Drop for GlesRenderbufferInternal {
    fn drop(&mut self) {
        let _ = self
            .destruction_callback_sender
            .send(CleanupResource::RenderbufferObject(self.rbo));
    }
}

/// A GL framebuffer
#[derive(Debug)]
pub struct GlesTarget<'a>(GlesTargetInternal<'a>);

#[derive(Debug)]
enum GlesTargetInternal<'a> {
    Image {
        // TODO: Ideally we would be able to share the texture between renderers with shared EGLContexts though.
        // But we definitly don't want to add user data to a dmabuf to facilitate this. Maybe use the EGLContexts userdata for storing the buffers?
        buf: GlesBuffer,
        dmabuf: &'a mut Dmabuf,
    },
    Surface {
        surface: &'a mut EGLSurface,
    },
    Texture {
        texture: GlesTexture,
        sync_lock: RwLockWriteGuard<'a, TextureSync>,
        fbo: ffi::types::GLuint,
        destruction_callback_sender: Sender<CleanupResource>,
    },
    Renderbuffer {
        buf: &'a mut GlesRenderbuffer,
        fbo: ffi::types::GLuint,
    },
}

impl Texture for GlesTarget<'_> {
    fn height(&self) -> u32 {
        self.size().h as u32
    }

    fn width(&self) -> u32 {
        self.size().w as u32
    }

    fn size(&self) -> Size<i32, BufferCoord> {
        match &self.0 {
            GlesTargetInternal::Image { dmabuf, .. } => dmabuf.size(),
            GlesTargetInternal::Surface { surface } => surface
                .get_size()
                .expect("a bound EGLSurface needs to have a size")
                .to_logical(1)
                .to_buffer(1, Transform::Normal),
            GlesTargetInternal::Texture { texture, .. } => texture.size(),
            GlesTargetInternal::Renderbuffer { buf, .. } => buf.size(),
        }
    }

    fn format(&self) -> Option<Fourcc> {
        let (gl_format, _) = self.0.format()?;
        gl_internal_format_to_fourcc(gl_format)
    }
}

impl GlesTargetInternal<'_> {
    fn format(&self) -> Option<(ffi::types::GLenum, bool)> {
        match self {
            GlesTargetInternal::Image { dmabuf, .. } => {
                let format = crate::backend::allocator::Buffer::format(*dmabuf).code;
                let has_alpha = has_alpha(format);
                let (format, _, _) = fourcc_to_gl_formats(format)?;

                Some((format, has_alpha))
            }
            GlesTargetInternal::Surface { surface, .. } => {
                let format = surface.pixel_format();
                let format = match (format.color_bits, format.alpha_bits) {
                    (24, 8) => ffi::RGB8,
                    (30, 2) => ffi::RGB10_A2,
                    (48, 16) => ffi::RGB16F,
                    _ => return None,
                };

                Some((format, true))
            }
            GlesTargetInternal::Texture { texture, .. } => Some((texture.0.format?, texture.0.has_alpha)),
            GlesTargetInternal::Renderbuffer { buf, .. } => Some((buf.0.format, buf.0.has_alpha)),
        }
    }

    #[profiling::function]
    fn make_current(&self, gl: &ffi::Gles2, egl: &EGLContext) -> Result<(), MakeCurrentError> {
        unsafe {
            if let GlesTargetInternal::Surface { surface, .. } = self {
                egl.make_current_with_surface(surface)?;
                gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
            } else {
                egl.make_current()?;
                match self {
                    GlesTargetInternal::Image { ref buf, .. } => {
                        gl.BindFramebuffer(ffi::FRAMEBUFFER, buf.fbo)
                    }
                    GlesTargetInternal::Texture { ref fbo, .. } => gl.BindFramebuffer(ffi::FRAMEBUFFER, *fbo),
                    GlesTargetInternal::Renderbuffer { ref fbo, .. } => {
                        gl.BindFramebuffer(ffi::FRAMEBUFFER, *fbo)
                    }
                    _ => unreachable!(),
                }
            }
            Ok(())
        }
    }
}

impl Drop for GlesTargetInternal<'_> {
    fn drop(&mut self) {
        match self {
            GlesTargetInternal::Texture {
                fbo,
                destruction_callback_sender,
                ..
            } => {
                let _ = destruction_callback_sender.send(CleanupResource::FramebufferObject(*fbo));
            }
            GlesTargetInternal::Renderbuffer { buf, fbo, .. } => {
                let _ = buf
                    .0
                    .destruction_callback_sender
                    .send(CleanupResource::FramebufferObject(*fbo));
            }
            _ => {}
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// Capabilities of the [`GlesRenderer`]
pub enum Capability {
    /// GlesRenderer supports Instancing for render optimizations
    Instancing,
    /// GlesRenderer supports blitting between framebuffers
    Blit,
    /// GlesRenderer supports 10 bit formats
    _10Bit,
    /// GlesRenderer supports creating of Renderbuffers with usable formats
    Renderbuffer,
    /// GlesRenderer supports fencing
    Fencing,
    /// GlesRenderer supports fencing and exporting it to EGL
    ExportFence,
    /// GlesRenderer supports GL debug
    Debug,
}

/// A renderer utilizing OpenGL ES
pub struct GlesRenderer {
    // state
    min_filter: TextureFilter,
    max_filter: TextureFilter,
    debug_flags: DebugFlags,

    // internals
    egl: EGLContext,
    #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
    egl_reader: Option<EGLBufferReader>,
    gl: ffi::Gles2,

    // optionals
    gl_version: GlVersion,
    pub(crate) extensions: Vec<String>,
    capabilities: Vec<Capability>,

    // shaders
    tex_program: GlesTexProgram,
    solid_program: GlesSolidProgram,

    // caches
    buffers: Vec<GlesBuffer>,
    dmabuf_cache: HashMap<WeakDmabuf, GlesTexture>,
    vbos: [ffi::types::GLuint; 2],
    vertices: Vec<f32>,
    non_opaque_damage: Vec<Rectangle<i32, Physical>>,
    opaque_damage: Vec<Rectangle<i32, Physical>>,

    // cleanup
    destruction_callback: Receiver<CleanupResource>,
    destruction_callback_sender: Sender<CleanupResource>,

    // markers
    _not_send: PhantomData<*mut ()>,

    // debug
    span: tracing::Span,
    gl_debug_span: Option<*mut tracing::Span>,
}

/// Handle to the currently rendered frame during [`GlesRenderer::render`](Renderer::render).
///
/// Leaking this frame will cause a variety of problems:
/// - It might prevent the frame from synchronizing the rendered framebuffer causing glitches.
/// - Depending on the bound target this can deadlock, if the same target is used later in any way.
/// - Additionally parts of the GL state might not be reset correctly, causing unexpected results for later render commands.
/// - The internal GL context and framebuffer will remain valid, no re-creation will be necessary.
pub struct GlesFrame<'frame, 'buffer> {
    renderer: &'frame mut GlesRenderer,
    target: &'frame mut GlesTarget<'buffer>,
    current_projection: Matrix3<f32>,
    transform: Transform,
    size: Size<i32, Physical>,
    tex_program_override: Option<(GlesTexProgram, Vec<Uniform<'static>>)>,
    finished: AtomicBool,

    span: EnteredSpan,
}

impl fmt::Debug for GlesFrame<'_, '_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("GlesFrame")
            .field("renderer", &self.renderer)
            .field("target", &self.target)
            .field("current_projection", &self.current_projection)
            .field("transform", &self.transform)
            .field("tex_program_override", &self.tex_program_override)
            .field("size", &self.size)
            .field("finished", &self.finished)
            .finish_non_exhaustive()
    }
}

impl fmt::Debug for GlesRenderer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("GlesRenderer")
            .field("buffers", &self.buffers)
            .field("extensions", &self.extensions)
            .field("capabilities", &self.capabilities)
            .field("tex_program", &self.tex_program)
            .field("solid_program", &self.solid_program)
            .field("dmabuf_cache", &self.dmabuf_cache)
            .field("egl", &self.egl)
            .field("gl_version", &self.gl_version)
            // ffi::Gles does not implement Debug
            .field("vbos", &self.vbos)
            .field("min_filter", &self.min_filter)
            .field("max_filter", &self.max_filter)
            .finish()
    }
}

extern "system" fn gl_debug_log(
    _source: ffi::types::GLenum,
    gltype: ffi::types::GLenum,
    _id: ffi::types::GLuint,
    _severity: ffi::types::GLenum,
    _length: ffi::types::GLsizei,
    message: *const ffi::types::GLchar,
    user_param: *mut std::ffi::c_void,
) {
    let _ = std::panic::catch_unwind(move || unsafe {
        let span = &mut *(user_param as *mut tracing::Span);
        let _guard = span.enter();
        let msg = CStr::from_ptr(message);
        let message_utf8 = msg.to_string_lossy();
        match gltype {
            ffi::DEBUG_TYPE_ERROR | ffi::DEBUG_TYPE_UNDEFINED_BEHAVIOR => {
                error!("[GL] {}", message_utf8)
            }
            ffi::DEBUG_TYPE_DEPRECATED_BEHAVIOR => warn!("[GL] {}", message_utf8),
            _ => debug!("[GL] {}", message_utf8),
        };
    });
}

impl GlesRenderer {
    /// Get the supported [`Capabilities`](Capability) of the renderer
    ///
    /// # Safety
    ///
    /// This operation will cause undefined behavior if the given EGLContext is active in another thread.
    pub unsafe fn supported_capabilities(context: &EGLContext) -> Result<Vec<Capability>, GlesError> {
        context.make_current()?;

        let gl = ffi::Gles2::load_with(|s| crate::backend::egl::get_proc_address(s) as *const _);
        let ext_ptr = gl.GetString(ffi::EXTENSIONS) as *const c_char;
        if ext_ptr.is_null() {
            return Err(GlesError::GLFunctionLoaderError);
        }

        let exts = {
            let p = CStr::from_ptr(ext_ptr);
            let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new());
            list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
        };

        let gl_version = version::GlVersion::try_from(&gl).unwrap_or_else(|_| {
            warn!("Failed to detect GLES version, defaulting to 2.0");
            version::GLES_2_0
        });

        let mut capabilities = Vec::new();
        // required for more optimized rendering, otherwise we render in batches
        if gl_version >= version::GLES_3_0
            || (exts.iter().any(|ext| ext == "GL_EXT_instanced_arrays")
                && exts.iter().any(|ext| ext == "GL_EXT_draw_instanced"))
        {
            capabilities.push(Capability::Instancing);
            debug!("Instancing is supported");
        }
        // required to use 8-bit color formats in renderbuffers, we don't deal with anything lower as a render target
        if gl_version >= version::GLES_3_0 || exts.iter().any(|ext| ext == "GL_OES_rgb8_rgba8") {
            capabilities.push(Capability::Renderbuffer);
            debug!("Rgba8 Renderbuffers are supported");
        }
        // required for blit operations
        if gl_version >= version::GLES_3_0 {
            capabilities.push(Capability::Blit);
            debug!("Blitting is supported");
            capabilities.push(Capability::_10Bit);
            debug!("10-bit formats are supported");
            capabilities.push(Capability::Fencing);
            debug!("Fencing is supported");
        }

        if exts.iter().any(|ext| ext == "GL_OES_EGL_sync") {
            debug!("EGL Fencing is supported");
            capabilities.push(Capability::ExportFence);
        }

        if exts.iter().any(|ext| ext == "GL_KHR_debug") {
            capabilities.push(Capability::Debug);
            debug!("GL Debug is supported");
        }

        Ok(capabilities)
    }

    /// Creates a new OpenGL ES renderer from a given [`EGLContext`]
    /// with all [`supported capabilities`](Self::supported_capabilities).
    ///
    /// # Safety
    ///
    /// This operation will cause undefined behavior if the given EGLContext is active in another thread.
    ///
    /// See: [`with_capabilities`](Self::with_capabilities) for more information
    pub unsafe fn new(context: EGLContext) -> Result<GlesRenderer, GlesError> {
        let supported_capabilities = Self::supported_capabilities(&context)?;
        Self::with_capabilities(context, supported_capabilities)
    }

    /// Creates a new OpenGL ES renderer from a given [`EGLContext`]
    /// with the specified [`Capabilities`](Capability). If a requested [`Capability`] is not supported an
    /// error will be returned.
    ///
    /// # Safety
    ///
    /// This operation will cause undefined behavior if the given EGLContext is active in another thread.
    ///
    /// # Implementation details
    ///
    /// - Texture handles created by the resulting renderer are valid for every rendered created with an
    ///   `EGLContext` shared with the given one (see `EGLContext::new_shared`) and can be used on
    ///   any of these renderers.
    /// - This renderer has no default framebuffer, use `Bind::bind` before rendering.
    /// - Shm buffers can be released after a successful import, without the texture handle becoming invalid.
    /// - Texture filtering starts with Linear-downscaling and Linear-upscaling.
    /// - If OpenGL ES 3.0 is not available and the underlying [`EGLContext`] is shared, memory textures
    ///   will insert `glFinish`-calls into the pipeline. Consider not sharing contexts, if OpenGL ES 3 isn't available.
    pub unsafe fn with_capabilities(
        context: EGLContext,
        capabilities: impl IntoIterator<Item = Capability>,
    ) -> Result<GlesRenderer, GlesError> {
        let span = info_span!(parent: &context.span, "renderer_gles2");
        let _guard = span.enter();

        context.make_current()?;

        let supported_capabilities = Self::supported_capabilities(&context)?;
        let requested_capabilities = capabilities.into_iter().collect::<Vec<_>>();

        let unsupported_capabilities = requested_capabilities
            .iter()
            .copied()
            .filter(|c| !supported_capabilities.contains(c))
            .collect::<Vec<_>>();

        if let Some(missing_capability) = unsupported_capabilities.first() {
            let err = match missing_capability {
                Capability::Instancing => {
                    GlesError::GLExtensionNotSupported(&["GL_EXT_instanced_arrays", "GL_EXT_draw_instanced"])
                }
                Capability::Blit | Capability::_10Bit | Capability::Fencing => {
                    GlesError::GLVersionNotSupported(version::GLES_3_0)
                }
                Capability::Renderbuffer => GlesError::GLExtensionNotSupported(&["GL_OES_rgb8_rgba8"]),
                Capability::ExportFence => GlesError::GLExtensionNotSupported(&["GL_OES_EGL_sync"]),
                Capability::Debug => GlesError::GLExtensionNotSupported(&["GL_KHR_debug"]),
            };
            return Err(err);
        };

        let (gl, gl_version, exts, capabilities, gl_debug_span) = {
            let gl = ffi::Gles2::load_with(|s| crate::backend::egl::get_proc_address(s) as *const _);
            let ext_ptr = gl.GetString(ffi::EXTENSIONS) as *const c_char;
            if ext_ptr.is_null() {
                return Err(GlesError::GLFunctionLoaderError);
            }

            let exts = {
                let p = CStr::from_ptr(ext_ptr);
                let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new());
                list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
            };

            info!("Initializing OpenGL ES Renderer");
            info!(
                "GL Version: {:?}",
                CStr::from_ptr(gl.GetString(ffi::VERSION) as *const c_char)
            );
            info!(
                "GL Vendor: {:?}",
                CStr::from_ptr(gl.GetString(ffi::VENDOR) as *const c_char)
            );
            info!(
                "GL Renderer: {:?}",
                CStr::from_ptr(gl.GetString(ffi::RENDERER) as *const c_char)
            );
            info!("Supported GL Extensions: {:?}", exts);

            let gl_version = version::GlVersion::try_from(&gl).unwrap_or_else(|_| {
                warn!("Failed to detect GLES version, defaulting to 2.0");
                version::GLES_2_0
            });

            // required for the manditory wl_shm formats
            if !exts.iter().any(|ext| ext == "GL_EXT_texture_format_BGRA8888") {
                return Err(GlesError::GLExtensionNotSupported(&[
                    "GL_EXT_texture_format_BGRA8888",
                ]));
            }

            // required for buffers without linear memory layout
            if gl_version < version::GLES_3_0 && !exts.iter().any(|ext| ext == "GL_EXT_unpack_subimage") {
                return Err(GlesError::GLExtensionNotSupported(&["GL_EXT_unpack_subimage"]));
            }

            let gl_debug_span = if requested_capabilities.contains(&Capability::Debug) {
                gl.Enable(ffi::DEBUG_OUTPUT);
                gl.Enable(ffi::DEBUG_OUTPUT_SYNCHRONOUS);
                let span = Box::into_raw(Box::new(span.clone()));
                gl.DebugMessageCallback(Some(gl_debug_log), span as *mut _);
                Some(span)
            } else {
                None
            };

            (gl, gl_version, exts, requested_capabilities, gl_debug_span)
        };

        let (tx, rx) = channel();
        let tex_program = texture_program(&gl, shaders::FRAGMENT_SHADER, &[], tx.clone())?;
        let solid_program = solid_program(&gl)?;

        // Initialize vertices based on drawing methodology.
        let vertices: &[ffi::types::GLfloat] = if capabilities.contains(&Capability::Instancing) {
            &INSTANCED_VERTS
        } else {
            &TRIANGLE_VERTS
        };

        let mut vbos = [0; 2];
        gl.GenBuffers(vbos.len() as i32, vbos.as_mut_ptr());
        gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
        gl.BufferData(
            ffi::ARRAY_BUFFER,
            std::mem::size_of_val(vertices) as isize,
            vertices.as_ptr() as *const _,
            ffi::STATIC_DRAW,
        );
        gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[1]);
        gl.BufferData(
            ffi::ARRAY_BUFFER,
            (std::mem::size_of::<ffi::types::GLfloat>() * OUTPUT_VERTS.len()) as isize,
            OUTPUT_VERTS.as_ptr() as *const _,
            ffi::STATIC_DRAW,
        );
        gl.BindBuffer(ffi::ARRAY_BUFFER, 0);

        context
            .user_data()
            .insert_if_missing_threadsafe(ContextId::<GlesTexture>::new);

        drop(_guard);

        let renderer = GlesRenderer {
            gl,
            egl: context,
            #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
            egl_reader: None,

            extensions: exts,
            gl_version,
            capabilities,

            tex_program,
            solid_program,
            vbos,
            min_filter: TextureFilter::Linear,
            max_filter: TextureFilter::Linear,

            buffers: Vec::new(),
            dmabuf_cache: std::collections::HashMap::new(),
            vertices: Vec::with_capacity(6 * 16),
            non_opaque_damage: Vec::with_capacity(16),
            opaque_damage: Vec::with_capacity(16),

            destruction_callback: rx,
            destruction_callback_sender: tx,

            debug_flags: DebugFlags::empty(),
            _not_send: PhantomData,
            span,
            gl_debug_span,
        };
        renderer.egl.unbind()?;
        Ok(renderer)
    }

    fn bind_texture<'a>(&mut self, texture: &'a GlesTexture) -> Result<GlesTarget<'a>, GlesError> {
        unsafe {
            self.egl.make_current()?;
        }

        let bind = || {
            let mut sync_lock = texture.0.sync.write().unwrap();
            let mut fbo = 0;
            unsafe {
                sync_lock.wait_for_all(&self.gl);
                self.gl.GenFramebuffers(1, &mut fbo as *mut _);
                self.gl.BindFramebuffer(ffi::FRAMEBUFFER, fbo);
                self.gl.FramebufferTexture2D(
                    ffi::FRAMEBUFFER,
                    ffi::COLOR_ATTACHMENT0,
                    ffi::TEXTURE_2D,
                    texture.0.texture,
                    0,
                );
                let status = self.gl.CheckFramebufferStatus(ffi::FRAMEBUFFER);
                self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);

                if status != ffi::FRAMEBUFFER_COMPLETE {
                    self.gl.DeleteFramebuffers(1, &mut fbo as *mut _);
                    return Err(GlesError::FramebufferBindingError);
                }
            }

            Ok(GlesTarget(GlesTargetInternal::Texture {
                texture: texture.clone(),
                sync_lock,
                destruction_callback_sender: self.destruction_callback_sender.clone(),
                fbo,
            }))
        };

        bind().inspect_err(|_| {
            if let Err(err) = self.unbind() {
                self.span.in_scope(|| warn!(?err, "Failed to unbind on err"));
            }
        })
    }

    #[profiling::function]
    fn unbind(&mut self) -> Result<(), GlesError> {
        unsafe {
            self.egl.make_current()?;
        }
        unsafe { self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0) };
        self.cleanup();
        self.egl.unbind()?;
        Ok(())
    }

    #[profiling::function]
    fn cleanup(&mut self) {
        self.dmabuf_cache.retain(|entry, _tex| !entry.is_gone());
        // Free outdated buffer resources
        // TODO: Replace with `drain_filter` once it lands
        let mut i = 0;
        while i != self.buffers.len() {
            if self.buffers[i].dmabuf.is_gone() {
                let old = self.buffers.remove(i);
                unsafe {
                    self.gl.DeleteFramebuffers(1, &old.fbo as *const _);
                    self.gl.DeleteRenderbuffers(1, &old.rbo as *const _);
                    ffi_egl::DestroyImageKHR(**self.egl.display().get_display_handle(), old.image);
                }
            } else {
                i += 1;
            }
        }
        for resource in self.destruction_callback.try_iter() {
            match resource {
                CleanupResource::Texture(texture) => unsafe {
                    self.gl.DeleteTextures(1, &texture);
                },
                CleanupResource::EGLImage(image) => unsafe {
                    ffi_egl::DestroyImageKHR(**self.egl.display().get_display_handle(), image);
                },
                CleanupResource::FramebufferObject(fbo) => unsafe {
                    self.gl.DeleteFramebuffers(1, &fbo);
                },
                CleanupResource::RenderbufferObject(rbo) => unsafe {
                    self.gl.DeleteRenderbuffers(1, &rbo);
                },
                CleanupResource::Mapping(pbo, mapping) => unsafe {
                    if !mapping.is_null() {
                        self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, pbo);
                        self.gl.UnmapBuffer(ffi::PIXEL_PACK_BUFFER);
                        self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);
                    }
                    self.gl.DeleteBuffers(1, &pbo);
                },
                CleanupResource::Program(program) => unsafe {
                    self.gl.DeleteProgram(program);
                },
                CleanupResource::Sync(sync) => unsafe {
                    self.gl.DeleteSync(sync);
                },
            }
        }
    }

    /// Returns the supported [`Capabilities`](Capability) of this renderer.
    pub fn capabilities(&self) -> &[Capability] {
        &self.capabilities
    }
}

#[cfg(feature = "wayland_frontend")]
impl ImportMemWl for GlesRenderer {
    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn import_shm_buffer(
        &mut self,
        buffer: &wl_buffer::WlBuffer,
        surface: Option<&crate::wayland::compositor::SurfaceData>,
        damage: &[Rectangle<i32, BufferCoord>],
    ) -> Result<GlesTexture, GlesError> {
        use crate::wayland::shm::with_buffer_contents;

        // why not store a `GlesTexture`? because the user might do so.
        // this is guaranteed a non-public internal type, so we are good.
        type CacheMap = HashMap<ContextId<GlesTexture>, Arc<GlesTextureInternal>>;

        let mut surface_lock = surface.as_ref().map(|surface_data| {
            surface_data
                .data_map
                .get_or_insert_threadsafe(|| Arc::new(Mutex::new(CacheMap::new())))
                .lock()
                .unwrap()
        });

        with_buffer_contents(buffer, |ptr, len, data| {
            let offset = data.offset;
            let width = data.width;
            let height = data.height;
            let stride = data.stride;
            let fourcc =
                shm_format_to_fourcc(data.format).ok_or(GlesError::UnsupportedWlPixelFormat(data.format))?;

            if self.gl_version.major >= 3 {
                if !SUPPORTED_MEM_FORMATS_3.contains(&fourcc) {
                    return Err(GlesError::UnsupportedWlPixelFormat(data.format));
                }
            } else if !SUPPORTED_MEM_FORMATS_2.contains(&fourcc) {
                return Err(GlesError::UnsupportedWlPixelFormat(data.format));
            }

            let has_alpha = has_alpha(fourcc);
            let (mut internal_format, read_format, type_) =
                fourcc_to_gl_formats(fourcc).ok_or(GlesError::UnsupportedWlPixelFormat(data.format))?;
            if self.gl_version.major == 2 {
                // es 2.0 doesn't define sized variants
                internal_format = match internal_format {
                    ffi::BGRA_EXT => ffi::BGRA_EXT,
                    ffi::RGBA8 => ffi::RGBA,
                    ffi::RGB8 => ffi::RGB,
                    _ => unreachable!(),
                };
            }

            // number of bytes per pixel
            let pixelsize = gl_bpp(read_format, type_).expect("We check the format before") / 8;
            // ensure consistency, the SHM handler of smithay should ensure this
            assert!((offset + (height - 1) * stride + width * pixelsize as i32) as usize <= len);

            let mut upload_full = false;

            unsafe {
                self.egl.make_current()?;
            }

            let id = self.context_id();
            let texture = GlesTexture(
                surface_lock
                    .as_ref()
                    .and_then(|cache| cache.get(&id).cloned())
                    .filter(|texture| texture.size == (width, height).into())
                    .unwrap_or_else(|| {
                        let mut tex = 0;
                        unsafe { self.gl.GenTextures(1, &mut tex) };
                        // new texture, upload in full
                        upload_full = true;
                        let new = Arc::new(GlesTextureInternal {
                            texture: tex,
                            sync: RwLock::default(),
                            format: Some(internal_format),
                            has_alpha,
                            is_external: false,
                            y_inverted: false,
                            size: (width, height).into(),
                            egl_images: None,
                            destruction_callback_sender: self.destruction_callback_sender.clone(),
                        });
                        if let Some(cache) = surface_lock.as_mut() {
                            cache.insert(id, new.clone());
                        }
                        new
                    }),
            );

            let mut sync_lock = texture.0.sync.write().unwrap();
            unsafe {
                sync_lock.wait_for_all(&self.gl);
                self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture);
                self.gl
                    .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
                self.gl
                    .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32);
                self.gl
                    .PixelStorei(ffi::UNPACK_ROW_LENGTH, stride / pixelsize as i32);

                if upload_full || damage.is_empty() {
                    trace!("Uploading shm texture");
                    self.gl.TexImage2D(
                        ffi::TEXTURE_2D,
                        0,
                        internal_format as i32,
                        width,
                        height,
                        0,
                        read_format,
                        type_,
                        ptr.offset(offset as isize) as *const _,
                    );
                } else {
                    for region in damage.iter() {
                        trace!("Uploading partial shm texture");
                        self.gl.PixelStorei(ffi::UNPACK_SKIP_PIXELS, region.loc.x);
                        self.gl.PixelStorei(ffi::UNPACK_SKIP_ROWS, region.loc.y);
                        self.gl.TexSubImage2D(
                            ffi::TEXTURE_2D,
                            0,
                            region.loc.x,
                            region.loc.y,
                            region.size.w,
                            region.size.h,
                            read_format,
                            type_,
                            ptr.offset(offset as isize) as *const _,
                        );
                        self.gl.PixelStorei(ffi::UNPACK_SKIP_PIXELS, 0);
                        self.gl.PixelStorei(ffi::UNPACK_SKIP_ROWS, 0);
                    }
                }

                self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, 0);
                self.gl.BindTexture(ffi::TEXTURE_2D, 0);

                if self.capabilities.contains(&Capability::Fencing) {
                    sync_lock.update_write(&self.gl);
                } else if self.egl.is_shared() {
                    self.gl.Finish();
                }
            }
            std::mem::drop(sync_lock);

            Ok(texture)
        })
        .map_err(GlesError::BufferAccessError)?
    }
}

const SUPPORTED_MEM_FORMATS_2: &[Fourcc] = &[
    Fourcc::Abgr8888,
    Fourcc::Xbgr8888,
    Fourcc::Argb8888,
    Fourcc::Xrgb8888,
];
const SUPPORTED_MEM_FORMATS_3: &[Fourcc] = &[
    Fourcc::Abgr8888,
    Fourcc::Xbgr8888,
    Fourcc::Argb8888,
    Fourcc::Xrgb8888,
    Fourcc::Abgr2101010,
    Fourcc::Xbgr2101010,
    Fourcc::Abgr16161616f,
    Fourcc::Xbgr16161616f,
];

impl ImportMem for GlesRenderer {
    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn import_memory(
        &mut self,
        data: &[u8],
        format: Fourcc,
        size: Size<i32, BufferCoord>,
        flipped: bool,
    ) -> Result<GlesTexture, GlesError> {
        if data.len()
            < (size.w * size.h) as usize
                * (get_bpp(format).ok_or(GlesError::UnsupportedPixelFormat(format))? / 8)
        {
            return Err(GlesError::UnexpectedSize);
        }

        if self.gl_version.major >= 3 {
            if !SUPPORTED_MEM_FORMATS_3.contains(&format) {
                return Err(GlesError::UnsupportedPixelFormat(format));
            }
        } else if !SUPPORTED_MEM_FORMATS_2.contains(&format) {
            return Err(GlesError::UnsupportedPixelFormat(format));
        }

        let has_alpha = has_alpha(format);
        let (mut internal, format, layout) =
            fourcc_to_gl_formats(format).expect("We check the format before");
        if self.gl_version.major == 2 {
            // es 2.0 doesn't define sized variants
            internal = match internal {
                ffi::RGBA8 => ffi::RGBA,
                ffi::RGB8 => ffi::RGB,
                ffi::BGRA_EXT => ffi::BGRA_EXT,
                _ => unreachable!(),
            };
        }

        let texture = GlesTexture(Arc::new({
            let mut tex = 0;
            unsafe {
                self.egl.make_current()?;
                self.gl.GenTextures(1, &mut tex);
                self.gl.BindTexture(ffi::TEXTURE_2D, tex);
                self.gl
                    .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
                self.gl
                    .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32);
                self.gl.TexImage2D(
                    ffi::TEXTURE_2D,
                    0,
                    internal as i32,
                    size.w,
                    size.h,
                    0,
                    format,
                    layout,
                    data.as_ptr() as *const _,
                );
                self.gl.BindTexture(ffi::TEXTURE_2D, 0);
            }

            let mut sync = RwLock::<TextureSync>::default();
            if self.capabilities.contains(&Capability::Fencing) {
                sync.get_mut().unwrap().update_write(&self.gl);
            } else if self.egl.is_shared() {
                unsafe {
                    self.gl.Finish();
                }
            };

            // new texture, upload in full
            GlesTextureInternal {
                texture: tex,
                sync,
                format: Some(internal),
                has_alpha,
                is_external: false,
                y_inverted: flipped,
                size,
                egl_images: None,
                destruction_callback_sender: self.destruction_callback_sender.clone(),
            }
        }));

        Ok(texture)
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn update_memory(
        &mut self,
        texture: &Self::TextureId,
        data: &[u8],
        region: Rectangle<i32, BufferCoord>,
    ) -> Result<(), Self::Error> {
        if texture.0.format.is_none() {
            return Err(GlesError::UnknownPixelFormat);
        }
        if texture.0.is_external {
            return Err(GlesError::UnsupportedPixelLayout);
        }
        let (read_format, type_) = gl_read_for_internal(texture.0.format.expect("We check that before"))
            .ok_or(GlesError::UnknownPixelFormat)?;

        if data.len()
            < (region.size.w * region.size.h) as usize
                * (gl_bpp(read_format, type_).ok_or(GlesError::UnknownPixelFormat)? / 8)
        {
            return Err(GlesError::UnexpectedSize);
        }

        let mut sync_lock = texture.0.sync.write().unwrap();
        unsafe {
            self.egl.make_current()?;
            sync_lock.wait_for_all(&self.gl);
            self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture);
            self.gl
                .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
            self.gl
                .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32);
            self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, texture.0.size.w);
            self.gl.PixelStorei(ffi::UNPACK_SKIP_PIXELS, region.loc.x);
            self.gl.PixelStorei(ffi::UNPACK_SKIP_ROWS, region.loc.y);
            self.gl.TexSubImage2D(
                ffi::TEXTURE_2D,
                0,
                region.loc.x,
                region.loc.y,
                region.size.w,
                region.size.h,
                read_format,
                type_,
                data.as_ptr() as *const _,
            );
            self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, 0);
            self.gl.PixelStorei(ffi::UNPACK_SKIP_PIXELS, 0);
            self.gl.PixelStorei(ffi::UNPACK_SKIP_ROWS, 0);
            self.gl.BindTexture(ffi::TEXTURE_2D, 0);

            if self.capabilities.contains(&Capability::Fencing) {
                sync_lock.update_write(&self.gl);
            } else if self.egl.is_shared() {
                self.gl.Finish();
            }
        }

        Ok(())
    }

    fn mem_formats(&self) -> Box<dyn Iterator<Item = Fourcc>> {
        if self.gl_version.major >= 3 {
            Box::new(SUPPORTED_MEM_FORMATS_3.iter().copied())
        } else {
            Box::new(SUPPORTED_MEM_FORMATS_2.iter().copied())
        }
    }
}

#[cfg(all(
    feature = "wayland_frontend",
    feature = "backend_egl",
    feature = "use_system_lib"
))]
impl ImportEgl for GlesRenderer {
    fn bind_wl_display(
        &mut self,
        display: &wayland_server::DisplayHandle,
    ) -> Result<(), crate::backend::egl::Error> {
        self.egl_reader = Some(self.egl.display().bind_wl_display(display)?);
        Ok(())
    }

    fn unbind_wl_display(&mut self) {
        self.egl_reader = None;
    }

    fn egl_reader(&self) -> Option<&EGLBufferReader> {
        self.egl_reader.as_ref()
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn import_egl_buffer(
        &mut self,
        buffer: &wl_buffer::WlBuffer,
        _surface: Option<&crate::wayland::compositor::SurfaceData>,
        _damage: &[Rectangle<i32, BufferCoord>],
    ) -> Result<GlesTexture, GlesError> {
        if !self.extensions.iter().any(|ext| ext == "GL_OES_EGL_image") {
            return Err(GlesError::GLExtensionNotSupported(&["GL_OES_EGL_image"]));
        }

        if self.egl_reader().is_none() {
            return Err(GlesError::EGLBufferAccessError(
                crate::backend::egl::BufferAccessError::NotManaged(crate::backend::egl::EGLError::BadDisplay),
            ));
        }

        // We can not use the caching logic for textures here as the
        // egl buffers a potentially managed external which will fail the
        // clean up check if the buffer is still alive. For wl_drm the
        // is_alive check will always return true and the cache entry
        // will never be cleaned up.
        let egl = self
            .egl_reader
            .as_ref()
            .unwrap()
            .egl_buffer_contents(buffer)
            .map_err(GlesError::EGLBufferAccessError)?;

        let tex = self.import_egl_image(egl.image(0).unwrap(), egl.format == EGLFormat::External, None)?;

        let texture = GlesTexture(Arc::new(GlesTextureInternal {
            texture: tex,
            sync: RwLock::default(),
            format: match egl.format {
                EGLFormat::RGB | EGLFormat::RGBA => Some(ffi::RGBA8),
                EGLFormat::External => None,
                _ => unreachable!("EGLBuffer currenly does not expose multi-planar buffers to us"),
            },
            has_alpha: !matches!(egl.format, EGLFormat::RGB),
            is_external: egl.format == EGLFormat::External,
            y_inverted: egl.y_inverted,
            size: egl.size,
            egl_images: Some(egl.into_images()),
            destruction_callback_sender: self.destruction_callback_sender.clone(),
        }));

        Ok(texture)
    }
}

impl ImportDma for GlesRenderer {
    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn import_dmabuf(
        &mut self,
        buffer: &Dmabuf,
        _damage: Option<&[Rectangle<i32, BufferCoord>]>,
    ) -> Result<GlesTexture, GlesError> {
        use crate::backend::allocator::Buffer;
        if !self.extensions.iter().any(|ext| ext == "GL_OES_EGL_image") {
            return Err(GlesError::GLExtensionNotSupported(&["GL_OES_EGL_image"]));
        }

        self.existing_dmabuf_texture(buffer)?.map(Ok).unwrap_or_else(|| {
            let is_external = !self.egl.dmabuf_render_formats().contains(&buffer.format());
            let image = self
                .egl
                .display()
                .create_image_from_dmabuf(buffer)
                .map_err(GlesError::BindBufferEGLError)?;

            let tex = self.import_egl_image(image, is_external, None)?;
            let format = fourcc_to_gl_formats(buffer.format().code)
                .map(|(internal, _, _)| internal)
                .unwrap_or(ffi::RGBA8);
            let has_alpha = has_alpha(buffer.format().code);
            let texture = GlesTexture(Arc::new(GlesTextureInternal {
                texture: tex,
                sync: RwLock::default(),
                format: Some(format),
                has_alpha,
                is_external,
                y_inverted: buffer.y_inverted(),
                size: buffer.size(),
                egl_images: Some(vec![image]),
                destruction_callback_sender: self.destruction_callback_sender.clone(),
            }));
            self.dmabuf_cache.insert(buffer.weak(), texture.clone());
            Ok(texture)
        })
    }

    fn dmabuf_formats(&self) -> FormatSet {
        self.egl.dmabuf_texture_formats().clone()
    }

    fn has_dmabuf_format(&self, format: Format) -> bool {
        self.egl.dmabuf_texture_formats().contains(&format)
    }
}

#[cfg(feature = "wayland_frontend")]
impl ImportDmaWl for GlesRenderer {}

impl GlesRenderer {
    #[profiling::function]
    fn existing_dmabuf_texture(&self, buffer: &Dmabuf) -> Result<Option<GlesTexture>, GlesError> {
        let Some(texture) = self.dmabuf_cache.get(&buffer.weak()) else {
            return Ok(None);
        };

        trace!("Re-using texture {:?} for {:?}", texture.0.texture, buffer);
        if let Some(egl_images) = texture.0.egl_images.as_ref() {
            if egl_images[0] == ffi_egl::NO_IMAGE_KHR {
                return Ok(None);
            }
            let tex = Some(texture.0.texture);
            self.import_egl_image(egl_images[0], texture.0.is_external, tex)?;
        }
        Ok(Some(texture.clone()))
    }

    #[profiling::function]
    fn import_egl_image(
        &self,
        image: EGLImage,
        is_external: bool,
        tex: Option<u32>,
    ) -> Result<u32, GlesError> {
        unsafe {
            self.egl.make_current()?;
        }
        let tex = tex.unwrap_or_else(|| unsafe {
            let mut tex = 0;
            self.gl.GenTextures(1, &mut tex);
            tex
        });
        let target = if is_external {
            ffi::TEXTURE_EXTERNAL_OES
        } else {
            ffi::TEXTURE_2D
        };
        unsafe {
            self.gl.BindTexture(target, tex);
            self.gl.EGLImageTargetTexture2DOES(target, image);
            self.gl.BindTexture(target, 0);
        }

        Ok(tex)
    }
}

impl ExportMem for GlesRenderer {
    type TextureMapping = GlesMapping;

    #[instrument(level = "trace", parent = &self.span, skip(self, target))]
    #[profiling::function]
    fn copy_framebuffer(
        &mut self,
        target: &GlesTarget<'_>,
        region: Rectangle<i32, BufferCoord>,
        fourcc: Fourcc,
    ) -> Result<Self::TextureMapping, Self::Error> {
        target.0.make_current(&self.gl, &self.egl)?;

        let (_, has_alpha) = target.0.format().ok_or(GlesError::UnknownPixelFormat)?;
        let (_, format, layout) = fourcc_to_gl_formats(fourcc).ok_or(GlesError::UnknownPixelFormat)?;

        let mut pbo = 0;
        let err = unsafe {
            self.gl.GetError(); // clear errors
            self.gl.GenBuffers(1, &mut pbo);
            self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, pbo);
            let bpp = gl_bpp(format, layout).ok_or(GlesError::UnsupportedPixelLayout)? / 8;
            let size = (region.size.w * region.size.h * bpp as i32) as isize;
            self.gl
                .BufferData(ffi::PIXEL_PACK_BUFFER, size, ptr::null(), ffi::STREAM_READ);
            self.gl
                .ReadBuffer(if matches!(target.0, GlesTargetInternal::Surface { .. }) {
                    ffi::BACK
                } else {
                    ffi::COLOR_ATTACHMENT0
                });
            self.gl.ReadPixels(
                region.loc.x,
                region.loc.y,
                region.size.w,
                region.size.h,
                format,
                layout,
                ptr::null_mut(),
            );
            self.gl.ReadBuffer(ffi::NONE);
            self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);
            self.gl.GetError()
        };

        match err {
            ffi::NO_ERROR => Ok(GlesMapping {
                pbo,
                format,
                layout,
                has_alpha,
                size: region.size,
                mapping: AtomicPtr::new(ptr::null_mut()),
                destruction_callback_sender: self.destruction_callback_sender.clone(),
            }),
            ffi::INVALID_ENUM | ffi::INVALID_OPERATION => Err(GlesError::UnsupportedPixelFormat(fourcc)),
            _ => Err(GlesError::UnknownPixelFormat),
        }
    }

    fn can_read_texture(&mut self, texture: &Self::TextureId) -> Result<bool, GlesError> {
        // if we can't bind the texture, we can't read it
        Ok(self.bind_texture(texture).is_ok())
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn copy_texture(
        &mut self,
        texture: &Self::TextureId,
        region: Rectangle<i32, BufferCoord>,
        fourcc: Fourcc,
    ) -> Result<Self::TextureMapping, Self::Error> {
        let mut pbo = 0;
        let target = self.bind_texture(texture)?;
        target.0.make_current(&self.gl, &self.egl)?;

        let (_, format, layout) = fourcc_to_gl_formats(fourcc).ok_or(GlesError::UnknownPixelFormat)?;
        let bpp = gl_bpp(format, layout).expect("We check the format before") / 8;

        let err = unsafe {
            self.gl.GetError(); // clear errors
            self.gl.GenBuffers(1, &mut pbo);
            self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, pbo);
            self.gl.BufferData(
                ffi::PIXEL_PACK_BUFFER,
                (region.size.w * region.size.h * bpp as i32) as isize,
                ptr::null(),
                ffi::STREAM_READ,
            );
            self.gl.ReadBuffer(ffi::COLOR_ATTACHMENT0);
            self.gl.ReadPixels(
                region.loc.x,
                region.loc.y,
                region.size.w,
                region.size.h,
                format,
                layout,
                ptr::null_mut(),
            );
            self.gl.ReadBuffer(ffi::NONE);
            self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);
            self.gl.GetError()
        };

        match err {
            ffi::NO_ERROR => Ok(GlesMapping {
                pbo,
                format,
                layout,
                has_alpha: texture.0.has_alpha,
                size: region.size,
                mapping: AtomicPtr::new(ptr::null_mut()),
                destruction_callback_sender: self.destruction_callback_sender.clone(),
            }),
            ffi::INVALID_ENUM | ffi::INVALID_OPERATION => Err(GlesError::UnsupportedPixelFormat(fourcc)),
            _ => Err(GlesError::UnknownPixelFormat),
        }
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn map_texture<'a>(
        &mut self,
        texture_mapping: &'a Self::TextureMapping,
    ) -> Result<&'a [u8], Self::Error> {
        unsafe {
            self.egl.make_current()?;
        }

        let size = texture_mapping.size();
        let len = size.w * size.h * 4;

        let mapping_ptr = texture_mapping.mapping.load(Ordering::SeqCst);
        let ptr = if mapping_ptr.is_null() {
            unsafe {
                self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, texture_mapping.pbo);
                let ptr = self
                    .gl
                    .MapBufferRange(ffi::PIXEL_PACK_BUFFER, 0, len as isize, ffi::MAP_READ_BIT);
                self.gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);

                if ptr.is_null() {
                    return Err(GlesError::MappingError);
                }

                texture_mapping.mapping.store(ptr, Ordering::SeqCst);
                ptr
            }
        } else {
            mapping_ptr
        };

        unsafe { Ok(slice::from_raw_parts(ptr as *const u8, len as usize)) }
    }
}

impl Bind<EGLSurface> for GlesRenderer {
    fn bind<'a>(&mut self, surface: &'a mut EGLSurface) -> Result<GlesTarget<'a>, GlesError> {
        Ok(GlesTarget(GlesTargetInternal::Surface { surface }))
    }
}

impl Bind<Dmabuf> for GlesRenderer {
    fn bind<'a>(&mut self, dmabuf: &'a mut Dmabuf) -> Result<GlesTarget<'a>, GlesError> {
        let mut bind = |dmabuf: &'a mut Dmabuf| {
            let buf = self
                .buffers
                .iter_mut()
                .find(|buffer| {
                    if let Some(dma) = buffer.dmabuf.upgrade() {
                        dma == *dmabuf
                    } else {
                        false
                    }
                })
                .map(|buf| Ok(buf.clone()))
                .unwrap_or_else(|| {
                    unsafe {
                        self.egl.make_current()?;
                    }

                    trace!("Creating EGLImage for Dmabuf: {:?}", dmabuf);
                    let image = self
                        .egl
                        .display()
                        .create_image_from_dmabuf(dmabuf)
                        .map_err(GlesError::BindBufferEGLError)?;

                    unsafe {
                        let mut rbo = 0;
                        self.gl.GenRenderbuffers(1, &mut rbo as *mut _);
                        self.gl.BindRenderbuffer(ffi::RENDERBUFFER, rbo);
                        self.gl
                            .EGLImageTargetRenderbufferStorageOES(ffi::RENDERBUFFER, image);
                        self.gl.BindRenderbuffer(ffi::RENDERBUFFER, 0);

                        let mut fbo = 0;
                        self.gl.GenFramebuffers(1, &mut fbo as *mut _);
                        self.gl.BindFramebuffer(ffi::FRAMEBUFFER, fbo);
                        self.gl.FramebufferRenderbuffer(
                            ffi::FRAMEBUFFER,
                            ffi::COLOR_ATTACHMENT0,
                            ffi::RENDERBUFFER,
                            rbo,
                        );
                        let status = self.gl.CheckFramebufferStatus(ffi::FRAMEBUFFER);
                        self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);

                        if status != ffi::FRAMEBUFFER_COMPLETE {
                            self.gl.DeleteFramebuffers(1, &mut fbo as *mut _);
                            self.gl.DeleteRenderbuffers(1, &mut rbo as *mut _);
                            ffi_egl::DestroyImageKHR(**self.egl.display().get_display_handle(), image);
                            return Err(GlesError::FramebufferBindingError);
                        }
                        let buf = GlesBuffer {
                            dmabuf: dmabuf.weak(),
                            image,
                            rbo,
                            fbo,
                        };

                        self.buffers.push(buf.clone());

                        Ok(buf)
                    }
                })?;

            Ok(GlesTarget(GlesTargetInternal::Image { buf, dmabuf }))
        };

        bind(dmabuf).inspect_err(|_| {
            if let Err(err) = self.unbind() {
                self.span.in_scope(|| warn!(?err, "Failed to unbind on err"));
            }
        })
    }

    fn supported_formats(&self) -> Option<FormatSet> {
        Some(self.egl.display().dmabuf_render_formats().clone())
    }
}

impl Bind<GlesTexture> for GlesRenderer {
    fn bind<'a>(&mut self, texture: &'a mut GlesTexture) -> Result<GlesTarget<'a>, GlesError> {
        self.bind_texture(texture)
    }
}

impl Bind<GlesRenderbuffer> for GlesRenderer {
    fn bind<'a>(&mut self, renderbuffer: &'a mut GlesRenderbuffer) -> Result<GlesTarget<'a>, GlesError> {
        unsafe {
            self.egl.make_current()?;
        }

        let bind = |renderbuffer: &'a mut GlesRenderbuffer| {
            let mut fbo = 0;
            unsafe {
                self.gl.GenFramebuffers(1, &mut fbo as *mut _);
                self.gl.BindFramebuffer(ffi::FRAMEBUFFER, fbo);
                self.gl.BindRenderbuffer(ffi::RENDERBUFFER, renderbuffer.0.rbo);
                self.gl.FramebufferRenderbuffer(
                    ffi::FRAMEBUFFER,
                    ffi::COLOR_ATTACHMENT0,
                    ffi::RENDERBUFFER,
                    renderbuffer.0.rbo,
                );
                let status = self.gl.CheckFramebufferStatus(ffi::FRAMEBUFFER);
                self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
                self.gl.BindRenderbuffer(ffi::RENDERBUFFER, 0);

                if status != ffi::FRAMEBUFFER_COMPLETE {
                    self.gl.DeleteFramebuffers(1, &mut fbo as *mut _);
                    return Err(GlesError::FramebufferBindingError);
                }
            }

            Ok(GlesTarget(GlesTargetInternal::Renderbuffer {
                buf: renderbuffer,
                fbo,
            }))
        };

        bind(renderbuffer).inspect_err(|_| {
            if let Err(err) = self.unbind() {
                self.span.in_scope(|| warn!(?err, "Failed to unbind on err"));
            }
        })
    }
}

impl Offscreen<GlesTexture> for GlesRenderer {
    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn create_buffer(
        &mut self,
        format: Fourcc,
        size: Size<i32, BufferCoord>,
    ) -> Result<GlesTexture, GlesError> {
        let has_alpha = has_alpha(format);
        let (internal, format, layout) =
            fourcc_to_gl_formats(format).ok_or(GlesError::UnsupportedPixelFormat(format))?;
        if (internal != ffi::RGBA8 && internal != ffi::BGRA_EXT)
            && !self.capabilities.contains(&Capability::_10Bit)
        {
            return Err(GlesError::UnsupportedPixelLayout);
        }

        let tex = unsafe {
            self.egl.make_current()?;
            let mut tex = 0;
            self.gl.GenTextures(1, &mut tex);
            self.gl.BindTexture(ffi::TEXTURE_2D, tex);
            self.gl.TexImage2D(
                ffi::TEXTURE_2D,
                0,
                internal as i32,
                size.w,
                size.h,
                0,
                format,
                layout,
                std::ptr::null(),
            );
            tex
        };

        Ok(unsafe { GlesTexture::from_raw(self, Some(internal), !has_alpha, tex, size) })
    }
}

impl Offscreen<GlesRenderbuffer> for GlesRenderer {
    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn create_buffer(
        &mut self,
        format: Fourcc,
        size: Size<i32, BufferCoord>,
    ) -> Result<GlesRenderbuffer, GlesError> {
        if !self.capabilities.contains(&Capability::Renderbuffer) {
            return Err(GlesError::UnsupportedPixelFormat(format));
        }
        let has_alpha = has_alpha(format);
        let (internal, _, _) =
            fourcc_to_gl_formats(format).ok_or(GlesError::UnsupportedPixelFormat(format))?;

        if internal != ffi::RGBA8 && !self.capabilities.contains(&Capability::_10Bit) {
            return Err(GlesError::UnsupportedPixelLayout);
        }

        unsafe {
            self.egl.make_current()?;

            let mut rbo = 0;
            self.gl.GenRenderbuffers(1, &mut rbo);
            self.gl.BindRenderbuffer(ffi::RENDERBUFFER, rbo);
            self.gl
                .RenderbufferStorage(ffi::RENDERBUFFER, internal, size.w, size.h);
            self.gl.BindRenderbuffer(ffi::RENDERBUFFER, 0);

            Ok(GlesRenderbuffer(Rc::new(GlesRenderbufferInternal {
                rbo,
                format: internal,
                has_alpha,
                size,
                destruction_callback_sender: self.destruction_callback_sender.clone(),
            })))
        }
    }
}

impl<'buffer> BlitFrame<GlesTarget<'buffer>> for GlesFrame<'_, 'buffer> {
    fn blit_to(
        &mut self,
        to: &mut GlesTarget<'buffer>,
        src: Rectangle<i32, Physical>,
        dst: Rectangle<i32, Physical>,
        filter: TextureFilter,
    ) -> Result<(), Self::Error> {
        let res = self.renderer.blit(self.target, to, src, dst, filter);
        self.target
            .0
            .make_current(&self.renderer.gl, &self.renderer.egl)?;
        res
    }

    fn blit_from(
        &mut self,
        from: &GlesTarget<'buffer>,
        src: Rectangle<i32, Physical>,
        dst: Rectangle<i32, Physical>,
        filter: TextureFilter,
    ) -> Result<(), Self::Error> {
        let res = self.renderer.blit(from, self.target, src, dst, filter);
        self.target
            .0
            .make_current(&self.renderer.gl, &self.renderer.egl)?;
        res
    }
}

impl Blit for GlesRenderer {
    #[instrument(level = "trace", parent = &self.span, skip(self, src_target, dst_target))]
    #[profiling::function]
    fn blit(
        &mut self,
        src_target: &GlesTarget<'_>,
        dst_target: &mut GlesTarget<'_>,
        src: Rectangle<i32, Physical>,
        dst: Rectangle<i32, Physical>,
        filter: TextureFilter,
    ) -> Result<(), GlesError> {
        // glBlitFramebuffer is sadly only available for GLES 3.0 and higher
        if self.gl_version < version::GLES_3_0 {
            return Err(GlesError::GLVersionNotSupported(version::GLES_3_0));
        }

        match (&src_target.0, &dst_target.0) {
            (
                GlesTargetInternal::Surface { surface: src, .. },
                GlesTargetInternal::Surface { surface: dst, .. },
            ) => unsafe {
                self.egl.make_current_with_draw_and_read_surface(dst, src)?;
            },
            (GlesTargetInternal::Surface { surface: src, .. }, _) => unsafe {
                self.egl.make_current_with_surface(src)?;
            },
            (_, GlesTargetInternal::Surface { surface: dst, .. }) => unsafe {
                self.egl.make_current_with_surface(dst)?;
            },
            (_, _) => unsafe {
                self.egl.make_current()?;
            },
        }

        match &src_target.0 {
            GlesTargetInternal::Image { ref buf, .. } => unsafe {
                self.gl.BindFramebuffer(ffi::READ_FRAMEBUFFER, buf.fbo)
            },
            GlesTargetInternal::Texture { ref fbo, .. } => unsafe {
                self.gl.BindFramebuffer(ffi::READ_FRAMEBUFFER, *fbo)
            },
            GlesTargetInternal::Renderbuffer { ref fbo, .. } => unsafe {
                self.gl.BindFramebuffer(ffi::READ_FRAMEBUFFER, *fbo)
            },
            _ => {} // Note: The only target missing is `Surface` and handled above
        }
        match &dst_target.0 {
            GlesTargetInternal::Image { ref buf, .. } => unsafe {
                self.gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, buf.fbo)
            },
            GlesTargetInternal::Texture { ref fbo, .. } => unsafe {
                self.gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, *fbo)
            },
            GlesTargetInternal::Renderbuffer { ref fbo, .. } => unsafe {
                self.gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, *fbo)
            },
            _ => {} // Note: The only target missing is `Surface` and handled above
        }

        let status = unsafe { self.gl.CheckFramebufferStatus(ffi::FRAMEBUFFER) };
        if status != ffi::FRAMEBUFFER_COMPLETE {
            let _ = self.unbind();
            return Err(GlesError::FramebufferBindingError);
        }

        let errno = unsafe {
            while self.gl.GetError() != ffi::NO_ERROR {} // clear flag before
            self.gl.BlitFramebuffer(
                src.loc.x,
                src.loc.y,
                src.loc.x + src.size.w,
                src.loc.y + src.size.h,
                dst.loc.x,
                dst.loc.y,
                dst.loc.x + dst.size.w,
                dst.loc.y + dst.size.h,
                ffi::COLOR_BUFFER_BIT,
                match filter {
                    TextureFilter::Linear => ffi::LINEAR,
                    TextureFilter::Nearest => ffi::NEAREST,
                },
            );
            self.gl.GetError()
        };

        if errno == ffi::INVALID_OPERATION {
            Err(GlesError::BlitError)
        } else {
            Ok(())
        }
    }
}

impl Drop for GlesRenderer {
    fn drop(&mut self) {
        let _guard = self.span.enter();
        unsafe {
            if self.egl.make_current().is_ok() {
                self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
                self.gl.DeleteProgram(self.solid_program.program);
                self.gl.DeleteBuffers(self.vbos.len() as i32, self.vbos.as_ptr());

                if self.extensions.iter().any(|ext| ext == "GL_KHR_debug") {
                    self.gl.Disable(ffi::DEBUG_OUTPUT);
                    self.gl.DebugMessageCallback(None, ptr::null());
                }

                #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
                let _ = self.egl_reader.take();
                let _ = self.egl.unbind();
            }

            if let Some(gl_debug_ptr) = self.gl_debug_span.take() {
                let _ = Box::from_raw(gl_debug_ptr);
            }
        }
    }
}

impl GlesRenderer {
    /// Get access to the underlying [`EGLContext`].
    ///
    /// *Note*: Modifying the context state, might result in rendering issues.
    /// The context state is considerd an implementation detail
    /// and no guarantee is made about what can or cannot be changed.
    /// To make sure a certain modification does not interfere with
    /// the renderer's behaviour, check the source.
    pub fn egl_context(&self) -> &EGLContext {
        &self.egl
    }

    /// Run custom code in the GL context owned by this renderer.
    ///
    /// The OpenGL state of the renderer is considered an implementation detail
    /// and no guarantee is made about what can or cannot be changed,
    /// as such you should reset everything you change back to its previous value
    /// or check the source code of the version of Smithay you are using to ensure
    /// your changes don't interfere with the renderer's behavior.
    /// Doing otherwise can lead to rendering errors while using other functions of this renderer.
    #[instrument(level = "trace", parent = &self.span, skip_all)]
    pub fn with_context<F, R>(&mut self, func: F) -> Result<R, GlesError>
    where
        F: FnOnce(&ffi::Gles2) -> R,
    {
        unsafe {
            self.egl.make_current()?;
        }
        Ok(func(&self.gl))
    }

    /// Compile a custom pixel shader for rendering with [`GlesFrame::render_pixel_shader_to`].
    ///
    /// Pixel shaders can be used for completely shader-driven drawing into a given region.
    ///
    /// They need to handle the following #define variants:
    /// - `DEBUG_FLAGS` see below
    ///
    /// They receive the following variables:
    /// - *varying* v_coords `vec2` - contains the position from the vertex shader
    /// - *uniform* size `vec2` - size of the viewport in pixels
    /// - *uniform* alpha `float` - for the alpha value passed by the renderer
    /// - *uniform* tint `float` - for the tint passed by the renderer (either 0.0 or 1.0) - only if `DEBUG_FLAGS` was defined
    ///
    /// Additional uniform values can be defined by passing `UniformName`s to the `additional_uniforms` argument
    /// and can then be set in functions utilizing `GlesPixelProgram` (like [`GlesFrame::render_pixel_shader_to`]).
    ///
    /// The shader must **not** contain a `#version` directive. It will be interpreted as version 100.
    ///
    /// ## Panics
    ///
    /// Panics if any of the names of the passed additional uniforms contains a `\0`/NUL-byte.
    pub fn compile_custom_pixel_shader(
        &mut self,
        src: impl AsRef<str>,
        additional_uniforms: &[UniformName<'_>],
    ) -> Result<GlesPixelProgram, GlesError> {
        unsafe {
            self.egl.make_current()?;
        }

        let shader = format!("#version 100\n{}", src.as_ref());
        let program = unsafe { link_program(&self.gl, shaders::VERTEX_SHADER, &shader)? };
        let debug_shader = format!("#version 100\n#define {}\n{}", shaders::DEBUG_FLAGS, src.as_ref());
        let debug_program = unsafe { link_program(&self.gl, shaders::VERTEX_SHADER, &debug_shader)? };

        let vert = c"vert";
        let vert_position = c"vert_position";
        let matrix = c"matrix";
        let tex_matrix = c"tex_matrix";
        let size = c"size";
        let alpha = c"alpha";
        let tint = c"tint";

        unsafe {
            Ok(GlesPixelProgram(Arc::new(GlesPixelProgramInner {
                normal: GlesPixelProgramInternal {
                    program,
                    uniform_matrix: self
                        .gl
                        .GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
                    uniform_tex_matrix: self
                        .gl
                        .GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
                    uniform_alpha: self
                        .gl
                        .GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
                    uniform_size: self
                        .gl
                        .GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar),
                    attrib_vert: self
                        .gl
                        .GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
                    attrib_position: self
                        .gl
                        .GetAttribLocation(program, vert_position.as_ptr() as *const ffi::types::GLchar),
                    additional_uniforms: additional_uniforms
                        .iter()
                        .map(|uniform| {
                            let name = CString::new(uniform.name.as_bytes()).expect("Interior null in name");
                            let location = self
                                .gl
                                .GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
                            (
                                uniform.name.clone().into_owned(),
                                UniformDesc {
                                    location,
                                    type_: uniform.type_,
                                },
                            )
                        })
                        .collect(),
                },
                debug: GlesPixelProgramInternal {
                    program: debug_program,
                    uniform_matrix: self
                        .gl
                        .GetUniformLocation(debug_program, matrix.as_ptr() as *const ffi::types::GLchar),
                    uniform_tex_matrix: self
                        .gl
                        .GetUniformLocation(debug_program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
                    uniform_alpha: self
                        .gl
                        .GetUniformLocation(debug_program, alpha.as_ptr() as *const ffi::types::GLchar),
                    uniform_size: self
                        .gl
                        .GetUniformLocation(debug_program, size.as_ptr() as *const ffi::types::GLchar),
                    attrib_vert: self
                        .gl
                        .GetAttribLocation(debug_program, vert.as_ptr() as *const ffi::types::GLchar),
                    attrib_position: self.gl.GetAttribLocation(
                        debug_program,
                        vert_position.as_ptr() as *const ffi::types::GLchar,
                    ),
                    additional_uniforms: additional_uniforms
                        .iter()
                        .map(|uniform| {
                            let name = CString::new(uniform.name.as_bytes()).expect("Interior null in name");
                            let location = self.gl.GetUniformLocation(
                                debug_program,
                                name.as_ptr() as *const ffi::types::GLchar,
                            );
                            (
                                uniform.name.clone().into_owned(),
                                UniformDesc {
                                    location,
                                    type_: uniform.type_,
                                },
                            )
                        })
                        .collect(),
                },
                destruction_callback_sender: self.destruction_callback_sender.clone(),
                uniform_tint: self
                    .gl
                    .GetUniformLocation(debug_program, tint.as_ptr() as *const ffi::types::GLchar),
            })))
        }
    }

    /// Compile a custom texture shader for rendering with [`GlesFrame::render_texture`] or [`GlesFrame::render_texture_from_to`].
    ///
    /// They need to handle the following #define variants:
    /// - `EXTERNAL` uses samplerExternalOES instead of sampler2D, requires the GL_OES_EGL_image_external extension
    /// - `NO_ALPHA` needs to ignore the alpha channel of the texture and replace it with 1.0
    /// - `DEBUG_FLAGS` see below
    ///
    /// They receive the following variables:
    /// - *varying* v_coords `vec2` - contains the position from the vertex shader
    /// - *uniform* tex `sample2d` - texture sampler
    /// - *uniform* alpha `float` - for the alpha value passed by the renderer
    /// - *uniform* tint `float` - for the tint passed by the renderer (either 0.0 or 1.0) - only if `DEBUG_FLAGS` was defined
    ///
    /// Additional uniform values can be defined by passing `UniformName`s to the `additional_uniforms` argument
    /// and can then be set in functions utilizing `GlesTexProgram` (like [`GlesFrame::render_texture`] or [`GlesFrame::render_texture_from_to`]).
    ///
    /// The shader must contain a line only containing `//_DEFINES`. It will be replaced by the renderer with corresponding `#define` directives.
    ///
    /// ## Panics
    ///
    /// Panics if any of the names of the passed additional uniforms contains a `\0`/NUL-byte.
    pub fn compile_custom_texture_shader(
        &mut self,
        shader: impl AsRef<str>,
        additional_uniforms: &[UniformName<'_>],
    ) -> Result<GlesTexProgram, GlesError> {
        unsafe {
            self.egl.make_current()?;
        }

        unsafe {
            texture_program(
                &self.gl,
                shader.as_ref(),
                additional_uniforms,
                self.destruction_callback_sender.clone(),
            )
        }
    }
}

impl GlesFrame<'_, '_> {
    /// Run custom code in the GL context owned by this renderer.
    ///
    /// The OpenGL state of the renderer is considered an implementation detail
    /// and no guarantee is made about what can or cannot be changed,
    /// as such you should reset everything you change back to its previous value
    /// or check the source code of the version of Smithay you are using to ensure
    /// your changes don't interfere with the renderer's behavior.
    /// Doing otherwise can lead to rendering errors while using other functions of this renderer.
    #[instrument(level = "trace", parent = &self.span, skip_all)]
    pub fn with_context<F, R>(&mut self, func: F) -> Result<R, GlesError>
    where
        F: FnOnce(&ffi::Gles2) -> R,
    {
        Ok(func(&self.renderer.gl))
    }
}

impl RendererSuper for GlesRenderer {
    type Error = GlesError;
    type TextureId = GlesTexture;
    type Framebuffer<'buffer> = GlesTarget<'buffer>;
    type Frame<'frame, 'buffer>
        = GlesFrame<'frame, 'buffer>
    where
        'buffer: 'frame;
}

impl Renderer for GlesRenderer {
    fn context_id(&self) -> ContextId<GlesTexture> {
        self.egl
            .user_data()
            .get::<ContextId<GlesTexture>>()
            .unwrap()
            .clone()
    }

    fn downscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> {
        self.min_filter = filter;
        Ok(())
    }
    fn upscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> {
        self.max_filter = filter;
        Ok(())
    }

    fn set_debug_flags(&mut self, flags: DebugFlags) {
        self.debug_flags = flags;
    }

    fn debug_flags(&self) -> DebugFlags {
        self.debug_flags
    }

    #[profiling::function]
    fn render<'frame, 'buffer>(
        &'frame mut self,
        target: &'frame mut GlesTarget<'buffer>,
        mut output_size: Size<i32, Physical>,
        transform: Transform,
    ) -> Result<GlesFrame<'frame, 'buffer>, GlesError>
    where
        'buffer: 'frame,
    {
        target.0.make_current(&self.gl, &self.egl)?;

        unsafe {
            self.gl.Viewport(0, 0, output_size.w, output_size.h);

            self.gl.Scissor(0, 0, output_size.w, output_size.h);
            self.gl.Enable(ffi::SCISSOR_TEST);

            self.gl.Enable(ffi::BLEND);
            self.gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA);
        }

        // Handle the width/height swap when the output is rotated by 90°/270°.
        if let Transform::_90 | Transform::_270 | Transform::Flipped90 | Transform::Flipped270 = transform {
            mem::swap(&mut output_size.w, &mut output_size.h);
        }

        // replicate https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml
        // glOrtho(0, width, 0, height, 1, 1);
        let mut renderer = Matrix3::<f32>::identity();
        let t = Matrix3::<f32>::identity();
        let x = 2.0 / (output_size.w as f32);
        let y = 2.0 / (output_size.h as f32);

        // Rotation & Reflection
        renderer[0][0] = x * t[0][0];
        renderer[1][0] = x * t[0][1];
        renderer[0][1] = y * -t[1][0];
        renderer[1][1] = y * -t[1][1];

        //Translation
        renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0]));
        renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1]));

        // We account for OpenGLs coordinate system here
        let flip180 = Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0);

        let current_projection = flip180 * transform.matrix() * renderer;
        let span = span!(parent: &self.span, Level::DEBUG, "renderer_gles2_frame", current_projection = ?current_projection, size = ?output_size, transform = ?transform).entered();

        Ok(GlesFrame {
            renderer: self,
            target,
            // output transformation passed in by the user
            current_projection,
            transform,
            size: output_size,
            tex_program_override: None,
            finished: AtomicBool::new(false),

            span,
        })
    }

    #[profiling::function]
    fn wait(&mut self, sync: &super::sync::SyncPoint) -> Result<(), Self::Error> {
        unsafe {
            self.egl.make_current()?;
        }

        let display = self.egl_context().display();

        // if the sync point holds a EGLFence we can try
        // to directly insert it in our context
        if let Some(fence) = sync.get::<EGLFence>() {
            if fence.wait(display).is_ok() {
                return Ok(());
            }
        }

        // alternative we try to create a temporary fence
        // out of the native fence if available and try
        // to insert it in our context
        if let Some(native) = EGLFence::supports_importing(display)
            .then(|| sync.export())
            .flatten()
        {
            if let Ok(fence) = EGLFence::import(display, native) {
                if fence.wait(display).is_ok() {
                    return Ok(());
                }
            }
        }

        // if everything above failed we can only
        // block until the sync point has been reached
        sync.wait().map_err(|_| GlesError::SyncInterrupted)
    }

    #[profiling::function]
    fn cleanup_texture_cache(&mut self) -> Result<(), Self::Error> {
        unsafe {
            self.egl.make_current()?;
        }
        self.cleanup();
        Ok(())
    }
}

/// Vertices for instanced rendering.
static INSTANCED_VERTS: [ffi::types::GLfloat; 8] = [
    1.0, 0.0, // top right
    0.0, 0.0, // top left
    1.0, 1.0, // bottom right
    0.0, 1.0, // bottom left
];

/// Vertices for rendering individual triangles.
const MAX_RECTS_PER_DRAW: usize = 10;
const TRIANGLE_VERTS: [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] = triangle_verts();
const fn triangle_verts() -> [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] {
    let mut verts = [0.; 12 * MAX_RECTS_PER_DRAW];
    let mut i = 0;
    loop {
        // Top Left.
        verts[i * 12] = 0.0;
        verts[i * 12 + 1] = 0.0;

        // Bottom left.
        verts[i * 12 + 2] = 0.0;
        verts[i * 12 + 3] = 1.0;

        // Bottom right.
        verts[i * 12 + 4] = 1.0;
        verts[i * 12 + 5] = 1.0;

        // Top left.
        verts[i * 12 + 6] = 0.0;
        verts[i * 12 + 7] = 0.0;

        // Bottom right.
        verts[i * 12 + 8] = 1.0;
        verts[i * 12 + 9] = 1.0;

        // Top right.
        verts[i * 12 + 10] = 1.0;
        verts[i * 12 + 11] = 0.0;

        i += 1;
        if i == MAX_RECTS_PER_DRAW {
            break;
        }
    }
    verts
}

/// Vertices for output rendering.
static OUTPUT_VERTS: [ffi::types::GLfloat; 8] = [
    -1.0, 1.0, // top right
    -1.0, -1.0, // top left
    1.0, 1.0, // bottom right
    1.0, -1.0, // bottom left
];

impl Frame for GlesFrame<'_, '_> {
    type Error = GlesError;
    type TextureId = GlesTexture;

    fn context_id(&self) -> ContextId<GlesTexture> {
        self.renderer.context_id()
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    fn clear(&mut self, color: Color32F, at: &[Rectangle<i32, Physical>]) -> Result<(), GlesError> {
        if at.is_empty() {
            return Ok(());
        }

        unsafe {
            self.renderer.gl.Disable(ffi::BLEND);
        }

        let res = self.draw_solid(Rectangle::from_size(self.size), at, color);

        unsafe {
            self.renderer.gl.Enable(ffi::BLEND);
            self.renderer.gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA);
        }
        res
    }

    #[instrument(level = "trace", skip(self), parent = &self.span)]
    #[profiling::function]
    fn draw_solid(
        &mut self,
        dst: Rectangle<i32, Physical>,
        damage: &[Rectangle<i32, Physical>],
        color: Color32F,
    ) -> Result<(), Self::Error> {
        if damage.is_empty() {
            return Ok(());
        }

        let is_opaque = color.is_opaque();

        if is_opaque {
            unsafe {
                self.renderer.gl.Disable(ffi::BLEND);
            }
        }

        let res = self.draw_solid(dst, damage, color);

        if is_opaque {
            unsafe {
                self.renderer.gl.Enable(ffi::BLEND);
                self.renderer.gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA);
            }
        }

        res
    }

    #[instrument(level = "trace", skip(self), parent = &self.span)]
    #[profiling::function]
    fn render_texture_from_to(
        &mut self,
        texture: &GlesTexture,
        src: Rectangle<f64, BufferCoord>,
        dest: Rectangle<i32, Physical>,
        damage: &[Rectangle<i32, Physical>],
        opaque_regions: &[Rectangle<i32, Physical>],
        transform: Transform,
        alpha: f32,
    ) -> Result<(), GlesError> {
        self.render_texture_from_to(
            texture,
            src,
            dest,
            damage,
            opaque_regions,
            transform,
            alpha,
            None,
            &[],
        )
    }

    fn transformation(&self) -> Transform {
        self.transform
    }

    #[profiling::function]
    fn wait(&mut self, sync: &SyncPoint) -> Result<(), Self::Error> {
        self.renderer.wait(sync)
    }

    #[profiling::function]
    fn finish(mut self) -> Result<SyncPoint, Self::Error> {
        self.finish_internal()
    }
}

impl GlesFrame<'_, '_> {
    #[profiling::function]
    fn finish_internal(&mut self) -> Result<SyncPoint, GlesError> {
        let _guard = self.span.enter();

        if self.finished.swap(true, Ordering::SeqCst) {
            return Ok(SyncPoint::signaled());
        }

        unsafe {
            self.renderer.gl.Disable(ffi::SCISSOR_TEST);
            self.renderer.gl.Disable(ffi::BLEND);
        }

        if let GlesTargetInternal::Texture { sync_lock, .. } = &mut self.target.0 {
            sync_lock.update_write(&self.renderer.gl);
        }

        // delayed destruction until the next frame rendering.
        self.renderer.cleanup();

        // if we support egl fences we should use it
        if self.renderer.capabilities.contains(&Capability::ExportFence) {
            if let Ok(fence) = EGLFence::create(self.renderer.egl.display()) {
                unsafe {
                    self.renderer.gl.Flush();
                }
                return Ok(SyncPoint::from(fence));
            }
        }

        // as a last option we force finish, this is unlikely to happen
        unsafe {
            self.renderer.gl.Finish();
        }
        Ok(SyncPoint::signaled())
    }

    /// Overrides the default texture shader used, if none is specified.
    ///
    /// This affects calls to [`Frame::render_texture_at`] or [`Frame::render_texture_from_to`] as well as
    /// calls to [`GlesFrame::render_texture_from_to`] or [`GlesFrame::render_texture`], if the passed in `program` is `None`.
    ///
    /// Override is active only for the lifetime of this `GlesFrame` and can be reset via [`GlesFrame::clear_tex_program_override`].
    pub fn override_default_tex_program(
        &mut self,
        program: GlesTexProgram,
        additional_uniforms: Vec<Uniform<'static>>,
    ) {
        self.tex_program_override = Some((program, additional_uniforms));
    }

    /// Resets a texture shader override previously set by [`GlesFrame::override_default_tex_program`].
    pub fn clear_tex_program_override(&mut self) {
        self.tex_program_override = None;
    }

    /// Draw a solid color to the current target at the specified destination with the specified color.
    #[instrument(level = "trace", skip(self), parent = &self.span)]
    #[profiling::function]
    pub fn draw_solid(
        &mut self,
        dest: Rectangle<i32, Physical>,
        damage: &[Rectangle<i32, Physical>],
        color: Color32F,
    ) -> Result<(), GlesError> {
        if damage.is_empty() {
            return Ok(());
        }

        let mut mat = Matrix3::<f32>::identity();
        mat = self.current_projection * mat;

        // prepare the vertices
        self.renderer.vertices.clear();
        if self.renderer.capabilities.contains(&Capability::Instancing) {
            self.renderer.vertices.extend(damage.iter().flat_map(|rect| {
                let dest_size = dest.size;

                let rect_constrained_loc = rect.loc.constrain(Rectangle::from_size(dest_size));
                let rect_clamped_size = rect
                    .size
                    .clamp((0, 0), (dest_size.to_point() - rect_constrained_loc).to_size());

                let rect = Rectangle::new(rect_constrained_loc, rect_clamped_size);
                [
                    (dest.loc.x + rect.loc.x) as f32,
                    (dest.loc.y + rect.loc.y) as f32,
                    rect.size.w as f32,
                    rect.size.h as f32,
                ]
            }))
        } else {
            self.renderer.vertices.extend(damage.iter().flat_map(|rect| {
                let dest_size = dest.size;

                let rect_constrained_loc = rect.loc.constrain(Rectangle::from_size(dest_size));
                let rect_clamped_size = rect
                    .size
                    .clamp((0, 0), (dest_size.to_point() - rect_constrained_loc).to_size());

                let rect = Rectangle::new(rect_constrained_loc, rect_clamped_size);
                // Add the 4 f32s per damage rectangle for each of the 6 vertices.
                (0..6).flat_map(move |_| {
                    [
                        (dest.loc.x + rect.loc.x) as f32,
                        (dest.loc.y + rect.loc.y) as f32,
                        rect.size.w as f32,
                        rect.size.h as f32,
                    ]
                })
            }));
        }

        let gl = &self.renderer.gl;
        unsafe {
            gl.UseProgram(self.renderer.solid_program.program);
            gl.Uniform4f(
                self.renderer.solid_program.uniform_color,
                color.r(),
                color.g(),
                color.b(),
                color.a(),
            );
            gl.UniformMatrix3fv(
                self.renderer.solid_program.uniform_matrix,
                1,
                ffi::FALSE,
                mat.as_ptr(),
            );

            gl.EnableVertexAttribArray(self.renderer.solid_program.attrib_vert as u32);
            gl.BindBuffer(ffi::ARRAY_BUFFER, self.renderer.vbos[0]);
            gl.VertexAttribPointer(
                self.renderer.solid_program.attrib_vert as u32,
                2,
                ffi::FLOAT,
                ffi::FALSE,
                0,
                std::ptr::null(),
            );

            gl.EnableVertexAttribArray(self.renderer.solid_program.attrib_position as u32);
            gl.BindBuffer(ffi::ARRAY_BUFFER, 0);

            gl.VertexAttribPointer(
                self.renderer.solid_program.attrib_position as u32,
                4,
                ffi::FLOAT,
                ffi::FALSE,
                0,
                self.renderer.vertices.as_ptr() as *const _,
            );

            let damage_len = damage.len() as i32;
            if self.renderer.capabilities.contains(&Capability::Instancing) {
                gl.VertexAttribDivisor(self.renderer.solid_program.attrib_vert as u32, 0);

                gl.VertexAttribDivisor(self.renderer.solid_program.attrib_position as u32, 1);

                gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len);
            } else {
                let count = damage_len * 6;
                gl.DrawArrays(ffi::TRIANGLES, 0, count);
            }

            gl.DisableVertexAttribArray(self.renderer.solid_program.attrib_vert as u32);
            gl.DisableVertexAttribArray(self.renderer.solid_program.attrib_position as u32);
        }

        Ok(())
    }

    /// Render part of a texture as given by src to the current target into the rectangle described by dst
    /// as a flat 2d-plane after applying the inverse of the given transformation.
    /// (Meaning `src_transform` should match the orientation of surface being rendered).
    ///
    /// Optionally allows a custom texture program and matching additional uniforms to be passed in.
    #[instrument(level = "trace", skip(self), parent = &self.span)]
    #[profiling::function]
    #[allow(clippy::too_many_arguments)]
    pub fn render_texture_from_to(
        &mut self,
        texture: &GlesTexture,
        src: Rectangle<f64, BufferCoord>,
        dest: Rectangle<i32, Physical>,
        damage: &[Rectangle<i32, Physical>],
        opaque_regions: &[Rectangle<i32, Physical>],
        transform: Transform,
        alpha: f32,
        program: Option<&GlesTexProgram>,
        additional_uniforms: &[Uniform<'_>],
    ) -> Result<(), GlesError> {
        let mut mat = Matrix3::<f32>::identity();

        // dest position and scale
        mat = mat * Matrix3::from_translation(Vector2::new(dest.loc.x as f32, dest.loc.y as f32));

        // src scale, position, tranform and y_inverted
        let tex_size = texture.size();
        let src_size = src.size;

        if src_size.is_empty() || tex_size.is_empty() {
            return Ok(());
        }

        let mut tex_mat = build_texture_mat(src, dest, tex_size, transform);
        if texture.0.y_inverted {
            tex_mat = Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0) * tex_mat;
        }

        let render_texture = |renderer: &mut Self, damage: &[Rectangle<i32, Physical>]| {
            let instances = damage.iter().flat_map(|rect| {
                let dest_size = dest.size;

                let rect_constrained_loc = rect.loc.constrain(Rectangle::from_size(dest_size));
                let rect_clamped_size = rect
                    .size
                    .clamp((0, 0), (dest_size.to_point() - rect_constrained_loc).to_size());

                let rect = Rectangle::new(rect_constrained_loc, rect_clamped_size);
                [
                    rect.loc.x as f32,
                    rect.loc.y as f32,
                    rect.size.w as f32,
                    rect.size.h as f32,
                ]
            });

            renderer.render_texture(
                texture,
                tex_mat,
                mat,
                Some(instances),
                alpha,
                program,
                additional_uniforms,
            )
        };

        // We split the damage in opaque and non opaque regions, for opaque regions we can
        // disable blending. Most likely we did not clear regions marked as opaque, which can
        // result in read-back when not disabling blending. This can be problematic on tile based
        // renderers.
        let mut non_opaque_damage = std::mem::take(&mut self.renderer.non_opaque_damage);
        let mut opaque_damage = std::mem::take(&mut self.renderer.opaque_damage);
        non_opaque_damage.clear();
        opaque_damage.clear();

        // If drawing is implicit opaque and we have no custom program we
        // can skip some logic and save a few operations. In case we have
        // some user-provided alpha we can not disable blending, but should
        // also have cleared the region previously anyway. In case we have
        // no opaque regions we can also short cut the logic a bit.
        let is_implicit_opaque = !texture.0.has_alpha && alpha == 1f32;
        if is_implicit_opaque && program.is_none() && self.tex_program_override.is_none() {
            opaque_damage.extend_from_slice(damage);
        } else if alpha != 1f32 || opaque_regions.is_empty() {
            non_opaque_damage.extend_from_slice(damage);
        } else {
            non_opaque_damage.extend_from_slice(damage);
            opaque_damage.extend_from_slice(damage);

            non_opaque_damage =
                Rectangle::subtract_rects_many_in_place(non_opaque_damage, opaque_regions.iter().copied());
            opaque_damage =
                Rectangle::subtract_rects_many_in_place(opaque_damage, non_opaque_damage.iter().copied());
        }

        tracing::trace!(non_opaque_damage = ?non_opaque_damage, opaque_damage = ?opaque_damage, "drawing texture");

        let non_opaque_render_res = if !non_opaque_damage.is_empty() {
            render_texture(self, &non_opaque_damage)
        } else {
            Ok(())
        };

        let opaque_render_res = if !opaque_damage.is_empty() {
            unsafe {
                self.renderer.gl.Disable(ffi::BLEND);
            }

            let res = render_texture(self, &opaque_damage);

            unsafe {
                self.renderer.gl.Enable(ffi::BLEND);
                self.renderer.gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA);
            }

            res
        } else {
            Ok(())
        };

        // Return the damage(s) to be able to re-use the allocation(s)
        std::mem::swap(&mut self.renderer.non_opaque_damage, &mut non_opaque_damage);
        std::mem::swap(&mut self.renderer.opaque_damage, &mut opaque_damage);

        non_opaque_render_res?;
        opaque_render_res?;

        Ok(())
    }

    /// Render a texture to the current target using given projection matrix and alpha.
    ///
    /// The instances are used to define the regions which should get drawn.
    /// Each instance has to define 4 [`GLfloat`](ffi::types::GLfloat) which define the
    /// relative offset and scale for the vertex position and range from `0.0` to `1.0`.
    /// The first 2 [`GLfloat`](ffi::types::GLfloat) define the relative x and y offset.
    /// The remaining 2 [`GLfloat`](ffi::types::GLfloat) define the x and y scale.
    /// This can be used to only update parts of the texture on screen.
    ///
    /// The given texture matrix is used to transform the instances into texture coordinates.
    /// In case the texture is rotated, flipped or y-inverted the matrix has to be set up accordingly.
    /// Additionally the matrix can be used to crop the texture.
    ///
    /// Optionally allows a custom texture program and matching additional uniforms to be passed in.
    #[instrument(level = "trace", skip(self, instances), parent = &self.span)]
    #[profiling::function]
    #[allow(clippy::too_many_arguments)]
    pub fn render_texture(
        &mut self,
        tex: &GlesTexture,
        tex_matrix: Matrix3<f32>,
        mut matrix: Matrix3<f32>,
        instances: Option<impl IntoIterator<Item = ffi::types::GLfloat>>,
        alpha: f32,
        program: Option<&GlesTexProgram>,
        additional_uniforms: &[Uniform<'_>],
    ) -> Result<(), GlesError> {
        // prepare the vertices
        self.renderer.vertices.clear();
        let damage_len = if let Some(instances) = instances {
            if self.renderer.capabilities.contains(&Capability::Instancing) {
                self.renderer.vertices.extend(instances);
                self.renderer.vertices.len() / 4
            } else {
                let mut damage = 0;
                let mut instances = instances.into_iter();
                while let Some(first) = instances.next() {
                    damage += 1;
                    let vertices = [
                        first,
                        instances.next().unwrap(),
                        instances.next().unwrap(),
                        instances.next().unwrap(),
                    ];
                    // Add the 4 f32s per damage rectangle for each of the 6 vertices.
                    for _ in 0..6 {
                        self.renderer.vertices.extend_from_slice(&vertices);
                    }
                }
                damage
            }
        } else if self.renderer.capabilities.contains(&Capability::Instancing) {
            self.renderer.vertices.extend_from_slice(&[0.0, 0.0, 1.0, 1.0]);
            1
        } else {
            // Add the 4 f32s per damage rectangle for each of the 6 vertices.
            for _ in 0..6 {
                self.renderer.vertices.extend_from_slice(&[0.0, 0.0, 1.0, 1.0]);
            }
            1
        };

        if self.renderer.vertices.is_empty() {
            return Ok(());
        }

        //apply output transformation
        matrix = self.current_projection * matrix;

        let target = if tex.0.is_external {
            ffi::TEXTURE_EXTERNAL_OES
        } else {
            ffi::TEXTURE_2D
        };
        let (tex_program, additional_uniforms) = program
            .map(|p| (p, additional_uniforms))
            .or_else(|| self.tex_program_override.as_ref().map(|(p, a)| (p, &**a)))
            .unwrap_or((&self.renderer.tex_program, &[]));
        let program_variant = tex_program.variant_for_format(
            if !tex.0.is_external { tex.0.format } else { None },
            tex.0.has_alpha,
        );
        let program = if self.renderer.debug_flags.is_empty() {
            &program_variant.normal
        } else {
            &program_variant.debug
        };

        // render
        let gl = &self.renderer.gl;
        let sync_lock = tex.0.sync.read().unwrap();
        unsafe {
            sync_lock.wait_for_upload(gl);
            gl.ActiveTexture(ffi::TEXTURE0);
            gl.BindTexture(target, tex.0.texture);
            gl.TexParameteri(
                target,
                ffi::TEXTURE_MIN_FILTER,
                match self.renderer.min_filter {
                    TextureFilter::Nearest => ffi::NEAREST as i32,
                    TextureFilter::Linear => ffi::LINEAR as i32,
                },
            );
            gl.TexParameteri(
                target,
                ffi::TEXTURE_MAG_FILTER,
                match self.renderer.max_filter {
                    TextureFilter::Nearest => ffi::NEAREST as i32,
                    TextureFilter::Linear => ffi::LINEAR as i32,
                },
            );
            gl.UseProgram(program.program);

            gl.Uniform1i(program.uniform_tex, 0);
            gl.UniformMatrix3fv(program.uniform_matrix, 1, ffi::FALSE, matrix.as_ptr());
            gl.UniformMatrix3fv(program.uniform_tex_matrix, 1, ffi::FALSE, tex_matrix.as_ptr());
            gl.Uniform1f(program.uniform_alpha, alpha);

            if !self.renderer.debug_flags.is_empty() {
                let tint = if self.renderer.debug_flags.contains(DebugFlags::TINT) {
                    1.0f32
                } else {
                    0.0f32
                };
                gl.Uniform1f(program_variant.uniform_tint, tint);
            }

            for uniform in additional_uniforms {
                let desc = program
                    .additional_uniforms
                    .get(&*uniform.name)
                    .ok_or_else(|| GlesError::UnknownUniform(uniform.name.clone().into_owned()))?;
                uniform.value.set(gl, desc)?;
            }

            gl.EnableVertexAttribArray(program.attrib_vert as u32);
            gl.BindBuffer(ffi::ARRAY_BUFFER, self.renderer.vbos[0]);
            gl.VertexAttribPointer(
                program.attrib_vert as u32,
                2,
                ffi::FLOAT,
                ffi::FALSE,
                0,
                std::ptr::null(),
            );

            // vert_position
            gl.EnableVertexAttribArray(program.attrib_vert_position as u32);
            gl.BindBuffer(ffi::ARRAY_BUFFER, 0);

            gl.VertexAttribPointer(
                program.attrib_vert_position as u32,
                4,
                ffi::FLOAT,
                ffi::FALSE,
                0,
                self.renderer.vertices.as_ptr() as *const _,
            );

            if self.renderer.capabilities.contains(&Capability::Instancing) {
                gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
                gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1);

                gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len as i32);
            } else {
                let count = damage_len * 6;
                gl.DrawArrays(ffi::TRIANGLES, 0, count as i32);
            }

            gl.BindTexture(target, 0);
            gl.DisableVertexAttribArray(program.attrib_vert as u32);
            gl.DisableVertexAttribArray(program.attrib_vert_position as u32);

            if self.renderer.capabilities.contains(&Capability::Fencing) {
                sync_lock.update_read(gl);
            } else if self.renderer.egl.is_shared() {
                gl.Finish();
            };
        }

        Ok(())
    }

    /// Render a pixel shader into the current target at a given `dest`-region.
    #[profiling::function]
    #[allow(clippy::too_many_arguments)]
    pub fn render_pixel_shader_to(
        &mut self,
        pixel_shader: &GlesPixelProgram,
        src: Rectangle<f64, BufferCoord>,
        dest: Rectangle<i32, Physical>,
        size: Size<i32, BufferCoord>,
        damage: Option<&[Rectangle<i32, Physical>]>,
        alpha: f32,
        additional_uniforms: &[Uniform<'_>],
    ) -> Result<(), GlesError> {
        let fallback_damage = &[Rectangle::from_size(dest.size)];
        let damage = damage.unwrap_or(fallback_damage);

        // prepare the vertices
        self.renderer.vertices.clear();
        if self.renderer.capabilities.contains(&Capability::Instancing) {
            self.renderer.vertices.extend(damage.iter().flat_map(|rect| {
                let dest_size = dest.size;

                let rect_constrained_loc = rect.loc.constrain(Rectangle::from_size(dest_size));
                let rect_clamped_size = rect
                    .size
                    .clamp((0, 0), (dest_size.to_point() - rect_constrained_loc).to_size());

                let rect = Rectangle::new(rect_constrained_loc, rect_clamped_size);
                [
                    rect.loc.x as f32,
                    rect.loc.y as f32,
                    rect.size.w as f32,
                    rect.size.h as f32,
                ]
            }));
        } else {
            self.renderer.vertices.extend(damage.iter().flat_map(|rect| {
                let dest_size = dest.size;

                let rect_constrained_loc = rect.loc.constrain(Rectangle::from_size(dest_size));
                let rect_clamped_size = rect
                    .size
                    .clamp((0, 0), (dest_size.to_point() - rect_constrained_loc).to_size());

                let rect = Rectangle::new(rect_constrained_loc, rect_clamped_size);
                // Add the 4 f32s per damage rectangle for each of the 6 vertices.
                (0..6).flat_map(move |_| {
                    [
                        rect.loc.x as f32,
                        rect.loc.y as f32,
                        rect.size.w as f32,
                        rect.size.h as f32,
                    ]
                })
            }));
        }

        if self.renderer.vertices.is_empty() {
            return Ok(());
        }

        let mut matrix = Matrix3::<f32>::identity();
        let tex_matrix = build_texture_mat(src, dest, size, Transform::Normal);

        // dest position and scale
        matrix = matrix * Matrix3::from_translation(Vector2::new(dest.loc.x as f32, dest.loc.y as f32));

        //apply output transformation
        matrix = self.current_projection * matrix;

        let program = if self.renderer.debug_flags.is_empty() {
            &pixel_shader.0.normal
        } else {
            &pixel_shader.0.debug
        };

        // render
        let gl = &self.renderer.gl;
        unsafe {
            gl.UseProgram(program.program);

            gl.UniformMatrix3fv(program.uniform_matrix, 1, ffi::FALSE, matrix.as_ptr());
            gl.UniformMatrix3fv(program.uniform_tex_matrix, 1, ffi::FALSE, tex_matrix.as_ptr());
            gl.Uniform2f(program.uniform_size, size.w as f32, size.h as f32);
            gl.Uniform1f(program.uniform_alpha, alpha);
            let tint = if self.renderer.debug_flags.contains(DebugFlags::TINT) {
                1.0f32
            } else {
                0.0f32
            };

            if !self.renderer.debug_flags.is_empty() {
                gl.Uniform1f(pixel_shader.0.uniform_tint, tint);
            }

            for uniform in additional_uniforms {
                let desc = program
                    .additional_uniforms
                    .get(&*uniform.name)
                    .ok_or_else(|| GlesError::UnknownUniform(uniform.name.clone().into_owned()))?;
                uniform.value.set(gl, desc)?;
            }

            gl.EnableVertexAttribArray(program.attrib_vert as u32);
            gl.BindBuffer(ffi::ARRAY_BUFFER, self.renderer.vbos[0]);
            gl.VertexAttribPointer(
                program.attrib_vert as u32,
                2,
                ffi::FLOAT,
                ffi::FALSE,
                0,
                std::ptr::null(),
            );

            // vert_position
            gl.EnableVertexAttribArray(program.attrib_position as u32);
            gl.BindBuffer(ffi::ARRAY_BUFFER, 0);

            gl.VertexAttribPointer(
                program.attrib_position as u32,
                4,
                ffi::FLOAT,
                ffi::FALSE,
                0,
                self.renderer.vertices.as_ptr() as *const _,
            );

            let damage_len = damage.len() as i32;
            if self.renderer.capabilities.contains(&Capability::Instancing) {
                gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
                gl.VertexAttribDivisor(program.attrib_position as u32, 1);

                gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len);
            } else {
                let count = damage_len * 6;
                gl.DrawArrays(ffi::TRIANGLES, 0, count);
            }

            gl.DisableVertexAttribArray(program.attrib_vert as u32);
            gl.DisableVertexAttribArray(program.attrib_position as u32);
        }

        Ok(())
    }

    /// Projection matrix for this frame
    pub fn projection(&self) -> &[f32; 9] {
        self.current_projection.as_ref()
    }

    /// Get access to the underlying [`EGLContext`].
    ///
    /// *Note*: Modifying the context state, might result in rendering issues.
    /// The context state is considerd an implementation detail
    /// and no guarantee is made about what can or cannot be changed.
    /// To make sure a certain modification does not interfere with
    /// the renderer's behaviour, check the source.
    pub fn egl_context(&self) -> &EGLContext {
        self.renderer.egl_context()
    }

    /// Returns the supported [`Capabilities`](Capability) of the underlying renderer.
    pub fn capabilities(&self) -> &[Capability] {
        self.renderer.capabilities()
    }

    /// Returns the current enabled [`DebugFlags`] of the underlying renderer.
    pub fn debug_flags(&self) -> DebugFlags {
        self.renderer.debug_flags()
    }
}

impl Drop for GlesFrame<'_, '_> {
    fn drop(&mut self) {
        match self.finish_internal() {
            Ok(sync) => {
                let _ = sync.wait(); // nothing we can do
            }
            Err(err) => {
                warn!("Ignored error finishing GlesFrame on drop: {}", err);
            }
        }
    }
}

fn build_texture_mat(
    src: Rectangle<f64, BufferCoord>,
    dest: Rectangle<i32, Physical>,
    texture: Size<i32, BufferCoord>,
    transform: Transform,
) -> Matrix3<f32> {
    let dst_src_size = transform.transform_size(src.size);
    let scale = dst_src_size.to_f64() / dest.size.to_f64();

    let mut tex_mat = Matrix3::<f32>::identity();

    // first bring the damage into src scale
    tex_mat = Matrix3::from_nonuniform_scale(scale.x as f32, scale.y as f32) * tex_mat;

    // then compensate for the texture transform
    let transform_mat = transform.matrix();
    let translation = match transform {
        Transform::Normal => Matrix3::identity(),
        Transform::_90 => Matrix3::from_translation(Vector2::new(0f32, dst_src_size.w as f32)),
        Transform::_180 => {
            Matrix3::from_translation(Vector2::new(dst_src_size.w as f32, dst_src_size.h as f32))
        }
        Transform::_270 => Matrix3::from_translation(Vector2::new(dst_src_size.h as f32, 0f32)),
        Transform::Flipped => Matrix3::from_translation(Vector2::new(dst_src_size.w as f32, 0f32)),
        Transform::Flipped90 => Matrix3::identity(),
        Transform::Flipped180 => Matrix3::from_translation(Vector2::new(0f32, dst_src_size.h as f32)),
        Transform::Flipped270 => {
            Matrix3::from_translation(Vector2::new(dst_src_size.h as f32, dst_src_size.w as f32))
        }
    };
    tex_mat = transform_mat * tex_mat;
    tex_mat = translation * tex_mat;

    // now we can add the src crop loc, the size already done implicit by the src size
    tex_mat = Matrix3::from_translation(Vector2::new(src.loc.x as f32, src.loc.y as f32)) * tex_mat;

    // at last we have to normalize the values for UV space
    tex_mat = Matrix3::from_nonuniform_scale(
        (1.0f64 / texture.w as f64) as f32,
        (1.0f64 / texture.h as f64) as f32,
    ) * tex_mat;

    tex_mat
}

#[cfg(test)]
mod tests {
    use super::build_texture_mat;
    use crate::utils::{Buffer, Physical, Rectangle, Size, Transform};
    use cgmath::Vector3;

    #[test]
    fn texture_normal_double_size() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((1000f64, 500f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((1000, 500));
        let transform = Transform::Normal;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(0f32, 1f32, 1f32));
    }

    #[test]
    fn texture_scaler_crop() {
        let src: Rectangle<f64, Buffer> = Rectangle::new((42.5f64, 50.5f64).into(), (110f64, 154f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((813, 214).into(), (55, 77).into());
        let texture_size: Size<i32, Buffer> = Size::from((842, 674));
        let transform = Transform::Normal;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(
            tex_mat * top_left,
            Vector3::new(0.05047506f32, 0.07492582f32, 1f32)
        );
        assert_eq!(
            tex_mat * top_right,
            Vector3::new(0.1811164f32, 0.07492582f32, 1f32)
        );
        assert_eq!(
            tex_mat * bottom_right,
            Vector3::new(0.1811164f32, 0.30341247f32, 1f32)
        );
        assert_eq!(
            tex_mat * bottom_left,
            Vector3::new(0.05047506f32, 0.30341247f32, 1f32)
        );
    }

    #[test]
    fn texture_normal() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((500f64, 250f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((500, 250));
        let transform = Transform::Normal;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(0f32, 1f32, 1f32));
    }

    #[test]
    fn texture_flipped() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((500f64, 250f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((500, 250));
        let transform = Transform::Flipped;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(0f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(1f32, 1f32, 1f32));
    }

    #[test]
    fn texture_90() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((250f64, 500f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((250, 500));
        let transform = Transform::_90;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(0f32, 1f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(1f32, 1f32, 1f32));
    }

    #[test]
    fn texture_180() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((500f64, 250f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((500, 250));
        let transform = Transform::_180;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(0f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(1f32, 0f32, 1f32));
    }

    #[test]
    fn texture_270() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((250f64, 500f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((250, 500));
        let transform = Transform::_270;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(0f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(0f32, 0f32, 1f32));
    }

    #[test]
    fn texture_flipped_90() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((250f64, 500f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((250, 500));
        let transform = Transform::Flipped90;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(0f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(1f32, 0f32, 1f32));
    }

    #[test]
    fn texture_flipped_180() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((500f64, 250f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((500, 250));
        let transform = Transform::Flipped180;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(0f32, 1f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(0f32, 0f32, 1f32));
    }

    #[test]
    fn texture_flipped_270() {
        let src: Rectangle<f64, Buffer> = Rectangle::from_size((250f64, 500f64).into());
        let dest: Rectangle<i32, Physical> = Rectangle::new((442, 144).into(), (500, 250).into());
        let texture_size: Size<i32, Buffer> = Size::from((250, 500));
        let transform = Transform::Flipped270;

        let tex_mat = build_texture_mat(src, dest, texture_size, transform);

        let top_left = Vector3::new(0f32, 0f32, 1f32);
        let top_right = Vector3::new(dest.size.w as f32, 0f32, 1f32);
        let bottom_right = Vector3::new(dest.size.w as f32, dest.size.h as f32, 1f32);
        let bottom_left = Vector3::new(0f32, dest.size.h as f32, 1f32);

        assert_eq!(tex_mat * top_left, Vector3::new(1f32, 1f32, 1f32));
        assert_eq!(tex_mat * top_right, Vector3::new(1f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_right, Vector3::new(0f32, 0f32, 1f32));
        assert_eq!(tex_mat * bottom_left, Vector3::new(0f32, 1f32, 1f32));
    }
}