diff --git a/src/angle/context.rs b/src/angle/context.rs index fdd51ffc..02f08d8a 100644 --- a/src/angle/context.rs +++ b/src/angle/context.rs @@ -1,26 +1,14 @@ //! Wrapper for EGL contexts managed by ANGLE using Direct3D 11 as a backend on Windows. -use super::device::Device; -use super::surface::{Surface, Synchronization, Win32Objects}; -use crate::base::egl::context::{self, CurrentContextGuard}; -use crate::base::egl::device::EGL_FUNCTIONS; -use crate::base::egl::error::ToWindowingApiError; +use super::surface::Surface; +pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; use crate::base::egl::surface::ExternalEGLSurfaces; -use crate::context::{ContextID, CREATE_CONTEXT_MUTEX}; +use crate::context::ContextID; use crate::egl; -use crate::egl::types::{EGLConfig, EGLContext, EGLint}; +use crate::egl::types::EGLContext; use crate::surface::Framebuffer; -use crate::{ContextAttributes, Error, Gl, SurfaceInfo}; -use euclid::default::Size2D; - -use glow::HasContext; -use std::mem; -use std::os::raw::c_void; +use crate::Gl; use std::thread; -use winapi::shared::winerror::S_OK; -use winapi::um::winbase::INFINITE; - -pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; /// Represents an OpenGL rendering context. /// @@ -41,8 +29,8 @@ pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; pub struct Context { pub(crate) egl_context: EGLContext, pub(crate) id: ContextID, - framebuffer: Framebuffer, - context_is_owned: bool, + pub(crate) framebuffer: Framebuffer, + pub(crate) context_is_owned: bool, pub(crate) gl: Gl, } @@ -54,371 +42,3 @@ impl Drop for Context { } } } - -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - #[inline] - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - unsafe { - ContextDescriptor::new( - self.egl_display, - attributes, - &[ - egl::BIND_TO_TEXTURE_RGBA as EGLint, - 1 as EGLint, - egl::SURFACE_TYPE as EGLint, - egl::PBUFFER_BIT as EGLint, - egl::RENDERABLE_TYPE as EGLint, - egl::OPENGL_ES2_BIT as EGLint, - ], - ) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - let (egl_context, id) = { - let mut next_context_id_lock = CREATE_CONTEXT_MUTEX.lock().unwrap(); - let egl_context = unsafe { - context::create_context( - self.egl_display, - descriptor, - share_with.map_or(egl::NO_CONTEXT, |ctx| ctx.egl_context), - self.gl_api(), - )? - }; - next_context_id_lock.0 += 1; - (egl_context, *next_context_id_lock) - }; - - unsafe { - EGL_FUNCTIONS.with(|egl| { - let result = egl.MakeCurrent( - self.egl_display, - egl::NO_SURFACE, - egl::NO_SURFACE, - egl_context, - ); - if result == egl::FALSE { - return Err(Error::MakeCurrentFailed( - egl.GetError().to_windowing_api_error(), - )); - } - Ok(()) - })?; - } - - let context = Context { - egl_context, - id, - framebuffer: Framebuffer::None, - context_is_owned: true, - gl: unsafe { Gl::from_loader_function(context::get_proc_address) }, - }; - Ok(context) - } - - /// Wraps a native `EGLContext` in a context object. - /// - /// The underlying `EGLContext` is not retained, as there is no way to do this in the EGL API. - /// Therefore, it is the caller's responsibility to keep it alive as long as this `Context` - /// remains alive. - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - - // Create the context. - let context = Context { - egl_context: native_context.egl_context, - id: *next_context_id, - framebuffer: Framebuffer::External(ExternalEGLSurfaces { - draw: native_context.egl_draw_surface, - read: native_context.egl_read_surface, - }), - context_is_owned: false, - gl: Gl::from_loader_function(context::get_proc_address), - }; - next_context_id.0 += 1; - - Ok(context) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if context.egl_context == egl::NO_CONTEXT { - return Ok(()); - } - - if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { - self.destroy_surface(context, &mut surface)?; - } - - EGL_FUNCTIONS.with(|egl| unsafe { - egl.MakeCurrent( - self.egl_display, - egl::NO_SURFACE, - egl::NO_SURFACE, - egl::NO_CONTEXT, - ); - - if context.context_is_owned { - let result = egl.DestroyContext(self.egl_display, context.egl_context); - assert_ne!(result, egl::FALSE); - } - - context.egl_context = egl::NO_CONTEXT; - }); - - Ok(()) - } - - /// Returns the descriptor that this context was created with. - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - ContextDescriptor::from_egl_context(&context.gl, self.egl_display, context.egl_context) - } - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - unsafe { - let (egl_draw_surface, egl_read_surface) = match context.framebuffer { - Framebuffer::Surface(ref surface) => (surface.egl_surface, surface.egl_surface), - Framebuffer::None => (egl::NO_SURFACE, egl::NO_SURFACE), - Framebuffer::External(ref surfaces) => (surfaces.draw, surfaces.read), - }; - - EGL_FUNCTIONS.with(|egl| { - let result = egl.MakeCurrent( - self.egl_display, - egl_draw_surface, - egl_read_surface, - context.egl_context, - ); - if result == egl::FALSE { - let err = egl.GetError().to_windowing_api_error(); - return Err(Error::MakeCurrentFailed(err)); - } - Ok(()) - }) - } - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { context::make_no_context_current(self.egl_display) } - } - - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - pub(crate) fn context_is_current(&self, context: &Context) -> bool { - EGL_FUNCTIONS.with(|egl| unsafe { egl.GetCurrentContext() == context.egl_context }) - } - - /// Returns the attributes that the context descriptor was created with. - #[inline] - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - unsafe { context_descriptor.attributes(self.egl_display) } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - context::get_proc_address(symbol_name) - } - - #[inline] - pub(crate) fn context_descriptor_to_egl_config( - &self, - context_descriptor: &ContextDescriptor, - ) -> EGLConfig { - unsafe { context::egl_config_from_id(self.egl_display, context_descriptor.egl_config_id) } - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - pub fn bind_surface_to_context( - &self, - context: &mut Context, - surface: Surface, - ) -> Result<(), (Error, Surface)> { - if context.id != surface.context_id { - return Err((Error::IncompatibleSurface, surface)); - } - - match context.framebuffer { - Framebuffer::None => {} - Framebuffer::External(_) => return Err((Error::ExternalRenderTarget, surface)), - Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, surface)), - } - - // If the surface is synchronized with GLFinish, then finish. - // FIXME(pcwalton): Is this necessary and sufficient? - if surface.uses_gl_finish() { - if let Ok(_guard) = self.temporarily_make_context_current(context) { - unsafe { - context.gl.finish(); - } - } - } - - let is_current = self.context_is_current(context); - - match surface.win32_objects { - Win32Objects::Pbuffer { - synchronization: Synchronization::KeyedMutex(ref keyed_mutex), - .. - } => unsafe { - let result = keyed_mutex.AcquireSync(0, INFINITE); - assert_eq!(result, S_OK); - }, - _ => {} - } - - context.framebuffer = Framebuffer::Surface(surface); - - if is_current { - // We need to make ourselves current again, because the surface changed. - drop(self.make_context_current(context)); - } - - Ok(()) - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - match context.framebuffer { - Framebuffer::None => return Ok(None), - Framebuffer::External(_) => return Err(Error::ExternalRenderTarget), - Framebuffer::Surface(_) => {} - } - - let surface = match mem::replace(&mut context.framebuffer, Framebuffer::None) { - Framebuffer::Surface(surface) => surface, - Framebuffer::None | Framebuffer::External(_) => unreachable!(), - }; - - match surface.win32_objects { - Win32Objects::Pbuffer { - synchronization: Synchronization::KeyedMutex(ref keyed_mutex), - .. - } => unsafe { - let result = keyed_mutex.ReleaseSync(0); - assert_eq!(result, S_OK); - }, - _ => {} - } - - Ok(Some(surface)) - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - match &context.framebuffer { - Framebuffer::Surface(surface) => surface.present(self), - _ => Ok(()), - } - } - - /// If the currently bound surface is a widget surface, resize it, - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - if let Framebuffer::Surface(surface) = &mut context.framebuffer { - surface.resize(size); - } - Ok(()) - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.id - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - match context.framebuffer { - Framebuffer::None => Ok(None), - Framebuffer::External(_) => Err(Error::ExternalRenderTarget), - Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), - } - } - - /// Given a context, returns its underlying EGL context and attached surfaces. - pub fn native_context(&self, context: &Context) -> NativeContext { - let (egl_draw_surface, egl_read_surface) = match context.framebuffer { - Framebuffer::Surface(Surface { egl_surface, .. }) => (egl_surface, egl_surface), - Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), - Framebuffer::None => (egl::NO_SURFACE, egl::NO_SURFACE), - }; - - NativeContext { - egl_context: context.egl_context, - egl_draw_surface, - egl_read_surface, - } - } -} diff --git a/src/angle/device.rs b/src/angle/device.rs index a8a245c8..fbe266d9 100644 --- a/src/angle/device.rs +++ b/src/angle/device.rs @@ -1,28 +1,52 @@ //! A thread-local handle to the device. use super::connection::Connection; +use super::context::{Context, ContextDescriptor}; +use super::surface::{Surface, Synchronization, Win32Objects}; +use crate::angle::context::NativeContext; +use crate::angle::surface::{NativeWidget, SurfaceDataGuard, SurfaceTexture}; +use crate::base::egl::context::{self, CurrentContextGuard}; use crate::base::egl::device::EGL_FUNCTIONS; -use crate::base::egl::ffi::EGL_DEVICE_EXT; -use crate::base::egl::ffi::{EGL_D3D11_DEVICE_ANGLE, EGL_EXTENSION_FUNCTIONS}; -use crate::base::egl::ffi::{EGL_NO_DEVICE_EXT, EGL_PLATFORM_DEVICE_EXT}; +use crate::base::egl::error::ToWindowingApiError; +use crate::base::egl::ffi::{ + EGL_D3D11_DEVICE_ANGLE, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, EGL_D3D_TEXTURE_ANGLE, + EGL_DEVICE_EXT, EGL_DXGI_KEYED_MUTEX_ANGLE, EGL_EXTENSION_FUNCTIONS, EGL_NO_DEVICE_EXT, + EGL_PLATFORM_DEVICE_EXT, +}; +use crate::base::egl::surface::ExternalEGLSurfaces; +use crate::context::ContextID; +use crate::context::CREATE_CONTEXT_MUTEX; use crate::egl; -use crate::egl::types::{EGLAttrib, EGLDeviceEXT, EGLDisplay, EGLint}; -use crate::{Error, GLApi}; - +use crate::egl::types::{EGLAttrib, EGLConfig, EGLDeviceEXT, EGLDisplay, EGLSurface, EGLint}; +use crate::gl; +use crate::surface::Framebuffer; +use crate::GLApi; +use crate::Gl; +use crate::{ContextAttributes, Error, SurfaceInfo}; +use crate::{SurfaceAccess, SurfaceType}; +use euclid::default::Size2D; +use glow::HasContext; use std::cell::{RefCell, RefMut}; +use std::marker::PhantomData; use std::mem; use std::os::raw::c_void; use std::ptr; +use winapi::shared::dxgi::IDXGIKeyedMutex; use winapi::shared::dxgi::{self, IDXGIAdapter, IDXGIDevice, IDXGIFactory1}; use winapi::shared::minwindef::UINT; use winapi::shared::winerror::{self, S_OK}; +use winapi::um::d3d11; use winapi::um::d3d11::{D3D11CreateDevice, ID3D11Device, D3D11_SDK_VERSION}; use winapi::um::d3dcommon::{ D3D_DRIVER_TYPE, D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_WARP, D3D_FEATURE_LEVEL_9_3, }; +use winapi::um::handleapi::INVALID_HANDLE_VALUE; +use winapi::um::winbase::INFINITE; use winapi::Interface; use wio::com::ComPtr; +const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; + thread_local! { static DXGI_FACTORY: RefCell>> = RefCell::new(None); } @@ -321,6 +345,827 @@ impl Device { pub fn gl_api(&self) -> GLApi { GLApi::GLES } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + #[inline] + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + unsafe { + ContextDescriptor::new( + self.egl_display, + attributes, + &[ + egl::BIND_TO_TEXTURE_RGBA as EGLint, + 1 as EGLint, + egl::SURFACE_TYPE as EGLint, + egl::PBUFFER_BIT as EGLint, + egl::RENDERABLE_TYPE as EGLint, + egl::OPENGL_ES2_BIT as EGLint, + ], + ) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + let (egl_context, id) = { + let mut next_context_id_lock = CREATE_CONTEXT_MUTEX.lock().unwrap(); + let egl_context = unsafe { + context::create_context( + self.egl_display, + descriptor, + share_with.map_or(egl::NO_CONTEXT, |ctx| ctx.egl_context), + self.gl_api(), + )? + }; + next_context_id_lock.0 += 1; + (egl_context, *next_context_id_lock) + }; + + unsafe { + EGL_FUNCTIONS.with(|egl| { + let result = egl.MakeCurrent( + self.egl_display, + egl::NO_SURFACE, + egl::NO_SURFACE, + egl_context, + ); + if result == egl::FALSE { + return Err(Error::MakeCurrentFailed( + egl.GetError().to_windowing_api_error(), + )); + } + Ok(()) + })?; + } + + let context = Context { + egl_context, + id, + framebuffer: Framebuffer::None, + context_is_owned: true, + gl: unsafe { Gl::from_loader_function(context::get_proc_address) }, + }; + Ok(context) + } + + /// Wraps a native `EGLContext` in a context object. + /// + /// The underlying `EGLContext` is not retained, as there is no way to do this in the EGL API. + /// Therefore, it is the caller's responsibility to keep it alive as long as this `Context` + /// remains alive. + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + + // Create the context. + let context = Context { + egl_context: native_context.egl_context, + id: *next_context_id, + framebuffer: Framebuffer::External(ExternalEGLSurfaces { + draw: native_context.egl_draw_surface, + read: native_context.egl_read_surface, + }), + context_is_owned: false, + gl: Gl::from_loader_function(context::get_proc_address), + }; + next_context_id.0 += 1; + + Ok(context) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if context.egl_context == egl::NO_CONTEXT { + return Ok(()); + } + + if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { + self.destroy_surface(context, &mut surface)?; + } + + EGL_FUNCTIONS.with(|egl| unsafe { + egl.MakeCurrent( + self.egl_display, + egl::NO_SURFACE, + egl::NO_SURFACE, + egl::NO_CONTEXT, + ); + + if context.context_is_owned { + let result = egl.DestroyContext(self.egl_display, context.egl_context); + assert_ne!(result, egl::FALSE); + } + + context.egl_context = egl::NO_CONTEXT; + }); + + Ok(()) + } + + /// Returns the descriptor that this context was created with. + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + ContextDescriptor::from_egl_context(&context.gl, self.egl_display, context.egl_context) + } + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + unsafe { + let (egl_draw_surface, egl_read_surface) = match context.framebuffer { + Framebuffer::Surface(ref surface) => (surface.egl_surface, surface.egl_surface), + Framebuffer::None => (egl::NO_SURFACE, egl::NO_SURFACE), + Framebuffer::External(ref surfaces) => (surfaces.draw, surfaces.read), + }; + + EGL_FUNCTIONS.with(|egl| { + let result = egl.MakeCurrent( + self.egl_display, + egl_draw_surface, + egl_read_surface, + context.egl_context, + ); + if result == egl::FALSE { + let err = egl.GetError().to_windowing_api_error(); + return Err(Error::MakeCurrentFailed(err)); + } + Ok(()) + }) + } + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { context::make_no_context_current(self.egl_display) } + } + + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + pub(crate) fn context_is_current(&self, context: &Context) -> bool { + EGL_FUNCTIONS.with(|egl| unsafe { egl.GetCurrentContext() == context.egl_context }) + } + + /// Returns the attributes that the context descriptor was created with. + #[inline] + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + unsafe { context_descriptor.attributes(self.egl_display) } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + context::get_proc_address(symbol_name) + } + + #[inline] + pub(crate) fn context_descriptor_to_egl_config( + &self, + context_descriptor: &ContextDescriptor, + ) -> EGLConfig { + unsafe { context::egl_config_from_id(self.egl_display, context_descriptor.egl_config_id) } + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + pub fn bind_surface_to_context( + &self, + context: &mut Context, + surface: Surface, + ) -> Result<(), (Error, Surface)> { + if context.id != surface.context_id { + return Err((Error::IncompatibleSurface, surface)); + } + + match context.framebuffer { + Framebuffer::None => {} + Framebuffer::External(_) => return Err((Error::ExternalRenderTarget, surface)), + Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, surface)), + } + + // If the surface is synchronized with GLFinish, then finish. + // FIXME(pcwalton): Is this necessary and sufficient? + if surface.uses_gl_finish() { + if let Ok(_guard) = self.temporarily_make_context_current(context) { + unsafe { + context.gl.finish(); + } + } + } + + let is_current = self.context_is_current(context); + + match surface.win32_objects { + Win32Objects::Pbuffer { + synchronization: Synchronization::KeyedMutex(ref keyed_mutex), + .. + } => unsafe { + let result = keyed_mutex.AcquireSync(0, INFINITE); + assert_eq!(result, S_OK); + }, + _ => {} + } + + context.framebuffer = Framebuffer::Surface(surface); + + if is_current { + // We need to make ourselves current again, because the surface changed. + drop(self.make_context_current(context)); + } + + Ok(()) + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + match context.framebuffer { + Framebuffer::None => return Ok(None), + Framebuffer::External(_) => return Err(Error::ExternalRenderTarget), + Framebuffer::Surface(_) => {} + } + + let surface = match mem::replace(&mut context.framebuffer, Framebuffer::None) { + Framebuffer::Surface(surface) => surface, + Framebuffer::None | Framebuffer::External(_) => unreachable!(), + }; + + match surface.win32_objects { + Win32Objects::Pbuffer { + synchronization: Synchronization::KeyedMutex(ref keyed_mutex), + .. + } => unsafe { + let result = keyed_mutex.ReleaseSync(0); + assert_eq!(result, S_OK); + }, + _ => {} + } + + Ok(Some(surface)) + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + match &context.framebuffer { + Framebuffer::Surface(surface) => surface.present(self), + _ => Ok(()), + } + } + + /// If the currently bound surface is a widget surface, resize it, + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + if let Framebuffer::Surface(surface) = &mut context.framebuffer { + surface.resize(size); + } + Ok(()) + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.id + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + match context.framebuffer { + Framebuffer::None => Ok(None), + Framebuffer::External(_) => Err(Error::ExternalRenderTarget), + Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), + } + } + + /// Given a context, returns its underlying EGL context and attached surfaces. + pub fn native_context(&self, context: &Context) -> NativeContext { + let (egl_draw_surface, egl_read_surface) = match context.framebuffer { + Framebuffer::Surface(Surface { egl_surface, .. }) => (egl_surface, egl_surface), + Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), + Framebuffer::None => (egl::NO_SURFACE, egl::NO_SURFACE), + }; + + NativeContext { + egl_context: context.egl_context, + egl_draw_surface, + egl_read_surface, + } + } + + /// Creates either a generic or a widget surface, depending on the supplied surface type. + /// + /// Only the given context may ever render to the surface, but generic surfaces can be wrapped + /// up in a `SurfaceTexture` for reading by other contexts. + pub fn create_surface( + &self, + context: &Context, + _: SurfaceAccess, + surface_type: SurfaceType, + ) -> Result { + match surface_type { + SurfaceType::Generic { ref size } => self.create_pbuffer_surface(context, size, None), + SurfaceType::Widget { ref native_widget } => { + self.create_window_surface(context, native_widget) + } + } + } + + #[allow(non_snake_case)] + fn create_pbuffer_surface( + &self, + context: &Context, + size: &Size2D, + texture: Option>, + ) -> Result { + let context_descriptor = self.context_descriptor(context); + let egl_config = self.context_descriptor_to_egl_config(&context_descriptor); + + unsafe { + let attributes = [ + egl::WIDTH as EGLint, + size.width as EGLint, + egl::HEIGHT as EGLint, + size.height as EGLint, + egl::TEXTURE_FORMAT as EGLint, + egl::TEXTURE_RGBA as EGLint, + egl::TEXTURE_TARGET as EGLint, + egl::TEXTURE_2D as EGLint, + egl::NONE as EGLint, + 0, + 0, + 0, + ]; + + EGL_FUNCTIONS.with(|egl| { + let egl_surface = if let Some(ref texture) = texture { + let surface = egl.CreatePbufferFromClientBuffer( + self.egl_display, + EGL_D3D_TEXTURE_ANGLE, + texture.as_raw() as *const _, + egl_config, + attributes.as_ptr(), + ); + assert_ne!(surface, egl::NO_SURFACE); + surface + } else { + let surface = + egl.CreatePbufferSurface(self.egl_display, egl_config, attributes.as_ptr()); + assert_ne!(surface, egl::NO_SURFACE); + surface + }; + + let eglQuerySurfacePointerANGLE = + EGL_EXTENSION_FUNCTIONS.QuerySurfacePointerANGLE.expect( + "Where's the `EGL_ANGLE_query_surface_pointer` \ + extension?", + ); + + let mut share_handle = INVALID_HANDLE_VALUE; + let result = eglQuerySurfacePointerANGLE( + self.egl_display, + egl_surface, + EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE as EGLint, + &mut share_handle, + ); + assert_ne!(result, egl::FALSE); + assert_ne!(share_handle, INVALID_HANDLE_VALUE); + + // `mozangle` builds ANGLE with keyed mutexes for sharing. Use the + // `EGL_ANGLE_keyed_mutex` extension to fetch the keyed mutex so we can grab it. + let mut keyed_mutex: *mut IDXGIKeyedMutex = ptr::null_mut(); + let result = eglQuerySurfacePointerANGLE( + self.egl_display, + egl_surface, + EGL_DXGI_KEYED_MUTEX_ANGLE as EGLint, + &mut keyed_mutex as *mut *mut IDXGIKeyedMutex as *mut *mut c_void, + ); + let synchronization = if result != egl::FALSE && !keyed_mutex.is_null() { + let keyed_mutex = ComPtr::from_raw(keyed_mutex); + keyed_mutex.AddRef(); + Synchronization::KeyedMutex(keyed_mutex) + } else if texture.is_none() { + Synchronization::GLFinish + } else { + Synchronization::None + }; + + Ok(Surface { + egl_surface, + size: *size, + context_id: context.id, + context_descriptor, + win32_objects: Win32Objects::Pbuffer { + share_handle, + synchronization, + texture, + }, + }) + }) + } + } + + /// Given a D3D11 texture, create a surface that wraps that texture. This method is unsafe + /// in that the resulting surface is only valid on the current thread. + pub unsafe fn create_surface_from_texture( + &self, + context: &Context, + size: &Size2D, + texture: ComPtr, + ) -> Result { + self.create_pbuffer_surface(context, size, Some(texture)) + } + + fn create_window_surface( + &self, + context: &Context, + native_widget: &NativeWidget, + ) -> Result { + let context_descriptor = self.context_descriptor(context); + let egl_config = self.context_descriptor_to_egl_config(&context_descriptor); + + unsafe { + EGL_FUNCTIONS.with(|egl| { + let attributes = [egl::NONE as EGLint]; + let egl_surface = egl.CreateWindowSurface( + self.egl_display, + egl_config, + native_widget.egl_native_window, + attributes.as_ptr(), + ); + assert_ne!(egl_surface, egl::NO_SURFACE); + + let mut width = 0; + let mut height = 0; + egl.QuerySurface( + self.egl_display, + egl_surface, + egl::WIDTH as EGLint, + &mut width, + ); + egl.QuerySurface( + self.egl_display, + egl_surface, + egl::HEIGHT as EGLint, + &mut height, + ); + assert_ne!(width, 0); + assert_ne!(height, 0); + + Ok(Surface { + egl_surface, + size: Size2D::new(width, height), + context_id: context.id, + context_descriptor, + win32_objects: Win32Objects::Window, + }) + }) + } + } + + /// Creates a surface texture from an existing generic surface for use with the given context. + /// + /// The surface texture is local to the supplied context and takes ownership of the surface. + /// Destroying the surface texture allows you to retrieve the surface again. + /// + /// *The supplied context does not have to be the same context that the surface is associated + /// with.* This allows you to render to a surface in one context and sample from that surface + /// in another context. + /// + /// Calling this method on a widget surface returns a `WidgetAttached` error. + #[allow(non_snake_case)] + pub fn create_surface_texture( + &self, + context: &mut Context, + surface: Surface, + ) -> Result { + let share_handle = match surface.win32_objects { + Win32Objects::Window => return Err((Error::WidgetAttached, surface)), + Win32Objects::Pbuffer { share_handle, .. } => share_handle, + }; + + let local_egl_config = self.context_descriptor_to_egl_config(&surface.context_descriptor); + EGL_FUNCTIONS.with(|egl| { + unsafe { + // First, create an EGL surface local to this thread. + let pbuffer_attributes = [ + egl::WIDTH as EGLint, + surface.size.width, + egl::HEIGHT as EGLint, + surface.size.height, + egl::TEXTURE_FORMAT as EGLint, + egl::TEXTURE_RGBA as EGLint, + egl::TEXTURE_TARGET as EGLint, + egl::TEXTURE_2D as EGLint, + egl::NONE as EGLint, + 0, + 0, + 0, + ]; + + let local_egl_surface = egl.CreatePbufferFromClientBuffer( + self.egl_display, + EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, + share_handle, + local_egl_config, + pbuffer_attributes.as_ptr(), + ); + if local_egl_surface == egl::NO_SURFACE { + let windowing_api_error = egl.GetError().to_windowing_api_error(); + return Err((Error::SurfaceImportFailed(windowing_api_error), surface)); + } + + let mut local_keyed_mutex: *mut IDXGIKeyedMutex = ptr::null_mut(); + let eglQuerySurfacePointerANGLE = + EGL_EXTENSION_FUNCTIONS.QuerySurfacePointerANGLE.unwrap(); + let result = eglQuerySurfacePointerANGLE( + self.egl_display, + local_egl_surface, + EGL_DXGI_KEYED_MUTEX_ANGLE as EGLint, + &mut local_keyed_mutex as *mut *mut IDXGIKeyedMutex as *mut *mut c_void, + ); + let local_keyed_mutex = if result != egl::FALSE && !local_keyed_mutex.is_null() { + let local_keyed_mutex = ComPtr::from_raw(local_keyed_mutex); + local_keyed_mutex.AddRef(); + + let result = local_keyed_mutex.AcquireSync(0, INFINITE); + assert_eq!(result, S_OK); + + Some(local_keyed_mutex) + } else { + None + }; + self.create_surface_texture_from_local_surface( + context, + surface, + local_egl_surface, + local_keyed_mutex, + ) + } + }) + } + + fn create_surface_texture_from_local_surface( + &self, + context: &Context, + surface: Surface, + local_egl_surface: EGLSurface, + local_keyed_mutex: Option>, + ) -> Result { + EGL_FUNCTIONS.with(|egl| { + unsafe { + let _guard = self.temporarily_make_context_current(context); + + let gl = &context.gl; + // Then bind that surface to the texture. + let texture = gl.create_texture().unwrap(); + + gl.bind_texture(gl::TEXTURE_2D, Some(texture)); + if egl.BindTexImage(self.egl_display, local_egl_surface, egl::BACK_BUFFER as _) + == egl::FALSE + { + let windowing_api_error = egl.GetError().to_windowing_api_error(); + return Err(( + Error::SurfaceTextureCreationFailed(windowing_api_error), + surface, + )); + } + + // Initialize the texture, for convenience. + gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); + gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _); + gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _); + + gl.bind_texture(gl::TEXTURE_2D, None); + debug_assert_eq!(gl.get_error(), gl::NO_ERROR); + + Ok(SurfaceTexture { + surface, + local_egl_surface, + local_keyed_mutex, + gl_texture: Some(texture), + phantom: PhantomData, + }) + } + }) + } + + /// Given a D3D11 texture, create a surface texture that wraps that texture. This method is unsafe + /// in that the resulting surface is only valid on the current thread, for the lifetime of `texture`. + /// It is the caller's responsibility to ensure that `texture` is not freed while the `SurfaceTexture` is live. + pub unsafe fn create_surface_texture_from_texture( + &self, + context: &mut Context, + size: &Size2D, + texture: ComPtr, + ) -> Result { + let surface = self.create_pbuffer_surface(context, size, Some(texture))?; + let local_egl_surface = surface.egl_surface; + self.create_surface_texture_from_local_surface(context, surface, local_egl_surface, None) + .map_err(|(err, mut surface)| { + let _ = self.destroy_surface(context, &mut surface); + err + }) + } + + /// Destroys a surface. + /// + /// The supplied context must be the context the surface is associated with, or this returns + /// an `IncompatibleSurface` error. + /// + /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in + /// the `drop` method. + pub fn destroy_surface( + &self, + context: &mut Context, + surface: &mut Surface, + ) -> Result<(), Error> { + if context.id != surface.context_id { + return Err(Error::IncompatibleSurface); + } + + EGL_FUNCTIONS.with(|egl| { + unsafe { + // If the surface is currently bound, unbind it. + if egl.GetCurrentSurface(egl::READ as EGLint) == surface.egl_surface + || egl.GetCurrentSurface(egl::DRAW as EGLint) == surface.egl_surface + { + self.make_no_context_current()?; + } + + egl.DestroySurface(self.egl_display, surface.egl_surface); + surface.egl_surface = egl::NO_SURFACE; + if let Win32Objects::Pbuffer { + ref mut texture, .. + } = surface.win32_objects + { + texture.take(); + } + } + Ok(()) + }) + } + + /// Destroys a surface texture and returns the underlying surface. + /// + /// The supplied context must be the same context the surface texture was created with, or an + /// `IncompatibleSurfaceTexture` error is returned. + /// + /// All surface textures must be explicitly destroyed with this function, or a panic will + /// occur. + pub fn destroy_surface_texture( + &self, + context: &mut Context, + mut surface_texture: SurfaceTexture, + ) -> Result { + unsafe { + if let Some(texture) = surface_texture.gl_texture.take() { + context.gl.delete_texture(texture); + } + + if let Some(ref local_keyed_mutex) = surface_texture.local_keyed_mutex { + let result = local_keyed_mutex.ReleaseSync(0); + assert_eq!(result, S_OK); + } + + EGL_FUNCTIONS.with(|egl| { + egl.ReleaseTexImage( + self.egl_display, + surface_texture.local_egl_surface, + egl::BACK_BUFFER as _, + ); + egl.DestroySurface(self.egl_display, surface_texture.local_egl_surface); + }) + } + + Ok(surface_texture.surface) + } + + /// Returns the OpenGL texture target needed to read from this surface texture. + /// + /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. + #[inline] + pub fn surface_gl_texture_target(&self) -> u32 { + SURFACE_GL_TEXTURE_TARGET + } + + /// Returns a pointer to the underlying surface data for reading or writing by the CPU. + #[inline] + pub fn lock_surface_data<'s>( + &self, + _surface: &'s mut Surface, + ) -> Result, Error> { + Err(Error::Unimplemented) + } + + /// Displays the contents of a widget surface on screen. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't show up in their + /// associated widgets until this method is called. + /// + /// The supplied context must match the context the surface was created with, or an + /// `IncompatibleSurface` error is returned. + pub fn present_surface(&self, _: &Context, surface: &mut Surface) -> Result<(), Error> { + surface.present(self) + } + + /// Resizes a widget surface. + pub fn resize_surface( + &self, + _context: &Context, + surface: &mut Surface, + size: Size2D, + ) -> Result<(), Error> { + surface.resize(size); + Ok(()) + } + + /// Returns various information about the surface, including the framebuffer object needed to + /// render to this surface. + /// + /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` + /// on the framebuffer object returned by this function. This framebuffer object may or not be + /// 0, the default framebuffer, depending on platform. + #[inline] + pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { + SurfaceInfo { + size: surface.size, + id: surface.id(), + context_id: surface.context_id, + framebuffer_object: None, + } + } + + /// Returns the OpenGL texture object containing the contents of this surface. + /// + /// It is only legal to read from, not write to, this texture object. + #[inline] + pub fn surface_texture_object( + &self, + surface_texture: &SurfaceTexture, + ) -> Option { + surface_texture.gl_texture + } } impl Drop for Device { diff --git a/src/angle/surface.rs b/src/angle/surface.rs index 8ad6fae1..1a037692 100644 --- a/src/angle/surface.rs +++ b/src/angle/surface.rs @@ -1,37 +1,23 @@ //! Surface management for Direct3D 11 on Windows using the ANGLE library as a frontend. -use super::context::{Context, ContextDescriptor}; +use super::context::ContextDescriptor; use super::device::Device; use crate::base::egl::device::EGL_FUNCTIONS; -use crate::base::egl::error::ToWindowingApiError; -use crate::base::egl::ffi::EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE; -use crate::base::egl::ffi::EGL_D3D_TEXTURE_ANGLE; -use crate::base::egl::ffi::EGL_DXGI_KEYED_MUTEX_ANGLE; -use crate::base::egl::ffi::EGL_EXTENSION_FUNCTIONS; use crate::context::ContextID; use crate::egl::types::EGLNativeWindowType; use crate::egl::types::EGLSurface; -use crate::egl::{self, EGLint}; -use crate::gl; -use crate::{Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType}; +use crate::egl::{self}; +use crate::{Error, SurfaceID}; use euclid::default::Size2D; -use glow::HasContext; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; -use std::os::raw::c_void; -use std::ptr; use std::thread; use winapi::shared::dxgi::IDXGIKeyedMutex; -use winapi::shared::winerror::S_OK; use winapi::um::d3d11; -use winapi::um::handleapi::INVALID_HANDLE_VALUE; -use winapi::um::winbase::INFINITE; use winapi::um::winnt::HANDLE; use wio::com::ComPtr; -const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; - /// Represents a hardware buffer of pixels that can be rendered to via the CPU or GPU and either /// displayed in a native widget or bound to a texture for reading. /// @@ -120,466 +106,9 @@ pub struct NativeWidget { pub egl_native_window: EGLNativeWindowType, } -impl Device { - /// Creates either a generic or a widget surface, depending on the supplied surface type. - /// - /// Only the given context may ever render to the surface, but generic surfaces can be wrapped - /// up in a `SurfaceTexture` for reading by other contexts. - pub fn create_surface( - &self, - context: &Context, - _: SurfaceAccess, - surface_type: SurfaceType, - ) -> Result { - match surface_type { - SurfaceType::Generic { ref size } => self.create_pbuffer_surface(context, size, None), - SurfaceType::Widget { ref native_widget } => { - self.create_window_surface(context, native_widget) - } - } - } - - #[allow(non_snake_case)] - fn create_pbuffer_surface( - &self, - context: &Context, - size: &Size2D, - texture: Option>, - ) -> Result { - let context_descriptor = self.context_descriptor(context); - let egl_config = self.context_descriptor_to_egl_config(&context_descriptor); - - unsafe { - let attributes = [ - egl::WIDTH as EGLint, - size.width as EGLint, - egl::HEIGHT as EGLint, - size.height as EGLint, - egl::TEXTURE_FORMAT as EGLint, - egl::TEXTURE_RGBA as EGLint, - egl::TEXTURE_TARGET as EGLint, - egl::TEXTURE_2D as EGLint, - egl::NONE as EGLint, - 0, - 0, - 0, - ]; - - EGL_FUNCTIONS.with(|egl| { - let egl_surface = if let Some(ref texture) = texture { - let surface = egl.CreatePbufferFromClientBuffer( - self.egl_display, - EGL_D3D_TEXTURE_ANGLE, - texture.as_raw() as *const _, - egl_config, - attributes.as_ptr(), - ); - assert_ne!(surface, egl::NO_SURFACE); - surface - } else { - let surface = - egl.CreatePbufferSurface(self.egl_display, egl_config, attributes.as_ptr()); - assert_ne!(surface, egl::NO_SURFACE); - surface - }; - - let eglQuerySurfacePointerANGLE = - EGL_EXTENSION_FUNCTIONS.QuerySurfacePointerANGLE.expect( - "Where's the `EGL_ANGLE_query_surface_pointer` \ - extension?", - ); - - let mut share_handle = INVALID_HANDLE_VALUE; - let result = eglQuerySurfacePointerANGLE( - self.egl_display, - egl_surface, - EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE as EGLint, - &mut share_handle, - ); - assert_ne!(result, egl::FALSE); - assert_ne!(share_handle, INVALID_HANDLE_VALUE); - - // `mozangle` builds ANGLE with keyed mutexes for sharing. Use the - // `EGL_ANGLE_keyed_mutex` extension to fetch the keyed mutex so we can grab it. - let mut keyed_mutex: *mut IDXGIKeyedMutex = ptr::null_mut(); - let result = eglQuerySurfacePointerANGLE( - self.egl_display, - egl_surface, - EGL_DXGI_KEYED_MUTEX_ANGLE as EGLint, - &mut keyed_mutex as *mut *mut IDXGIKeyedMutex as *mut *mut c_void, - ); - let synchronization = if result != egl::FALSE && !keyed_mutex.is_null() { - let keyed_mutex = ComPtr::from_raw(keyed_mutex); - keyed_mutex.AddRef(); - Synchronization::KeyedMutex(keyed_mutex) - } else if texture.is_none() { - Synchronization::GLFinish - } else { - Synchronization::None - }; - - Ok(Surface { - egl_surface, - size: *size, - context_id: context.id, - context_descriptor, - win32_objects: Win32Objects::Pbuffer { - share_handle, - synchronization, - texture, - }, - }) - }) - } - } - - /// Given a D3D11 texture, create a surface that wraps that texture. This method is unsafe - /// in that the resulting surface is only valid on the current thread. - pub unsafe fn create_surface_from_texture( - &self, - context: &Context, - size: &Size2D, - texture: ComPtr, - ) -> Result { - self.create_pbuffer_surface(context, size, Some(texture)) - } - - fn create_window_surface( - &self, - context: &Context, - native_widget: &NativeWidget, - ) -> Result { - let context_descriptor = self.context_descriptor(context); - let egl_config = self.context_descriptor_to_egl_config(&context_descriptor); - - unsafe { - EGL_FUNCTIONS.with(|egl| { - let attributes = [egl::NONE as EGLint]; - let egl_surface = egl.CreateWindowSurface( - self.egl_display, - egl_config, - native_widget.egl_native_window, - attributes.as_ptr(), - ); - assert_ne!(egl_surface, egl::NO_SURFACE); - - let mut width = 0; - let mut height = 0; - egl.QuerySurface( - self.egl_display, - egl_surface, - egl::WIDTH as EGLint, - &mut width, - ); - egl.QuerySurface( - self.egl_display, - egl_surface, - egl::HEIGHT as EGLint, - &mut height, - ); - assert_ne!(width, 0); - assert_ne!(height, 0); - - Ok(Surface { - egl_surface, - size: Size2D::new(width, height), - context_id: context.id, - context_descriptor, - win32_objects: Win32Objects::Window, - }) - }) - } - } - - /// Creates a surface texture from an existing generic surface for use with the given context. - /// - /// The surface texture is local to the supplied context and takes ownership of the surface. - /// Destroying the surface texture allows you to retrieve the surface again. - /// - /// *The supplied context does not have to be the same context that the surface is associated - /// with.* This allows you to render to a surface in one context and sample from that surface - /// in another context. - /// - /// Calling this method on a widget surface returns a `WidgetAttached` error. - #[allow(non_snake_case)] - pub fn create_surface_texture( - &self, - context: &mut Context, - surface: Surface, - ) -> Result { - let share_handle = match surface.win32_objects { - Win32Objects::Window => return Err((Error::WidgetAttached, surface)), - Win32Objects::Pbuffer { share_handle, .. } => share_handle, - }; - - let local_egl_config = self.context_descriptor_to_egl_config(&surface.context_descriptor); - EGL_FUNCTIONS.with(|egl| { - unsafe { - // First, create an EGL surface local to this thread. - let pbuffer_attributes = [ - egl::WIDTH as EGLint, - surface.size.width, - egl::HEIGHT as EGLint, - surface.size.height, - egl::TEXTURE_FORMAT as EGLint, - egl::TEXTURE_RGBA as EGLint, - egl::TEXTURE_TARGET as EGLint, - egl::TEXTURE_2D as EGLint, - egl::NONE as EGLint, - 0, - 0, - 0, - ]; - - let local_egl_surface = egl.CreatePbufferFromClientBuffer( - self.egl_display, - EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, - share_handle, - local_egl_config, - pbuffer_attributes.as_ptr(), - ); - if local_egl_surface == egl::NO_SURFACE { - let windowing_api_error = egl.GetError().to_windowing_api_error(); - return Err((Error::SurfaceImportFailed(windowing_api_error), surface)); - } - - let mut local_keyed_mutex: *mut IDXGIKeyedMutex = ptr::null_mut(); - let eglQuerySurfacePointerANGLE = - EGL_EXTENSION_FUNCTIONS.QuerySurfacePointerANGLE.unwrap(); - let result = eglQuerySurfacePointerANGLE( - self.egl_display, - local_egl_surface, - EGL_DXGI_KEYED_MUTEX_ANGLE as EGLint, - &mut local_keyed_mutex as *mut *mut IDXGIKeyedMutex as *mut *mut c_void, - ); - let local_keyed_mutex = if result != egl::FALSE && !local_keyed_mutex.is_null() { - let local_keyed_mutex = ComPtr::from_raw(local_keyed_mutex); - local_keyed_mutex.AddRef(); - - let result = local_keyed_mutex.AcquireSync(0, INFINITE); - assert_eq!(result, S_OK); - - Some(local_keyed_mutex) - } else { - None - }; - self.create_surface_texture_from_local_surface( - context, - surface, - local_egl_surface, - local_keyed_mutex, - ) - } - }) - } - - fn create_surface_texture_from_local_surface( - &self, - context: &Context, - surface: Surface, - local_egl_surface: EGLSurface, - local_keyed_mutex: Option>, - ) -> Result { - EGL_FUNCTIONS.with(|egl| { - unsafe { - let _guard = self.temporarily_make_context_current(context); - - let gl = &context.gl; - // Then bind that surface to the texture. - let mut texture = gl.create_texture().unwrap(); - - gl.bind_texture(gl::TEXTURE_2D, Some(texture)); - if egl.BindTexImage(self.egl_display, local_egl_surface, egl::BACK_BUFFER as _) - == egl::FALSE - { - let windowing_api_error = egl.GetError().to_windowing_api_error(); - return Err(( - Error::SurfaceTextureCreationFailed(windowing_api_error), - surface, - )); - } - - // Initialize the texture, for convenience. - gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); - gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); - gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _); - gl.tex_parameter_i32(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _); - - gl.bind_texture(gl::TEXTURE_2D, None); - debug_assert_eq!(gl.get_error(), gl::NO_ERROR); - - Ok(SurfaceTexture { - surface, - local_egl_surface, - local_keyed_mutex, - gl_texture: Some(texture), - phantom: PhantomData, - }) - } - }) - } - - /// Given a D3D11 texture, create a surface texture that wraps that texture. This method is unsafe - /// in that the resulting surface is only valid on the current thread, for the lifetime of `texture`. - /// It is the caller's responsibility to ensure that `texture` is not freed while the `SurfaceTexture` is live. - pub unsafe fn create_surface_texture_from_texture( - &self, - context: &mut Context, - size: &Size2D, - texture: ComPtr, - ) -> Result { - let surface = self.create_pbuffer_surface(context, size, Some(texture))?; - let local_egl_surface = surface.egl_surface; - self.create_surface_texture_from_local_surface(context, surface, local_egl_surface, None) - .map_err(|(err, mut surface)| { - let _ = self.destroy_surface(context, &mut surface); - err - }) - } - - /// Destroys a surface. - /// - /// The supplied context must be the context the surface is associated with, or this returns - /// an `IncompatibleSurface` error. - /// - /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in - /// the `drop` method. - pub fn destroy_surface( - &self, - context: &mut Context, - surface: &mut Surface, - ) -> Result<(), Error> { - if context.id != surface.context_id { - return Err(Error::IncompatibleSurface); - } - - EGL_FUNCTIONS.with(|egl| { - unsafe { - // If the surface is currently bound, unbind it. - if egl.GetCurrentSurface(egl::READ as EGLint) == surface.egl_surface - || egl.GetCurrentSurface(egl::DRAW as EGLint) == surface.egl_surface - { - self.make_no_context_current()?; - } - - egl.DestroySurface(self.egl_display, surface.egl_surface); - surface.egl_surface = egl::NO_SURFACE; - if let Win32Objects::Pbuffer { - ref mut texture, .. - } = surface.win32_objects - { - texture.take(); - } - } - Ok(()) - }) - } - - /// Destroys a surface texture and returns the underlying surface. - /// - /// The supplied context must be the same context the surface texture was created with, or an - /// `IncompatibleSurfaceTexture` error is returned. - /// - /// All surface textures must be explicitly destroyed with this function, or a panic will - /// occur. - pub fn destroy_surface_texture( - &self, - context: &mut Context, - mut surface_texture: SurfaceTexture, - ) -> Result { - unsafe { - if let Some(texture) = surface_texture.gl_texture.take() { - context.gl.delete_texture(texture); - } - - if let Some(ref local_keyed_mutex) = surface_texture.local_keyed_mutex { - let result = local_keyed_mutex.ReleaseSync(0); - assert_eq!(result, S_OK); - } - - EGL_FUNCTIONS.with(|egl| { - egl.ReleaseTexImage( - self.egl_display, - surface_texture.local_egl_surface, - egl::BACK_BUFFER as _, - ); - egl.DestroySurface(self.egl_display, surface_texture.local_egl_surface); - }) - } - - Ok(surface_texture.surface) - } - - /// Returns the OpenGL texture target needed to read from this surface texture. - /// - /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. - #[inline] - pub fn surface_gl_texture_target(&self) -> u32 { - SURFACE_GL_TEXTURE_TARGET - } - - /// Returns a pointer to the underlying surface data for reading or writing by the CPU. - #[inline] - pub fn lock_surface_data<'s>( - &self, - _surface: &'s mut Surface, - ) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Displays the contents of a widget surface on screen. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't show up in their - /// associated widgets until this method is called. - /// - /// The supplied context must match the context the surface was created with, or an - /// `IncompatibleSurface` error is returned. - pub fn present_surface(&self, _: &Context, surface: &mut Surface) -> Result<(), Error> { - surface.present(self) - } - - /// Resizes a widget surface. - pub fn resize_surface( - &self, - _context: &Context, - surface: &mut Surface, - size: Size2D, - ) -> Result<(), Error> { - surface.resize(size); - Ok(()) - } - - /// Returns various information about the surface, including the framebuffer object needed to - /// render to this surface. - /// - /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` - /// on the framebuffer object returned by this function. This framebuffer object may or not be - /// 0, the default framebuffer, depending on platform. - #[inline] - pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { - SurfaceInfo { - size: surface.size, - id: surface.id(), - context_id: surface.context_id, - framebuffer_object: None, - } - } - - /// Returns the OpenGL texture object containing the contents of this surface. - /// - /// It is only legal to read from, not write to, this texture object. - #[inline] - pub fn surface_texture_object( - &self, - surface_texture: &SurfaceTexture, - ) -> Option { - surface_texture.gl_texture - } -} - impl Surface { #[inline] - fn id(&self) -> SurfaceID { + pub(crate) fn id(&self) -> SurfaceID { SurfaceID(self.egl_surface as usize) } diff --git a/src/cgl/context.rs b/src/cgl/context.rs index 6e4bdee9..c734e463 100644 --- a/src/cgl/context.rs +++ b/src/cgl/context.rs @@ -1,54 +1,18 @@ //! Wrapper for Core OpenGL contexts. -use super::device::Device; -use super::error::ToWindowingApiError; use super::ffi::{CGLReleaseContext, CGLRetainContext}; use super::surface::Surface; -use crate::context::{ContextID, CREATE_CONTEXT_MUTEX}; -use crate::gl_utils; +use crate::context::ContextID; use crate::surface::Framebuffer; -use crate::{ContextAttributeFlags, ContextAttributes, Error, GLVersion, Gl, SurfaceInfo}; +use crate::{Error, Gl}; -use cgl::{kCGLPFAAllowOfflineRenderers, kCGLPFAAlphaSize, kCGLPFADepthSize}; -use cgl::{kCGLPFAOpenGLProfile, kCGLPFAStencilSize}; -use cgl::{ - CGLChoosePixelFormat, CGLContextObj, CGLCreateContext, CGLDescribePixelFormat, CGLError, -}; -use cgl::{CGLGetCurrentContext, CGLGetPixelFormat, CGLPixelFormatAttribute, CGLPixelFormatObj}; +use cgl::CGLContextObj; +use cgl::{CGLGetCurrentContext, CGLPixelFormatObj}; use cgl::{CGLReleasePixelFormat, CGLRetainPixelFormat, CGLSetCurrentContext}; -use euclid::default::Size2D; -use glow::HasContext; -use objc2_core_foundation::{CFBundle, CFRetained, CFString}; -use std::mem; -use std::os::raw::c_void; use std::ptr; use std::rc::Rc; use std::thread; -// No CGL error occurred. -#[allow(non_upper_case_globals)] -const kCGLNoError: CGLError = 0; - -// Choose a renderer compatible with GL 1.0. -#[allow(non_upper_case_globals)] -const kCGLOGLPVersion_Legacy: CGLPixelFormatAttribute = 0x1000; -// Choose a renderer capable of GL3.2 or later. -#[allow(non_upper_case_globals)] -const kCGLOGLPVersion_3_2_Core: CGLPixelFormatAttribute = 0x3200; -// Choose a renderer capable of GL4.1 or later. -#[allow(non_upper_case_globals)] -const kCGLOGLPVersion_GL4_Core: CGLPixelFormatAttribute = 0x4100; - -static OPENGL_FRAMEWORK_IDENTIFIER: &str = "com.apple.opengl"; - -thread_local! { - static OPENGL_FRAMEWORK: CFRetained = { - let framework_identifier = CFString::from_str(OPENGL_FRAMEWORK_IDENTIFIER); - let framework = CFBundle::bundle_with_identifier(Some(&framework_identifier)); - framework.unwrap() - }; -} - /// Represents an OpenGL rendering context. /// /// A context allows you to issue rendering commands to a surface. When initially created, a @@ -68,7 +32,7 @@ thread_local! { pub struct Context { pub(crate) cgl_context: CGLContextObj, pub(crate) id: ContextID, - framebuffer: Framebuffer, + pub(crate) framebuffer: Framebuffer, pub(crate) gl: Rc, } @@ -88,7 +52,7 @@ impl Drop for Context { /// /// This corresponds to a "pixel format" object in many APIs. These are thread-safe. pub struct ContextDescriptor { - cgl_pixel_format: CGLPixelFormatObj, + pub(crate) cgl_pixel_format: CGLPixelFormatObj, } impl Drop for ContextDescriptor { @@ -112,412 +76,8 @@ impl Clone for ContextDescriptor { } } -fn make_cgl_context_current(cgl_context: CGLContextObj) -> Result<(), Error> { - unsafe { - let err = CGLSetCurrentContext(cgl_context); - if err != kCGLNoError { - return Err(Error::MakeCurrentFailed(err.to_windowing_api_error())); - } - Ok(()) - } -} - unsafe impl Send for ContextDescriptor {} -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - if attributes - .flags - .contains(ContextAttributeFlags::COMPATIBILITY_PROFILE) - && attributes.version.major > 2 - { - return Err(Error::UnsupportedGLProfile); - }; - - let profile = if attributes.version.major >= 4 { - kCGLOGLPVersion_GL4_Core - } else if attributes.version.major == 3 { - kCGLOGLPVersion_3_2_Core - } else { - kCGLOGLPVersion_Legacy - }; - - let flags = attributes.flags; - let alpha_size = if flags.contains(ContextAttributeFlags::ALPHA) { - 8 - } else { - 0 - }; - let depth_size = if flags.contains(ContextAttributeFlags::DEPTH) { - 24 - } else { - 0 - }; - let stencil_size = if flags.contains(ContextAttributeFlags::STENCIL) { - 8 - } else { - 0 - }; - - let mut cgl_pixel_format_attributes = vec![ - kCGLPFAOpenGLProfile, - profile, - kCGLPFAAlphaSize, - alpha_size, - kCGLPFADepthSize, - depth_size, - kCGLPFAStencilSize, - stencil_size, - ]; - - // This means "opt into the integrated GPU". - // - // https://supermegaultragroovy.com/2016/12/10/auto-graphics-switching/ - if self.adapter().0.is_low_power { - cgl_pixel_format_attributes.push(kCGLPFAAllowOfflineRenderers); - } - - cgl_pixel_format_attributes.extend_from_slice(&[0, 0]); - - unsafe { - let (mut cgl_pixel_format, mut cgl_pixel_format_count) = (ptr::null_mut(), 0); - let err = CGLChoosePixelFormat( - cgl_pixel_format_attributes.as_ptr(), - &mut cgl_pixel_format, - &mut cgl_pixel_format_count, - ); - if err != kCGLNoError { - return Err(Error::PixelFormatSelectionFailed( - err.to_windowing_api_error(), - )); - } - if cgl_pixel_format_count == 0 { - return Err(Error::NoPixelFormatFound); - } - - Ok(ContextDescriptor { cgl_pixel_format }) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - // Take a lock so that we're only creating one context at a time. `CGLChoosePixelFormat` - // will fail, returning `kCGLBadConnection`, if multiple threads try to open a display - // connection simultaneously. - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - - unsafe { - // Create the CGL context. - let mut cgl_context = ptr::null_mut(); - let err = CGLCreateContext( - descriptor.cgl_pixel_format, - share_with.map_or(ptr::null_mut(), |ctx| ctx.cgl_context), - &mut cgl_context, - ); - if err != kCGLNoError { - return Err(Error::ContextCreationFailed(err.to_windowing_api_error())); - } - debug_assert_ne!(cgl_context, ptr::null_mut()); - - make_cgl_context_current(cgl_context)?; - // Wrap and return the context. - let context = Context { - cgl_context, - id: *next_context_id, - framebuffer: Framebuffer::None, - gl: Rc::new(Gl::from_loader_function(get_proc_address)), - }; - next_context_id.0 += 1; - Ok(context) - } - } - - /// Wraps a `CGLContext` in a `surfman` context and returns it. - /// - /// This function takes ownership of the native context and does not adjust its reference - /// count. - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - let context = Context { - cgl_context: native_context.0, - id: *next_context_id, - framebuffer: Framebuffer::None, - gl: Rc::new(Gl::from_loader_function(get_proc_address)), - }; - next_context_id.0 += 1; - mem::forget(native_context); - Ok(context) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if context.cgl_context.is_null() { - return Ok(()); - } - - if let Framebuffer::Surface(mut surface) = - mem::replace(&mut context.framebuffer, Framebuffer::None) - { - self.destroy_surface(context, &mut surface)?; - } - - unsafe { - CGLSetCurrentContext(ptr::null_mut()); - CGLReleaseContext(context.cgl_context); - context.cgl_context = ptr::null_mut(); - } - - Ok(()) - } - - /// Returns the descriptor that this context was created with. - #[inline] - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - let mut cgl_pixel_format = CGLGetPixelFormat(context.cgl_context); - cgl_pixel_format = CGLRetainPixelFormat(cgl_pixel_format); - ContextDescriptor { cgl_pixel_format } - } - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - make_cgl_context_current(context.cgl_context) - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { - let err = CGLSetCurrentContext(ptr::null_mut()); - if err != kCGLNoError { - return Err(Error::MakeCurrentFailed(err.to_windowing_api_error())); - } - Ok(()) - } - } - - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - pub fn bind_surface_to_context( - &self, - context: &mut Context, - new_surface: Surface, - ) -> Result<(), (Error, Surface)> { - match context.framebuffer { - Framebuffer::External(_) => return Err((Error::ExternalRenderTarget, new_surface)), - Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, new_surface)), - Framebuffer::None => {} - } - - if new_surface.context_id != context.id { - return Err((Error::IncompatibleSurface, new_surface)); - } - - context.framebuffer = Framebuffer::Surface(new_surface); - Ok(()) - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - match context.framebuffer { - Framebuffer::External(_) => return Err(Error::ExternalRenderTarget), - Framebuffer::None | Framebuffer::Surface(_) => {} - } - - match mem::replace(&mut context.framebuffer, Framebuffer::None) { - Framebuffer::External(_) => unreachable!(), - Framebuffer::None => Ok(None), - Framebuffer::Surface(surface) => { - // Make sure all changes are synchronized. Apple requires this. - // - // TODO(pcwalton): Use `glClientWaitSync` instead to avoid starving the window - // server. - - let _guard = self.temporarily_make_context_current(context)?; - let gl = &context.gl; - unsafe { - gl.flush(); - } - - if let Some(framebuffer) = surface.framebuffer_object { - gl_utils::unbind_framebuffer_if_necessary(gl, framebuffer); - } - Ok(Some(surface)) - } - } - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - if let Framebuffer::Surface(surface) = &mut context.framebuffer { - self.0.present_surface(&mut surface.system_surface)?; - surface.bind_to_texture(&context.gl); - } - Ok(()) - } - - /// If the currently bound surface is a widget surface, resize it, - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - let _guard = self.temporarily_make_context_current(context); - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - if let Framebuffer::Surface(surface) = &mut context.framebuffer { - return self.resize_inner(surface, size, &context.gl, context_attributes); - } - Ok(()) - } - - /// Returns the attributes that the context descriptor was created with. - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - unsafe { - let alpha_size = get_pixel_format_attribute(context_descriptor, kCGLPFAAlphaSize); - let depth_size = get_pixel_format_attribute(context_descriptor, kCGLPFADepthSize); - let stencil_size = get_pixel_format_attribute(context_descriptor, kCGLPFAStencilSize); - let gl_profile = get_pixel_format_attribute(context_descriptor, kCGLPFAOpenGLProfile); - - let mut attribute_flags = ContextAttributeFlags::empty(); - attribute_flags.set(ContextAttributeFlags::ALPHA, alpha_size != 0); - attribute_flags.set(ContextAttributeFlags::DEPTH, depth_size != 0); - attribute_flags.set(ContextAttributeFlags::STENCIL, stencil_size != 0); - - let mut version = GLVersion::new( - ((gl_profile >> 12) & 0xf) as u8, - ((gl_profile >> 8) & 0xf) as u8, - ); - if version.major <= 2 { - version.major = 2; - version.minor = 1; - attribute_flags.insert(ContextAttributeFlags::COMPATIBILITY_PROFILE); - } - - return ContextAttributes { - flags: attribute_flags, - version, - }; - } - - unsafe fn get_pixel_format_attribute( - context_descriptor: &ContextDescriptor, - attribute: CGLPixelFormatAttribute, - ) -> i32 { - let mut value = 0; - let err = CGLDescribePixelFormat( - context_descriptor.cgl_pixel_format, - 0, - attribute, - &mut value, - ); - debug_assert_eq!(err, kCGLNoError); - value - } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - get_proc_address(symbol_name) - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - match context.framebuffer { - Framebuffer::None => Ok(None), - Framebuffer::External(_) => Err(Error::ExternalRenderTarget), - Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), - } - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.id - } - - /// Given a context, returns its underlying CGL context object. - /// - /// The reference count on that context is incremented via `CGLRetainContext()` before - /// returning. - #[inline] - pub fn native_context(&self, context: &Context) -> NativeContext { - unsafe { NativeContext(CGLRetainContext(context.cgl_context)) } - } -} - -fn get_proc_address(symbol_name: &str) -> *const c_void { - OPENGL_FRAMEWORK.with(|framework| { - let symbol_name = CFString::from_str(symbol_name); - framework.function_pointer_for_name(Some(&symbol_name)) - }) -} - #[must_use] pub(crate) struct CurrentContextGuard { old_cgl_context: CGLContextObj, @@ -532,7 +92,7 @@ impl Drop for CurrentContextGuard { } impl CurrentContextGuard { - fn new() -> CurrentContextGuard { + pub(crate) fn new() -> CurrentContextGuard { unsafe { CurrentContextGuard { old_cgl_context: CGLGetCurrentContext(), diff --git a/src/cgl/device.rs b/src/cgl/device.rs index fa1689c8..f7474ec2 100644 --- a/src/cgl/device.rs +++ b/src/cgl/device.rs @@ -2,10 +2,62 @@ use super::connection::Connection; use crate::base::io_surface::device::{Adapter as SystemAdapter, Device as SystemDevice}; -use crate::GLApi; +use crate::cgl::context::CurrentContextGuard; +use crate::cgl::error::ToWindowingApiError; +use crate::cgl::ffi::{CGLReleaseContext, CGLRetainContext}; +use crate::cgl::surface::{surface_bind_to_gl_texture, NativeSurface}; +use crate::context::{ContextID, CREATE_CONTEXT_MUTEX}; +use crate::renderbuffers::Renderbuffers; +use crate::surface::Framebuffer; +use crate::{ + gl, gl_utils, Context, GLVersion, NativeContext, NativeWidget, Surface, SurfaceAccess, + SurfaceInfo, SurfaceTexture, SurfaceType, WindowingApiError, +}; +use crate::{ContextAttributeFlags, ContextAttributes, ContextDescriptor, Error, GLApi, Gl}; +use cgl::{ + kCGLPFAAllowOfflineRenderers, kCGLPFAAlphaSize, kCGLPFADepthSize, kCGLPFAOpenGLProfile, + kCGLPFAStencilSize, CGLChoosePixelFormat, CGLContextObj, CGLCreateContext, + CGLDescribePixelFormat, CGLError, CGLGetPixelFormat, CGLPixelFormatAttribute, + CGLRetainPixelFormat, CGLSetCurrentContext, +}; +use euclid::default::Size2D; +use glow::HasContext; +use glow::Texture; +use objc2_core_foundation::{CFBundle, CFRetained, CFString}; +use objc2_io_surface::IOSurfaceRef; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::os::raw::c_void; +use std::rc::Rc; +use std::{mem, ptr}; pub use crate::base::io_surface::device::NativeDevice; +const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_RECTANGLE; + +// No CGL error occurred. +#[allow(non_upper_case_globals)] +const kCGLNoError: CGLError = 0; + +// Choose a renderer compatible with GL 1.0. +#[allow(non_upper_case_globals)] +const kCGLOGLPVersion_Legacy: CGLPixelFormatAttribute = 0x1000; +// Choose a renderer capable of GL3.2 or later. +#[allow(non_upper_case_globals)] +const kCGLOGLPVersion_3_2_Core: CGLPixelFormatAttribute = 0x3200; +// Choose a renderer capable of GL4.1 or later. +#[allow(non_upper_case_globals)] +const kCGLOGLPVersion_GL4_Core: CGLPixelFormatAttribute = 0x4100; + +thread_local! { + static OPENGL_FRAMEWORK: CFRetained = { + static OPENGL_FRAMEWORK_IDENTIFIER: &str = "com.apple.opengl"; + let framework_identifier = CFString::from_str(OPENGL_FRAMEWORK_IDENTIFIER); + let framework = CFBundle::bundle_with_identifier(Some(&framework_identifier)); + framework.unwrap() + }; +} + /// Represents a hardware display adapter that can be used for rendering (including the CPU). /// /// Adapters can be sent between threads. To render with an adapter, open a thread-local `Device`. @@ -42,4 +94,745 @@ impl Device { pub fn gl_api(&self) -> GLApi { GLApi::GL } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + if attributes + .flags + .contains(ContextAttributeFlags::COMPATIBILITY_PROFILE) + && attributes.version.major > 2 + { + return Err(Error::UnsupportedGLProfile); + }; + + let profile = if attributes.version.major >= 4 { + kCGLOGLPVersion_GL4_Core + } else if attributes.version.major == 3 { + kCGLOGLPVersion_3_2_Core + } else { + kCGLOGLPVersion_Legacy + }; + + let flags = attributes.flags; + let alpha_size = if flags.contains(ContextAttributeFlags::ALPHA) { + 8 + } else { + 0 + }; + let depth_size = if flags.contains(ContextAttributeFlags::DEPTH) { + 24 + } else { + 0 + }; + let stencil_size = if flags.contains(ContextAttributeFlags::STENCIL) { + 8 + } else { + 0 + }; + + let mut cgl_pixel_format_attributes = vec![ + kCGLPFAOpenGLProfile, + profile, + kCGLPFAAlphaSize, + alpha_size, + kCGLPFADepthSize, + depth_size, + kCGLPFAStencilSize, + stencil_size, + ]; + + // This means "opt into the integrated GPU". + // + // https://supermegaultragroovy.com/2016/12/10/auto-graphics-switching/ + if self.adapter().0.is_low_power { + cgl_pixel_format_attributes.push(kCGLPFAAllowOfflineRenderers); + } + + cgl_pixel_format_attributes.extend_from_slice(&[0, 0]); + + unsafe { + let (mut cgl_pixel_format, mut cgl_pixel_format_count) = (ptr::null_mut(), 0); + let err = CGLChoosePixelFormat( + cgl_pixel_format_attributes.as_ptr(), + &mut cgl_pixel_format, + &mut cgl_pixel_format_count, + ); + if err != kCGLNoError { + return Err(Error::PixelFormatSelectionFailed( + err.to_windowing_api_error(), + )); + } + if cgl_pixel_format_count == 0 { + return Err(Error::NoPixelFormatFound); + } + + Ok(ContextDescriptor { cgl_pixel_format }) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + // Take a lock so that we're only creating one context at a time. `CGLChoosePixelFormat` + // will fail, returning `kCGLBadConnection`, if multiple threads try to open a display + // connection simultaneously. + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + + unsafe { + // Create the CGL context. + let mut cgl_context = ptr::null_mut(); + let err = CGLCreateContext( + descriptor.cgl_pixel_format, + share_with.map_or(ptr::null_mut(), |ctx| ctx.cgl_context), + &mut cgl_context, + ); + if err != kCGLNoError { + return Err(Error::ContextCreationFailed(err.to_windowing_api_error())); + } + debug_assert_ne!(cgl_context, ptr::null_mut()); + + make_cgl_context_current(cgl_context)?; + // Wrap and return the context. + let context = Context { + cgl_context, + id: *next_context_id, + framebuffer: Framebuffer::None, + gl: Rc::new(Gl::from_loader_function(get_proc_address)), + }; + next_context_id.0 += 1; + Ok(context) + } + } + + /// Wraps a `CGLContext` in a `surfman` context and returns it. + /// + /// This function takes ownership of the native context and does not adjust its reference + /// count. + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + let context = Context { + cgl_context: native_context.0, + id: *next_context_id, + framebuffer: Framebuffer::None, + gl: Rc::new(Gl::from_loader_function(get_proc_address)), + }; + next_context_id.0 += 1; + mem::forget(native_context); + Ok(context) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if context.cgl_context.is_null() { + return Ok(()); + } + + if let Framebuffer::Surface(mut surface) = + mem::replace(&mut context.framebuffer, Framebuffer::None) + { + self.destroy_surface(context, &mut surface)?; + } + + unsafe { + CGLSetCurrentContext(ptr::null_mut()); + CGLReleaseContext(context.cgl_context); + context.cgl_context = ptr::null_mut(); + } + + Ok(()) + } + + /// Returns the descriptor that this context was created with. + #[inline] + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + let mut cgl_pixel_format = CGLGetPixelFormat(context.cgl_context); + cgl_pixel_format = CGLRetainPixelFormat(cgl_pixel_format); + ContextDescriptor { cgl_pixel_format } + } + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + make_cgl_context_current(context.cgl_context) + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { + let err = CGLSetCurrentContext(ptr::null_mut()); + if err != kCGLNoError { + return Err(Error::MakeCurrentFailed(err.to_windowing_api_error())); + } + Ok(()) + } + } + + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + pub fn bind_surface_to_context( + &self, + context: &mut Context, + new_surface: Surface, + ) -> Result<(), (Error, Surface)> { + match context.framebuffer { + Framebuffer::External(_) => return Err((Error::ExternalRenderTarget, new_surface)), + Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, new_surface)), + Framebuffer::None => {} + } + + if new_surface.context_id != context.id { + return Err((Error::IncompatibleSurface, new_surface)); + } + + context.framebuffer = Framebuffer::Surface(new_surface); + Ok(()) + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + match context.framebuffer { + Framebuffer::External(_) => return Err(Error::ExternalRenderTarget), + Framebuffer::None | Framebuffer::Surface(_) => {} + } + + match mem::replace(&mut context.framebuffer, Framebuffer::None) { + Framebuffer::External(_) => unreachable!(), + Framebuffer::None => Ok(None), + Framebuffer::Surface(surface) => { + // Make sure all changes are synchronized. Apple requires this. + // + // TODO(pcwalton): Use `glClientWaitSync` instead to avoid starving the window + // server. + + let _guard = self.temporarily_make_context_current(context)?; + let gl = &context.gl; + unsafe { + gl.flush(); + } + + if let Some(framebuffer) = surface.framebuffer_object { + gl_utils::unbind_framebuffer_if_necessary(gl, framebuffer); + } + Ok(Some(surface)) + } + } + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + if let Framebuffer::Surface(surface) = &mut context.framebuffer { + self.0.present_surface(&mut surface.system_surface)?; + surface.bind_to_texture(&context.gl); + } + Ok(()) + } + + /// If the currently bound surface is a widget surface, resize it, + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + let _guard = self.temporarily_make_context_current(context); + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + if let Framebuffer::Surface(surface) = &mut context.framebuffer { + return self.resize_inner(surface, size, &context.gl, context_attributes); + } + Ok(()) + } + + /// Returns the attributes that the context descriptor was created with. + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + unsafe { + let alpha_size = get_pixel_format_attribute(context_descriptor, kCGLPFAAlphaSize); + let depth_size = get_pixel_format_attribute(context_descriptor, kCGLPFADepthSize); + let stencil_size = get_pixel_format_attribute(context_descriptor, kCGLPFAStencilSize); + let gl_profile = get_pixel_format_attribute(context_descriptor, kCGLPFAOpenGLProfile); + + let mut attribute_flags = ContextAttributeFlags::empty(); + attribute_flags.set(ContextAttributeFlags::ALPHA, alpha_size != 0); + attribute_flags.set(ContextAttributeFlags::DEPTH, depth_size != 0); + attribute_flags.set(ContextAttributeFlags::STENCIL, stencil_size != 0); + + let mut version = GLVersion::new( + ((gl_profile >> 12) & 0xf) as u8, + ((gl_profile >> 8) & 0xf) as u8, + ); + if version.major <= 2 { + version.major = 2; + version.minor = 1; + attribute_flags.insert(ContextAttributeFlags::COMPATIBILITY_PROFILE); + } + + return ContextAttributes { + flags: attribute_flags, + version, + }; + } + + unsafe fn get_pixel_format_attribute( + context_descriptor: &ContextDescriptor, + attribute: CGLPixelFormatAttribute, + ) -> i32 { + let mut value = 0; + let err = CGLDescribePixelFormat( + context_descriptor.cgl_pixel_format, + 0, + attribute, + &mut value, + ); + debug_assert_eq!(err, kCGLNoError); + value + } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + get_proc_address(symbol_name) + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + match context.framebuffer { + Framebuffer::None => Ok(None), + Framebuffer::External(_) => Err(Error::ExternalRenderTarget), + Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), + } + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.id + } + + /// Given a context, returns its underlying CGL context object. + /// + /// The reference count on that context is incremented via `CGLRetainContext()` before + /// returning. + #[inline] + pub fn native_context(&self, context: &Context) -> NativeContext { + unsafe { NativeContext(CGLRetainContext(context.cgl_context)) } + } + + /// Creates either a generic or a widget surface, depending on the supplied surface type. + /// + /// Only the given context may ever render to the surface, but generic surfaces can be wrapped + /// up in a `SurfaceTexture` for reading by other contexts. + pub fn create_surface( + &self, + context: &Context, + access: SurfaceAccess, + surface_type: SurfaceType, + ) -> Result { + let mut system_surface = self.0.create_surface(access, surface_type)?; + self.0.set_surface_flipped(&mut system_surface, true); + + let _guard = self.temporarily_make_context_current(context); + let gl = &context.gl; + unsafe { + let texture_object = + self.bind_to_gl_texture(gl, &system_surface.io_surface, &system_surface.size); + + let framebuffer_object = gl.create_framebuffer().unwrap(); + let _guard = + self.temporarily_bind_framebuffer(context.gl.clone(), Some(framebuffer_object)); + + gl.framebuffer_texture_2d( + gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + SURFACE_GL_TEXTURE_TARGET, + Some(texture_object), + 0, + ); + + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + + let mut renderbuffers = + Renderbuffers::new(gl, &system_surface.size, &context_attributes); + renderbuffers.bind_to_current_framebuffer(gl); + + if gl.get_error() != gl::NO_ERROR + || gl.check_framebuffer_status(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE + { + // On macos, surface creation can fail silently (e.g. due to OOM) and AFAICT + // the way to tell that it has failed is to look at the framebuffer status + // while the surface is attached. + renderbuffers.destroy(gl); + gl.delete_framebuffer(framebuffer_object); + gl.delete_texture(texture_object); + let _ = self.0.destroy_surface(&mut system_surface); + // TODO: convert the GL error into a surfman error? + return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed)); + } + + Ok(Surface { + system_surface, + context_id: context.id, + framebuffer_object: Some(framebuffer_object), + texture_object: Some(texture_object), + renderbuffers, + }) + } + } + + /// Creates a surface texture from an existing generic surface for use with the given context. + /// + /// The surface texture is local to the supplied context and takes ownership of the surface. + /// Destroying the surface texture allows you to retrieve the surface again. + /// + /// *The supplied context does not have to be the same context that the surface is associated + /// with.* This allows you to render to a surface in one context and sample from that surface + /// in another context. + /// + /// Calling this method on a widget surface returns a `WidgetAttached` error. + pub fn create_surface_texture( + &self, + context: &mut Context, + surface: Surface, + ) -> Result { + if surface.system_surface.view_info.is_some() { + return Err((Error::WidgetAttached, surface)); + } + + let _guard = self.temporarily_make_context_current(context).unwrap(); + + let texture_object = self.bind_to_gl_texture( + &context.gl, + &surface.system_surface.io_surface, + &surface.system_surface.size, + ); + Ok(SurfaceTexture { + surface, + texture_object: Some(texture_object), + phantom: PhantomData, + }) + } + + fn bind_to_gl_texture( + &self, + gl: &Gl, + io_surface: &IOSurfaceRef, + size: &Size2D, + ) -> Texture { + unsafe { + let texture = gl.create_texture().unwrap(); + + gl.bind_texture(gl::TEXTURE_RECTANGLE, Some(texture)); + surface_bind_to_gl_texture(io_surface, size.width, size.height, true); + + gl.tex_parameter_i32( + gl::TEXTURE_RECTANGLE, + gl::TEXTURE_MAG_FILTER, + gl::NEAREST as _, + ); + gl.tex_parameter_i32( + gl::TEXTURE_RECTANGLE, + gl::TEXTURE_MIN_FILTER, + gl::NEAREST as _, + ); + gl.tex_parameter_i32( + gl::TEXTURE_RECTANGLE, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + gl::TEXTURE_RECTANGLE, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_EDGE as _, + ); + + gl.bind_texture(gl::TEXTURE_RECTANGLE, None); + + debug_assert_eq!(gl.get_error(), gl::NO_ERROR); + + texture + } + } + + /// Destroys a surface. + /// + /// The supplied context must be the context the surface is associated with, or this returns + /// an `IncompatibleSurface` error. + /// + /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in + /// the `drop` method. + pub fn destroy_surface( + &self, + context: &mut Context, + surface: &mut Surface, + ) -> Result<(), Error> { + let gl = &context.gl; + if context.id != surface.context_id { + return Err(Error::IncompatibleSurface); + } + + unsafe { + if let Some(fbo) = surface.framebuffer_object.take() { + gl_utils::destroy_framebuffer(gl, fbo); + } + + surface.renderbuffers.destroy(gl); + if let Some(texture) = surface.texture_object.take() { + gl.delete_texture(texture); + } + } + + self.0.destroy_surface(&mut surface.system_surface) + } + + /// Destroys a surface texture and returns the underlying surface. + /// + /// The supplied context must be the same context the surface texture was created with, or an + /// `IncompatibleSurfaceTexture` error is returned. + /// + /// All surface textures must be explicitly destroyed with this function, or a panic will + /// occur. + pub fn destroy_surface_texture( + &self, + context: &mut Context, + mut surface_texture: SurfaceTexture, + ) -> Result { + let gl = &context.gl; + if let Some(texture) = surface_texture.texture_object.take() { + unsafe { + gl.delete_texture(texture); + } + } + + Ok(surface_texture.surface) + } + + /// Returns the OpenGL texture object containing the contents of this surface. + /// + /// It is only legal to read from, not write to, this texture object. + #[inline] + pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { + surface_texture.texture_object + } + + /// Returns the OpenGL texture target needed to read from this surface texture. + /// + /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. + #[inline] + pub fn surface_gl_texture_target(&self) -> u32 { + SURFACE_GL_TEXTURE_TARGET + } + + /// Displays the contents of a widget surface on screen. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't show up in their + /// associated widgets until this method is called. + /// + /// The supplied context must match the context the surface was created with, or an + /// `IncompatibleSurface` error is returned. + pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { + self.0.present_surface(&mut surface.system_surface)?; + surface.bind_to_texture(&context.gl); + Ok(()) + } + + /// Resizes a widget surface. + pub fn resize_surface( + &self, + context: &Context, + surface: &mut Surface, + size: Size2D, + ) -> Result<(), Error> { + if context.id != surface.context_id { + return Err(Error::IncompatibleSurface); + } + + let _guard = self.temporarily_make_context_current(context); + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + self.resize_inner(surface, size, &context.gl, context_attributes) + } + + pub(crate) fn resize_inner( + &self, + surface: &mut Surface, + size: Size2D, + gl: &Rc, + context_attributes: ContextAttributes, + ) -> Result<(), Error> { + let _guard = self.temporarily_bind_framebuffer(gl.clone(), surface.framebuffer_object); + + self.0.resize_surface(&mut surface.system_surface, size)?; + + unsafe { + // Recreate the GL texture and bind it to the FBO + let texture_object = + self.bind_to_gl_texture(gl, &surface.system_surface.io_surface, &size); + gl.framebuffer_texture_2d( + gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + SURFACE_GL_TEXTURE_TARGET, + Some(texture_object), + 0, + ); + + // Recreate the GL renderbuffers and bind them to the FBO + let renderbuffers = Renderbuffers::new(gl, &size, &context_attributes); + renderbuffers.bind_to_current_framebuffer(gl); + + if let Some(texture) = surface.texture_object { + gl.delete_texture(texture); + } + surface.renderbuffers.destroy(gl); + + surface.texture_object = Some(texture_object); + surface.renderbuffers = renderbuffers; + + debug_assert_eq!( + (gl.get_error(), gl.check_framebuffer_status(gl::FRAMEBUFFER)), + (gl::NO_ERROR, gl::FRAMEBUFFER_COMPLETE), + ); + } + + Ok(()) + } + + fn temporarily_bind_framebuffer( + &self, + gl: Rc, + new_framebuffer: Option, + ) -> FramebufferGuard { + unsafe { + let current_draw_framebuffer = + gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING); + let current_read_framebuffer = + gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING); + gl.bind_framebuffer(gl::FRAMEBUFFER, new_framebuffer); + FramebufferGuard { + gl, + draw: current_draw_framebuffer, + read: current_read_framebuffer, + } + } + } + + /// Returns various information about the surface, including the framebuffer object needed to + /// render to this surface. + /// + /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` + /// on the framebuffer object returned by this function. This framebuffer object may or not be + /// 0, the default framebuffer, depending on platform. + #[inline] + pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { + let system_surface_info = self.0.surface_info(&surface.system_surface); + SurfaceInfo { + size: system_surface_info.size, + id: system_surface_info.id, + context_id: surface.context_id, + framebuffer_object: surface.framebuffer_object, + } + } + + /// Returns the native `IOSurface` corresponding to this surface. + /// + /// The reference count is increased on the `IOSurface` before returning. + #[inline] + pub fn native_surface(&self, surface: &Surface) -> NativeSurface { + self.0.native_surface(&surface.system_surface) + } +} + +fn make_cgl_context_current(cgl_context: CGLContextObj) -> Result<(), Error> { + unsafe { + let err = CGLSetCurrentContext(cgl_context); + if err != kCGLNoError { + return Err(Error::MakeCurrentFailed(err.to_windowing_api_error())); + } + Ok(()) + } +} + +fn get_proc_address(symbol_name: &str) -> *const c_void { + OPENGL_FRAMEWORK.with(|framework| { + let symbol_name = CFString::from_str(symbol_name); + framework.function_pointer_for_name(Some(&symbol_name)) + }) +} + +#[must_use] +struct FramebufferGuard { + gl: Rc, + draw: Option, + read: Option, +} + +impl Drop for FramebufferGuard { + fn drop(&mut self) { + unsafe { + self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.read); + self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.draw); + } + } } diff --git a/src/cgl/surface.rs b/src/cgl/surface.rs index d42b2fc8..e61e06c8 100644 --- a/src/cgl/surface.rs +++ b/src/cgl/surface.rs @@ -1,19 +1,12 @@ //! Surface management for macOS. -use super::context::Context; -use super::device::Device; use crate::base::io_surface::surface::Surface as SystemSurface; use crate::context::ContextID; -use crate::gl_utils; use crate::renderbuffers::Renderbuffers; -use crate::{ - gl, ContextAttributes, Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, - WindowingApiError, -}; +use crate::{gl, SurfaceID}; use cgl::{kCGLNoError, CGLErrorString, CGLGetCurrentContext, CGLTexImageIOSurface2D, GLenum}; use glow::Context as Gl; -use euclid::default::Size2D; use glow::{HasContext, Texture}; use objc2_io_surface::IOSurfaceRef; use std::ffi::CStr; @@ -23,8 +16,6 @@ use std::rc::Rc; pub use crate::base::io_surface::surface::{NativeSurface, NativeWidget}; -const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_RECTANGLE; - /// Represents a hardware buffer of pixels that can be rendered to via the CPU or GPU and either /// displayed in a native widget or bound to a texture for reading. /// @@ -78,7 +69,12 @@ impl Debug for SurfaceTexture { } } -fn surface_bind_to_gl_texture(surface: &IOSurfaceRef, width: i32, height: i32, has_alpha: bool) { +pub(crate) fn surface_bind_to_gl_texture( + surface: &IOSurfaceRef, + width: i32, + height: i32, + has_alpha: bool, +) { const BGRA: GLenum = 0x80E1; const RGBA: GLenum = 0x1908; const RGB: GLenum = 0x1907; @@ -111,6 +107,11 @@ fn surface_bind_to_gl_texture(surface: &IOSurfaceRef, width: i32, height: i32, h } impl Surface { + #[inline] + pub(crate) fn id(&self) -> SurfaceID { + SurfaceID(&*self.system_surface.io_surface as *const IOSurfaceRef as usize) + } + pub(crate) fn bind_to_texture(&self, gl: &Rc) { let size = self.system_surface.size; unsafe { gl.bind_texture(gl::TEXTURE_RECTANGLE, self.texture_object) }; @@ -123,351 +124,3 @@ impl Surface { unsafe { gl.bind_texture(gl::TEXTURE_RECTANGLE, None) }; } } - -impl Device { - /// Creates either a generic or a widget surface, depending on the supplied surface type. - /// - /// Only the given context may ever render to the surface, but generic surfaces can be wrapped - /// up in a `SurfaceTexture` for reading by other contexts. - pub fn create_surface( - &self, - context: &Context, - access: SurfaceAccess, - surface_type: SurfaceType, - ) -> Result { - let mut system_surface = self.0.create_surface(access, surface_type)?; - self.0.set_surface_flipped(&mut system_surface, true); - - let _guard = self.temporarily_make_context_current(context); - let gl = &context.gl; - unsafe { - let texture_object = - self.bind_to_gl_texture(gl, &system_surface.io_surface, &system_surface.size); - - let framebuffer_object = gl.create_framebuffer().unwrap(); - let _guard = - self.temporarily_bind_framebuffer(context.gl.clone(), Some(framebuffer_object)); - - gl.framebuffer_texture_2d( - gl::FRAMEBUFFER, - gl::COLOR_ATTACHMENT0, - SURFACE_GL_TEXTURE_TARGET, - Some(texture_object), - 0, - ); - - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - - let mut renderbuffers = - Renderbuffers::new(gl, &system_surface.size, &context_attributes); - renderbuffers.bind_to_current_framebuffer(gl); - - if gl.get_error() != gl::NO_ERROR - || gl.check_framebuffer_status(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE - { - // On macos, surface creation can fail silently (e.g. due to OOM) and AFAICT - // the way to tell that it has failed is to look at the framebuffer status - // while the surface is attached. - renderbuffers.destroy(gl); - gl.delete_framebuffer(framebuffer_object); - gl.delete_texture(texture_object); - let _ = self.0.destroy_surface(&mut system_surface); - // TODO: convert the GL error into a surfman error? - return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed)); - } - - Ok(Surface { - system_surface, - context_id: context.id, - framebuffer_object: Some(framebuffer_object), - texture_object: Some(texture_object), - renderbuffers, - }) - } - } - - /// Creates a surface texture from an existing generic surface for use with the given context. - /// - /// The surface texture is local to the supplied context and takes ownership of the surface. - /// Destroying the surface texture allows you to retrieve the surface again. - /// - /// *The supplied context does not have to be the same context that the surface is associated - /// with.* This allows you to render to a surface in one context and sample from that surface - /// in another context. - /// - /// Calling this method on a widget surface returns a `WidgetAttached` error. - pub fn create_surface_texture( - &self, - context: &mut Context, - surface: Surface, - ) -> Result { - if surface.system_surface.view_info.is_some() { - return Err((Error::WidgetAttached, surface)); - } - - let _guard = self.temporarily_make_context_current(context).unwrap(); - - let texture_object = self.bind_to_gl_texture( - &context.gl, - &surface.system_surface.io_surface, - &surface.system_surface.size, - ); - Ok(SurfaceTexture { - surface, - texture_object: Some(texture_object), - phantom: PhantomData, - }) - } - - fn bind_to_gl_texture( - &self, - gl: &Gl, - io_surface: &IOSurfaceRef, - size: &Size2D, - ) -> Texture { - unsafe { - let texture = gl.create_texture().unwrap(); - - gl.bind_texture(gl::TEXTURE_RECTANGLE, Some(texture)); - surface_bind_to_gl_texture(io_surface, size.width, size.height, true); - - gl.tex_parameter_i32( - gl::TEXTURE_RECTANGLE, - gl::TEXTURE_MAG_FILTER, - gl::NEAREST as _, - ); - gl.tex_parameter_i32( - gl::TEXTURE_RECTANGLE, - gl::TEXTURE_MIN_FILTER, - gl::NEAREST as _, - ); - gl.tex_parameter_i32( - gl::TEXTURE_RECTANGLE, - gl::TEXTURE_WRAP_S, - gl::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - gl::TEXTURE_RECTANGLE, - gl::TEXTURE_WRAP_T, - gl::CLAMP_TO_EDGE as _, - ); - - gl.bind_texture(gl::TEXTURE_RECTANGLE, None); - - debug_assert_eq!(gl.get_error(), gl::NO_ERROR); - - texture - } - } - - /// Destroys a surface. - /// - /// The supplied context must be the context the surface is associated with, or this returns - /// an `IncompatibleSurface` error. - /// - /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in - /// the `drop` method. - pub fn destroy_surface( - &self, - context: &mut Context, - surface: &mut Surface, - ) -> Result<(), Error> { - let gl = &context.gl; - if context.id != surface.context_id { - return Err(Error::IncompatibleSurface); - } - - unsafe { - if let Some(fbo) = surface.framebuffer_object.take() { - gl_utils::destroy_framebuffer(gl, fbo); - } - - surface.renderbuffers.destroy(gl); - if let Some(texture) = surface.texture_object.take() { - gl.delete_texture(texture); - } - } - - self.0.destroy_surface(&mut surface.system_surface) - } - - /// Destroys a surface texture and returns the underlying surface. - /// - /// The supplied context must be the same context the surface texture was created with, or an - /// `IncompatibleSurfaceTexture` error is returned. - /// - /// All surface textures must be explicitly destroyed with this function, or a panic will - /// occur. - pub fn destroy_surface_texture( - &self, - context: &mut Context, - mut surface_texture: SurfaceTexture, - ) -> Result { - let gl = &context.gl; - if let Some(texture) = surface_texture.texture_object.take() { - unsafe { - gl.delete_texture(texture); - } - } - - Ok(surface_texture.surface) - } - - /// Returns the OpenGL texture object containing the contents of this surface. - /// - /// It is only legal to read from, not write to, this texture object. - #[inline] - pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { - surface_texture.texture_object - } - - /// Returns the OpenGL texture target needed to read from this surface texture. - /// - /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. - #[inline] - pub fn surface_gl_texture_target(&self) -> u32 { - SURFACE_GL_TEXTURE_TARGET - } - - /// Displays the contents of a widget surface on screen. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't show up in their - /// associated widgets until this method is called. - /// - /// The supplied context must match the context the surface was created with, or an - /// `IncompatibleSurface` error is returned. - pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { - self.0.present_surface(&mut surface.system_surface)?; - surface.bind_to_texture(&context.gl); - Ok(()) - } - - /// Resizes a widget surface. - pub fn resize_surface( - &self, - context: &Context, - surface: &mut Surface, - size: Size2D, - ) -> Result<(), Error> { - if context.id != surface.context_id { - return Err(Error::IncompatibleSurface); - } - - let _guard = self.temporarily_make_context_current(context); - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - self.resize_inner(surface, size, &context.gl, context_attributes) - } - - pub(crate) fn resize_inner( - &self, - surface: &mut Surface, - size: Size2D, - gl: &Rc, - context_attributes: ContextAttributes, - ) -> Result<(), Error> { - let _guard = self.temporarily_bind_framebuffer(gl.clone(), surface.framebuffer_object); - - self.0.resize_surface(&mut surface.system_surface, size)?; - - unsafe { - // Recreate the GL texture and bind it to the FBO - let texture_object = - self.bind_to_gl_texture(gl, &surface.system_surface.io_surface, &size); - gl.framebuffer_texture_2d( - gl::FRAMEBUFFER, - gl::COLOR_ATTACHMENT0, - SURFACE_GL_TEXTURE_TARGET, - Some(texture_object), - 0, - ); - - // Recreate the GL renderbuffers and bind them to the FBO - let renderbuffers = Renderbuffers::new(gl, &size, &context_attributes); - renderbuffers.bind_to_current_framebuffer(gl); - - if let Some(texture) = surface.texture_object { - gl.delete_texture(texture); - } - surface.renderbuffers.destroy(gl); - - surface.texture_object = Some(texture_object); - surface.renderbuffers = renderbuffers; - - debug_assert_eq!( - (gl.get_error(), gl.check_framebuffer_status(gl::FRAMEBUFFER)), - (gl::NO_ERROR, gl::FRAMEBUFFER_COMPLETE), - ); - } - - Ok(()) - } - - fn temporarily_bind_framebuffer( - &self, - gl: Rc, - new_framebuffer: Option, - ) -> FramebufferGuard { - unsafe { - let current_draw_framebuffer = - gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING); - let current_read_framebuffer = - gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING); - gl.bind_framebuffer(gl::FRAMEBUFFER, new_framebuffer); - FramebufferGuard { - gl, - draw: current_draw_framebuffer, - read: current_read_framebuffer, - } - } - } - - /// Returns various information about the surface, including the framebuffer object needed to - /// render to this surface. - /// - /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` - /// on the framebuffer object returned by this function. This framebuffer object may or not be - /// 0, the default framebuffer, depending on platform. - #[inline] - pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { - let system_surface_info = self.0.surface_info(&surface.system_surface); - SurfaceInfo { - size: system_surface_info.size, - id: system_surface_info.id, - context_id: surface.context_id, - framebuffer_object: surface.framebuffer_object, - } - } - - /// Returns the native `IOSurface` corresponding to this surface. - /// - /// The reference count is increased on the `IOSurface` before returning. - #[inline] - pub fn native_surface(&self, surface: &Surface) -> NativeSurface { - self.0.native_surface(&surface.system_surface) - } -} - -impl Surface { - #[inline] - fn id(&self) -> SurfaceID { - SurfaceID(&*self.system_surface.io_surface as *const IOSurfaceRef as usize) - } -} - -#[must_use] -struct FramebufferGuard { - gl: Rc, - draw: Option, - read: Option, -} - -impl Drop for FramebufferGuard { - fn drop(&mut self) { - unsafe { - self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.read); - self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.draw); - } - } -} diff --git a/src/hardware_buffer/context.rs b/src/hardware_buffer/context.rs index c24b9882..f5d610e4 100644 --- a/src/hardware_buffer/context.rs +++ b/src/hardware_buffer/context.rs @@ -1,22 +1,12 @@ //! OpenGL rendering contexts. -use glow::HasContext; - -use super::device::Device; -use super::surface::{Surface, SurfaceObjects}; -use crate::base::egl::context::{self, CurrentContextGuard}; -use crate::base::egl::device::EGL_FUNCTIONS; -use crate::base::egl::error::ToWindowingApiError; +use super::surface::Surface; use crate::base::egl::surface::ExternalEGLSurfaces; -use crate::context::{ContextID, CREATE_CONTEXT_MUTEX}; +use crate::context::ContextID; use crate::egl; -use crate::egl::types::{EGLConfig, EGLContext, EGLSurface, EGLint}; +use crate::egl::types::{EGLContext, EGLSurface}; use crate::surface::Framebuffer; -use crate::{ContextAttributes, Error, Gl, SurfaceInfo}; -use euclid::default::Size2D; - -use std::mem; -use std::os::raw::c_void; +use crate::Gl; use std::thread; pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; @@ -42,8 +32,8 @@ pub struct Context { pub(crate) id: ContextID, pub(crate) pbuffer: EGLSurface, pub(crate) gl: Gl, - framebuffer: Framebuffer, - context_is_owned: bool, + pub(crate) framebuffer: Framebuffer, + pub(crate) context_is_owned: bool, } impl Drop for Context { @@ -54,361 +44,3 @@ impl Drop for Context { } } } - -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - #[inline] - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - unsafe { - ContextDescriptor::new( - self.egl_display, - attributes, - &[ - egl::COLOR_BUFFER_TYPE as EGLint, - egl::RGB_BUFFER as EGLint, - egl::SURFACE_TYPE as EGLint, - egl::PBUFFER_BIT as EGLint, - egl::RENDERABLE_TYPE as EGLint, - egl::OPENGL_ES2_BIT as EGLint, - ], - ) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - - let egl_display = self.egl_display; - - unsafe { - // Create the EGL context. - let gl_api = self.gl_api(); - let egl_context = context::create_context( - egl_display, - descriptor, - share_with.map_or(egl::NO_CONTEXT, |ctx| ctx.egl_context), - gl_api, - )?; - - // Create a dummy pbuffer. - let pbuffer = context::create_dummy_pbuffer(egl_display, egl_context).unwrap(); - - EGL_FUNCTIONS.with(|egl| { - if egl.MakeCurrent(egl_display, pbuffer, pbuffer, egl_context) == egl::FALSE { - let err = egl.GetError().to_windowing_api_error(); - return Err(Error::MakeCurrentFailed(err)); - } - Ok(()) - })?; - - // Wrap up the EGL context. - let context = Context { - egl_context, - id: *next_context_id, - pbuffer, - framebuffer: Framebuffer::None, - context_is_owned: true, - gl: Gl::from_loader_function(context::get_proc_address), - }; - next_context_id.0 += 1; - Ok(context) - } - } - - /// Wraps a native `EGLContext` in a context object. - /// - /// The underlying `EGLContext` is not retained, as there is no way to do this in the EGL API. - /// Therefore, it is the caller's responsibility to keep it alive as long as this `Context` - /// remains alive. - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - - // Create a dummy pbuffer. - let pbuffer = - context::create_dummy_pbuffer(self.egl_display, native_context.egl_context).unwrap(); - - // Create the context. - let context = Context { - egl_context: native_context.egl_context, - id: *next_context_id, - pbuffer, - framebuffer: Framebuffer::External(ExternalEGLSurfaces { - draw: native_context.egl_draw_surface, - read: native_context.egl_read_surface, - }), - context_is_owned: false, - gl: Gl::from_loader_function(context::get_proc_address), - }; - next_context_id.0 += 1; - - Ok(context) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if context.egl_context == egl::NO_CONTEXT { - return Ok(()); - } - - unsafe { - if let Framebuffer::Surface(mut target) = - mem::replace(&mut context.framebuffer, Framebuffer::None) - { - self.destroy_surface(context, &mut target)?; - } - - EGL_FUNCTIONS.with(|egl| { - let result = egl.DestroySurface(self.egl_display, context.pbuffer); - assert_ne!(result, egl::FALSE); - context.pbuffer = egl::NO_SURFACE; - - egl.MakeCurrent( - self.egl_display, - egl::NO_SURFACE, - egl::NO_SURFACE, - egl::NO_CONTEXT, - ); - - if context.context_is_owned { - let result = egl.DestroyContext(self.egl_display, context.egl_context); - assert_ne!(result, egl::FALSE); - } - - context.egl_context = egl::NO_CONTEXT; - }); - } - - Ok(()) - } - - /// Returns the descriptor that this context was created with. - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - ContextDescriptor::from_egl_context(&context.gl, self.egl_display, context.egl_context) - } - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - unsafe { - let egl_display = self.egl_display; - let egl_context = context.egl_context; - - let (egl_draw_surface, egl_read_surface) = match context.framebuffer { - Framebuffer::Surface(Surface { - objects: SurfaceObjects::Window { egl_surface }, - .. - }) => (egl_surface, egl_surface), - Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), - Framebuffer::Surface(Surface { - objects: SurfaceObjects::HardwareBuffer { .. }, - .. - }) => (context.pbuffer, context.pbuffer), - Framebuffer::None => (context.pbuffer, context.pbuffer), - }; - - EGL_FUNCTIONS.with(|egl| { - let result = - egl.MakeCurrent(egl_display, egl_draw_surface, egl_read_surface, egl_context); - if result == egl::FALSE { - let err = egl.GetError().to_windowing_api_error(); - return Err(Error::MakeCurrentFailed(err)); - } - Ok(()) - }) - } - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { context::make_no_context_current(self.egl_display) } - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - pub fn bind_surface_to_context( - &self, - context: &mut Context, - new_surface: Surface, - ) -> Result<(), (Error, Surface)> { - if context.id != new_surface.context_id { - return Err((Error::IncompatibleSurface, new_surface)); - } - - match context.framebuffer { - Framebuffer::External { .. } => return Err((Error::ExternalRenderTarget, new_surface)), - Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, new_surface)), - Framebuffer::None => {} - } - - context.framebuffer = Framebuffer::Surface(new_surface); - Ok(()) - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - match context.framebuffer { - Framebuffer::External { .. } => return Err(Error::ExternalRenderTarget), - Framebuffer::None => return Ok(None), - Framebuffer::Surface(_) => {} - } - - // Make sure all changes are synchronized. - // - // FIXME(pcwalton): Is this necessary? - let _guard = self.temporarily_make_context_current(context)?; - unsafe { - context.gl.flush(); - }; - - match mem::replace(&mut context.framebuffer, Framebuffer::None) { - Framebuffer::Surface(surface) => return Ok(Some(surface)), - Framebuffer::External { .. } | Framebuffer::None => unreachable!(), - } - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - match &context.framebuffer { - Framebuffer::Surface(surface) => self.present_surface_inner(context, surface), - _ => Ok(()), - } - } - - /// If the currently bound surface is a widget surface, resize it, - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - if let Framebuffer::Surface(surface) = &mut context.framebuffer { - surface.resize(size); - } - Ok(()) - } - - /// Returns the attributes that the context descriptor was created with. - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - unsafe { context_descriptor.attributes(self.egl_display) } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - context::get_proc_address(symbol_name) - } - - pub(crate) fn context_to_egl_config(&self, context: &Context) -> EGLConfig { - unsafe { - context::egl_config_from_id( - self.egl_display, - context::get_context_attr( - self.egl_display, - context.egl_context, - egl::CONFIG_ID as EGLint, - ), - ) - } - } - - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.id - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - match context.framebuffer { - Framebuffer::None => Ok(None), - Framebuffer::External { .. } => Err(Error::ExternalRenderTarget), - Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), - } - } - - /// Given a context, returns its underlying EGL context and attached surfaces. - pub fn native_context(&self, context: &Context) -> NativeContext { - let (egl_draw_surface, egl_read_surface) = match context.framebuffer { - Framebuffer::Surface(Surface { - objects: SurfaceObjects::Window { egl_surface }, - .. - }) => (egl_surface, egl_surface), - Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), - Framebuffer::Surface(Surface { - objects: SurfaceObjects::HardwareBuffer { .. }, - .. - }) => (context.pbuffer, context.pbuffer), - Framebuffer::None => (context.pbuffer, context.pbuffer), - }; - - NativeContext { - egl_context: context.egl_context, - egl_draw_surface, - egl_read_surface, - } - } -} diff --git a/src/hardware_buffer/device.rs b/src/hardware_buffer/device.rs index 645deca5..2ac9ba02 100644 --- a/src/hardware_buffer/device.rs +++ b/src/hardware_buffer/device.rs @@ -1,10 +1,20 @@ //! A thread-local handle to the device. use super::connection::Connection; +use crate::base::egl::context::{self, CurrentContextGuard}; use crate::base::egl::device::EGL_FUNCTIONS; -use crate::egl; -use crate::egl::types::EGLDisplay; -use crate::{Error, GLApi}; +use crate::base::egl::error::ToWindowingApiError; +use crate::base::egl::surface::ExternalEGLSurfaces; +use crate::context::{ContextID, CREATE_CONTEXT_MUTEX}; +use crate::egl::types::{EGLConfig, EGLDisplay, EGLint}; +use crate::hardware_buffer::surface::SurfaceObjects; +use crate::surface::Framebuffer; +use crate::{egl, ContextDescriptor, NativeContext, Surface}; +use crate::{Context, ContextAttributes, Error, GLApi, Gl, SurfaceInfo}; +use euclid::default::Size2D; +use glow::HasContext; +use std::mem; +use std::os::raw::c_void; /// Represents a hardware display adapter that can be used for rendering (including the CPU). /// @@ -90,4 +100,360 @@ impl Device { pub fn gl_api(&self) -> GLApi { GLApi::GLES } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + #[inline] + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + unsafe { + ContextDescriptor::new( + self.egl_display, + attributes, + &[ + egl::COLOR_BUFFER_TYPE as EGLint, + egl::RGB_BUFFER as EGLint, + egl::SURFACE_TYPE as EGLint, + egl::PBUFFER_BIT as EGLint, + egl::RENDERABLE_TYPE as EGLint, + egl::OPENGL_ES2_BIT as EGLint, + ], + ) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + + let egl_display = self.egl_display; + + unsafe { + // Create the EGL context. + let gl_api = self.gl_api(); + let egl_context = context::create_context( + egl_display, + descriptor, + share_with.map_or(egl::NO_CONTEXT, |ctx| ctx.egl_context), + gl_api, + )?; + + // Create a dummy pbuffer. + let pbuffer = context::create_dummy_pbuffer(egl_display, egl_context).unwrap(); + + EGL_FUNCTIONS.with(|egl| { + if egl.MakeCurrent(egl_display, pbuffer, pbuffer, egl_context) == egl::FALSE { + let err = egl.GetError().to_windowing_api_error(); + return Err(Error::MakeCurrentFailed(err)); + } + Ok(()) + })?; + + // Wrap up the EGL context. + let context = Context { + egl_context, + id: *next_context_id, + pbuffer, + framebuffer: Framebuffer::None, + context_is_owned: true, + gl: Gl::from_loader_function(context::get_proc_address), + }; + next_context_id.0 += 1; + Ok(context) + } + } + + /// Wraps a native `EGLContext` in a context object. + /// + /// The underlying `EGLContext` is not retained, as there is no way to do this in the EGL API. + /// Therefore, it is the caller's responsibility to keep it alive as long as this `Context` + /// remains alive. + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + + // Create a dummy pbuffer. + let pbuffer = + context::create_dummy_pbuffer(self.egl_display, native_context.egl_context).unwrap(); + + // Create the context. + let context = Context { + egl_context: native_context.egl_context, + id: *next_context_id, + pbuffer, + framebuffer: Framebuffer::External(ExternalEGLSurfaces { + draw: native_context.egl_draw_surface, + read: native_context.egl_read_surface, + }), + context_is_owned: false, + gl: Gl::from_loader_function(context::get_proc_address), + }; + next_context_id.0 += 1; + + Ok(context) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if context.egl_context == egl::NO_CONTEXT { + return Ok(()); + } + + unsafe { + if let Framebuffer::Surface(mut target) = + mem::replace(&mut context.framebuffer, Framebuffer::None) + { + self.destroy_surface(context, &mut target)?; + } + + EGL_FUNCTIONS.with(|egl| { + let result = egl.DestroySurface(self.egl_display, context.pbuffer); + assert_ne!(result, egl::FALSE); + context.pbuffer = egl::NO_SURFACE; + + egl.MakeCurrent( + self.egl_display, + egl::NO_SURFACE, + egl::NO_SURFACE, + egl::NO_CONTEXT, + ); + + if context.context_is_owned { + let result = egl.DestroyContext(self.egl_display, context.egl_context); + assert_ne!(result, egl::FALSE); + } + + context.egl_context = egl::NO_CONTEXT; + }); + } + + Ok(()) + } + + /// Returns the descriptor that this context was created with. + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + ContextDescriptor::from_egl_context(&context.gl, self.egl_display, context.egl_context) + } + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + unsafe { + let egl_display = self.egl_display; + let egl_context = context.egl_context; + + let (egl_draw_surface, egl_read_surface) = match context.framebuffer { + Framebuffer::Surface(Surface { + objects: SurfaceObjects::Window { egl_surface }, + .. + }) => (egl_surface, egl_surface), + Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), + Framebuffer::Surface(Surface { + objects: SurfaceObjects::HardwareBuffer { .. }, + .. + }) => (context.pbuffer, context.pbuffer), + Framebuffer::None => (context.pbuffer, context.pbuffer), + }; + + EGL_FUNCTIONS.with(|egl| { + let result = + egl.MakeCurrent(egl_display, egl_draw_surface, egl_read_surface, egl_context); + if result == egl::FALSE { + let err = egl.GetError().to_windowing_api_error(); + return Err(Error::MakeCurrentFailed(err)); + } + Ok(()) + }) + } + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { context::make_no_context_current(self.egl_display) } + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + pub fn bind_surface_to_context( + &self, + context: &mut Context, + new_surface: Surface, + ) -> Result<(), (Error, Surface)> { + if context.id != new_surface.context_id { + return Err((Error::IncompatibleSurface, new_surface)); + } + + match context.framebuffer { + Framebuffer::External { .. } => return Err((Error::ExternalRenderTarget, new_surface)), + Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, new_surface)), + Framebuffer::None => {} + } + + context.framebuffer = Framebuffer::Surface(new_surface); + Ok(()) + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + match context.framebuffer { + Framebuffer::External { .. } => return Err(Error::ExternalRenderTarget), + Framebuffer::None => return Ok(None), + Framebuffer::Surface(_) => {} + } + + // Make sure all changes are synchronized. + // + // FIXME(pcwalton): Is this necessary? + let _guard = self.temporarily_make_context_current(context)?; + unsafe { + context.gl.flush(); + }; + + match mem::replace(&mut context.framebuffer, Framebuffer::None) { + Framebuffer::Surface(surface) => return Ok(Some(surface)), + Framebuffer::External { .. } | Framebuffer::None => unreachable!(), + } + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + match &context.framebuffer { + Framebuffer::Surface(surface) => self.present_surface_inner(context, surface), + _ => Ok(()), + } + } + + /// If the currently bound surface is a widget surface, resize it, + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + if let Framebuffer::Surface(surface) = &mut context.framebuffer { + surface.resize(size); + } + Ok(()) + } + + /// Returns the attributes that the context descriptor was created with. + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + unsafe { context_descriptor.attributes(self.egl_display) } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + context::get_proc_address(symbol_name) + } + + pub(crate) fn context_to_egl_config(&self, context: &Context) -> EGLConfig { + unsafe { + context::egl_config_from_id( + self.egl_display, + context::get_context_attr( + self.egl_display, + context.egl_context, + egl::CONFIG_ID as EGLint, + ), + ) + } + } + + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.id + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + match context.framebuffer { + Framebuffer::None => Ok(None), + Framebuffer::External { .. } => Err(Error::ExternalRenderTarget), + Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), + } + } + + /// Given a context, returns its underlying EGL context and attached surfaces. + pub fn native_context(&self, context: &Context) -> NativeContext { + let (egl_draw_surface, egl_read_surface) = match context.framebuffer { + Framebuffer::Surface(Surface { + objects: SurfaceObjects::Window { egl_surface }, + .. + }) => (egl_surface, egl_surface), + Framebuffer::External(ExternalEGLSurfaces { draw, read }) => (draw, read), + Framebuffer::Surface(Surface { + objects: SurfaceObjects::HardwareBuffer { .. }, + .. + }) => (context.pbuffer, context.pbuffer), + Framebuffer::None => (context.pbuffer, context.pbuffer), + }; + + NativeContext { + egl_context: context.egl_context, + egl_draw_surface, + egl_read_surface, + } + } } diff --git a/src/mesa_surfaceless/context.rs b/src/mesa_surfaceless/context.rs index 8bf5fa6c..1d097363 100644 --- a/src/mesa_surfaceless/context.rs +++ b/src/mesa_surfaceless/context.rs @@ -1,18 +1,8 @@ //! OpenGL rendering contexts on surfaceless Mesa. -use euclid::default::Size2D; - -use super::device::Device; -use super::surface::Surface; -use crate::base::egl::context::{self, CurrentContextGuard, EGLBackedContext}; -use crate::context::ContextID; -use crate::egl; -use crate::egl::types::EGLint; -use crate::{ContextAttributes, Error, Gl, SurfaceInfo}; - -use std::os::raw::c_void; - +use crate::base::egl::context::EGLBackedContext; pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; +use crate::Gl; /// Represents an OpenGL rendering context. /// @@ -31,230 +21,3 @@ pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; /// /// A context must be explicitly destroyed with `destroy_context()`, or a panic will occur. pub struct Context(pub(crate) EGLBackedContext, pub(crate) Gl); - -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - #[inline] - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - // Set environment variables as appropriate. - self.adapter.set_environment_variables(); - - unsafe { - ContextDescriptor::new( - self.native_connection.egl_display, - attributes, - &[ - egl::SURFACE_TYPE as EGLint, - egl::PBUFFER_BIT as EGLint, - egl::RENDERABLE_TYPE as EGLint, - egl::OPENGL_BIT as EGLint, - egl::COLOR_BUFFER_TYPE as EGLint, - egl::RGB_BUFFER as EGLint, - ], - ) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - #[inline] - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - unsafe { - let context = EGLBackedContext::new( - self.native_connection.egl_display, - descriptor, - share_with.map(|ctx| &ctx.0), - self.gl_api(), - )?; - context.make_current(self.native_connection.egl_display)?; - Ok(Context( - context, - Gl::from_loader_function(context::get_proc_address), - )) - } - } - - /// Wraps an `EGLContext` in a native context and returns it. The context must be current. - /// - /// The context is not retained, as there is no way to do this in the EGL API. Therefore, - /// it is the caller's responsibility to ensure that the returned `Context` object remains - /// alive as long as the `EGLContext` is. - #[inline] - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - Ok(Context( - EGLBackedContext::from_native_context(native_context), - Gl::from_loader_function(context::get_proc_address), - )) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { - self.destroy_surface(context, &mut surface)?; - } - - unsafe { - context.0.destroy(self.native_connection.egl_display); - Ok(()) - } - } - - /// Given a context, returns its underlying EGL context and attached surfaces. - #[inline] - pub fn native_context(&self, context: &Context) -> NativeContext { - context.0.native_context() - } - - /// Returns the descriptor that this context was created with. - #[inline] - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - ContextDescriptor::from_egl_context( - &context.1, - self.native_connection.egl_display, - context.0.egl_context, - ) - } - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - #[inline] - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - unsafe { context.0.make_current(self.native_connection.egl_display) } - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - #[inline] - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { context::make_no_context_current(self.native_connection.egl_display) } - } - - #[inline] - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - /// Returns the attributes that the context descriptor was created with. - #[inline] - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - unsafe { context_descriptor.attributes(self.native_connection.egl_display) } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - context::get_proc_address(symbol_name) - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - #[inline] - pub fn bind_surface_to_context( - &self, - context: &mut Context, - surface: Surface, - ) -> Result<(), (Error, Surface)> { - unsafe { - context - .0 - .bind_surface(self.native_connection.egl_display, surface.0) - .map_err(|(err, surface)| (err, Surface(surface))) - } - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - unsafe { - context - .0 - .unbind_surface(&context.1, self.native_connection.egl_display) - .map(|maybe_surface| maybe_surface.map(Surface)) - } - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - context - .0 - .present_bound_surface(self.native_connection.egl_display) - } - - /// Resizes a widget surface. - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - context.0.resize_bound_surface(size) - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.0.id - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - #[inline] - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - context.0.surface_info() - } -} diff --git a/src/mesa_surfaceless/device.rs b/src/mesa_surfaceless/device.rs index 981b1cb4..2f52006c 100644 --- a/src/mesa_surfaceless/device.rs +++ b/src/mesa_surfaceless/device.rs @@ -1,14 +1,29 @@ //! A wrapper around surfaceless Mesa `EGLDisplay`s. use super::connection::{Connection, NativeConnectionWrapper}; -use crate::{Error, GLApi}; - +use super::surface::SurfaceDataGuard; +use crate::base::egl::context::{self, CurrentContextGuard, EGLBackedContext}; +use crate::base::egl::surface::EGLBackedSurface; +use crate::context::ContextID; +use crate::egl; +use crate::egl::types::EGLint; +use crate::gl; +use crate::mesa_surfaceless::context::Context; +use crate::mesa_surfaceless::surface::{NativeWidget, Surface, SurfaceTexture}; +use crate::{ContextAttributes, Gl, SurfaceInfo}; +use crate::{ContextDescriptor, Error, GLApi, NativeContext, SurfaceAccess, SurfaceType}; +use euclid::default::Size2D; +use glow::Texture; use std::env; +use std::os::raw::c_void; use std::sync::Arc; static MESA_SOFTWARE_RENDERING_ENV_VAR: &str = "LIBGL_ALWAYS_SOFTWARE"; static MESA_DRI_PRIME_ENV_VAR: &str = "DRI_PRIME"; +// FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? +const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; + /// Represents a hardware display adapter that can be used for rendering (including the CPU). /// /// Adapters can be sent between threads. To render with an adapter, open a thread-local `Device`. @@ -116,4 +131,382 @@ impl Device { pub fn gl_api(&self) -> GLApi { GLApi::GL } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + #[inline] + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + // Set environment variables as appropriate. + self.adapter.set_environment_variables(); + + unsafe { + ContextDescriptor::new( + self.native_connection.egl_display, + attributes, + &[ + egl::SURFACE_TYPE as EGLint, + egl::PBUFFER_BIT as EGLint, + egl::RENDERABLE_TYPE as EGLint, + egl::OPENGL_BIT as EGLint, + egl::COLOR_BUFFER_TYPE as EGLint, + egl::RGB_BUFFER as EGLint, + ], + ) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + #[inline] + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + unsafe { + let context = EGLBackedContext::new( + self.native_connection.egl_display, + descriptor, + share_with.map(|ctx| &ctx.0), + self.gl_api(), + )?; + context.make_current(self.native_connection.egl_display)?; + Ok(Context( + context, + Gl::from_loader_function(context::get_proc_address), + )) + } + } + + /// Wraps an `EGLContext` in a native context and returns it. The context must be current. + /// + /// The context is not retained, as there is no way to do this in the EGL API. Therefore, + /// it is the caller's responsibility to ensure that the returned `Context` object remains + /// alive as long as the `EGLContext` is. + #[inline] + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + Ok(Context( + EGLBackedContext::from_native_context(native_context), + Gl::from_loader_function(context::get_proc_address), + )) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { + self.destroy_surface(context, &mut surface)?; + } + + unsafe { + context.0.destroy(self.native_connection.egl_display); + Ok(()) + } + } + + /// Given a context, returns its underlying EGL context and attached surfaces. + #[inline] + pub fn native_context(&self, context: &Context) -> NativeContext { + context.0.native_context() + } + + /// Returns the descriptor that this context was created with. + #[inline] + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + ContextDescriptor::from_egl_context( + &context.1, + self.native_connection.egl_display, + context.0.egl_context, + ) + } + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + #[inline] + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + unsafe { context.0.make_current(self.native_connection.egl_display) } + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + #[inline] + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { context::make_no_context_current(self.native_connection.egl_display) } + } + + #[inline] + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + /// Returns the attributes that the context descriptor was created with. + #[inline] + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + unsafe { context_descriptor.attributes(self.native_connection.egl_display) } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + context::get_proc_address(symbol_name) + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + #[inline] + pub fn bind_surface_to_context( + &self, + context: &mut Context, + surface: Surface, + ) -> Result<(), (Error, Surface)> { + unsafe { + context + .0 + .bind_surface(self.native_connection.egl_display, surface.0) + .map_err(|(err, surface)| (err, Surface(surface))) + } + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + unsafe { + context + .0 + .unbind_surface(&context.1, self.native_connection.egl_display) + .map(|maybe_surface| maybe_surface.map(Surface)) + } + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + context + .0 + .present_bound_surface(self.native_connection.egl_display) + } + + /// Resizes a widget surface. + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + context.0.resize_bound_surface(size) + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.0.id + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + #[inline] + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + context.0.surface_info() + } + + /// Creates either a generic or a widget surface, depending on the supplied surface type. + /// + /// Only the given context may ever render to the surface, but generic surfaces can be wrapped + /// up in a `SurfaceTexture` for reading by other contexts. + pub fn create_surface( + &self, + context: &Context, + _: SurfaceAccess, + surface_type: SurfaceType, + ) -> Result { + match surface_type { + SurfaceType::Generic { size } => self.create_generic_surface(context, &size), + SurfaceType::Widget { .. } => Err(Error::UnsupportedOnThisPlatform), + } + } + + fn create_generic_surface( + &self, + context: &Context, + size: &Size2D, + ) -> Result { + let _guard = self.temporarily_make_context_current(context)?; + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + + Ok(Surface(EGLBackedSurface::new_generic( + &context.1, + self.native_connection.egl_display, + context.0.egl_context, + context.0.id, + &context_attributes, + size, + ))) + } + + /// Creates a surface texture from an existing generic surface for use with the given context. + /// + /// The surface texture is local to the supplied context and takes ownership of the surface. + /// Destroying the surface texture allows you to retrieve the surface again. + /// + /// *The supplied context does not have to be the same context that the surface is associated + /// with.* This allows you to render to a surface in one context and sample from that surface + /// in another context. + /// + /// Calling this method on a widget surface returns a `WidgetAttached` error. + pub fn create_surface_texture( + &self, + context: &mut Context, + surface: Surface, + ) -> Result { + let _guard = match self.temporarily_make_context_current(context) { + Ok(guard) => guard, + Err(err) => return Err((err, surface)), + }; + + match surface.0.to_surface_texture(&context.1) { + Ok(surface_texture) => Ok(SurfaceTexture(surface_texture)), + Err((err, surface)) => Err((err, Surface(surface))), + } + } + + /// Destroys a surface. + /// + /// The supplied context must be the context the surface is associated with, or this returns + /// an `IncompatibleSurface` error. + /// + /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in + /// the `drop` method. + pub fn destroy_surface( + &self, + context: &mut Context, + surface: &mut Surface, + ) -> Result<(), Error> { + let egl_display = self.native_connection.egl_display; + let window = surface.0.destroy(&context.1, egl_display, context.0.id)?; + debug_assert!(window.is_none()); + Ok(()) + } + + /// Destroys a surface texture and returns the underlying surface. + /// + /// The supplied context must be the same context the surface texture was created with, or an + /// `IncompatibleSurfaceTexture` error is returned. + /// + /// All surface textures must be explicitly destroyed with this function, or a panic will + /// occur. + pub fn destroy_surface_texture( + &self, + context: &mut Context, + surface_texture: SurfaceTexture, + ) -> Result { + match self.temporarily_make_context_current(context) { + Ok(_guard) => Ok(Surface(surface_texture.0.destroy(&context.1))), + Err(err) => Err((err, surface_texture)), + } + } + + /// Displays the contents of a widget surface on screen. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't show up in their + /// associated widgets until this method is called. + /// + /// The supplied context must match the context the surface was created with, or an + /// `IncompatibleSurface` error is returned. + pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { + surface + .0 + .present(self.native_connection.egl_display, context.0.egl_context) + } + + /// Resizes a widget surface. + pub fn resize_surface( + &self, + _context: &Context, + surface: &mut Surface, + size: Size2D, + ) -> Result<(), Error> { + surface.0.size = size; + Ok(()) + } + + /// Returns a pointer to the underlying surface data for reading or writing by the CPU. + #[inline] + pub fn lock_surface_data<'s>(&self, _: &'s mut Surface) -> Result, Error> { + Err(Error::Unimplemented) + } + + /// Returns the OpenGL texture target needed to read from this surface texture. + /// + /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. + #[inline] + pub fn surface_gl_texture_target(&self) -> u32 { + SURFACE_GL_TEXTURE_TARGET + } + + /// Returns various information about the surface, including the framebuffer object needed to + /// render to this surface. + /// + /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` + /// on the framebuffer object returned by this function. This framebuffer object may or not be + /// 0, the default framebuffer, depending on platform. + pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { + surface.0.info() + } + + /// Returns the OpenGL texture object containing the contents of this surface. + /// + /// It is only legal to read from, not write to, this texture object. + #[inline] + pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { + surface_texture.0.texture_object + } } diff --git a/src/mesa_surfaceless/surface.rs b/src/mesa_surfaceless/surface.rs index 48c0472e..71dce051 100644 --- a/src/mesa_surfaceless/surface.rs +++ b/src/mesa_surfaceless/surface.rs @@ -2,19 +2,10 @@ //! //! Wrapper for EGL surfaces on Mesa. -use super::context::Context; -use super::device::Device; use crate::base::egl::surface::{EGLBackedSurface, EGLSurfaceTexture}; -use crate::gl; -use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; -use euclid::default::Size2D; -use glow::Texture; use std::marker::PhantomData; -// FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? -const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; - /// Represents a hardware buffer of pixels that can be rendered to via the CPU or GPU and either /// displayed in a native widget or bound to a texture for reading. /// @@ -52,161 +43,6 @@ pub struct NativeWidget; unsafe impl Send for Surface {} -impl Device { - /// Creates either a generic or a widget surface, depending on the supplied surface type. - /// - /// Only the given context may ever render to the surface, but generic surfaces can be wrapped - /// up in a `SurfaceTexture` for reading by other contexts. - pub fn create_surface( - &self, - context: &Context, - _: SurfaceAccess, - surface_type: SurfaceType, - ) -> Result { - match surface_type { - SurfaceType::Generic { size } => self.create_generic_surface(context, &size), - SurfaceType::Widget { .. } => Err(Error::UnsupportedOnThisPlatform), - } - } - - fn create_generic_surface( - &self, - context: &Context, - size: &Size2D, - ) -> Result { - let _guard = self.temporarily_make_context_current(context)?; - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - - Ok(Surface(EGLBackedSurface::new_generic( - &context.1, - self.native_connection.egl_display, - context.0.egl_context, - context.0.id, - &context_attributes, - size, - ))) - } - - /// Creates a surface texture from an existing generic surface for use with the given context. - /// - /// The surface texture is local to the supplied context and takes ownership of the surface. - /// Destroying the surface texture allows you to retrieve the surface again. - /// - /// *The supplied context does not have to be the same context that the surface is associated - /// with.* This allows you to render to a surface in one context and sample from that surface - /// in another context. - /// - /// Calling this method on a widget surface returns a `WidgetAttached` error. - pub fn create_surface_texture( - &self, - context: &mut Context, - surface: Surface, - ) -> Result { - let _guard = match self.temporarily_make_context_current(context) { - Ok(guard) => guard, - Err(err) => return Err((err, surface)), - }; - - match surface.0.to_surface_texture(&context.1) { - Ok(surface_texture) => Ok(SurfaceTexture(surface_texture)), - Err((err, surface)) => Err((err, Surface(surface))), - } - } - - /// Destroys a surface. - /// - /// The supplied context must be the context the surface is associated with, or this returns - /// an `IncompatibleSurface` error. - /// - /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in - /// the `drop` method. - pub fn destroy_surface( - &self, - context: &mut Context, - surface: &mut Surface, - ) -> Result<(), Error> { - let egl_display = self.native_connection.egl_display; - let window = surface.0.destroy(&context.1, egl_display, context.0.id)?; - debug_assert!(window.is_none()); - Ok(()) - } - - /// Destroys a surface texture and returns the underlying surface. - /// - /// The supplied context must be the same context the surface texture was created with, or an - /// `IncompatibleSurfaceTexture` error is returned. - /// - /// All surface textures must be explicitly destroyed with this function, or a panic will - /// occur. - pub fn destroy_surface_texture( - &self, - context: &mut Context, - surface_texture: SurfaceTexture, - ) -> Result { - match self.temporarily_make_context_current(context) { - Ok(_guard) => Ok(Surface(surface_texture.0.destroy(&context.1))), - Err(err) => Err((err, surface_texture)), - } - } - - /// Displays the contents of a widget surface on screen. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't show up in their - /// associated widgets until this method is called. - /// - /// The supplied context must match the context the surface was created with, or an - /// `IncompatibleSurface` error is returned. - pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { - surface - .0 - .present(self.native_connection.egl_display, context.0.egl_context) - } - - /// Resizes a widget surface. - pub fn resize_surface( - &self, - _context: &Context, - surface: &mut Surface, - size: Size2D, - ) -> Result<(), Error> { - surface.0.size = size; - Ok(()) - } - - /// Returns a pointer to the underlying surface data for reading or writing by the CPU. - #[inline] - pub fn lock_surface_data<'s>(&self, _: &'s mut Surface) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Returns the OpenGL texture target needed to read from this surface texture. - /// - /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. - #[inline] - pub fn surface_gl_texture_target(&self) -> u32 { - SURFACE_GL_TEXTURE_TARGET - } - - /// Returns various information about the surface, including the framebuffer object needed to - /// render to this surface. - /// - /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` - /// on the framebuffer object returned by this function. This framebuffer object may or not be - /// 0, the default framebuffer, depending on platform. - pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { - surface.0.info() - } - - /// Returns the OpenGL texture object containing the contents of this surface. - /// - /// It is only legal to read from, not write to, this texture object. - #[inline] - pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { - surface_texture.0.texture_object - } -} - /// Represents the CPU view of the pixel data of this surface. pub struct SurfaceDataGuard<'a> { phantom: PhantomData<&'a ()>, diff --git a/src/wayland/context.rs b/src/wayland/context.rs index a91440c2..622a04c1 100644 --- a/src/wayland/context.rs +++ b/src/wayland/context.rs @@ -1,19 +1,8 @@ //! OpenGL rendering contexts on Wayland. -use euclid::default::Size2D; - -use super::device::Device; -use super::surface::Surface; -use crate::base::egl::context::{self, CurrentContextGuard, EGLBackedContext}; -use crate::context::ContextID; -use crate::egl; -use crate::egl::types::EGLint; -use crate::surface::Framebuffer; -use crate::{ContextAttributes, Error, Gl, SurfaceInfo}; - -use std::os::raw::c_void; - +use crate::base::egl::context::EGLBackedContext; pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; +use crate::Gl; /// Represents an OpenGL rendering context. /// @@ -32,231 +21,3 @@ pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; /// /// A context must be explicitly destroyed with `destroy_context()`, or a panic will occur. pub struct Context(pub(crate) EGLBackedContext, pub(crate) Gl); - -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - #[inline] - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - // Set environment variables as appropriate. - self.adapter.set_environment_variables(); - - unsafe { - ContextDescriptor::new( - self.native_connection.egl_display, - attributes, - &[ - egl::SURFACE_TYPE as EGLint, - egl::WINDOW_BIT as EGLint, - egl::RENDERABLE_TYPE as EGLint, - egl::OPENGL_BIT as EGLint, - ], - ) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - #[inline] - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - unsafe { - let context = EGLBackedContext::new( - self.native_connection.egl_display, - descriptor, - share_with.map(|ctx| &ctx.0), - self.gl_api(), - )?; - context.make_current(self.native_connection.egl_display)?; - Ok(Context( - context, - Gl::from_loader_function(context::get_proc_address), - )) - } - } - - /// Wraps an `EGLContext` in a native context and returns it. - /// - /// The context is not retained, as there is no way to do this in the EGL API. Therefore, - /// it is the caller's responsibility to ensure that the returned `Context` object remains - /// alive as long as the `EGLContext` is. - #[inline] - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - Ok(Context( - EGLBackedContext::from_native_context(native_context), - Gl::from_loader_function(context::get_proc_address), - )) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { - self.destroy_surface(context, &mut surface)?; - } - - unsafe { - context.0.destroy(self.native_connection.egl_display); - Ok(()) - } - } - - /// Given a context, returns its underlying EGL context and attached surfaces. - #[inline] - pub fn native_context(&self, context: &Context) -> NativeContext { - context.0.native_context() - } - - /// Returns the descriptor that this context was created with. - #[inline] - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - ContextDescriptor::from_egl_context( - &context.1, - self.native_connection.egl_display, - context.0.egl_context, - ) - } - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - #[inline] - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - unsafe { context.0.make_current(self.native_connection.egl_display) } - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - #[inline] - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { context::make_no_context_current(self.native_connection.egl_display) } - } - - #[inline] - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - /// Returns the attributes that the context descriptor was created with. - #[inline] - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - unsafe { context_descriptor.attributes(self.native_connection.egl_display) } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - context::get_proc_address(symbol_name) - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - #[inline] - pub fn bind_surface_to_context( - &self, - context: &mut Context, - surface: Surface, - ) -> Result<(), (Error, Surface)> { - unsafe { - context - .0 - .bind_surface(self.native_connection.egl_display, surface.0) - .map_err(|(err, surface)| (err, Surface(surface))) - } - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - unsafe { - context - .0 - .unbind_surface(&context.1, self.native_connection.egl_display) - .map(|maybe_surface| maybe_surface.map(Surface)) - } - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - context - .0 - .present_bound_surface(self.native_connection.egl_display) - } - - /// If the currently bound surface is a widget surface, resize it, - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - match &mut context.0.framebuffer { - Framebuffer::Surface(surface) => surface.resize_for_wayland(size), - _ => Ok(()), - } - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.0.id - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - #[inline] - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - context.0.surface_info() - } -} diff --git a/src/wayland/device.rs b/src/wayland/device.rs index 22eb0668..39942d00 100644 --- a/src/wayland/device.rs +++ b/src/wayland/device.rs @@ -1,11 +1,29 @@ //! A wrapper around Wayland `EGLDisplay`s. use super::connection::{Connection, NativeConnectionWrapper}; -use crate::{Error, GLApi}; - +use super::surface::{Surface, SurfaceDataGuard, SurfaceTexture}; +use crate::base::egl::context::{self, CurrentContextGuard, EGLBackedContext}; +use crate::base::egl::surface::EGLBackedSurface; +use crate::context::ContextID; +use crate::egl; +use crate::egl::types::EGLint; +use crate::gl; +pub use crate::mesa_surfaceless::device::Adapter; +use crate::surface::Framebuffer; +use crate::{ + Context, ContextDescriptor, Error, GLApi, NativeContext, NativeWidget, SurfaceAccess, + SurfaceType, +}; +use crate::{ContextAttributes, Gl, SurfaceInfo}; +use euclid::default::Size2D; +use glow::Texture; +use std::os::raw::c_void; use std::sync::Arc; +use wayland_sys::client::wl_proxy; +use wayland_sys::egl::{wayland_egl_handle, wl_egl_window}; -pub use crate::mesa_surfaceless::device::Adapter; +// FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? +const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; /// A thread-local handle to a device. /// @@ -67,4 +85,419 @@ impl Device { GLApi::GL } } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + #[inline] + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + // Set environment variables as appropriate. + self.adapter.set_environment_variables(); + + unsafe { + ContextDescriptor::new( + self.native_connection.egl_display, + attributes, + &[ + egl::SURFACE_TYPE as EGLint, + egl::WINDOW_BIT as EGLint, + egl::RENDERABLE_TYPE as EGLint, + egl::OPENGL_BIT as EGLint, + ], + ) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + #[inline] + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + unsafe { + let context = EGLBackedContext::new( + self.native_connection.egl_display, + descriptor, + share_with.map(|ctx| &ctx.0), + self.gl_api(), + )?; + context.make_current(self.native_connection.egl_display)?; + Ok(Context( + context, + Gl::from_loader_function(context::get_proc_address), + )) + } + } + + /// Wraps an `EGLContext` in a native context and returns it. + /// + /// The context is not retained, as there is no way to do this in the EGL API. Therefore, + /// it is the caller's responsibility to ensure that the returned `Context` object remains + /// alive as long as the `EGLContext` is. + #[inline] + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + Ok(Context( + EGLBackedContext::from_native_context(native_context), + Gl::from_loader_function(context::get_proc_address), + )) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { + self.destroy_surface(context, &mut surface)?; + } + + unsafe { + context.0.destroy(self.native_connection.egl_display); + Ok(()) + } + } + + /// Given a context, returns its underlying EGL context and attached surfaces. + #[inline] + pub fn native_context(&self, context: &Context) -> NativeContext { + context.0.native_context() + } + + /// Returns the descriptor that this context was created with. + #[inline] + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + ContextDescriptor::from_egl_context( + &context.1, + self.native_connection.egl_display, + context.0.egl_context, + ) + } + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + #[inline] + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + unsafe { context.0.make_current(self.native_connection.egl_display) } + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + #[inline] + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { context::make_no_context_current(self.native_connection.egl_display) } + } + + #[inline] + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + /// Returns the attributes that the context descriptor was created with. + #[inline] + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + unsafe { context_descriptor.attributes(self.native_connection.egl_display) } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + context::get_proc_address(symbol_name) + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + #[inline] + pub fn bind_surface_to_context( + &self, + context: &mut Context, + surface: Surface, + ) -> Result<(), (Error, Surface)> { + unsafe { + context + .0 + .bind_surface(self.native_connection.egl_display, surface.0) + .map_err(|(err, surface)| (err, Surface(surface))) + } + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + unsafe { + context + .0 + .unbind_surface(&context.1, self.native_connection.egl_display) + .map(|maybe_surface| maybe_surface.map(Surface)) + } + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + context + .0 + .present_bound_surface(self.native_connection.egl_display) + } + + /// If the currently bound surface is a widget surface, resize it, + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + match &mut context.0.framebuffer { + Framebuffer::Surface(surface) => surface.resize_for_wayland(size), + _ => Ok(()), + } + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.0.id + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + #[inline] + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + context.0.surface_info() + } + + /// Creates either a generic or a widget surface, depending on the supplied surface type. + /// + /// Only the given context may ever render to the surface, but generic surfaces can be wrapped + /// up in a `SurfaceTexture` for reading by other contexts. + pub fn create_surface( + &self, + context: &Context, + _: SurfaceAccess, + surface_type: SurfaceType, + ) -> Result { + match surface_type { + SurfaceType::Generic { size } => self.create_generic_surface(context, &size), + SurfaceType::Widget { native_widget } => unsafe { + self.create_window_surface( + context, + native_widget.wayland_surface, + &native_widget.size, + ) + }, + } + } + + fn create_generic_surface( + &self, + context: &Context, + size: &Size2D, + ) -> Result { + let _guard = self.temporarily_make_context_current(context)?; + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + + Ok(Surface(EGLBackedSurface::new_generic( + &context.1, + self.native_connection.egl_display, + context.0.egl_context, + context.0.id, + &context_attributes, + size, + ))) + } + + unsafe fn create_window_surface( + &self, + context: &Context, + wayland_surface: *mut wl_proxy, + size: &Size2D, + ) -> Result { + let egl_window = + (wayland_egl_handle().wl_egl_window_create)(wayland_surface, size.width, size.height); + assert!(!egl_window.is_null()); + + let context_descriptor = self.context_descriptor(context); + let egl_config = context::egl_config_from_id( + self.native_connection.egl_display, + context_descriptor.egl_config_id, + ); + + Ok(Surface(EGLBackedSurface::new_window( + self.native_connection.egl_display, + egl_config, + egl_window as *mut c_void, + context.0.id, + size, + ))) + } + + /// Creates a surface texture from an existing generic surface for use with the given context. + /// + /// The surface texture is local to the supplied context and takes ownership of the surface. + /// Destroying the surface texture allows you to retrieve the surface again. + /// + /// *The supplied context does not have to be the same context that the surface is associated + /// with.* This allows you to render to a surface in one context and sample from that surface + /// in another context. + /// + /// Calling this method on a widget surface returns a `WidgetAttached` error. + pub fn create_surface_texture( + &self, + context: &mut Context, + surface: Surface, + ) -> Result { + let _guard = match self.temporarily_make_context_current(context) { + Ok(guard) => guard, + Err(err) => return Err((err, surface)), + }; + + match surface.0.to_surface_texture(&context.1) { + Ok(surface_texture) => Ok(SurfaceTexture(surface_texture)), + Err((err, surface)) => Err((err, Surface(surface))), + } + } + + /// Destroys a surface. + /// + /// The supplied context must be the context the surface is associated with, or this returns + /// an `IncompatibleSurface` error. + /// + /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in + /// the `drop` method. + pub fn destroy_surface( + &self, + context: &mut Context, + surface: &mut Surface, + ) -> Result<(), Error> { + let egl_display = self.native_connection.egl_display; + if let Some(wayland_egl_window) = + surface.0.destroy(&context.1, egl_display, context.0.id)? + { + unsafe { + let wayland_egl_window = wayland_egl_window as *mut wl_egl_window; + (wayland_egl_handle().wl_egl_window_destroy)(wayland_egl_window); + } + } + Ok(()) + } + + /// Destroys a surface texture and returns the underlying surface. + /// + /// The supplied context must be the same context the surface texture was created with, or an + /// `IncompatibleSurfaceTexture` error is returned. + /// + /// All surface textures must be explicitly destroyed with this function, or a panic will + /// occur. + pub fn destroy_surface_texture( + &self, + context: &mut Context, + surface_texture: SurfaceTexture, + ) -> Result { + match self.temporarily_make_context_current(context) { + Ok(_guard) => Ok(Surface(surface_texture.0.destroy(&context.1))), + Err(err) => Err((err, surface_texture)), + } + } + + /// Displays the contents of a widget surface on screen. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't show up in their + /// associated widgets until this method is called. + /// + /// The supplied context must match the context the surface was created with, or an + /// `IncompatibleSurface` error is returned. + pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { + surface + .0 + .present(self.native_connection.egl_display, context.0.egl_context) + } + + /// Resizes a widget surface. + pub fn resize_surface( + &self, + _context: &Context, + surface: &mut Surface, + size: Size2D, + ) -> Result<(), Error> { + surface.0.resize_for_wayland(size) + } + + /// Returns a pointer to the underlying surface data for reading or writing by the CPU. + #[inline] + pub fn lock_surface_data<'s>(&self, _: &'s mut Surface) -> Result, Error> { + Err(Error::Unimplemented) + } + + /// Returns the OpenGL texture target needed to read from this surface texture. + /// + /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. + #[inline] + pub fn surface_gl_texture_target(&self) -> u32 { + SURFACE_GL_TEXTURE_TARGET + } + + /// Returns various information about the surface, including the framebuffer object needed to + /// render to this surface. + /// + /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` + /// on the framebuffer object returned by this function. This framebuffer object may or not be + /// 0, the default framebuffer, depending on platform. + pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { + surface.0.info() + } + + /// Returns the OpenGL texture object containing the contents of this surface. + /// + /// It is only legal to read from, not write to, this texture object. + #[inline] + pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { + surface_texture.0.texture_object + } } diff --git a/src/wayland/surface.rs b/src/wayland/surface.rs index 897ea8d3..38901b6a 100644 --- a/src/wayland/surface.rs +++ b/src/wayland/surface.rs @@ -1,22 +1,14 @@ //! A surface implementation using Wayland surfaces backed by TextureImage. -use super::context::Context; -use super::device::Device; -use crate::base::egl::context; use crate::base::egl::surface::{EGLBackedSurface, EGLSurfaceTexture}; -use crate::gl; -use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; +use crate::Error; use euclid::default::Size2D; -use glow::Texture; use std::marker::PhantomData; use std::os::raw::c_void; use wayland_sys::client::wl_proxy; use wayland_sys::egl::{wayland_egl_handle, wl_egl_window}; -// FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? -const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; - /// Represents a hardware buffer of pixels that can be rendered to via the CPU or GPU and either /// displayed in a native widget or bound to a texture for reading. /// @@ -57,197 +49,6 @@ pub struct NativeWidget { unsafe impl Send for Surface {} -impl Device { - /// Creates either a generic or a widget surface, depending on the supplied surface type. - /// - /// Only the given context may ever render to the surface, but generic surfaces can be wrapped - /// up in a `SurfaceTexture` for reading by other contexts. - pub fn create_surface( - &self, - context: &Context, - _: SurfaceAccess, - surface_type: SurfaceType, - ) -> Result { - match surface_type { - SurfaceType::Generic { size } => self.create_generic_surface(context, &size), - SurfaceType::Widget { native_widget } => unsafe { - self.create_window_surface( - context, - native_widget.wayland_surface, - &native_widget.size, - ) - }, - } - } - - fn create_generic_surface( - &self, - context: &Context, - size: &Size2D, - ) -> Result { - let _guard = self.temporarily_make_context_current(context)?; - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - - Ok(Surface(EGLBackedSurface::new_generic( - &context.1, - self.native_connection.egl_display, - context.0.egl_context, - context.0.id, - &context_attributes, - size, - ))) - } - - unsafe fn create_window_surface( - &self, - context: &Context, - wayland_surface: *mut wl_proxy, - size: &Size2D, - ) -> Result { - let egl_window = - (wayland_egl_handle().wl_egl_window_create)(wayland_surface, size.width, size.height); - assert!(!egl_window.is_null()); - - let context_descriptor = self.context_descriptor(context); - let egl_config = context::egl_config_from_id( - self.native_connection.egl_display, - context_descriptor.egl_config_id, - ); - - Ok(Surface(EGLBackedSurface::new_window( - self.native_connection.egl_display, - egl_config, - egl_window as *mut c_void, - context.0.id, - size, - ))) - } - - /// Creates a surface texture from an existing generic surface for use with the given context. - /// - /// The surface texture is local to the supplied context and takes ownership of the surface. - /// Destroying the surface texture allows you to retrieve the surface again. - /// - /// *The supplied context does not have to be the same context that the surface is associated - /// with.* This allows you to render to a surface in one context and sample from that surface - /// in another context. - /// - /// Calling this method on a widget surface returns a `WidgetAttached` error. - pub fn create_surface_texture( - &self, - context: &mut Context, - surface: Surface, - ) -> Result { - let _guard = match self.temporarily_make_context_current(context) { - Ok(guard) => guard, - Err(err) => return Err((err, surface)), - }; - - match surface.0.to_surface_texture(&context.1) { - Ok(surface_texture) => Ok(SurfaceTexture(surface_texture)), - Err((err, surface)) => Err((err, Surface(surface))), - } - } - - /// Destroys a surface. - /// - /// The supplied context must be the context the surface is associated with, or this returns - /// an `IncompatibleSurface` error. - /// - /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in - /// the `drop` method. - pub fn destroy_surface( - &self, - context: &mut Context, - surface: &mut Surface, - ) -> Result<(), Error> { - let egl_display = self.native_connection.egl_display; - if let Some(wayland_egl_window) = - surface.0.destroy(&context.1, egl_display, context.0.id)? - { - unsafe { - let wayland_egl_window = wayland_egl_window as *mut wl_egl_window; - (wayland_egl_handle().wl_egl_window_destroy)(wayland_egl_window); - } - } - Ok(()) - } - - /// Destroys a surface texture and returns the underlying surface. - /// - /// The supplied context must be the same context the surface texture was created with, or an - /// `IncompatibleSurfaceTexture` error is returned. - /// - /// All surface textures must be explicitly destroyed with this function, or a panic will - /// occur. - pub fn destroy_surface_texture( - &self, - context: &mut Context, - surface_texture: SurfaceTexture, - ) -> Result { - match self.temporarily_make_context_current(context) { - Ok(_guard) => Ok(Surface(surface_texture.0.destroy(&context.1))), - Err(err) => Err((err, surface_texture)), - } - } - - /// Displays the contents of a widget surface on screen. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't show up in their - /// associated widgets until this method is called. - /// - /// The supplied context must match the context the surface was created with, or an - /// `IncompatibleSurface` error is returned. - pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { - surface - .0 - .present(self.native_connection.egl_display, context.0.egl_context) - } - - /// Resizes a widget surface. - pub fn resize_surface( - &self, - _context: &Context, - surface: &mut Surface, - size: Size2D, - ) -> Result<(), Error> { - surface.0.resize_for_wayland(size) - } - - /// Returns a pointer to the underlying surface data for reading or writing by the CPU. - #[inline] - pub fn lock_surface_data<'s>(&self, _: &'s mut Surface) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Returns the OpenGL texture target needed to read from this surface texture. - /// - /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. - #[inline] - pub fn surface_gl_texture_target(&self) -> u32 { - SURFACE_GL_TEXTURE_TARGET - } - - /// Returns various information about the surface, including the framebuffer object needed to - /// render to this surface. - /// - /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` - /// on the framebuffer object returned by this function. This framebuffer object may or not be - /// 0, the default framebuffer, depending on platform. - pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { - surface.0.info() - } - - /// Returns the OpenGL texture object containing the contents of this surface. - /// - /// It is only legal to read from, not write to, this texture object. - #[inline] - pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { - surface_texture.0.texture_object - } -} - /// Represents the CPU view of the pixel data of this surface. pub struct SurfaceDataGuard<'a> { phantom: PhantomData<&'a ()>, diff --git a/src/wgl/context.rs b/src/wgl/context.rs index 88aac81b..ca3d9646 100644 --- a/src/wgl/context.rs +++ b/src/wgl/context.rs @@ -2,22 +2,15 @@ // //! Wrapper for WGL contexts on Windows. -use super::device::{DCGuard, Device, HiddenWindow}; -use super::surface::{Surface, Win32Objects}; -use crate::context::{self, CREATE_CONTEXT_MUTEX}; -use crate::surface::Framebuffer; -use crate::{ContextAttributeFlags, ContextAttributes, ContextID, Error, GLVersion}; -use crate::{SurfaceInfo, WindowingApiError}; - +use super::device::HiddenWindow; +use super::surface::Surface; use crate::gl; -type GLenum = c_uint; -type GLint = c_int; -type GLuint = c_uint; +use crate::surface::Framebuffer; use crate::Gl; -use euclid::default::Size2D; +use crate::{ContextID, Error, GLVersion}; use glow::HasContext; use std::borrow::Cow; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::ptr; @@ -35,38 +28,24 @@ use winapi::um::wingdi::{PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTO use winapi::um::winuser::{self, COLOR_BACKGROUND, CREATESTRUCTA, CS_OWNDC, WM_CREATE, WNDCLASSA}; use winapi::um::winuser::{WS_OVERLAPPEDWINDOW, WS_VISIBLE}; -const WGL_DRAW_TO_WINDOW_ARB: GLenum = 0x2001; -const WGL_ACCELERATION_ARB: GLenum = 0x2003; -const WGL_SUPPORT_OPENGL_ARB: GLenum = 0x2010; -const WGL_DOUBLE_BUFFER_ARB: GLenum = 0x2011; -const WGL_PIXEL_TYPE_ARB: GLenum = 0x2013; -const WGL_COLOR_BITS_ARB: GLenum = 0x2014; -const WGL_ALPHA_BITS_ARB: GLenum = 0x201b; -const WGL_DEPTH_BITS_ARB: GLenum = 0x2022; -const WGL_STENCIL_BITS_ARB: GLenum = 0x2023; -const WGL_FULL_ACCELERATION_ARB: GLenum = 0x2027; -const WGL_TYPE_RGBA_ARB: GLenum = 0x202b; -const WGL_CONTEXT_MAJOR_VERSION_ARB: GLenum = 0x2091; -const WGL_CONTEXT_MINOR_VERSION_ARB: GLenum = 0x2092; -const WGL_CONTEXT_PROFILE_MASK_ARB: GLenum = 0x9126; - -const WGL_CONTEXT_CORE_PROFILE_BIT_ARB: GLenum = 0x00000001; -const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: GLenum = 0x00000002; +type GLenum = c_uint; +type GLint = c_int; +type GLuint = c_uint; #[allow(non_snake_case)] #[derive(Default)] pub(crate) struct WGLExtensionFunctions { - CreateContextAttribsARB: Option< + pub(crate) CreateContextAttribsARB: Option< unsafe extern "C" fn(hDC: HDC, shareContext: HGLRC, attribList: *const c_int) -> HGLRC, >, - GetExtensionsStringARB: Option *const c_char>, + pub(crate) GetExtensionsStringARB: Option *const c_char>, pub(crate) pixel_format_functions: Option, pub(crate) dx_interop_functions: Option, } #[allow(non_snake_case)] pub(crate) struct WGLPixelFormatExtensionFunctions { - ChoosePixelFormatARB: unsafe extern "C" fn( + pub(crate) ChoosePixelFormatARB: unsafe extern "C" fn( hdc: HDC, piAttribIList: *const c_int, pfAttribFList: *const FLOAT, @@ -74,7 +53,7 @@ pub(crate) struct WGLPixelFormatExtensionFunctions { piFormats: *mut c_int, nNumFormats: *mut UINT, ) -> BOOL, - GetPixelFormatAttribivARB: unsafe extern "C" fn( + pub(crate) GetPixelFormatAttribivARB: unsafe extern "C" fn( hdc: HDC, iPixelFormat: c_int, iLayerPlane: c_int, @@ -109,9 +88,9 @@ pub(crate) struct WGLDXInteropExtensionFunctions { /// These are local to a device. #[derive(Clone)] pub struct ContextDescriptor { - pixel_format: c_int, - gl_version: GLVersion, - compatibility_profile: bool, + pub(crate) pixel_format: c_int, + pub(crate) gl_version: GLVersion, + pub(crate) compatibility_profile: bool, } /// Represents an OpenGL rendering context. @@ -134,13 +113,13 @@ pub struct Context { pub(crate) glrc: HGLRC, pub(crate) id: ContextID, pub(crate) gl: Gl, - hidden_window: Option, + pub(crate) hidden_window: Option, pub(crate) framebuffer: Framebuffer, - status: ContextStatus, + pub(crate) status: ContextStatus, } #[derive(Clone, Copy, Debug, PartialEq)] -enum ContextStatus { +pub(crate) enum ContextStatus { Owned, Referenced, Destroyed, @@ -151,7 +130,7 @@ enum ContextStatus { pub struct NativeContext(pub HGLRC); thread_local! { - static OPENGL_LIBRARY: HMODULE = { + pub(crate) static OPENGL_LIBRARY: HMODULE = { unsafe { libloaderapi::LoadLibraryA(c"opengl32.dll".as_ptr()) } @@ -161,492 +140,6 @@ thread_local! { pub(crate) static WGL_EXTENSION_FUNCTIONS: LazyLock = LazyLock::new(|| thread::spawn(extension_loader_thread).join().unwrap()); -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - #[allow(non_snake_case)] - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - let flags = attributes.flags; - let alpha_bits = if flags.contains(ContextAttributeFlags::ALPHA) { - 8 - } else { - 0 - }; - let depth_bits = if flags.contains(ContextAttributeFlags::DEPTH) { - 24 - } else { - 0 - }; - let stencil_bits = if flags.contains(ContextAttributeFlags::STENCIL) { - 8 - } else { - 0 - }; - let compatibility_profile = flags.contains(ContextAttributeFlags::COMPATIBILITY_PROFILE); - - let attrib_i_list = [ - WGL_DRAW_TO_WINDOW_ARB as c_int, - gl::TRUE as c_int, - WGL_SUPPORT_OPENGL_ARB as c_int, - gl::TRUE as c_int, - WGL_DOUBLE_BUFFER_ARB as c_int, - gl::TRUE as c_int, - WGL_PIXEL_TYPE_ARB as c_int, - WGL_TYPE_RGBA_ARB as c_int, - WGL_ACCELERATION_ARB as c_int, - WGL_FULL_ACCELERATION_ARB as c_int, - WGL_COLOR_BITS_ARB as c_int, - 32, - WGL_ALPHA_BITS_ARB as c_int, - alpha_bits, - WGL_DEPTH_BITS_ARB as c_int, - depth_bits, - WGL_STENCIL_BITS_ARB as c_int, - stencil_bits, - 0, - ]; - - let wglChoosePixelFormatARB = match WGL_EXTENSION_FUNCTIONS.pixel_format_functions { - None => return Err(Error::RequiredExtensionUnavailable), - Some(ref pixel_format_functions) => pixel_format_functions.ChoosePixelFormatARB, - }; - - let hidden_window_dc = self.hidden_window.get_dc(); - unsafe { - let (mut pixel_format, mut pixel_format_count) = (0, 0); - let ok = wglChoosePixelFormatARB( - hidden_window_dc.dc, - attrib_i_list.as_ptr(), - ptr::null(), - 1, - &mut pixel_format, - &mut pixel_format_count, - ); - if ok == FALSE { - return Err(Error::PixelFormatSelectionFailed(WindowingApiError::Failed)); - } - if pixel_format_count == 0 { - return Err(Error::NoPixelFormatFound); - } - - Ok(ContextDescriptor { - pixel_format, - gl_version: attributes.version, - compatibility_profile, - }) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - #[allow(non_snake_case)] - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - let wglCreateContextAttribsARB = match WGL_EXTENSION_FUNCTIONS.CreateContextAttribsARB { - None => return Err(Error::RequiredExtensionUnavailable), - Some(wglCreateContextAttribsARB) => wglCreateContextAttribsARB, - }; - - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - unsafe { - let (glrc, gl); - - // Get a suitable DC. - let hidden_window = HiddenWindow::new(); - - { - // Set the pixel format on the hidden window DC. - let hidden_window_dc = hidden_window.get_dc(); - let dc = hidden_window_dc.dc; - set_dc_pixel_format(dc, descriptor.pixel_format); - - // Make the context. - let profile_mask = if descriptor.compatibility_profile { - WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB - } else { - WGL_CONTEXT_CORE_PROFILE_BIT_ARB - }; - let wgl_attributes = [ - WGL_CONTEXT_MAJOR_VERSION_ARB as c_int, - descriptor.gl_version.major as c_int, - WGL_CONTEXT_MINOR_VERSION_ARB as c_int, - descriptor.gl_version.minor as c_int, - WGL_CONTEXT_PROFILE_MASK_ARB as c_int, - profile_mask as c_int, - 0, - ]; - glrc = wglCreateContextAttribsARB( - dc, - share_with.map_or(ptr::null_mut(), |ctx| ctx.glrc), - wgl_attributes.as_ptr(), - ); - if glrc.is_null() { - return Err(Error::ContextCreationFailed(WindowingApiError::Failed)); - } - - // Temporarily make the context current. - let _guard = CurrentContextGuard::new(); - let ok = wglMakeCurrent(dc, glrc); - assert_ne!(ok, FALSE); - - // Load the GL functions. - gl = Gl::from_loader_function(get_proc_address); - } - - // Create the initial context. - let context = Context { - glrc, - id: *next_context_id, - gl, - hidden_window: Some(hidden_window), - framebuffer: Framebuffer::None, - status: ContextStatus::Owned, - }; - next_context_id.0 += 1; - Ok(context) - } - } - - /// Wraps an `HGLRC` in a `surfman` context and returns it. - /// - /// The `HGLRC` is not retained, as there is no way to do this in the Win32 API. Therefore, it - /// is the caller's responsibility to make sure the OpenGL context is not destroyed before this - /// `Context` is. - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); - let hidden_window = HiddenWindow::new(); - - // Load the GL functions. - let gl = { - let hidden_window_dc = hidden_window.get_dc(); - let dc = hidden_window_dc.dc; - let _guard = CurrentContextGuard::new(); - let ok = wglMakeCurrent(dc, native_context.0); - assert_ne!(ok, FALSE); - Gl::from_loader_function(get_proc_address) - }; - - let context = Context { - glrc: native_context.0, - id: *next_context_id, - gl, - hidden_window: Some(hidden_window), - framebuffer: Framebuffer::External(()), - status: ContextStatus::Referenced, - }; - next_context_id.0 += 1; - Ok(context) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if context.status == ContextStatus::Destroyed { - return Ok(()); - } - - if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { - self.destroy_surface(context, &mut surface)?; - } - - unsafe { - if wglGetCurrentContext() == context.glrc { - wglMakeCurrent(ptr::null_mut(), ptr::null_mut()); - } - - if context.status == ContextStatus::Owned { - wglDeleteContext(context.glrc); - } - } - - context.glrc = ptr::null_mut(); - context.status = ContextStatus::Destroyed; - Ok(()) - } - - /// Returns the descriptor that this context was created with. - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - let dc_guard = self.get_context_dc(context); - let pixel_format = wingdi::GetPixelFormat(dc_guard.dc); - - let _guard = self.temporarily_make_context_current(context); - - let gl_version = GLVersion::current(&context.gl); - let compatibility_profile = - context::current_context_uses_compatibility_profile(&context.gl); - - ContextDescriptor { - pixel_format, - gl_version, - compatibility_profile, - } - } - } - - /// Returns the attributes that the context descriptor was created with. - #[allow(non_snake_case)] - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - let wglGetPixelFormatAttribivARB = WGL_EXTENSION_FUNCTIONS - .pixel_format_functions - .as_ref() - .expect( - "How did you make a context descriptor without \ - pixel format extensions?", - ) - .GetPixelFormatAttribivARB; - - let dc_guard = self.hidden_window.get_dc(); - - unsafe { - let attrib_name_i_list = [ - WGL_ALPHA_BITS_ARB as c_int, - WGL_DEPTH_BITS_ARB as c_int, - WGL_STENCIL_BITS_ARB as c_int, - ]; - let mut attrib_value_i_list = [0; 3]; - let ok = wglGetPixelFormatAttribivARB( - dc_guard.dc, - context_descriptor.pixel_format, - 0, - attrib_name_i_list.len() as UINT, - attrib_name_i_list.as_ptr(), - attrib_value_i_list.as_mut_ptr(), - ); - assert_ne!(ok, FALSE); - let (alpha_bits, depth_bits, stencil_bits) = ( - attrib_value_i_list[0], - attrib_value_i_list[1], - attrib_value_i_list[2], - ); - - let mut attributes = ContextAttributes { - version: context_descriptor.gl_version, - flags: ContextAttributeFlags::empty(), - }; - if alpha_bits > 0 { - attributes.flags.insert(ContextAttributeFlags::ALPHA); - } - if depth_bits > 0 { - attributes.flags.insert(ContextAttributeFlags::DEPTH); - } - if stencil_bits > 0 { - attributes.flags.insert(ContextAttributeFlags::STENCIL); - } - - attributes - } - } - - pub(crate) fn temporarily_bind_framebuffer<'a>( - &self, - context: &'a Context, - framebuffer: Option, - ) -> FramebufferGuard<'a> { - unsafe { - let guard = FramebufferGuard::new(context); - context.gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer); - guard - } - } - - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - unsafe { - let dc_guard = self.get_context_dc(context); - let ok = wglMakeCurrent(dc_guard.dc, context.glrc); - if ok != FALSE { - Ok(()) - } else { - Err(Error::MakeCurrentFailed(WindowingApiError::Failed)) - } - } - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - #[inline] - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { - let ok = wglMakeCurrent(ptr::null_mut(), ptr::null_mut()); - if ok != FALSE { - Ok(()) - } else { - Err(Error::MakeCurrentFailed(WindowingApiError::Failed)) - } - } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - get_proc_address(symbol_name) - } - - #[inline] - fn context_is_current(&self, context: &Context) -> bool { - unsafe { wglGetCurrentContext() == context.glrc } - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - pub fn bind_surface_to_context( - &self, - context: &mut Context, - surface: Surface, - ) -> Result<(), (Error, Surface)> { - if context.id != surface.context_id { - return Err((Error::IncompatibleSurface, surface)); - } - - match context.framebuffer { - Framebuffer::None => {} - Framebuffer::External(()) => return Err((Error::ExternalRenderTarget, surface)), - Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, surface)), - } - - let is_current = self.context_is_current(context); - - self.lock_surface(&surface); - context.framebuffer = Framebuffer::Surface(surface); - - if is_current { - // We need to make ourselves current again, because the surface changed. - drop(self.make_context_current(context)); - } - - Ok(()) - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - match mem::replace(&mut context.framebuffer, Framebuffer::None) { - Framebuffer::Surface(surface) => { - self.unlock_surface(&surface); - Ok(Some(surface)) - } - Framebuffer::External(()) => Err(Error::ExternalRenderTarget), - Framebuffer::None => Ok(None), - } - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - match &context.framebuffer { - Framebuffer::Surface(surface) => surface.present(), - _ => Ok(()), - } - } - - /// If the currently bound surface is a widget surface, resize it, - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - if let Framebuffer::Surface(surface) = &mut context.framebuffer { - surface.resize(size); - } - Ok(()) - } - - pub(crate) fn get_context_dc<'a>(&self, context: &'a Context) -> DCGuard<'a> { - unsafe { - match context.framebuffer { - Framebuffer::Surface(Surface { - win32_objects: Win32Objects::Widget { window_handle }, - .. - }) => DCGuard::new(winuser::GetDC(window_handle), Some(window_handle)), - Framebuffer::Surface(Surface { - win32_objects: Win32Objects::Texture { .. }, - .. - }) - | Framebuffer::External(()) - | Framebuffer::None => context.hidden_window.as_ref().unwrap().get_dc(), - } - } - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.id - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - match context.framebuffer { - Framebuffer::None => Ok(None), - Framebuffer::External(()) => Err(Error::ExternalRenderTarget), - Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), - } - } - - /// Given a context, returns its underlying `HGLRC`. - #[inline] - pub fn native_context(&self, context: &Context) -> NativeContext { - NativeContext(context.glrc) - } -} - impl NativeContext { /// Returns the current context, if there is one. /// @@ -852,7 +345,7 @@ impl<'a> Drop for FramebufferGuard<'a> { } impl<'a> FramebufferGuard<'a> { - fn new(context: &'a Context) -> FramebufferGuard<'a> { + pub(crate) fn new(context: &'a Context) -> FramebufferGuard<'a> { unsafe { let current_draw_framebuffer = context .gl @@ -887,7 +380,7 @@ impl Drop for CurrentContextGuard { impl CurrentContextGuard { #[inline] - fn new() -> CurrentContextGuard { + pub(crate) fn new() -> CurrentContextGuard { unsafe { CurrentContextGuard { old_dc: wglGetCurrentDC(), @@ -896,33 +389,3 @@ impl CurrentContextGuard { } } } - -fn get_proc_address(symbol_name: &str) -> *const c_void { - unsafe { - // https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions#Windows - let symbol_name: CString = CString::new(symbol_name).unwrap(); - let symbol_ptr = symbol_name.as_ptr(); - let addr = wglGetProcAddress(symbol_ptr) as *const c_void; - if !addr.is_null() { - return addr; - } - OPENGL_LIBRARY.with(|opengl_library| { - libloaderapi::GetProcAddress(*opengl_library, symbol_ptr) as *const c_void - }) - } -} - -pub(crate) fn set_dc_pixel_format(dc: HDC, pixel_format: c_int) { - unsafe { - let mut pixel_format_descriptor = mem::zeroed(); - let pixel_format_count = wingdi::DescribePixelFormat( - dc, - pixel_format, - mem::size_of::() as UINT, - &mut pixel_format_descriptor, - ); - assert_ne!(pixel_format_count, 0); - let ok = wingdi::SetPixelFormat(dc, pixel_format, &mut pixel_format_descriptor); - assert_ne!(ok, FALSE); - } -} diff --git a/src/wgl/device.rs b/src/wgl/device.rs index 07421842..900b7515 100644 --- a/src/wgl/device.rs +++ b/src/wgl/device.rs @@ -4,27 +4,80 @@ use super::connection::Connection; use super::context::WGL_EXTENSION_FUNCTIONS; -use crate::{Error, GLApi}; - -use std::ffi::CStr; +use super::surface::{Surface, Win32Objects}; +use crate::context::{self, ContextID, CREATE_CONTEXT_MUTEX}; +use crate::error::WindowingApiError; +use crate::renderbuffers::Renderbuffers; +use crate::surface::Framebuffer; +use crate::wgl::context::{ContextStatus, CurrentContextGuard, FramebufferGuard, OPENGL_LIBRARY}; +use crate::wgl::surface::SurfaceDataGuard; +use crate::{gl, ContextDescriptor, SurfaceAccess, SurfaceType}; +use crate::{gl_utils, NativeWidget}; +use crate::{Context, GLApi}; +use crate::{ContextAttributeFlags, ContextAttributes, Error, GLVersion}; +use crate::{Gl, NativeContext}; +use crate::{SurfaceInfo, SurfaceTexture}; +use euclid::default::Size2D; +use glow::HasContext; +use libc::c_uint; +use log::warn; +use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::mem; use std::os::raw::{c_int, c_void}; use std::ptr; use std::sync::mpsc::{self, Sender}; use std::thread::{self, JoinHandle}; +use winapi::shared::dxgi::IDXGIResource; use winapi::shared::dxgi::{IDXGIAdapter, IDXGIDevice}; +use winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM; +use winapi::shared::dxgitype::DXGI_SAMPLE_DESC; use winapi::shared::minwindef::{self, FALSE, UINT}; use winapi::shared::ntdef::HANDLE; use winapi::shared::windef::{HBRUSH, HDC, HWND}; -use winapi::shared::winerror::{self, S_OK}; -use winapi::um::d3d11::{D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, D3D11_SDK_VERSION}; +use winapi::shared::winerror; +use winapi::shared::winerror::S_OK; +use winapi::um::d3d11::{ + D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, + D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX, + D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, +}; use winapi::um::d3dcommon::D3D_DRIVER_TYPE_HARDWARE; +use winapi::um::handleapi::INVALID_HANDLE_VALUE; use winapi::um::libloaderapi; +use winapi::um::wingdi::{self, PIXELFORMATDESCRIPTOR}; +use winapi::um::wingdi::{ + wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, wglMakeCurrent, +}; use winapi::um::winuser::{self, COLOR_BACKGROUND, CS_OWNDC, MSG, WM_CLOSE}; use winapi::um::winuser::{WNDCLASSA, WS_OVERLAPPEDWINDOW}; +use winapi::Interface; use wio::com::ComPtr; +type GLenum = c_uint; +type GLint = c_int; + +const SURFACE_GL_TEXTURE_TARGET: GLenum = gl::TEXTURE_2D; + +const WGL_ACCESS_READ_ONLY_NV: GLenum = 0x0000; +const WGL_ACCESS_READ_WRITE_NV: GLenum = 0x0001; +const WGL_DRAW_TO_WINDOW_ARB: GLenum = 0x2001; +const WGL_ACCELERATION_ARB: GLenum = 0x2003; +const WGL_SUPPORT_OPENGL_ARB: GLenum = 0x2010; +const WGL_DOUBLE_BUFFER_ARB: GLenum = 0x2011; +const WGL_PIXEL_TYPE_ARB: GLenum = 0x2013; +const WGL_COLOR_BITS_ARB: GLenum = 0x2014; +const WGL_ALPHA_BITS_ARB: GLenum = 0x201b; +const WGL_DEPTH_BITS_ARB: GLenum = 0x2022; +const WGL_STENCIL_BITS_ARB: GLenum = 0x2023; +const WGL_FULL_ACCELERATION_ARB: GLenum = 0x2027; +const WGL_TYPE_RGBA_ARB: GLenum = 0x202b; +const WGL_CONTEXT_MAJOR_VERSION_ARB: GLenum = 0x2091; +const WGL_CONTEXT_MINOR_VERSION_ARB: GLenum = 0x2092; +const WGL_CONTEXT_PROFILE_MASK_ARB: GLenum = 0x9126; +const WGL_CONTEXT_CORE_PROFILE_BIT_ARB: GLenum = 0x00000001; +const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: GLenum = 0x00000002; + pub(crate) const HIDDEN_WINDOW_SIZE: c_int = 16; const INTEL_PCI_ID: UINT = 0x8086; @@ -236,6 +289,1001 @@ impl Device { pub fn gl_api(&self) -> GLApi { GLApi::GL } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + #[allow(non_snake_case)] + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + let flags = attributes.flags; + let alpha_bits = if flags.contains(ContextAttributeFlags::ALPHA) { + 8 + } else { + 0 + }; + let depth_bits = if flags.contains(ContextAttributeFlags::DEPTH) { + 24 + } else { + 0 + }; + let stencil_bits = if flags.contains(ContextAttributeFlags::STENCIL) { + 8 + } else { + 0 + }; + let compatibility_profile = flags.contains(ContextAttributeFlags::COMPATIBILITY_PROFILE); + + let attrib_i_list = [ + WGL_DRAW_TO_WINDOW_ARB as c_int, + gl::TRUE as c_int, + WGL_SUPPORT_OPENGL_ARB as c_int, + gl::TRUE as c_int, + WGL_DOUBLE_BUFFER_ARB as c_int, + gl::TRUE as c_int, + WGL_PIXEL_TYPE_ARB as c_int, + WGL_TYPE_RGBA_ARB as c_int, + WGL_ACCELERATION_ARB as c_int, + WGL_FULL_ACCELERATION_ARB as c_int, + WGL_COLOR_BITS_ARB as c_int, + 32, + WGL_ALPHA_BITS_ARB as c_int, + alpha_bits, + WGL_DEPTH_BITS_ARB as c_int, + depth_bits, + WGL_STENCIL_BITS_ARB as c_int, + stencil_bits, + 0, + ]; + + let wglChoosePixelFormatARB = match WGL_EXTENSION_FUNCTIONS.pixel_format_functions { + None => return Err(Error::RequiredExtensionUnavailable), + Some(ref pixel_format_functions) => pixel_format_functions.ChoosePixelFormatARB, + }; + + let hidden_window_dc = self.hidden_window.get_dc(); + unsafe { + let (mut pixel_format, mut pixel_format_count) = (0, 0); + let ok = wglChoosePixelFormatARB( + hidden_window_dc.dc, + attrib_i_list.as_ptr(), + ptr::null(), + 1, + &mut pixel_format, + &mut pixel_format_count, + ); + if ok == FALSE { + return Err(Error::PixelFormatSelectionFailed(WindowingApiError::Failed)); + } + if pixel_format_count == 0 { + return Err(Error::NoPixelFormatFound); + } + + Ok(ContextDescriptor { + pixel_format, + gl_version: attributes.version, + compatibility_profile, + }) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + #[allow(non_snake_case)] + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + let wglCreateContextAttribsARB = match WGL_EXTENSION_FUNCTIONS.CreateContextAttribsARB { + None => return Err(Error::RequiredExtensionUnavailable), + Some(wglCreateContextAttribsARB) => wglCreateContextAttribsARB, + }; + + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + unsafe { + let (glrc, gl); + + // Get a suitable DC. + let hidden_window = HiddenWindow::new(); + + { + // Set the pixel format on the hidden window DC. + let hidden_window_dc = hidden_window.get_dc(); + let dc = hidden_window_dc.dc; + set_dc_pixel_format(dc, descriptor.pixel_format); + + // Make the context. + let profile_mask = if descriptor.compatibility_profile { + WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB + } else { + WGL_CONTEXT_CORE_PROFILE_BIT_ARB + }; + let wgl_attributes = [ + WGL_CONTEXT_MAJOR_VERSION_ARB as c_int, + descriptor.gl_version.major as c_int, + WGL_CONTEXT_MINOR_VERSION_ARB as c_int, + descriptor.gl_version.minor as c_int, + WGL_CONTEXT_PROFILE_MASK_ARB as c_int, + profile_mask as c_int, + 0, + ]; + glrc = wglCreateContextAttribsARB( + dc, + share_with.map_or(ptr::null_mut(), |ctx| ctx.glrc), + wgl_attributes.as_ptr(), + ); + if glrc.is_null() { + return Err(Error::ContextCreationFailed(WindowingApiError::Failed)); + } + + // Temporarily make the context current. + let _guard = CurrentContextGuard::new(); + let ok = wglMakeCurrent(dc, glrc); + assert_ne!(ok, FALSE); + + // Load the GL functions. + gl = Gl::from_loader_function(get_proc_address); + } + + // Create the initial context. + let context = Context { + glrc, + id: *next_context_id, + gl, + hidden_window: Some(hidden_window), + framebuffer: Framebuffer::None, + status: ContextStatus::Owned, + }; + next_context_id.0 += 1; + Ok(context) + } + } + + /// Wraps an `HGLRC` in a `surfman` context and returns it. + /// + /// The `HGLRC` is not retained, as there is no way to do this in the Win32 API. Therefore, it + /// is the caller's responsibility to make sure the OpenGL context is not destroyed before this + /// `Context` is. + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap(); + let hidden_window = HiddenWindow::new(); + + // Load the GL functions. + let gl = { + let hidden_window_dc = hidden_window.get_dc(); + let dc = hidden_window_dc.dc; + let _guard = CurrentContextGuard::new(); + let ok = wglMakeCurrent(dc, native_context.0); + assert_ne!(ok, FALSE); + Gl::from_loader_function(get_proc_address) + }; + + let context = Context { + glrc: native_context.0, + id: *next_context_id, + gl, + hidden_window: Some(hidden_window), + framebuffer: Framebuffer::External(()), + status: ContextStatus::Referenced, + }; + next_context_id.0 += 1; + Ok(context) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if context.status == ContextStatus::Destroyed { + return Ok(()); + } + + if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { + self.destroy_surface(context, &mut surface)?; + } + + unsafe { + if wglGetCurrentContext() == context.glrc { + wglMakeCurrent(ptr::null_mut(), ptr::null_mut()); + } + + if context.status == ContextStatus::Owned { + wglDeleteContext(context.glrc); + } + } + + context.glrc = ptr::null_mut(); + context.status = ContextStatus::Destroyed; + Ok(()) + } + + /// Returns the descriptor that this context was created with. + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + let dc_guard = self.get_context_dc(context); + let pixel_format = wingdi::GetPixelFormat(dc_guard.dc); + + let _guard = self.temporarily_make_context_current(context); + + let gl_version = GLVersion::current(&context.gl); + let compatibility_profile = + context::current_context_uses_compatibility_profile(&context.gl); + + ContextDescriptor { + pixel_format, + gl_version, + compatibility_profile, + } + } + } + + /// Returns the attributes that the context descriptor was created with. + #[allow(non_snake_case)] + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + let wglGetPixelFormatAttribivARB = WGL_EXTENSION_FUNCTIONS + .pixel_format_functions + .as_ref() + .expect( + "How did you make a context descriptor without \ + pixel format extensions?", + ) + .GetPixelFormatAttribivARB; + + let dc_guard = self.hidden_window.get_dc(); + + unsafe { + let attrib_name_i_list = [ + WGL_ALPHA_BITS_ARB as c_int, + WGL_DEPTH_BITS_ARB as c_int, + WGL_STENCIL_BITS_ARB as c_int, + ]; + let mut attrib_value_i_list = [0; 3]; + let ok = wglGetPixelFormatAttribivARB( + dc_guard.dc, + context_descriptor.pixel_format, + 0, + attrib_name_i_list.len() as UINT, + attrib_name_i_list.as_ptr(), + attrib_value_i_list.as_mut_ptr(), + ); + assert_ne!(ok, FALSE); + let (alpha_bits, depth_bits, stencil_bits) = ( + attrib_value_i_list[0], + attrib_value_i_list[1], + attrib_value_i_list[2], + ); + + let mut attributes = ContextAttributes { + version: context_descriptor.gl_version, + flags: ContextAttributeFlags::empty(), + }; + if alpha_bits > 0 { + attributes.flags.insert(ContextAttributeFlags::ALPHA); + } + if depth_bits > 0 { + attributes.flags.insert(ContextAttributeFlags::DEPTH); + } + if stencil_bits > 0 { + attributes.flags.insert(ContextAttributeFlags::STENCIL); + } + + attributes + } + } + + pub(crate) fn temporarily_bind_framebuffer<'a>( + &self, + context: &'a Context, + framebuffer: Option, + ) -> FramebufferGuard<'a> { + unsafe { + let guard = FramebufferGuard::new(context); + context.gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer); + guard + } + } + + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + unsafe { + let dc_guard = self.get_context_dc(context); + let ok = wglMakeCurrent(dc_guard.dc, context.glrc); + if ok != FALSE { + Ok(()) + } else { + Err(Error::MakeCurrentFailed(WindowingApiError::Failed)) + } + } + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + #[inline] + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { + let ok = wglMakeCurrent(ptr::null_mut(), ptr::null_mut()); + if ok != FALSE { + Ok(()) + } else { + Err(Error::MakeCurrentFailed(WindowingApiError::Failed)) + } + } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + get_proc_address(symbol_name) + } + + #[inline] + fn context_is_current(&self, context: &Context) -> bool { + unsafe { wglGetCurrentContext() == context.glrc } + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + pub fn bind_surface_to_context( + &self, + context: &mut Context, + surface: Surface, + ) -> Result<(), (Error, Surface)> { + if context.id != surface.context_id { + return Err((Error::IncompatibleSurface, surface)); + } + + match context.framebuffer { + Framebuffer::None => {} + Framebuffer::External(()) => return Err((Error::ExternalRenderTarget, surface)), + Framebuffer::Surface(_) => return Err((Error::SurfaceAlreadyBound, surface)), + } + + let is_current = self.context_is_current(context); + + self.lock_surface(&surface); + context.framebuffer = Framebuffer::Surface(surface); + + if is_current { + // We need to make ourselves current again, because the surface changed. + drop(self.make_context_current(context)); + } + + Ok(()) + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + match mem::replace(&mut context.framebuffer, Framebuffer::None) { + Framebuffer::Surface(surface) => { + self.unlock_surface(&surface); + Ok(Some(surface)) + } + Framebuffer::External(()) => Err(Error::ExternalRenderTarget), + Framebuffer::None => Ok(None), + } + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + match &context.framebuffer { + Framebuffer::Surface(surface) => surface.present(), + _ => Ok(()), + } + } + + /// If the currently bound surface is a widget surface, resize it, + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + if let Framebuffer::Surface(surface) = &mut context.framebuffer { + surface.resize(size); + } + Ok(()) + } + + pub(crate) fn get_context_dc<'a>(&self, context: &'a Context) -> DCGuard<'a> { + unsafe { + match context.framebuffer { + Framebuffer::Surface(Surface { + win32_objects: Win32Objects::Widget { window_handle }, + .. + }) => DCGuard::new(winuser::GetDC(window_handle), Some(window_handle)), + Framebuffer::Surface(Surface { + win32_objects: Win32Objects::Texture { .. }, + .. + }) + | Framebuffer::External(()) + | Framebuffer::None => context.hidden_window.as_ref().unwrap().get_dc(), + } + } + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.id + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + match context.framebuffer { + Framebuffer::None => Ok(None), + Framebuffer::External(()) => Err(Error::ExternalRenderTarget), + Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))), + } + } + + /// Given a context, returns its underlying `HGLRC`. + #[inline] + pub fn native_context(&self, context: &Context) -> NativeContext { + NativeContext(context.glrc) + } + + /// Creates either a generic or a widget surface, depending on the supplied surface type. + /// + /// Only the given context may ever render to the surface, but generic surfaces can be wrapped + /// up in a `SurfaceTexture` for reading by other contexts. + pub fn create_surface( + &self, + context: &Context, + _: SurfaceAccess, + surface_type: SurfaceType, + ) -> Result { + match surface_type { + SurfaceType::Generic { size } => self.create_generic_surface(context, &size), + SurfaceType::Widget { native_widget } => { + self.create_widget_surface(context, native_widget) + } + } + } + + fn create_generic_surface( + &self, + context: &Context, + size: &Size2D, + ) -> Result { + let dx_interop_functions = match WGL_EXTENSION_FUNCTIONS.dx_interop_functions { + None => return Err(Error::RequiredExtensionUnavailable), + Some(ref dx_interop_functions) => dx_interop_functions, + }; + + unsafe { + let _guard = self.temporarily_make_context_current(context)?; + + // Create the Direct3D 11 texture. + let d3d11_texture2d_desc = D3D11_TEXTURE2D_DESC { + Width: size.width as UINT, + Height: size.height as UINT, + MipLevels: 1, + ArraySize: 1, + Format: DXGI_FORMAT_R8G8B8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_DEFAULT, + BindFlags: D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, + CPUAccessFlags: 0, + MiscFlags: D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX, + }; + let mut d3d11_texture = ptr::null_mut(); + let mut result = self.d3d11_device.CreateTexture2D( + &d3d11_texture2d_desc, + ptr::null(), + &mut d3d11_texture, + ); + if !winerror::SUCCEEDED(result) { + return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed)); + } + assert!(!d3d11_texture.is_null()); + let d3d11_texture = ComPtr::from_raw(d3d11_texture); + + // Upcast it to a DXGI resource. + let mut dxgi_resource: *mut IDXGIResource = ptr::null_mut(); + result = d3d11_texture.QueryInterface( + &IDXGIResource::uuidof(), + &mut dxgi_resource as *mut *mut IDXGIResource as *mut *mut c_void, + ); + assert!(winerror::SUCCEEDED(result)); + assert!(!dxgi_resource.is_null()); + let dxgi_resource = ComPtr::from_raw(dxgi_resource); + + // Get the share handle. We'll need it both to bind to GL and to share the texture + // across contexts. + let mut dxgi_share_handle = INVALID_HANDLE_VALUE; + result = dxgi_resource.GetSharedHandle(&mut dxgi_share_handle); + assert!(winerror::SUCCEEDED(result)); + assert_ne!(dxgi_share_handle, INVALID_HANDLE_VALUE); + + // Tell GL about the share handle. + let ok = (dx_interop_functions.DXSetResourceShareHandleNV)( + d3d11_texture.as_raw() as *mut c_void, + dxgi_share_handle, + ); + assert_ne!(ok, FALSE); + + // Make our texture object on the GL side. + let gl_texture = context.gl.create_texture().unwrap(); + + // Bind the GL texture to the D3D11 texture. + let gl_dx_interop_object = (dx_interop_functions.DXRegisterObjectNV)( + self.gl_dx_interop_device, + d3d11_texture.as_raw() as *mut c_void, + gl_texture.0.get(), + gl::TEXTURE_2D, + WGL_ACCESS_READ_WRITE_NV, + ); + // Per the spec, and unlike other HANDLEs, null indicates an error. + if gl_dx_interop_object.is_null() { + let msg = std::io::Error::last_os_error(); // Equivalent to GetLastError(). + error!( + "Unable to share surface between OpenGL and DirectX. OS error '{}'.", + msg + ); + return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed)); + } + + // Build our FBO. + let gl_framebuffer = context.gl.create_framebuffer().unwrap(); + let _guard = self.temporarily_bind_framebuffer(context, Some(gl_framebuffer)); + + // Attach the reflected D3D11 texture to that FBO. + context.gl.framebuffer_texture_2d( + gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + SURFACE_GL_TEXTURE_TARGET, + Some(gl_texture), + 0, + ); + + // Create renderbuffers as appropriate, and attach them. + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + let renderbuffers = Renderbuffers::new(&context.gl, &size, &context_attributes); + renderbuffers.bind_to_current_framebuffer(&context.gl); + + // FIXME(pcwalton): Do we need to acquire the keyed mutex, or does the GL driver do + // that? + + Ok(Surface { + size: *size, + context_id: context.id, + win32_objects: Win32Objects::Texture { + d3d11_texture, + dxgi_share_handle, + gl_dx_interop_object, + gl_texture: Some(gl_texture), + gl_framebuffer: Some(gl_framebuffer), + renderbuffers, + }, + destroyed: false, + }) + } + } + + fn create_widget_surface( + &self, + context: &Context, + native_widget: NativeWidget, + ) -> Result { + unsafe { + // Get the bounds of the native HWND. + let mut widget_rect = mem::zeroed(); + let ok = winuser::GetWindowRect(native_widget.window_handle, &mut widget_rect); + if ok == FALSE { + return Err(Error::InvalidNativeWidget); + } + + // Set its pixel format. + { + let context_dc_guard = self.get_context_dc(context); + let pixel_format = wingdi::GetPixelFormat(context_dc_guard.dc); + let window_dc = winuser::GetDC(native_widget.window_handle); + set_dc_pixel_format(window_dc, pixel_format); + } + + Ok(Surface { + size: Size2D::new( + widget_rect.right - widget_rect.left, + widget_rect.bottom - widget_rect.top, + ), + context_id: context.id, + win32_objects: Win32Objects::Widget { + window_handle: native_widget.window_handle, + }, + destroyed: false, + }) + } + } + + /// Destroys a surface. + /// + /// The supplied context must be the context the surface is associated with, or this returns + /// an `IncompatibleSurface` error. + /// + /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in + /// the `drop` method. + pub fn destroy_surface( + &self, + context: &mut Context, + surface: &mut Surface, + ) -> Result<(), Error> { + let dx_interop_functions = WGL_EXTENSION_FUNCTIONS + .dx_interop_functions + .as_ref() + .expect("How did you make a surface without DX interop?"); + + if context.id != surface.context_id { + return Err(Error::IncompatibleSurface); + } + + let _guard = self.temporarily_make_context_current(context)?; + + unsafe { + match surface.win32_objects { + Win32Objects::Texture { + ref mut gl_dx_interop_object, + ref mut gl_texture, + ref mut gl_framebuffer, + ref mut renderbuffers, + d3d11_texture: _, + dxgi_share_handle: _, + } => { + renderbuffers.destroy(&context.gl); + + if let Some(fbo) = gl_framebuffer.take() { + gl_utils::destroy_framebuffer(&context.gl, fbo); + } + + if let Some(texture) = gl_texture.take() { + context.gl.delete_texture(texture); + } + + let ok = (dx_interop_functions.DXUnregisterObjectNV)( + self.gl_dx_interop_device, + *gl_dx_interop_object, + ); + assert_ne!(ok, FALSE); + *gl_dx_interop_object = INVALID_HANDLE_VALUE; + } + Win32Objects::Widget { window_handle: _ } => {} + } + + surface.destroyed = true; + } + + Ok(()) + } + + /// Creates a surface texture from an existing generic surface for use with the given context. + /// + /// The surface texture is local to the supplied context and takes ownership of the surface. + /// Destroying the surface texture allows you to retrieve the surface again. + /// + /// *The supplied context does not have to be the same context that the surface is associated + /// with.* This allows you to render to a surface in one context and sample from that surface + /// in another context. + /// + /// Calling this method on a widget surface returns a `WidgetAttached` error. + pub fn create_surface_texture( + &self, + context: &mut Context, + surface: Surface, + ) -> Result { + let dxgi_share_handle = match surface.win32_objects { + Win32Objects::Widget { .. } => return Err((Error::WidgetAttached, surface)), + Win32Objects::Texture { + dxgi_share_handle, .. + } => dxgi_share_handle, + }; + + let dx_interop_functions = WGL_EXTENSION_FUNCTIONS + .dx_interop_functions + .as_ref() + .expect("How did you make a surface without DX interop?"); + + let _guard = match self.temporarily_make_context_current(context) { + Ok(guard) => guard, + Err(err) => return Err((err, surface)), + }; + + unsafe { + // Create a new texture wrapping the shared handle. + let mut local_d3d11_texture = ptr::null_mut(); + let result = self.d3d11_device.OpenSharedResource( + dxgi_share_handle, + &ID3D11Texture2D::uuidof(), + &mut local_d3d11_texture, + ); + if !winerror::SUCCEEDED(result) || local_d3d11_texture.is_null() { + return Err(( + Error::SurfaceImportFailed(WindowingApiError::Failed), + surface, + )); + } + let local_d3d11_texture = ComPtr::from_raw(local_d3d11_texture as *mut ID3D11Texture2D); + + // Make GL aware of the connection between the share handle and the texture. + let ok = (dx_interop_functions.DXSetResourceShareHandleNV)( + local_d3d11_texture.as_raw() as *mut c_void, + dxgi_share_handle, + ); + assert_ne!(ok, FALSE); + + // Create a GL texture. + let gl_texture = context.gl.create_texture().unwrap(); + + // Register that texture with GL/DX interop. + let mut local_gl_dx_interop_object = (dx_interop_functions.DXRegisterObjectNV)( + self.gl_dx_interop_device, + local_d3d11_texture.as_raw() as *mut c_void, + gl_texture.0.get(), + gl::TEXTURE_2D, + WGL_ACCESS_READ_ONLY_NV, + ); + + // Lock the texture so that we can use it. + let ok = (dx_interop_functions.DXLockObjectsNV)( + self.gl_dx_interop_device, + 1, + &mut local_gl_dx_interop_object, + ); + assert_ne!(ok, FALSE); + + // Initialize the texture, for convenience. + // FIXME(pcwalton): We should probably reset the bound texture after this. + context.gl.bind_texture(gl::TEXTURE_2D, Some(gl_texture)); + context.gl.tex_parameter_i32( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + gl::LINEAR as GLint, + ); + context.gl.tex_parameter_i32( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::LINEAR as GLint, + ); + context.gl.tex_parameter_i32( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_EDGE as GLint, + ); + context.gl.tex_parameter_i32( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_EDGE as GLint, + ); + + // Finish up. + Ok(SurfaceTexture { + surface, + local_d3d11_texture, + local_gl_dx_interop_object, + gl_texture: Some(gl_texture), + phantom: PhantomData, + }) + } + } + + /// Destroys a surface texture and returns the underlying surface. + /// + /// The supplied context must be the same context the surface texture was created with, or an + /// `IncompatibleSurfaceTexture` error is returned. + /// + /// All surface textures must be explicitly destroyed with this function, or a panic will + /// occur. + pub fn destroy_surface_texture( + &self, + context: &mut Context, + mut surface_texture: SurfaceTexture, + ) -> Result { + let dx_interop_functions = WGL_EXTENSION_FUNCTIONS + .dx_interop_functions + .as_ref() + .expect("How did you make a surface without DX interop?"); + + let _guard = match self.temporarily_make_context_current(context) { + Ok(guard) => guard, + Err(err) => return Err((err, surface_texture)), + }; + + unsafe { + // Unlock the texture. + let ok = (dx_interop_functions.DXUnlockObjectsNV)( + self.gl_dx_interop_device, + 1, + &mut surface_texture.local_gl_dx_interop_object, + ); + assert_ne!(ok, FALSE); + + // Unregister the texture from GL/DX interop. + let ok = (dx_interop_functions.DXUnregisterObjectNV)( + self.gl_dx_interop_device, + surface_texture.local_gl_dx_interop_object, + ); + assert_ne!(ok, FALSE); + surface_texture.local_gl_dx_interop_object = INVALID_HANDLE_VALUE; + + // Destroy the GL texture. + if let Some(texture) = surface_texture.gl_texture.take() { + context.gl.delete_texture(texture); + } + } + + Ok(surface_texture.surface) + } + + pub(crate) fn lock_surface(&self, surface: &Surface) { + let mut gl_dx_interop_object = match surface.win32_objects { + Win32Objects::Widget { .. } => return, + Win32Objects::Texture { + gl_dx_interop_object, + .. + } => gl_dx_interop_object, + }; + + let dx_interop_functions = WGL_EXTENSION_FUNCTIONS + .dx_interop_functions + .as_ref() + .expect("How did you make a surface without DX interop?"); + + unsafe { + let ok = (dx_interop_functions.DXLockObjectsNV)( + self.gl_dx_interop_device, + 1, + &mut gl_dx_interop_object, + ); + assert_ne!(ok, FALSE); + } + } + + pub(crate) fn unlock_surface(&self, surface: &Surface) { + let mut gl_dx_interop_object = match surface.win32_objects { + Win32Objects::Widget { .. } => return, + Win32Objects::Texture { + gl_dx_interop_object, + .. + } => gl_dx_interop_object, + }; + + let dx_interop_functions = WGL_EXTENSION_FUNCTIONS + .dx_interop_functions + .as_ref() + .expect("How did you make a surface without DX interop?"); + + unsafe { + let ok = (dx_interop_functions.DXUnlockObjectsNV)( + self.gl_dx_interop_device, + 1, + &mut gl_dx_interop_object, + ); + assert_ne!(ok, FALSE); + } + } + + /// Returns a pointer to the underlying surface data for reading or writing by the CPU. + #[inline] + pub fn lock_surface_data<'s>( + &self, + _surface: &'s mut Surface, + ) -> Result, Error> { + Err(Error::Unimplemented) + } + + /// Returns the OpenGL texture target needed to read from this surface texture. + /// + /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. + #[inline] + pub fn surface_gl_texture_target(&self) -> GLenum { + gl::TEXTURE_2D + } + + /// Displays the contents of a widget surface on screen. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't show up in their + /// associated widgets until this method is called. + /// + /// The supplied context must match the context the surface was created with, or an + /// `IncompatibleSurface` error is returned. + pub fn present_surface(&self, _: &Context, surface: &mut Surface) -> Result<(), Error> { + surface.present() + } + + /// Resizes a widget surface. + pub fn resize_surface( + &self, + _scontext: &Context, + surface: &mut Surface, + size: Size2D, + ) -> Result<(), Error> { + surface.resize(size); + Ok(()) + } + + /// Returns various information about the surface, including the framebuffer object needed to + /// render to this surface. + /// + /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` + /// on the framebuffer object returned by this function. This framebuffer object may or not be + /// 0, the default framebuffer, depending on platform. + #[inline] + pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { + SurfaceInfo { + size: surface.size, + id: surface.id(), + context_id: surface.context_id, + framebuffer_object: match surface.win32_objects { + Win32Objects::Texture { gl_framebuffer, .. } => gl_framebuffer, + Win32Objects::Widget { .. } => None, + }, + } + } + + /// Returns the OpenGL texture object containing the contents of this surface. + /// + /// It is only legal to read from, not write to, this texture object. + #[inline] + pub fn surface_texture_object( + &self, + surface_texture: &SurfaceTexture, + ) -> Option { + surface_texture.gl_texture + } } impl Adapter { @@ -364,3 +1412,33 @@ impl<'a> DCGuard<'a> { } } } + +fn get_proc_address(symbol_name: &str) -> *const c_void { + unsafe { + // https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions#Windows + let symbol_name: CString = CString::new(symbol_name).unwrap(); + let symbol_ptr = symbol_name.as_ptr(); + let addr = wglGetProcAddress(symbol_ptr) as *const c_void; + if !addr.is_null() { + return addr; + } + OPENGL_LIBRARY.with(|opengl_library| { + libloaderapi::GetProcAddress(*opengl_library, symbol_ptr) as *const c_void + }) + } +} + +pub(crate) fn set_dc_pixel_format(dc: HDC, pixel_format: c_int) { + unsafe { + let mut pixel_format_descriptor = mem::zeroed(); + let pixel_format_count = wingdi::DescribePixelFormat( + dc, + pixel_format, + mem::size_of::() as UINT, + &mut pixel_format_descriptor, + ); + assert_ne!(pixel_format_count, 0); + let ok = wingdi::SetPixelFormat(dc, pixel_format, &mut pixel_format_descriptor); + assert_ne!(ok, FALSE); + } +} diff --git a/src/wgl/surface.rs b/src/wgl/surface.rs index c993502f..1c816b53 100644 --- a/src/wgl/surface.rs +++ b/src/wgl/surface.rs @@ -2,46 +2,21 @@ // //! An implementation of the GPU device for Windows using WGL/Direct3D interoperability. -use super::context::{self, Context, WGL_EXTENSION_FUNCTIONS}; -use super::device::Device; -use crate::error::WindowingApiError; use crate::renderbuffers::Renderbuffers; -use crate::{ContextID, Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType}; +use crate::{ContextID, Error, SurfaceID}; -use crate::gl; -type GLenum = c_uint; -type GLint = c_int; -use crate::gl_utils; use euclid::default::Size2D; -use glow::HasContext; -use std::ffi::{c_int, c_uint}; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; -use std::mem; -use std::os::raw::c_void; -use std::ptr; use std::thread; -use winapi::shared::dxgi::IDXGIResource; -use winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM; -use winapi::shared::dxgitype::DXGI_SAMPLE_DESC; -use winapi::shared::minwindef::{FALSE, UINT}; +use winapi::shared::minwindef::FALSE; use winapi::shared::ntdef::HANDLE; use winapi::shared::windef::HWND; -use winapi::shared::winerror; -use winapi::um::d3d11::{ID3D11Texture2D, D3D11_USAGE_DEFAULT}; -use winapi::um::d3d11::{D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE}; -use winapi::um::d3d11::{D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX, D3D11_TEXTURE2D_DESC}; -use winapi::um::handleapi::INVALID_HANDLE_VALUE; +use winapi::um::d3d11::ID3D11Texture2D; use winapi::um::wingdi; use winapi::um::winuser; -use winapi::Interface; use wio::com::ComPtr; -const SURFACE_GL_TEXTURE_TARGET: GLenum = gl::TEXTURE_2D; - -const WGL_ACCESS_READ_ONLY_NV: GLenum = 0x0000; -const WGL_ACCESS_READ_WRITE_NV: GLenum = 0x0001; - /// Represents a hardware buffer of pixels that can be rendered to via the CPU or GPU and either /// displayed in a native widget or bound to a texture for reading. /// @@ -92,7 +67,7 @@ pub struct SurfaceTexture { pub(crate) surface: Surface, #[allow(dead_code)] pub(crate) local_d3d11_texture: ComPtr, - local_gl_dx_interop_object: HANDLE, + pub(crate) local_gl_dx_interop_object: HANDLE, pub(crate) gl_texture: Option, pub(crate) phantom: PhantomData<*const ()>, } @@ -127,519 +102,6 @@ pub struct NativeWidget { pub window_handle: HWND, } -impl Device { - /// Creates either a generic or a widget surface, depending on the supplied surface type. - /// - /// Only the given context may ever render to the surface, but generic surfaces can be wrapped - /// up in a `SurfaceTexture` for reading by other contexts. - pub fn create_surface( - &self, - context: &Context, - _: SurfaceAccess, - surface_type: SurfaceType, - ) -> Result { - match surface_type { - SurfaceType::Generic { size } => self.create_generic_surface(context, &size), - SurfaceType::Widget { native_widget } => { - self.create_widget_surface(context, native_widget) - } - } - } - - fn create_generic_surface( - &self, - context: &Context, - size: &Size2D, - ) -> Result { - let dx_interop_functions = match WGL_EXTENSION_FUNCTIONS.dx_interop_functions { - None => return Err(Error::RequiredExtensionUnavailable), - Some(ref dx_interop_functions) => dx_interop_functions, - }; - - unsafe { - let _guard = self.temporarily_make_context_current(context)?; - - // Create the Direct3D 11 texture. - let d3d11_texture2d_desc = D3D11_TEXTURE2D_DESC { - Width: size.width as UINT, - Height: size.height as UINT, - MipLevels: 1, - ArraySize: 1, - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - SampleDesc: DXGI_SAMPLE_DESC { - Count: 1, - Quality: 0, - }, - Usage: D3D11_USAGE_DEFAULT, - BindFlags: D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, - CPUAccessFlags: 0, - MiscFlags: D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX, - }; - let mut d3d11_texture = ptr::null_mut(); - let mut result = self.d3d11_device.CreateTexture2D( - &d3d11_texture2d_desc, - ptr::null(), - &mut d3d11_texture, - ); - if !winerror::SUCCEEDED(result) { - return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed)); - } - assert!(!d3d11_texture.is_null()); - let d3d11_texture = ComPtr::from_raw(d3d11_texture); - - // Upcast it to a DXGI resource. - let mut dxgi_resource: *mut IDXGIResource = ptr::null_mut(); - result = d3d11_texture.QueryInterface( - &IDXGIResource::uuidof(), - &mut dxgi_resource as *mut *mut IDXGIResource as *mut *mut c_void, - ); - assert!(winerror::SUCCEEDED(result)); - assert!(!dxgi_resource.is_null()); - let dxgi_resource = ComPtr::from_raw(dxgi_resource); - - // Get the share handle. We'll need it both to bind to GL and to share the texture - // across contexts. - let mut dxgi_share_handle = INVALID_HANDLE_VALUE; - result = dxgi_resource.GetSharedHandle(&mut dxgi_share_handle); - assert!(winerror::SUCCEEDED(result)); - assert_ne!(dxgi_share_handle, INVALID_HANDLE_VALUE); - - // Tell GL about the share handle. - let ok = (dx_interop_functions.DXSetResourceShareHandleNV)( - d3d11_texture.as_raw() as *mut c_void, - dxgi_share_handle, - ); - assert_ne!(ok, FALSE); - - // Make our texture object on the GL side. - let gl_texture = context.gl.create_texture().unwrap(); - - // Bind the GL texture to the D3D11 texture. - let gl_dx_interop_object = (dx_interop_functions.DXRegisterObjectNV)( - self.gl_dx_interop_device, - d3d11_texture.as_raw() as *mut c_void, - gl_texture.0.get(), - gl::TEXTURE_2D, - WGL_ACCESS_READ_WRITE_NV, - ); - // Per the spec, and unlike other HANDLEs, null indicates an error. - if gl_dx_interop_object.is_null() { - let msg = std::io::Error::last_os_error(); // Equivalent to GetLastError(). - error!( - "Unable to share surface between OpenGL and DirectX. OS error '{}'.", - msg - ); - return Err(Error::SurfaceCreationFailed(WindowingApiError::Failed)); - } - - // Build our FBO. - let gl_framebuffer = context.gl.create_framebuffer().unwrap(); - let _guard = self.temporarily_bind_framebuffer(context, Some(gl_framebuffer)); - - // Attach the reflected D3D11 texture to that FBO. - context.gl.framebuffer_texture_2d( - gl::FRAMEBUFFER, - gl::COLOR_ATTACHMENT0, - SURFACE_GL_TEXTURE_TARGET, - Some(gl_texture), - 0, - ); - - // Create renderbuffers as appropriate, and attach them. - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - let renderbuffers = Renderbuffers::new(&context.gl, &size, &context_attributes); - renderbuffers.bind_to_current_framebuffer(&context.gl); - - // FIXME(pcwalton): Do we need to acquire the keyed mutex, or does the GL driver do - // that? - - Ok(Surface { - size: *size, - context_id: context.id, - win32_objects: Win32Objects::Texture { - d3d11_texture, - dxgi_share_handle, - gl_dx_interop_object, - gl_texture: Some(gl_texture), - gl_framebuffer: Some(gl_framebuffer), - renderbuffers, - }, - destroyed: false, - }) - } - } - - fn create_widget_surface( - &self, - context: &Context, - native_widget: NativeWidget, - ) -> Result { - unsafe { - // Get the bounds of the native HWND. - let mut widget_rect = mem::zeroed(); - let ok = winuser::GetWindowRect(native_widget.window_handle, &mut widget_rect); - if ok == FALSE { - return Err(Error::InvalidNativeWidget); - } - - // Set its pixel format. - { - let context_dc_guard = self.get_context_dc(context); - let pixel_format = wingdi::GetPixelFormat(context_dc_guard.dc); - let window_dc = winuser::GetDC(native_widget.window_handle); - context::set_dc_pixel_format(window_dc, pixel_format); - } - - Ok(Surface { - size: Size2D::new( - widget_rect.right - widget_rect.left, - widget_rect.bottom - widget_rect.top, - ), - context_id: context.id, - win32_objects: Win32Objects::Widget { - window_handle: native_widget.window_handle, - }, - destroyed: false, - }) - } - } - - /// Destroys a surface. - /// - /// The supplied context must be the context the surface is associated with, or this returns - /// an `IncompatibleSurface` error. - /// - /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in - /// the `drop` method. - pub fn destroy_surface( - &self, - context: &mut Context, - surface: &mut Surface, - ) -> Result<(), Error> { - let dx_interop_functions = WGL_EXTENSION_FUNCTIONS - .dx_interop_functions - .as_ref() - .expect("How did you make a surface without DX interop?"); - - if context.id != surface.context_id { - return Err(Error::IncompatibleSurface); - } - - let _guard = self.temporarily_make_context_current(context)?; - - unsafe { - match surface.win32_objects { - Win32Objects::Texture { - ref mut gl_dx_interop_object, - ref mut gl_texture, - ref mut gl_framebuffer, - ref mut renderbuffers, - d3d11_texture: _, - dxgi_share_handle: _, - } => { - renderbuffers.destroy(&context.gl); - - if let Some(fbo) = gl_framebuffer.take() { - gl_utils::destroy_framebuffer(&context.gl, fbo); - } - - if let Some(texture) = gl_texture.take() { - context.gl.delete_texture(texture); - } - - let ok = (dx_interop_functions.DXUnregisterObjectNV)( - self.gl_dx_interop_device, - *gl_dx_interop_object, - ); - assert_ne!(ok, FALSE); - *gl_dx_interop_object = INVALID_HANDLE_VALUE; - } - Win32Objects::Widget { window_handle: _ } => {} - } - - surface.destroyed = true; - } - - Ok(()) - } - - /// Creates a surface texture from an existing generic surface for use with the given context. - /// - /// The surface texture is local to the supplied context and takes ownership of the surface. - /// Destroying the surface texture allows you to retrieve the surface again. - /// - /// *The supplied context does not have to be the same context that the surface is associated - /// with.* This allows you to render to a surface in one context and sample from that surface - /// in another context. - /// - /// Calling this method on a widget surface returns a `WidgetAttached` error. - pub fn create_surface_texture( - &self, - context: &mut Context, - surface: Surface, - ) -> Result { - let dxgi_share_handle = match surface.win32_objects { - Win32Objects::Widget { .. } => return Err((Error::WidgetAttached, surface)), - Win32Objects::Texture { - dxgi_share_handle, .. - } => dxgi_share_handle, - }; - - let dx_interop_functions = WGL_EXTENSION_FUNCTIONS - .dx_interop_functions - .as_ref() - .expect("How did you make a surface without DX interop?"); - - let _guard = match self.temporarily_make_context_current(context) { - Ok(guard) => guard, - Err(err) => return Err((err, surface)), - }; - - unsafe { - // Create a new texture wrapping the shared handle. - let mut local_d3d11_texture = ptr::null_mut(); - let result = self.d3d11_device.OpenSharedResource( - dxgi_share_handle, - &ID3D11Texture2D::uuidof(), - &mut local_d3d11_texture, - ); - if !winerror::SUCCEEDED(result) || local_d3d11_texture.is_null() { - return Err(( - Error::SurfaceImportFailed(WindowingApiError::Failed), - surface, - )); - } - let local_d3d11_texture = ComPtr::from_raw(local_d3d11_texture as *mut ID3D11Texture2D); - - // Make GL aware of the connection between the share handle and the texture. - let ok = (dx_interop_functions.DXSetResourceShareHandleNV)( - local_d3d11_texture.as_raw() as *mut c_void, - dxgi_share_handle, - ); - assert_ne!(ok, FALSE); - - // Create a GL texture. - let gl_texture = context.gl.create_texture().unwrap(); - - // Register that texture with GL/DX interop. - let mut local_gl_dx_interop_object = (dx_interop_functions.DXRegisterObjectNV)( - self.gl_dx_interop_device, - local_d3d11_texture.as_raw() as *mut c_void, - gl_texture.0.get(), - gl::TEXTURE_2D, - WGL_ACCESS_READ_ONLY_NV, - ); - - // Lock the texture so that we can use it. - let ok = (dx_interop_functions.DXLockObjectsNV)( - self.gl_dx_interop_device, - 1, - &mut local_gl_dx_interop_object, - ); - assert_ne!(ok, FALSE); - - // Initialize the texture, for convenience. - // FIXME(pcwalton): We should probably reset the bound texture after this. - context.gl.bind_texture(gl::TEXTURE_2D, Some(gl_texture)); - context.gl.tex_parameter_i32( - gl::TEXTURE_2D, - gl::TEXTURE_MAG_FILTER, - gl::LINEAR as GLint, - ); - context.gl.tex_parameter_i32( - gl::TEXTURE_2D, - gl::TEXTURE_MIN_FILTER, - gl::LINEAR as GLint, - ); - context.gl.tex_parameter_i32( - gl::TEXTURE_2D, - gl::TEXTURE_WRAP_S, - gl::CLAMP_TO_EDGE as GLint, - ); - context.gl.tex_parameter_i32( - gl::TEXTURE_2D, - gl::TEXTURE_WRAP_T, - gl::CLAMP_TO_EDGE as GLint, - ); - - // Finish up. - Ok(SurfaceTexture { - surface, - local_d3d11_texture, - local_gl_dx_interop_object, - gl_texture: Some(gl_texture), - phantom: PhantomData, - }) - } - } - - /// Destroys a surface texture and returns the underlying surface. - /// - /// The supplied context must be the same context the surface texture was created with, or an - /// `IncompatibleSurfaceTexture` error is returned. - /// - /// All surface textures must be explicitly destroyed with this function, or a panic will - /// occur. - pub fn destroy_surface_texture( - &self, - context: &mut Context, - mut surface_texture: SurfaceTexture, - ) -> Result { - let dx_interop_functions = WGL_EXTENSION_FUNCTIONS - .dx_interop_functions - .as_ref() - .expect("How did you make a surface without DX interop?"); - - let _guard = match self.temporarily_make_context_current(context) { - Ok(guard) => guard, - Err(err) => return Err((err, surface_texture)), - }; - - unsafe { - // Unlock the texture. - let ok = (dx_interop_functions.DXUnlockObjectsNV)( - self.gl_dx_interop_device, - 1, - &mut surface_texture.local_gl_dx_interop_object, - ); - assert_ne!(ok, FALSE); - - // Unregister the texture from GL/DX interop. - let ok = (dx_interop_functions.DXUnregisterObjectNV)( - self.gl_dx_interop_device, - surface_texture.local_gl_dx_interop_object, - ); - assert_ne!(ok, FALSE); - surface_texture.local_gl_dx_interop_object = INVALID_HANDLE_VALUE; - - // Destroy the GL texture. - if let Some(texture) = surface_texture.gl_texture.take() { - context.gl.delete_texture(texture); - } - } - - Ok(surface_texture.surface) - } - - pub(crate) fn lock_surface(&self, surface: &Surface) { - let mut gl_dx_interop_object = match surface.win32_objects { - Win32Objects::Widget { .. } => return, - Win32Objects::Texture { - gl_dx_interop_object, - .. - } => gl_dx_interop_object, - }; - - let dx_interop_functions = WGL_EXTENSION_FUNCTIONS - .dx_interop_functions - .as_ref() - .expect("How did you make a surface without DX interop?"); - - unsafe { - let ok = (dx_interop_functions.DXLockObjectsNV)( - self.gl_dx_interop_device, - 1, - &mut gl_dx_interop_object, - ); - assert_ne!(ok, FALSE); - } - } - - pub(crate) fn unlock_surface(&self, surface: &Surface) { - let mut gl_dx_interop_object = match surface.win32_objects { - Win32Objects::Widget { .. } => return, - Win32Objects::Texture { - gl_dx_interop_object, - .. - } => gl_dx_interop_object, - }; - - let dx_interop_functions = WGL_EXTENSION_FUNCTIONS - .dx_interop_functions - .as_ref() - .expect("How did you make a surface without DX interop?"); - - unsafe { - let ok = (dx_interop_functions.DXUnlockObjectsNV)( - self.gl_dx_interop_device, - 1, - &mut gl_dx_interop_object, - ); - assert_ne!(ok, FALSE); - } - } - - /// Returns a pointer to the underlying surface data for reading or writing by the CPU. - #[inline] - pub fn lock_surface_data<'s>( - &self, - _surface: &'s mut Surface, - ) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Returns the OpenGL texture target needed to read from this surface texture. - /// - /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. - #[inline] - pub fn surface_gl_texture_target(&self) -> GLenum { - gl::TEXTURE_2D - } - - /// Displays the contents of a widget surface on screen. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't show up in their - /// associated widgets until this method is called. - /// - /// The supplied context must match the context the surface was created with, or an - /// `IncompatibleSurface` error is returned. - pub fn present_surface(&self, _: &Context, surface: &mut Surface) -> Result<(), Error> { - surface.present() - } - - /// Resizes a widget surface. - pub fn resize_surface( - &self, - _scontext: &Context, - surface: &mut Surface, - size: Size2D, - ) -> Result<(), Error> { - surface.resize(size); - Ok(()) - } - - /// Returns various information about the surface, including the framebuffer object needed to - /// render to this surface. - /// - /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` - /// on the framebuffer object returned by this function. This framebuffer object may or not be - /// 0, the default framebuffer, depending on platform. - #[inline] - pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { - SurfaceInfo { - size: surface.size, - id: surface.id(), - context_id: surface.context_id, - framebuffer_object: match surface.win32_objects { - Win32Objects::Texture { gl_framebuffer, .. } => gl_framebuffer, - Win32Objects::Widget { .. } => None, - }, - } - } - - /// Returns the OpenGL texture object containing the contents of this surface. - /// - /// It is only legal to read from, not write to, this texture object. - #[inline] - pub fn surface_texture_object( - &self, - surface_texture: &SurfaceTexture, - ) -> Option { - surface_texture.gl_texture - } -} - impl Surface { pub(crate) fn id(&self) -> SurfaceID { match self.win32_objects { diff --git a/src/x11/context.rs b/src/x11/context.rs index 9db4ede9..5facd79d 100644 --- a/src/x11/context.rs +++ b/src/x11/context.rs @@ -1,18 +1,8 @@ //! OpenGL rendering contexts on X11 via EGL. -use euclid::default::Size2D; - -use super::device::Device; -use super::surface::Surface; -use crate::base::egl::context::{self, CurrentContextGuard, EGLBackedContext}; -use crate::context::ContextID; -use crate::egl; -use crate::egl::types::EGLint; -use crate::{ContextAttributes, Error, Gl, SurfaceInfo}; - -use std::os::raw::c_void; - +use crate::base::egl::context::EGLBackedContext; pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; +use crate::Gl; /// Represents an OpenGL rendering context. /// @@ -31,228 +21,3 @@ pub use crate::base::egl::context::{ContextDescriptor, NativeContext}; /// /// A context must be explicitly destroyed with `destroy_context()`, or a panic will occur. pub struct Context(pub(crate) EGLBackedContext, pub(crate) Gl); - -impl Device { - /// Creates a context descriptor with the given attributes. - /// - /// Context descriptors are local to this device. - #[inline] - pub fn create_context_descriptor( - &self, - attributes: &ContextAttributes, - ) -> Result { - // Set environment variables as appropriate. - self.adapter.set_environment_variables(); - - unsafe { - ContextDescriptor::new( - self.native_connection.egl_display, - attributes, - &[ - egl::SURFACE_TYPE as EGLint, - egl::WINDOW_BIT as EGLint, - egl::RENDERABLE_TYPE as EGLint, - egl::OPENGL_BIT as EGLint, - ], - ) - } - } - - /// Creates a new OpenGL context. - /// - /// The context initially has no surface attached. Until a surface is bound to it, rendering - /// commands will fail or have no effect. - #[inline] - pub fn create_context( - &self, - descriptor: &ContextDescriptor, - share_with: Option<&Context>, - ) -> Result { - unsafe { - let context = EGLBackedContext::new( - self.native_connection.egl_display, - descriptor, - share_with.map(|ctx| &ctx.0), - self.gl_api(), - )?; - context.make_current(self.native_connection.egl_display)?; - Ok(Context( - context, - Gl::from_loader_function(context::get_proc_address), - )) - } - } - - /// Wraps an `EGLContext` in a native context and returns it. - /// - /// The context is not retained, as there is no way to do this in the EGL API. Therefore, - /// it is the caller's responsibility to ensure that the returned `Context` object remains - /// alive as long as the `EGLContext` is. - #[inline] - pub unsafe fn create_context_from_native_context( - &self, - native_context: NativeContext, - ) -> Result { - Ok(Context( - EGLBackedContext::from_native_context(native_context), - Gl::from_loader_function(context::get_proc_address), - )) - } - - /// Destroys a context. - /// - /// The context must have been created on this device. - pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { - if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { - self.destroy_surface(context, &mut surface)?; - } - - unsafe { - context.0.destroy(self.native_connection.egl_display); - Ok(()) - } - } - - /// Given a context, returns its underlying EGL context and attached surfaces. - #[inline] - pub fn native_context(&self, context: &Context) -> NativeContext { - context.0.native_context() - } - - /// Returns the descriptor that this context was created with. - #[inline] - pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { - unsafe { - ContextDescriptor::from_egl_context( - &context.1, - self.native_connection.egl_display, - context.0.egl_context, - ) - } - } - - /// Makes the context the current OpenGL context for this thread. - /// - /// After calling this function, it is valid to use OpenGL rendering commands. - #[inline] - pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { - unsafe { context.0.make_current(self.native_connection.egl_display) } - } - - /// Removes the current OpenGL context from this thread. - /// - /// After calling this function, OpenGL rendering commands will fail until a new context is - /// made current. - #[inline] - pub fn make_no_context_current(&self) -> Result<(), Error> { - unsafe { context::make_no_context_current(self.native_connection.egl_display) } - } - - #[inline] - pub(crate) fn temporarily_make_context_current( - &self, - context: &Context, - ) -> Result { - let guard = CurrentContextGuard::new(); - self.make_context_current(context)?; - Ok(guard) - } - - /// Returns the attributes that the context descriptor was created with. - #[inline] - pub fn context_descriptor_attributes( - &self, - context_descriptor: &ContextDescriptor, - ) -> ContextAttributes { - unsafe { context_descriptor.attributes(self.native_connection.egl_display) } - } - - /// Fetches the address of an OpenGL function associated with this context. - /// - /// OpenGL functions are local to a context. You should not use OpenGL functions on one context - /// with any other context. - /// - /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to - /// load OpenGL function pointers. - #[inline] - pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { - context::get_proc_address(symbol_name) - } - - /// Attaches a surface to a context for rendering. - /// - /// This function takes ownership of the surface. The surface must have been created with this - /// context, or an `IncompatibleSurface` error is returned. - /// - /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is - /// returned. To avoid this error, first unbind the existing surface with - /// `unbind_surface_from_context`. - /// - /// If an error is returned, the surface is returned alongside it. - #[inline] - pub fn bind_surface_to_context( - &self, - context: &mut Context, - surface: Surface, - ) -> Result<(), (Error, Surface)> { - unsafe { - context - .0 - .bind_surface(self.native_connection.egl_display, surface.0) - .map_err(|(err, surface)| (err, Surface(surface))) - } - } - - /// Removes and returns any attached surface from this context. - /// - /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the - /// surface is safe to read from immediately when this function returns. - pub fn unbind_surface_from_context( - &self, - context: &mut Context, - ) -> Result, Error> { - unsafe { - context - .0 - .unbind_surface(&context.1, self.native_connection.egl_display) - .map(|maybe_surface| maybe_surface.map(Surface)) - } - } - - /// Displays the contents of the currently bound surface to the screen, if - /// it is a widget surface. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't - /// show up in their associated widgets until this method is called. - pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { - context - .0 - .present_bound_surface(self.native_connection.egl_display) - } - - /// If the currently bound surface is a widget surface, resize it, - pub fn resize_bound_surface( - &self, - context: &mut Context, - size: Size2D, - ) -> Result<(), Error> { - context.0.resize_bound_surface(size) - } - - /// Returns a unique ID representing a context. - /// - /// This ID is unique to all currently-allocated contexts. If you destroy a context and create - /// a new one, the new context might have the same ID as the destroyed one. - #[inline] - pub fn context_id(&self, context: &Context) -> ContextID { - context.0.id - } - - /// Returns various information about the surface attached to a context. - /// - /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. - #[inline] - pub fn context_surface_info(&self, context: &Context) -> Result, Error> { - context.0.surface_info() - } -} diff --git a/src/x11/device.rs b/src/x11/device.rs index 22e45565..44e0801a 100644 --- a/src/x11/device.rs +++ b/src/x11/device.rs @@ -1,11 +1,29 @@ //! A wrapper around X11 `EGLDisplay`s. use super::connection::{Connection, NativeConnectionWrapper}; -use crate::{Error, GLApi}; - +use super::context::Context; +use super::surface::Surface; +use crate::base::egl::{ + context::{self, CurrentContextGuard, EGLBackedContext}, + surface::EGLBackedSurface, +}; +use crate::context::ContextID; +use crate::egl::types::EGLint; +pub use crate::mesa_surfaceless::device::Adapter; +use crate::x11::surface::{NativeWidget, SurfaceDataGuard, SurfaceTexture}; +use crate::{ + egl, ContextAttributes, ContextDescriptor, Error, GLApi, Gl, SurfaceAccess, SurfaceInfo, + SurfaceType, +}; +use crate::{gl, NativeContext}; +use euclid::default::Size2D; +use glow::Texture; +use std::os::raw::c_void; use std::sync::Arc; +use x11_dl::xlib::Window; -pub use crate::mesa_surfaceless::device::Adapter; +// FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? +const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; /// A thread-local handle to a device. /// @@ -63,4 +81,419 @@ impl Device { pub fn gl_api(&self) -> GLApi { GLApi::GL } + + /// Creates a context descriptor with the given attributes. + /// + /// Context descriptors are local to this device. + #[inline] + pub fn create_context_descriptor( + &self, + attributes: &ContextAttributes, + ) -> Result { + // Set environment variables as appropriate. + self.adapter.set_environment_variables(); + + unsafe { + ContextDescriptor::new( + self.native_connection.egl_display, + attributes, + &[ + egl::SURFACE_TYPE as EGLint, + egl::WINDOW_BIT as EGLint, + egl::RENDERABLE_TYPE as EGLint, + egl::OPENGL_BIT as EGLint, + ], + ) + } + } + + /// Creates a new OpenGL context. + /// + /// The context initially has no surface attached. Until a surface is bound to it, rendering + /// commands will fail or have no effect. + #[inline] + pub fn create_context( + &self, + descriptor: &ContextDescriptor, + share_with: Option<&Context>, + ) -> Result { + unsafe { + let context = EGLBackedContext::new( + self.native_connection.egl_display, + descriptor, + share_with.map(|ctx| &ctx.0), + self.gl_api(), + )?; + context.make_current(self.native_connection.egl_display)?; + Ok(Context( + context, + Gl::from_loader_function(context::get_proc_address), + )) + } + } + + /// Wraps an `EGLContext` in a native context and returns it. + /// + /// The context is not retained, as there is no way to do this in the EGL API. Therefore, + /// it is the caller's responsibility to ensure that the returned `Context` object remains + /// alive as long as the `EGLContext` is. + #[inline] + pub unsafe fn create_context_from_native_context( + &self, + native_context: NativeContext, + ) -> Result { + Ok(Context( + EGLBackedContext::from_native_context(native_context), + Gl::from_loader_function(context::get_proc_address), + )) + } + + /// Destroys a context. + /// + /// The context must have been created on this device. + pub fn destroy_context(&self, context: &mut Context) -> Result<(), Error> { + if let Ok(Some(mut surface)) = self.unbind_surface_from_context(context) { + self.destroy_surface(context, &mut surface)?; + } + + unsafe { + context.0.destroy(self.native_connection.egl_display); + Ok(()) + } + } + + /// Given a context, returns its underlying EGL context and attached surfaces. + #[inline] + pub fn native_context(&self, context: &Context) -> NativeContext { + context.0.native_context() + } + + /// Returns the descriptor that this context was created with. + #[inline] + pub fn context_descriptor(&self, context: &Context) -> ContextDescriptor { + unsafe { + ContextDescriptor::from_egl_context( + &context.1, + self.native_connection.egl_display, + context.0.egl_context, + ) + } + } + + /// Makes the context the current OpenGL context for this thread. + /// + /// After calling this function, it is valid to use OpenGL rendering commands. + #[inline] + pub fn make_context_current(&self, context: &Context) -> Result<(), Error> { + unsafe { context.0.make_current(self.native_connection.egl_display) } + } + + /// Removes the current OpenGL context from this thread. + /// + /// After calling this function, OpenGL rendering commands will fail until a new context is + /// made current. + #[inline] + pub fn make_no_context_current(&self) -> Result<(), Error> { + unsafe { context::make_no_context_current(self.native_connection.egl_display) } + } + + #[inline] + pub(crate) fn temporarily_make_context_current( + &self, + context: &Context, + ) -> Result { + let guard = CurrentContextGuard::new(); + self.make_context_current(context)?; + Ok(guard) + } + + /// Returns the attributes that the context descriptor was created with. + #[inline] + pub fn context_descriptor_attributes( + &self, + context_descriptor: &ContextDescriptor, + ) -> ContextAttributes { + unsafe { context_descriptor.attributes(self.native_connection.egl_display) } + } + + /// Fetches the address of an OpenGL function associated with this context. + /// + /// OpenGL functions are local to a context. You should not use OpenGL functions on one context + /// with any other context. + /// + /// This method is typically used with a function like `gl::load_with()` from the `gl` crate to + /// load OpenGL function pointers. + #[inline] + pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void { + context::get_proc_address(symbol_name) + } + + /// Attaches a surface to a context for rendering. + /// + /// This function takes ownership of the surface. The surface must have been created with this + /// context, or an `IncompatibleSurface` error is returned. + /// + /// If this function is called with a surface already bound, a `SurfaceAlreadyBound` error is + /// returned. To avoid this error, first unbind the existing surface with + /// `unbind_surface_from_context`. + /// + /// If an error is returned, the surface is returned alongside it. + #[inline] + pub fn bind_surface_to_context( + &self, + context: &mut Context, + surface: Surface, + ) -> Result<(), (Error, Surface)> { + unsafe { + context + .0 + .bind_surface(self.native_connection.egl_display, surface.0) + .map_err(|(err, surface)| (err, Surface(surface))) + } + } + + /// Removes and returns any attached surface from this context. + /// + /// Any pending OpenGL commands targeting this surface will be automatically flushed, so the + /// surface is safe to read from immediately when this function returns. + pub fn unbind_surface_from_context( + &self, + context: &mut Context, + ) -> Result, Error> { + unsafe { + context + .0 + .unbind_surface(&context.1, self.native_connection.egl_display) + .map(|maybe_surface| maybe_surface.map(Surface)) + } + } + + /// Displays the contents of the currently bound surface to the screen, if + /// it is a widget surface. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't + /// show up in their associated widgets until this method is called. + pub fn present_bound_surface(&self, context: &mut Context) -> Result<(), Error> { + context + .0 + .present_bound_surface(self.native_connection.egl_display) + } + + /// If the currently bound surface is a widget surface, resize it, + pub fn resize_bound_surface( + &self, + context: &mut Context, + size: Size2D, + ) -> Result<(), Error> { + context.0.resize_bound_surface(size) + } + + /// Returns a unique ID representing a context. + /// + /// This ID is unique to all currently-allocated contexts. If you destroy a context and create + /// a new one, the new context might have the same ID as the destroyed one. + #[inline] + pub fn context_id(&self, context: &Context) -> ContextID { + context.0.id + } + + /// Returns various information about the surface attached to a context. + /// + /// This includes, most notably, the OpenGL framebuffer object needed to render to the surface. + #[inline] + pub fn context_surface_info(&self, context: &Context) -> Result, Error> { + context.0.surface_info() + } + + /// Creates either a generic or a widget surface, depending on the supplied surface type. + /// + /// Only the given context may ever render to the surface, but generic surfaces can be wrapped + /// up in a `SurfaceTexture` for reading by other contexts. + pub fn create_surface( + &self, + context: &Context, + _: SurfaceAccess, + surface_type: SurfaceType, + ) -> Result { + match surface_type { + SurfaceType::Generic { size } => self.create_generic_surface(context, &size), + SurfaceType::Widget { native_widget } => unsafe { + self.create_window_surface(context, native_widget.window) + }, + } + } + + fn create_generic_surface( + &self, + context: &Context, + size: &Size2D, + ) -> Result { + let _guard = self.temporarily_make_context_current(context)?; + let context_descriptor = self.context_descriptor(context); + let context_attributes = self.context_descriptor_attributes(&context_descriptor); + + Ok(Surface(EGLBackedSurface::new_generic( + &context.1, + self.native_connection.egl_display, + context.0.egl_context, + context.0.id, + &context_attributes, + size, + ))) + } + + unsafe fn create_window_surface( + &self, + context: &Context, + mut x11_window: Window, + ) -> Result { + let egl_config_id = context::get_context_attr( + self.native_connection.egl_display, + context.0.egl_context, + egl::CONFIG_ID as EGLint, + ); + let egl_config = + context::egl_config_from_id(self.native_connection.egl_display, egl_config_id); + + let display_guard = self.native_connection.lock_display(); + let (mut root_window, mut x, mut y, mut width, mut height) = (0, 0, 0, 0, 0); + let (mut border_width, mut depth) = (0, 0); + (self.native_connection.xlib.XGetGeometry)( + display_guard.display(), + x11_window, + &mut root_window, + &mut x, + &mut y, + &mut width, + &mut height, + &mut border_width, + &mut depth, + ); + let size = Size2D::new(width as i32, height as i32); + + Ok(Surface(EGLBackedSurface::new_window( + self.native_connection.egl_display, + egl_config, + &mut x11_window as *mut Window as *mut c_void, + context.0.id, + &size, + ))) + } + + /// Creates a surface texture from an existing generic surface for use with the given context. + /// + /// The surface texture is local to the supplied context and takes ownership of the surface. + /// Destroying the surface texture allows you to retrieve the surface again. + /// + /// *The supplied context does not have to be the same context that the surface is associated + /// with.* This allows you to render to a surface in one context and sample from that surface + /// in another context. + /// + /// Calling this method on a widget surface returns a `WidgetAttached` error. + pub fn create_surface_texture( + &self, + context: &mut Context, + surface: Surface, + ) -> Result { + let _guard = match self.temporarily_make_context_current(context) { + Ok(guard) => guard, + Err(err) => return Err((err, surface)), + }; + + match surface.0.to_surface_texture(&context.1) { + Ok(surface_texture) => Ok(SurfaceTexture(surface_texture)), + Err((err, surface)) => Err((err, Surface(surface))), + } + } + + /// Destroys a surface. + /// + /// The supplied context must be the context the surface is associated with, or this returns + /// an `IncompatibleSurface` error. + /// + /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in + /// the `drop` method. + pub fn destroy_surface( + &self, + context: &mut Context, + surface: &mut Surface, + ) -> Result<(), Error> { + let egl_display = self.native_connection.egl_display; + surface.0.destroy(&context.1, egl_display, context.0.id)?; + Ok(()) + } + + /// Destroys a surface texture and returns the underlying surface. + /// + /// The supplied context must be the same context the surface texture was created with, or an + /// `IncompatibleSurfaceTexture` error is returned. + /// + /// All surface textures must be explicitly destroyed with this function, or a panic will + /// occur. + pub fn destroy_surface_texture( + &self, + context: &mut Context, + surface_texture: SurfaceTexture, + ) -> Result { + match self.temporarily_make_context_current(context) { + Ok(_guard) => Ok(Surface(surface_texture.0.destroy(&context.1))), + Err(err) => Err((err, surface_texture)), + } + } + + /// Displays the contents of a widget surface on screen. + /// + /// Widget surfaces are internally double-buffered, so changes to them don't show up in their + /// associated widgets until this method is called. + /// + /// The supplied context must match the context the surface was created with, or an + /// `IncompatibleSurface` error is returned. + pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { + surface + .0 + .present(self.native_connection.egl_display, context.0.egl_context) + } + + /// Resizes a widget surface. + pub fn resize_surface( + &self, + _context: &Context, + surface: &mut Surface, + size: Size2D, + ) -> Result<(), Error> { + surface.0.resize(size); + Ok(()) + } + + /// Returns a pointer to the underlying surface data for reading or writing by the CPU. + #[inline] + pub fn lock_surface_data<'s>(&self, _: &'s mut Surface) -> Result, Error> { + Err(Error::Unimplemented) + } + + /// Returns the OpenGL texture target needed to read from this surface texture. + /// + /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. + #[inline] + pub fn surface_gl_texture_target(&self) -> u32 { + SURFACE_GL_TEXTURE_TARGET + } + + /// Returns various information about the surface, including the framebuffer object needed to + /// render to this surface. + /// + /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` + /// on the framebuffer object returned by this function. This framebuffer object may or not be + /// 0, the default framebuffer, depending on platform. + pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { + surface.0.info() + } + + /// Returns the OpenGL texture object containing the contents of this surface. + /// + /// It is only legal to read from, not write to, this texture object. + #[inline] + pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { + surface_texture.0.texture_object + } } diff --git a/src/x11/surface.rs b/src/x11/surface.rs index ff566d9f..c2e976f3 100644 --- a/src/x11/surface.rs +++ b/src/x11/surface.rs @@ -2,24 +2,10 @@ // //! A surface implementation using X11 surfaces backed by TextureImage. -use super::context::Context; -use super::device::Device; -use crate::base::egl::context; use crate::base::egl::surface::{EGLBackedSurface, EGLSurfaceTexture}; -use crate::egl; -use crate::egl::types::EGLint; -use crate::gl; -use crate::{Error, SurfaceAccess, SurfaceInfo, SurfaceType}; - -use euclid::default::Size2D; -use glow::Texture; use std::marker::PhantomData; -use std::os::raw::c_void; use x11_dl::xlib::Window; -// FIXME(pcwalton): Is this right, or should it be `TEXTURE_EXTERNAL_OES`? -const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_2D; - /// Represents a hardware buffer of pixels that can be rendered to via the CPU or GPU and either /// displayed in a native widget or bound to a texture for reading. /// @@ -59,200 +45,6 @@ pub struct NativeWidget { unsafe impl Send for Surface {} -impl Device { - /// Creates either a generic or a widget surface, depending on the supplied surface type. - /// - /// Only the given context may ever render to the surface, but generic surfaces can be wrapped - /// up in a `SurfaceTexture` for reading by other contexts. - pub fn create_surface( - &self, - context: &Context, - _: SurfaceAccess, - surface_type: SurfaceType, - ) -> Result { - match surface_type { - SurfaceType::Generic { size } => self.create_generic_surface(context, &size), - SurfaceType::Widget { native_widget } => unsafe { - self.create_window_surface(context, native_widget.window) - }, - } - } - - fn create_generic_surface( - &self, - context: &Context, - size: &Size2D, - ) -> Result { - let _guard = self.temporarily_make_context_current(context)?; - let context_descriptor = self.context_descriptor(context); - let context_attributes = self.context_descriptor_attributes(&context_descriptor); - - Ok(Surface(EGLBackedSurface::new_generic( - &context.1, - self.native_connection.egl_display, - context.0.egl_context, - context.0.id, - &context_attributes, - size, - ))) - } - - unsafe fn create_window_surface( - &self, - context: &Context, - mut x11_window: Window, - ) -> Result { - let egl_config_id = context::get_context_attr( - self.native_connection.egl_display, - context.0.egl_context, - egl::CONFIG_ID as EGLint, - ); - let egl_config = - context::egl_config_from_id(self.native_connection.egl_display, egl_config_id); - - let display_guard = self.native_connection.lock_display(); - let (mut root_window, mut x, mut y, mut width, mut height) = (0, 0, 0, 0, 0); - let (mut border_width, mut depth) = (0, 0); - (self.native_connection.xlib.XGetGeometry)( - display_guard.display(), - x11_window, - &mut root_window, - &mut x, - &mut y, - &mut width, - &mut height, - &mut border_width, - &mut depth, - ); - let size = Size2D::new(width as i32, height as i32); - - Ok(Surface(EGLBackedSurface::new_window( - self.native_connection.egl_display, - egl_config, - &mut x11_window as *mut Window as *mut c_void, - context.0.id, - &size, - ))) - } - - /// Creates a surface texture from an existing generic surface for use with the given context. - /// - /// The surface texture is local to the supplied context and takes ownership of the surface. - /// Destroying the surface texture allows you to retrieve the surface again. - /// - /// *The supplied context does not have to be the same context that the surface is associated - /// with.* This allows you to render to a surface in one context and sample from that surface - /// in another context. - /// - /// Calling this method on a widget surface returns a `WidgetAttached` error. - pub fn create_surface_texture( - &self, - context: &mut Context, - surface: Surface, - ) -> Result { - let _guard = match self.temporarily_make_context_current(context) { - Ok(guard) => guard, - Err(err) => return Err((err, surface)), - }; - - match surface.0.to_surface_texture(&context.1) { - Ok(surface_texture) => Ok(SurfaceTexture(surface_texture)), - Err((err, surface)) => Err((err, Surface(surface))), - } - } - - /// Destroys a surface. - /// - /// The supplied context must be the context the surface is associated with, or this returns - /// an `IncompatibleSurface` error. - /// - /// You must explicitly call this method to dispose of a surface. Otherwise, a panic occurs in - /// the `drop` method. - pub fn destroy_surface( - &self, - context: &mut Context, - surface: &mut Surface, - ) -> Result<(), Error> { - let egl_display = self.native_connection.egl_display; - surface.0.destroy(&context.1, egl_display, context.0.id)?; - Ok(()) - } - - /// Destroys a surface texture and returns the underlying surface. - /// - /// The supplied context must be the same context the surface texture was created with, or an - /// `IncompatibleSurfaceTexture` error is returned. - /// - /// All surface textures must be explicitly destroyed with this function, or a panic will - /// occur. - pub fn destroy_surface_texture( - &self, - context: &mut Context, - surface_texture: SurfaceTexture, - ) -> Result { - match self.temporarily_make_context_current(context) { - Ok(_guard) => Ok(Surface(surface_texture.0.destroy(&context.1))), - Err(err) => Err((err, surface_texture)), - } - } - - /// Displays the contents of a widget surface on screen. - /// - /// Widget surfaces are internally double-buffered, so changes to them don't show up in their - /// associated widgets until this method is called. - /// - /// The supplied context must match the context the surface was created with, or an - /// `IncompatibleSurface` error is returned. - pub fn present_surface(&self, context: &Context, surface: &mut Surface) -> Result<(), Error> { - surface - .0 - .present(self.native_connection.egl_display, context.0.egl_context) - } - - /// Resizes a widget surface. - pub fn resize_surface( - &self, - _context: &Context, - surface: &mut Surface, - size: Size2D, - ) -> Result<(), Error> { - surface.0.resize(size); - Ok(()) - } - - /// Returns a pointer to the underlying surface data for reading or writing by the CPU. - #[inline] - pub fn lock_surface_data<'s>(&self, _: &'s mut Surface) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Returns the OpenGL texture target needed to read from this surface texture. - /// - /// This will be `GL_TEXTURE_2D` or `GL_TEXTURE_RECTANGLE`, depending on platform. - #[inline] - pub fn surface_gl_texture_target(&self) -> u32 { - SURFACE_GL_TEXTURE_TARGET - } - - /// Returns various information about the surface, including the framebuffer object needed to - /// render to this surface. - /// - /// Before rendering to a surface attached to a context, you must call `glBindFramebuffer()` - /// on the framebuffer object returned by this function. This framebuffer object may or not be - /// 0, the default framebuffer, depending on platform. - pub fn surface_info(&self, surface: &Surface) -> SurfaceInfo { - surface.0.info() - } - - /// Returns the OpenGL texture object containing the contents of this surface. - /// - /// It is only legal to read from, not write to, this texture object. - #[inline] - pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option { - surface_texture.0.texture_object - } -} - /// Represents the CPU view of the pixel data of this surface. pub struct SurfaceDataGuard<'a> { phantom: PhantomData<&'a ()>,