use super::connection::Connection;
use crate::base::io_surface::device::{Adapter as SystemAdapter, Device as SystemDevice};
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;
#[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;
thread_local! {
static OPENGL_FRAMEWORK: CFRetained<CFBundle> = {
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()
};
}
#[derive(Clone, Debug)]
pub struct Adapter(pub(crate) SystemAdapter);
#[derive(Clone)]
pub struct Device(pub(crate) SystemDevice);
impl Device {
#[inline]
pub fn native_device(&self) -> NativeDevice {
self.0.native_device()
}
#[inline]
pub fn connection(&self) -> Connection {
Connection(self.0.connection())
}
#[inline]
pub fn adapter(&self) -> Adapter {
Adapter(self.0.adapter())
}
#[inline]
pub fn gl_api(&self) -> GLApi {
GLApi::GL
}
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 present_bound_surface(&self, context: &mut Context) -> Result<(), Error> {
if let Framebuffer::Surface(surface) = &mut context.framebuffer {
unsafe { context.gl.flush() };
self.0.present_surface(&mut surface.system_surface)?;
surface.bind_to_texture(&context.gl);
}
Ok(())
}
pub fn resize_bound_surface(
&self,
context: &mut Context,
size: Size2D<i32>,
) -> 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(())
}
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)) }
}
pub fn create_surface(
&self,
context: &Context,
access: SurfaceAccess,
surface_type: SurfaceType<NativeWidget>,
) -> Result<Surface, Error> {
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
{
renderbuffers.destroy(gl);
gl.delete_framebuffer(framebuffer_object);
gl.delete_texture(texture_object);
let _ = self.0.destroy_surface(&mut system_surface);
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,
})
}
}
pub fn create_surface_texture(
&self,
context: &mut Context,
surface: Surface,
) -> Result<SurfaceTexture, (Error, Surface)> {
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<i32>,
) -> 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
}
}
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)
}
pub fn destroy_surface_texture(
&self,
context: &mut Context,
mut surface_texture: SurfaceTexture,
) -> Result<Surface, (Error, SurfaceTexture)> {
let gl = &context.gl;
if let Some(texture) = surface_texture.texture_object.take() {
unsafe {
gl.delete_texture(texture);
}
}
Ok(surface_texture.surface)
}
#[inline]
pub fn surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option<Texture> {
surface_texture.texture_object
}
#[inline]
pub fn surface_gl_texture_target(&self) -> u32 {
SURFACE_GL_TEXTURE_TARGET
}
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(())
}
pub fn resize_surface(
&self,
context: &Context,
surface: &mut Surface,
size: Size2D<i32>,
) -> 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<i32>,
gl: &Rc<gl::Context>,
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 {
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,
);
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<Gl>,
new_framebuffer: Option<glow::Framebuffer>,
) -> 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,
}
}
}
#[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,
}
}
#[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<Gl>,
draw: Option<glow::Framebuffer>,
read: Option<glow::Framebuffer>,
}
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);
}
}
}