use cocoa::{
appkit::{self, NSOpenGLContext, NSOpenGLPixelFormat},
base::{id, nil},
};
use owning_ref::OwningRefMut;
use std::{
cell::{Cell, RefCell},
ops::{Deref, DerefMut},
};
use winit::{platform::macos::WindowExtMacOS, window::Window};
use super::{
align::Align, buffer::Buffer, cglffi as gl, objcutils::IdRef, Config, Format, ImageInfo,
NullContextImpl,
};
#[derive(Debug)]
pub struct SurfaceImpl {
gl_context: IdRef,
gl_tex: gl::GLuint,
image: RefCell<Buffer>,
image_info: Cell<ImageInfo>,
scanline_align: Align,
}
impl SurfaceImpl {
pub(crate) unsafe fn new(window: &Window, _: &NullContextImpl, config: &Config) -> Self {
let scanline_align = Align::new(config.scanline_align).unwrap();
let attrs = [
appkit::NSOpenGLPFAOpenGLProfile as u32,
appkit::NSOpenGLPFAOpenGLProfiles::NSOpenGLProfileVersionLegacy as u32,
appkit::NSOpenGLPFAColorSize as u32,
24,
appkit::NSOpenGLPFAAlphaSize as u32,
8,
appkit::NSOpenGLPFADoubleBuffer as u32,
0,
];
let pixel_format = IdRef::new(NSOpenGLPixelFormat::alloc(nil).initWithAttributes_(&attrs))
.non_nil()
.expect("no available pixel format");
let gl_context = IdRef::new(
NSOpenGLContext::alloc(nil).initWithFormat_shareContext_(*pixel_format, nil),
)
.non_nil()
.expect("could not create a OpenGL context");
gl_context.setView_(window.ns_view() as id);
gl_context.setValues_forParameter_(
&(config.vsync as i32),
appkit::NSOpenGLContextParameter::NSOpenGLCPSwapInterval,
);
if !config.opaque {
gl::CGLSetParameter(
gl_context.CGLContextObj() as *mut _,
gl::kCGLCPSurfaceOpacity,
&mut 0, );
}
gl_context.makeCurrentContext();
let mut gl_tex: gl::GLuint = 0;
gl::glGenTextures(1, &mut gl_tex);
Self {
gl_context,
gl_tex,
image: RefCell::new(Buffer::from_size_align(1, config.align).unwrap()),
image_info: Cell::new(ImageInfo::default()),
scanline_align,
}
}
pub fn update_surface(&self, extent: [u32; 2], format: Format) {
assert_ne!(extent[0], 0);
assert_ne!(extent[1], 0);
assert!(extent[0] <= <i32>::max_value() as u32);
assert!(extent[1] <= <i32>::max_value() as u32);
use std::convert::TryInto;
let extent_usize: [usize; 2] = [
extent[0].try_into().expect("overflow"),
extent[1].try_into().expect("overflow"),
];
let stride = extent_usize[0]
.checked_mul(4)
.and_then(|x| self.scanline_align.align_up(x))
.expect("overflow");
let size = stride.checked_mul(extent_usize[1]).expect("overflow");
let (ifmt, fmt, ty) = translate_format(format);
let mut image = self.image.borrow_mut();
let gl_context = &self.gl_context;
unsafe {
gl_context.update();
gl_context.makeCurrentContext();
gl::glBindTexture(gl::GL_TEXTURE_2D, self.gl_tex);
gl::glTexImage2D(
gl::GL_TEXTURE_2D,
0,
ifmt,
extent[0] as i32,
extent[1] as i32,
0,
fmt,
ty,
std::ptr::null(),
);
gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MAG_FILTER, gl::GL_LINEAR);
gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MIN_FILTER, gl::GL_LINEAR);
image.resize(size);
}
self.image_info.set(ImageInfo {
extent,
stride,
format,
});
}
pub fn supported_formats(&self) -> impl Iterator<Item = Format> + '_ {
[Format::Argb8888, Format::Xrgb8888].iter().cloned()
}
pub fn image_info(&self) -> ImageInfo {
self.image_info.get()
}
pub fn num_images(&self) -> usize {
1
}
pub fn does_preserve_image(&self) -> bool {
true
}
pub fn poll_next_image(&self) -> Option<usize> {
Some(0)
}
pub fn lock_image(&self, i: usize) -> impl Deref<Target = [u8]> + DerefMut + '_ {
assert_eq!(i, 0);
OwningRefMut::new(self.image.borrow_mut()).map_mut(|p| &mut **p)
}
pub fn present_image(&self, i: usize) {
assert_eq!(i, 0);
let gl_context = &self.gl_context;
let image_info = self.image_info.get();
let image = self
.image
.try_borrow()
.expect("the image is currently locked");
let (_ifmt, fmt, ty) = translate_format(image_info.format);
unsafe {
gl_context.makeCurrentContext();
gl::glBindTexture(gl::GL_TEXTURE_2D, self.gl_tex);
gl::glPixelStorei(gl::GL_UNPACK_ROW_LENGTH, (image_info.stride / 4) as _);
gl::glTexSubImage2D(
gl::GL_TEXTURE_2D,
0,
0,
0,
image_info.extent[0] as _,
image_info.extent[1] as _,
fmt,
ty,
image.as_ptr() as *const _,
);
gl::glPixelStorei(gl::GL_UNPACK_ROW_LENGTH, 0);
gl::glClearColor(0.0, 0.0, 0.0, 0.0);
gl::glClear(gl::GL_COLOR_BUFFER_BIT);
gl::glEnable(gl::GL_TEXTURE_2D);
gl::glBegin(gl::GL_TRIANGLE_STRIP);
gl::glTexCoord2f(0.0, 0.0);
gl::glVertex2f(-1.0, 1.0);
gl::glTexCoord2f(2.0, 0.0);
gl::glVertex2f(3.0, 1.0);
gl::glTexCoord2f(0.0, 2.0);
gl::glVertex2f(-1.0, -3.0);
gl::glEnd();
gl_context.flushBuffer();
}
}
}
fn translate_format(format: Format) -> (gl::GLenum, gl::GLenum, gl::GLenum) {
match format {
Format::Argb8888 => (gl::GL_RGBA, gl::GL_BGRA, gl::GL_UNSIGNED_BYTE),
Format::Xrgb8888 => (gl::GL_RGB, gl::GL_BGRA, gl::GL_UNSIGNED_INT_8_8_8_8_REV),
}
}