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::surface::Framebuffer;
use crate::{ContextAttributeFlags, ContextAttributes, Error, GLVersion, Gl, SurfaceInfo};
use cgl::{kCGLPFAAllowOfflineRenderers, kCGLPFAAlphaSize, kCGLPFADepthSize};
use cgl::{kCGLPFAOpenGLProfile, kCGLPFAStencilSize};
use cgl::{
CGLChoosePixelFormat, CGLContextObj, CGLCreateContext, CGLDescribePixelFormat, CGLError,
};
use cgl::{CGLGetCurrentContext, CGLGetPixelFormat, CGLPixelFormatAttribute, CGLPixelFormatObj};
use cgl::{CGLReleasePixelFormat, CGLRetainPixelFormat, CGLSetCurrentContext};
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;
#[allow(non_upper_case_globals)]
const kCGLNoError: CGLError = 0;
#[allow(non_upper_case_globals)]
const kCGLOGLPVersion_Legacy: CGLPixelFormatAttribute = 0x1000;
#[allow(non_upper_case_globals)]
const kCGLOGLPVersion_3_2_Core: CGLPixelFormatAttribute = 0x3200;
#[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<CFBundle> = {
let framework_identifier = CFString::from_str(OPENGL_FRAMEWORK_IDENTIFIER);
let framework = CFBundle::bundle_with_identifier(Some(&framework_identifier));
framework.unwrap()
};
}
pub struct Context {
pub(crate) cgl_context: CGLContextObj,
pub(crate) id: ContextID,
framebuffer: Framebuffer<Surface, ()>,
pub(crate) gl: Rc<Gl>,
}
pub struct NativeContext(pub CGLContextObj);
impl Drop for Context {
#[inline]
fn drop(&mut self) {
if !self.cgl_context.is_null() && !thread::panicking() {
panic!("Contexts must be destroyed explicitly with `destroy_context`!")
}
}
}
pub struct ContextDescriptor {
cgl_pixel_format: CGLPixelFormatObj,
}
impl Drop for ContextDescriptor {
#[inline]
fn drop(&mut self) {
unsafe {
CGLReleasePixelFormat(self.cgl_pixel_format);
}
}
}
impl Clone for ContextDescriptor {
#[inline]
fn clone(&self) -> ContextDescriptor {
unsafe {
ContextDescriptor {
cgl_pixel_format: CGLRetainPixelFormat(self.cgl_pixel_format),
}
}
}
}
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 {
pub fn create_context_descriptor(
&self,
attributes: &ContextAttributes,
) -> Result<ContextDescriptor, Error> {
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,
];
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 })
}
}
pub fn create_context(
&self,
descriptor: &ContextDescriptor,
share_with: Option<&Context>,
) -> Result<Context, Error> {
let mut next_context_id = CREATE_CONTEXT_MUTEX.lock().unwrap();
unsafe {
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)?;
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)
}
}
pub unsafe fn create_context_from_native_context(
&self,
native_context: NativeContext,
) -> Result<Context, Error> {
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)
}
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(())
}
#[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 }
}
}
pub fn make_context_current(&self, context: &Context) -> Result<(), Error> {
make_cgl_context_current(context.cgl_context)
}
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<CurrentContextGuard, Error> {
let guard = CurrentContextGuard::new();
self.make_context_current(context)?;
Ok(guard)
}
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(())
}
pub fn unbind_surface_from_context(
&self,
context: &mut Context,
) -> Result<Option<Surface>, 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) => {
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))
}
}
}
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
}
}
#[inline]
pub fn get_proc_address(&self, _: &Context, symbol_name: &str) -> *const c_void {
get_proc_address(symbol_name)
}
pub fn context_surface_info(&self, context: &Context) -> Result<Option<SurfaceInfo>, Error> {
match context.framebuffer {
Framebuffer::None => Ok(None),
Framebuffer::External(_) => Err(Error::ExternalRenderTarget),
Framebuffer::Surface(ref surface) => Ok(Some(self.surface_info(surface))),
}
}
#[inline]
pub fn context_id(&self, context: &Context) -> ContextID {
context.id
}
#[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,
}
impl Drop for CurrentContextGuard {
fn drop(&mut self) {
unsafe {
CGLSetCurrentContext(self.old_cgl_context);
}
}
}
impl CurrentContextGuard {
fn new() -> CurrentContextGuard {
unsafe {
CurrentContextGuard {
old_cgl_context: CGLGetCurrentContext(),
}
}
}
}
impl Clone for NativeContext {
#[inline]
fn clone(&self) -> NativeContext {
unsafe { NativeContext(CGLRetainContext(self.0)) }
}
}
impl Drop for NativeContext {
#[inline]
fn drop(&mut self) {
unsafe {
CGLReleaseContext(self.0);
self.0 = ptr::null_mut();
}
}
}
impl NativeContext {
#[inline]
pub fn current() -> Result<NativeContext, Error> {
unsafe {
let cgl_context = CGLGetCurrentContext();
if !cgl_context.is_null() {
Ok(NativeContext(cgl_context))
} else {
Err(Error::NoCurrentContext)
}
}
}
}