use super::context::Context;
use super::device::Device;
use crate::context::ContextID;
use crate::gl_utils;
use crate::platform::macos::system::surface::Surface as SystemSurface;
use crate::renderbuffers::Renderbuffers;
use crate::{gl, Error, SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, WindowingApiError};
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;
use std::fmt::{self, Debug, Formatter};
use std::marker::PhantomData;
use std::rc::Rc;
pub use crate::platform::macos::system::surface::{NativeSurface, NativeWidget};
const SURFACE_GL_TEXTURE_TARGET: u32 = gl::TEXTURE_RECTANGLE;
pub struct Surface {
pub(crate) system_surface: SystemSurface,
pub(crate) context_id: ContextID,
pub(crate) framebuffer_object: Option<glow::Framebuffer>,
pub(crate) texture_object: Option<Texture>,
pub(crate) renderbuffers: Renderbuffers,
}
pub struct SurfaceTexture {
pub(crate) surface: Surface,
pub(crate) texture_object: Option<Texture>,
pub(crate) phantom: PhantomData<*const ()>,
}
unsafe impl Send for Surface {}
impl Debug for Surface {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "Surface({:x})", self.id().0)
}
}
impl Debug for SurfaceTexture {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "SurfaceTexture({:?})", self.surface)
}
}
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;
const TEXTURE_RECTANGLE_ARB: GLenum = 0x84F5;
const UNSIGNED_INT_8_8_8_8_REV: GLenum = 0x8367;
unsafe {
let context = CGLGetCurrentContext();
let gl_error = CGLTexImageIOSurface2D(
context,
TEXTURE_RECTANGLE_ARB,
if has_alpha {
RGBA as GLenum
} else {
RGB as GLenum
},
width,
height,
BGRA as GLenum,
UNSIGNED_INT_8_8_8_8_REV,
surface as *const IOSurfaceRef as cgl::IOSurfaceRef,
0,
);
if gl_error != kCGLNoError {
let error_msg = CStr::from_ptr(CGLErrorString(gl_error));
panic!("{}", error_msg.to_string_lossy());
}
}
}
impl Device {
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)?;
let gl = &context.gl;
unsafe {
let size = surface.system_surface.size;
gl.bind_texture(gl::TEXTURE_RECTANGLE, surface.texture_object);
surface_bind_to_gl_texture(
&surface.system_surface.io_surface,
size.width,
size.height,
true,
);
gl.bind_texture(gl::TEXTURE_RECTANGLE, None);
}
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 _guard =
self.temporarily_bind_framebuffer(context.gl.clone(), surface.framebuffer_object);
self.0.resize_surface(&mut surface.system_surface, size)?;
let context_descriptor = self.context_descriptor(context);
let context_attributes = self.context_descriptor_attributes(&context_descriptor);
let gl = &context.gl;
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)
}
}
impl Surface {
#[inline]
fn id(&self) -> SurfaceID {
SurfaceID(&*self.system_surface.io_surface as *const IOSurfaceRef as usize)
}
}
#[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);
}
}
}