#![allow(missing_docs)]
use crate::device::Device as DeviceAPI;
use crate::{ContextID, Error, SurfaceAccess, SurfaceInfo, SurfaceType};
use euclid::default::Size2D;
use fnv::{FnvHashMap, FnvHashSet};
use glow as gl;
use glow::Context as Gl;
use glow::HasContext;
use log::debug;
use std::collections::hash_map::Entry;
use std::fmt::Debug;
use std::hash::Hash;
use std::mem;
use std::num::NonZero;
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
impl crate::SurfaceInfo {
fn framebuffer(&self) -> Option<gl::NativeFramebuffer> {
NonZero::new(self.framebuffer_object).map(gl::NativeFramebuffer)
}
}
struct SwapChainData<Device: DeviceAPI> {
size: Size2D<i32>,
context_id: ContextID,
surface_access: SurfaceAccess,
back_buffer: BackBuffer<Device>,
pending_surface: Option<Device::Surface>,
recycled_surfaces: Vec<Device::Surface>,
}
pub enum PreserveBuffer<'a> {
Yes(&'a Gl),
No,
}
enum BackBuffer<Device: DeviceAPI> {
Attached,
Detached(Device::Surface),
TakenAttached,
TakenDetached,
}
impl<Device: DeviceAPI> BackBuffer<Device> {
fn take_surface(
&mut self,
device: &Device,
context: &mut Device::Context,
) -> Result<Device::Surface, Error> {
let new_back_buffer = match self {
BackBuffer::Attached => BackBuffer::TakenAttached,
BackBuffer::Detached(_) => BackBuffer::TakenDetached,
_ => return Err(Error::Failed),
};
let surface = match mem::replace(self, new_back_buffer) {
BackBuffer::Attached => device.unbind_surface_from_context(context)?.unwrap(),
BackBuffer::Detached(surface) => surface,
_ => unreachable!(),
};
Ok(surface)
}
fn take_surface_texture(
&mut self,
device: &Device,
context: &mut Device::Context,
) -> Result<Device::SurfaceTexture, Error> {
let surface = self.take_surface(device, context)?;
device
.create_surface_texture(context, surface)
.map_err(|(err, surface)| {
let _ = self.replace_surface(device, context, surface);
err
})
}
fn replace_surface(
&mut self,
device: &Device,
context: &mut Device::Context,
surface: Device::Surface,
) -> Result<(), Error> {
let new_back_buffer = match self {
BackBuffer::TakenAttached => {
if let Err((err, mut surface)) = device.bind_surface_to_context(context, surface) {
debug!("Oh no, destroying surface");
let _ = device.destroy_surface(context, &mut surface);
return Err(err);
}
BackBuffer::Attached
}
BackBuffer::TakenDetached => BackBuffer::Detached(surface),
_ => return Err(Error::Failed),
};
*self = new_back_buffer;
Ok(())
}
fn replace_surface_texture(
&mut self,
device: &Device,
context: &mut Device::Context,
surface_texture: Device::SurfaceTexture,
) -> Result<(), Error> {
let surface = device
.destroy_surface_texture(context, surface_texture)
.map_err(|(err, _)| err)?;
self.replace_surface(device, context, surface)
}
}
impl<Device: DeviceAPI> SwapChainData<Device> {
fn validate_context(&self, device: &Device, context: &Device::Context) -> Result<(), Error> {
if self.context_id == device.context_id(context) {
Ok(())
} else {
Err(Error::IncompatibleContext)
}
}
fn swap_buffers(
&mut self,
device: &mut Device,
context: &mut Device::Context,
preserve_buffer: PreserveBuffer<'_>,
) -> Result<(), Error> {
debug!("Swap buffers on context {:?}", self.context_id);
self.validate_context(device, context)?;
if let Some(old_front_buffer) = self.pending_surface.take() {
let SurfaceInfo { id, size, .. } = device.surface_info(&old_front_buffer);
debug!(
"Recycling surface {:?} ({:?}) for context {:?}",
id, size, self.context_id
);
self.recycle_surface(old_front_buffer);
}
let new_back_buffer = self
.recycled_surfaces
.iter()
.position(|surface| device.surface_info(surface).size == self.size)
.map(|index| {
debug!("Recycling surface for context {:?}", self.context_id);
Ok(self.recycled_surfaces.swap_remove(index))
})
.unwrap_or_else(|| {
debug!(
"Creating a new surface ({:?}) for context {:?}",
self.size, self.context_id
);
let surface_type = SurfaceType::Generic { size: self.size };
device.create_surface(context, self.surface_access, surface_type)
})?;
let back_info = device.surface_info(&new_back_buffer);
debug!(
"Surface {:?} is the new back buffer for context {:?}",
device.surface_info(&new_back_buffer).id,
self.context_id
);
let new_front_buffer = self.back_buffer.take_surface(device, context)?;
self.back_buffer
.replace_surface(device, context, new_back_buffer)?;
if let PreserveBuffer::Yes(gl) = preserve_buffer {
let front_info = device.surface_info(&new_front_buffer);
unsafe {
gl.bind_framebuffer(gl::READ_FRAMEBUFFER, front_info.framebuffer());
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, back_info.framebuffer());
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
gl.blit_framebuffer(
0,
0,
front_info.size.width,
front_info.size.height,
0,
0,
back_info.size.width,
back_info.size.height,
gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT,
gl::NEAREST,
);
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
}
}
debug!(
"Surface {:?} is the new front buffer for context {:?}",
device.surface_info(&new_front_buffer).id,
self.context_id
);
self.pending_surface = Some(new_front_buffer);
for mut surface in self.recycled_surfaces.drain(..) {
debug!("Destroying a surface for context {:?}", self.context_id);
device.destroy_surface(context, &mut surface)?;
}
Ok(())
}
fn take_attachment_from(
&mut self,
device: &mut Device,
context: &mut Device::Context,
other: &mut SwapChainData<Device>,
) -> Result<(), Error> {
self.validate_context(device, context)?;
other.validate_context(device, context)?;
let our_surface = self.back_buffer.take_surface(device, context)?;
let their_surface = other.back_buffer.take_surface(device, context)?;
mem::swap(&mut self.back_buffer, &mut other.back_buffer);
self.back_buffer
.replace_surface(device, context, our_surface)?;
other
.back_buffer
.replace_surface(device, context, their_surface)?;
Ok(())
}
fn resize(
&mut self,
device: &mut Device,
context: &mut Device::Context,
size: Size2D<i32>,
) -> Result<(), Error> {
debug!(
"Resizing context {:?} to {:?}",
device.context_id(context),
size
);
self.validate_context(device, context)?;
if (size.width < 1) || (size.height < 1) {
return Err(Error::Failed);
}
let surface_type = SurfaceType::Generic { size };
let new_back_buffer = device.create_surface(context, self.surface_access, surface_type)?;
let mut old_back_buffer = self.back_buffer.take_surface(device, context)?;
self.back_buffer
.replace_surface(device, context, new_back_buffer)?;
device.destroy_surface(context, &mut old_back_buffer)?;
self.size = size;
Ok(())
}
fn size(&self) -> Size2D<i32> {
self.size
}
fn take_surface_texture(
&mut self,
device: &Device,
context: &mut Device::Context,
) -> Result<Device::SurfaceTexture, Error> {
self.validate_context(device, context)?;
self.back_buffer.take_surface_texture(device, context)
}
fn recycle_surface_texture(
&mut self,
device: &Device,
context: &mut Device::Context,
surface_texture: Device::SurfaceTexture,
) -> Result<(), Error> {
self.validate_context(device, context)?;
self.back_buffer
.replace_surface_texture(device, context, surface_texture)
}
fn take_surface(&mut self) -> Option<Device::Surface> {
self.pending_surface
.take()
.or_else(|| self.recycled_surfaces.pop())
}
fn take_pending_surface(&mut self) -> Option<Device::Surface> {
self.pending_surface.take()
}
fn recycle_surface(&mut self, surface: Device::Surface) {
self.recycled_surfaces.push(surface)
}
fn clear_surface(
&mut self,
device: &mut Device,
context: &mut Device::Context,
gl: &Gl,
color: [f32; 4],
) -> Result<(), Error> {
self.validate_context(device, context)?;
let draw_fbo;
let read_fbo;
let mut clear_color = [0., 0., 0., 0.];
let mut clear_depth = [0.];
let mut clear_stencil = [0];
let color_mask;
let depth_mask;
let mut stencil_mask = [0];
let scissor_enabled = unsafe { gl.is_enabled(gl::SCISSOR_TEST) };
let rasterizer_enabled = unsafe { gl.is_enabled(gl::RASTERIZER_DISCARD) };
unsafe {
draw_fbo = gl.get_parameter_framebuffer(gl::DRAW_FRAMEBUFFER_BINDING);
read_fbo = gl.get_parameter_framebuffer(gl::READ_FRAMEBUFFER_BINDING);
gl.get_parameter_f32_slice(gl::COLOR_CLEAR_VALUE, &mut clear_color[..]);
gl.get_parameter_f32_slice(gl::DEPTH_CLEAR_VALUE, &mut clear_depth[..]);
gl.get_parameter_i32_slice(gl::STENCIL_CLEAR_VALUE, &mut clear_stencil[..]);
depth_mask = gl.get_parameter_bool(gl::DEPTH_WRITEMASK);
gl.get_parameter_i32_slice(gl::STENCIL_WRITEMASK, &mut stencil_mask[..]);
color_mask = gl.get_parameter_bool_array::<4>(gl::COLOR_WRITEMASK);
}
let reattach = if self.is_attached() {
None
} else {
let surface = self.back_buffer.take_surface(device, context)?;
let mut reattach = device.unbind_surface_from_context(context)?;
if let Err((err, mut surface)) = device.bind_surface_to_context(context, surface) {
debug!("Oh no, destroying surfaces");
let _ = device.destroy_surface(context, &mut surface);
if let Some(ref mut reattach) = reattach {
let _ = device.destroy_surface(context, reattach);
}
return Err(err);
}
reattach
};
let fbo = device
.context_surface_info(context)
.unwrap()
.unwrap()
.framebuffer();
unsafe {
gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
gl.clear_color(color[0], color[1], color[2], color[3]);
gl.clear_depth(1.);
gl.clear_stencil(0);
gl.disable(gl::SCISSOR_TEST);
gl.disable(gl::RASTERIZER_DISCARD);
gl.depth_mask(true);
gl.stencil_mask(0xFFFFFFFF);
gl.color_mask(true, true, true, true);
gl.clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
}
if let Some(surface) = reattach {
let mut old_surface = device.unbind_surface_from_context(context)?.unwrap();
if let Err((err, mut surface)) = device.bind_surface_to_context(context, surface) {
debug!("Oh no, destroying surface");
let _ = device.destroy_surface(context, &mut surface);
let _ = device.destroy_surface(context, &mut old_surface);
return Err(err);
}
self.back_buffer
.replace_surface(device, context, old_surface)?;
}
unsafe {
gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, draw_fbo);
gl.bind_framebuffer(gl::READ_FRAMEBUFFER, read_fbo);
gl.clear_color(
clear_color[0],
clear_color[1],
clear_color[2],
clear_color[3],
);
gl.color_mask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]);
gl.clear_depth(clear_depth[0] as f64);
gl.clear_stencil(clear_stencil[0]);
gl.depth_mask(depth_mask);
gl.stencil_mask(stencil_mask[0] as _);
if scissor_enabled {
gl.enable(gl::SCISSOR_TEST);
}
if rasterizer_enabled {
gl.enable(gl::RASTERIZER_DISCARD);
}
}
Ok(())
}
fn is_attached(&self) -> bool {
match self.back_buffer {
BackBuffer::Attached | BackBuffer::TakenAttached => true,
BackBuffer::Detached(_) | BackBuffer::TakenDetached => false,
}
}
fn destroy(&mut self, device: &mut Device, context: &mut Device::Context) -> Result<(), Error> {
self.validate_context(device, context)?;
let surfaces = self
.pending_surface
.take()
.into_iter()
.chain(self.back_buffer.take_surface(device, context).into_iter())
.chain(self.recycled_surfaces.drain(..));
for mut surface in surfaces {
device.destroy_surface(context, &mut surface)?;
}
Ok(())
}
}
pub struct SwapChain<Device: DeviceAPI>(Arc<Mutex<SwapChainData<Device>>>);
impl<Device: DeviceAPI> Clone for SwapChain<Device> {
fn clone(&self) -> Self {
SwapChain(self.0.clone())
}
}
impl<Device: DeviceAPI> SwapChain<Device> {
fn lock(&self) -> MutexGuard<SwapChainData<Device>> {
self.0.lock().unwrap_or_else(|err| err.into_inner())
}
pub fn swap_buffers(
&self,
device: &mut Device,
context: &mut Device::Context,
preserve_buffer: PreserveBuffer<'_>,
) -> Result<(), Error> {
self.lock().swap_buffers(device, context, preserve_buffer)
}
pub fn take_attachment_from(
&self,
device: &mut Device,
context: &mut Device::Context,
other: &SwapChain<Device>,
) -> Result<(), Error> {
self.lock()
.take_attachment_from(device, context, &mut *other.lock())
}
pub fn resize(
&self,
device: &mut Device,
context: &mut Device::Context,
size: Size2D<i32>,
) -> Result<(), Error> {
self.lock().resize(device, context, size)
}
pub fn size(&self) -> Size2D<i32> {
self.lock().size()
}
pub fn take_surface_texture(
&self,
device: &Device,
context: &mut Device::Context,
) -> Result<Device::SurfaceTexture, Error> {
self.lock().take_surface_texture(device, context)
}
pub fn recycle_surface_texture(
&self,
device: &Device,
context: &mut Device::Context,
surface_texture: Device::SurfaceTexture,
) -> Result<(), Error> {
self.lock()
.recycle_surface_texture(device, context, surface_texture)
}
pub fn take_pending_surface(&self) -> Option<Device::Surface> {
self.lock().take_pending_surface()
}
pub fn clear_surface(
&self,
device: &mut Device,
context: &mut Device::Context,
gl: &Gl,
color: [f32; 4],
) -> Result<(), Error> {
self.lock().clear_surface(device, context, gl, color)
}
pub fn is_attached(&self) -> bool {
self.lock().is_attached()
}
pub fn destroy(&self, device: &mut Device, context: &mut Device::Context) -> Result<(), Error> {
self.lock().destroy(device, context)
}
pub fn create_attached(
device: &mut Device,
context: &mut Device::Context,
surface_access: SurfaceAccess,
) -> Result<SwapChain<Device>, Error> {
let size = device.context_surface_info(context).unwrap().unwrap().size;
Ok(SwapChain(Arc::new(Mutex::new(SwapChainData {
size,
context_id: device.context_id(context),
surface_access,
back_buffer: BackBuffer::Attached,
pending_surface: None,
recycled_surfaces: Vec::new(),
}))))
}
pub fn create_detached(
device: &mut Device,
context: &mut Device::Context,
surface_access: SurfaceAccess,
size: Size2D<i32>,
) -> Result<SwapChain<Device>, Error> {
let surface_type = SurfaceType::Generic { size };
let surface = device.create_surface(context, surface_access, surface_type)?;
Ok(SwapChain(Arc::new(Mutex::new(SwapChainData {
size,
context_id: device.context_id(context),
surface_access,
back_buffer: BackBuffer::Detached(surface),
pending_surface: None,
recycled_surfaces: Vec::new(),
}))))
}
}
impl<Device> SwapChainAPI for SwapChain<Device>
where
Device: 'static + DeviceAPI,
Device::Surface: Send,
{
type Surface = Device::Surface;
fn take_surface(&self) -> Option<Device::Surface> {
self.lock().take_surface()
}
fn recycle_surface(&self, surface: Device::Surface) {
self.lock().recycle_surface(surface)
}
}
#[derive(Default)]
pub struct SwapChains<SwapChainID: Eq + Hash, Device: DeviceAPI> {
ids: Arc<Mutex<FnvHashMap<ContextID, FnvHashSet<SwapChainID>>>>,
table: Arc<RwLock<FnvHashMap<SwapChainID, SwapChain<Device>>>>,
}
impl<SwapChainID: Eq + Hash, Device: DeviceAPI> Clone for SwapChains<SwapChainID, Device> {
fn clone(&self) -> Self {
SwapChains {
ids: self.ids.clone(),
table: self.table.clone(),
}
}
}
impl<SwapChainID, Device> SwapChains<SwapChainID, Device>
where
SwapChainID: Clone + Eq + Hash + Debug,
Device: DeviceAPI,
{
pub fn new() -> SwapChains<SwapChainID, Device> {
SwapChains {
ids: Arc::new(Mutex::new(FnvHashMap::default())),
table: Arc::new(RwLock::new(FnvHashMap::default())),
}
}
fn ids(&self) -> MutexGuard<FnvHashMap<ContextID, FnvHashSet<SwapChainID>>> {
self.ids.lock().unwrap_or_else(|err| err.into_inner())
}
fn table(&self) -> RwLockReadGuard<FnvHashMap<SwapChainID, SwapChain<Device>>> {
self.table.read().unwrap_or_else(|err| err.into_inner())
}
fn table_mut(&self) -> RwLockWriteGuard<FnvHashMap<SwapChainID, SwapChain<Device>>> {
self.table.write().unwrap_or_else(|err| err.into_inner())
}
pub fn create_attached_swap_chain(
&self,
id: SwapChainID,
device: &mut Device,
context: &mut Device::Context,
surface_access: SurfaceAccess,
) -> Result<(), Error> {
match self.table_mut().entry(id.clone()) {
Entry::Occupied(_) => Err(Error::Failed)?,
Entry::Vacant(entry) => {
entry.insert(SwapChain::create_attached(device, context, surface_access)?)
}
};
self.ids()
.entry(device.context_id(context))
.or_insert_with(Default::default)
.insert(id);
Ok(())
}
pub fn create_detached_swap_chain(
&self,
id: SwapChainID,
size: Size2D<i32>,
device: &mut Device,
context: &mut Device::Context,
surface_access: SurfaceAccess,
) -> Result<(), Error> {
match self.table_mut().entry(id.clone()) {
Entry::Occupied(_) => Err(Error::Failed)?,
Entry::Vacant(entry) => entry.insert(SwapChain::create_detached(
device,
context,
surface_access,
size,
)?),
};
self.ids()
.entry(device.context_id(context))
.or_insert_with(Default::default)
.insert(id);
Ok(())
}
pub fn destroy(
&self,
id: SwapChainID,
device: &mut Device,
context: &mut Device::Context,
) -> Result<(), Error> {
if let Some(swap_chain) = self.table_mut().remove(&id) {
swap_chain.destroy(device, context)?;
}
if let Some(ids) = self.ids().get_mut(&device.context_id(context)) {
ids.remove(&id);
}
Ok(())
}
pub fn destroy_all(
&self,
device: &mut Device,
context: &mut Device::Context,
) -> Result<(), Error> {
if let Some(mut ids) = self.ids().remove(&device.context_id(context)) {
for id in ids.drain() {
if let Some(swap_chain) = self.table_mut().remove(&id) {
swap_chain.destroy(device, context)?;
}
}
}
Ok(())
}
pub fn iter(
&self,
device: &mut Device,
context: &mut Device::Context,
) -> impl Iterator<Item = (SwapChainID, SwapChain<Device>)> {
self.ids()
.get(&device.context_id(context))
.iter()
.flat_map(|ids| ids.iter())
.filter_map(|id| Some((id.clone(), self.table().get(id)?.clone())))
.collect::<Vec<_>>()
.into_iter()
}
}
impl<SwapChainID, Device> SwapChainsAPI<SwapChainID> for SwapChains<SwapChainID, Device>
where
SwapChainID: 'static + Clone + Eq + Hash + Debug + Sync + Send,
Device: 'static + DeviceAPI,
Device::Surface: Send,
{
type Surface = Device::Surface;
type SwapChain = SwapChain<Device>;
fn get(&self, id: SwapChainID) -> Option<SwapChain<Device>> {
debug!("Getting swap chain {:?}", id);
self.table().get(&id).cloned()
}
}
pub trait SwapChainAPI: 'static + Clone + Send {
type Surface;
fn take_surface(&self) -> Option<Self::Surface>;
fn recycle_surface(&self, surface: Self::Surface);
}
pub trait SwapChainsAPI<SwapChainID>: 'static + Clone + Send {
type Surface;
type SwapChain: SwapChainAPI<Surface = Self::Surface>;
fn get(&self, id: SwapChainID) -> Option<Self::SwapChain>;
}