use std::collections::HashMap;
use std::cmp;
use std::mem;
use std::cell::RefCell;
use std::marker::PhantomData;
use GlObject;
use TextureExt;
use texture::TextureAny;
use framebuffer::RenderBufferAny;
use gl;
use context::CommandContext;
use version::Version;
use version::Api;
#[derive(Clone)]
pub struct FramebufferAttachments<'a> {
pub colors: Vec<(u32, Attachment<'a>)>,
pub depth_stencil: FramebufferDepthStencilAttachments<'a>,
}
#[derive(Copy, Clone)]
pub enum FramebufferDepthStencilAttachments<'a> {
None,
DepthAttachment(Attachment<'a>),
StencilAttachment(Attachment<'a>),
DepthAndStencilAttachments(Attachment<'a>, Attachment<'a>),
DepthStencilAttachment(Attachment<'a>),
}
#[derive(Copy, Clone)]
pub enum Attachment<'a> {
Texture {
texture: &'a TextureAny,
level: u32,
},
TextureLayer {
texture: &'a TextureAny,
layer: u32,
level: u32,
},
RenderBuffer(&'a RenderBufferAny),
}
impl<'a> FramebufferAttachments<'a> {
pub fn validate(self)
-> Result<ValidatedAttachments<'a>, ValidationError>
{
let (raw_attachments, dimensions, depth_bits, stencil_bits) = {
fn handle_attachment(a: &Attachment, dim: &mut Option<(u32, u32)>,
num_bits: Option<&mut Option<u16>>)
-> RawAttachment
{
match a {
&Attachment::Texture { ref texture, level } => {
if let Some(num_bits) = num_bits {
*num_bits = Some(texture.get_internal_format_if_supported()
.map(|f| f.get_total_bits()).unwrap_or(24) as u16); }
match dim {
d @ &mut None => *d = Some((texture.get_width(), texture.get_height().unwrap_or(1))),
&mut Some((ref mut x, ref mut y)) => {
*x = cmp::min(*x, texture.get_width());
*y = cmp::min(*y, texture.get_height().unwrap_or(1));
}
}
RawAttachment::Texture {
texture: texture.get_id(),
bind_point: texture.get_bind_point(),
layer: 0,
level: level, }
},
&Attachment::TextureLayer { ref texture, level, layer } => {
if let Some(num_bits) = num_bits {
*num_bits = Some(texture.get_internal_format_if_supported()
.map(|f| f.get_total_bits()).unwrap_or(24) as u16); }
match dim {
d @ &mut None => *d = Some((texture.get_width(), texture.get_height().unwrap_or(1))),
&mut Some((ref mut x, ref mut y)) => {
*x = cmp::min(*x, texture.get_width());
*y = cmp::min(*y, texture.get_height().unwrap_or(1));
}
}
RawAttachment::Texture {
texture: texture.get_id(),
bind_point: texture.get_bind_point(),
layer: layer, level: level, }
},
&Attachment::RenderBuffer(ref buffer) => {
if let Some(num_bits) = num_bits {
*num_bits = Some(24); }
match dim {
d @ &mut None => *d = Some(buffer.get_dimensions()),
&mut Some((ref mut x, ref mut y)) => {
let curr = buffer.get_dimensions();
*x = cmp::min(*x, curr.0);
*y = cmp::min(*y, curr.1);
}
}
RawAttachment::RenderBuffer(buffer.get_id())
},
}
}
let mut dimensions = None;
let mut depth_bits = None;
let mut stencil_bits = None;
let mut raw_attachments = RawAttachments {
color: Vec::with_capacity(self.colors.len()),
depth: None,
stencil: None,
depth_stencil: None,
};
for &(index, ref a) in &self.colors {
raw_attachments.color.push((index, handle_attachment(a, &mut dimensions, None)));
}
match self.depth_stencil {
FramebufferDepthStencilAttachments::None => (),
FramebufferDepthStencilAttachments::DepthAttachment(ref a) => {
raw_attachments.depth = Some(handle_attachment(a, &mut dimensions, Some(&mut depth_bits)));
},
FramebufferDepthStencilAttachments::StencilAttachment(ref a) => {
raw_attachments.stencil = Some(handle_attachment(a, &mut dimensions, Some(&mut stencil_bits)));
},
FramebufferDepthStencilAttachments::DepthAndStencilAttachments(ref d, ref s) => {
raw_attachments.depth = Some(handle_attachment(d, &mut dimensions, Some(&mut depth_bits)));
raw_attachments.stencil = Some(handle_attachment(s, &mut dimensions, Some(&mut stencil_bits)));
},
FramebufferDepthStencilAttachments::DepthStencilAttachment(ref a) => {
raw_attachments.depth_stencil = Some(handle_attachment(a, &mut dimensions, None)); },
}
let dimensions = match dimensions {
Some(d) => d,
None => return Err(ValidationError::EmptyFramebufferObjectsNotSupported)
};
(raw_attachments, dimensions, depth_bits, stencil_bits)
};
Ok(ValidatedAttachments {
raw: raw_attachments,
marker: PhantomData,
dimensions: dimensions,
depth_buffer_bits: depth_bits,
stencil_buffer_bits: stencil_bits,
})
}
}
#[derive(Clone)]
pub struct ValidatedAttachments<'a> {
marker: PhantomData<&'a ()>,
raw: RawAttachments,
dimensions: (u32, u32),
depth_buffer_bits: Option<u16>,
stencil_buffer_bits: Option<u16>,
}
impl<'a> ValidatedAttachments<'a> {
pub fn get_dimensions(&self) -> (u32, u32) {
self.dimensions
}
pub fn get_depth_buffer_bits(&self) -> Option<u16> {
self.depth_buffer_bits
}
pub fn get_stencil_buffer_bits(&self) -> Option<u16> {
self.stencil_buffer_bits
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ValidationError {
EmptyFramebufferObjectsNotSupported,
}
#[derive(Hash, Clone, Eq, PartialEq)]
struct RawAttachments {
color: Vec<(u32, RawAttachment)>,
depth: Option<RawAttachment>,
stencil: Option<RawAttachment>,
depth_stencil: Option<RawAttachment>,
}
#[derive(Hash, Copy, Clone, Eq, PartialEq)]
enum RawAttachment {
Texture {
bind_point: gl::types::GLenum,
texture: gl::types::GLuint,
layer: u32,
level: u32,
},
RenderBuffer(gl::types::GLuint),
}
pub struct FramebuffersContainer {
framebuffers: RefCell<HashMap<RawAttachments, FrameBufferObject>>,
}
struct FrameBufferObject {
id: gl::types::GLuint,
current_read_buffer: gl::types::GLenum,
}
impl FramebuffersContainer {
pub fn new() -> FramebuffersContainer {
FramebuffersContainer {
framebuffers: RefCell::new(HashMap::new()),
}
}
pub fn purge_all(&self, ctxt: &mut CommandContext) {
let mut other = HashMap::new();
mem::swap(&mut *self.framebuffers.borrow_mut(), &mut other);
for (_, obj) in other.into_iter() {
obj.destroy(ctxt);
}
}
pub fn purge_texture(&self, texture: gl::types::GLuint, ctxt: &mut CommandContext) {
self.purge_if(|a| {
match a {
&RawAttachment::Texture { texture: id, .. } if id == texture => true,
_ => false
}
}, ctxt);
}
pub fn purge_renderbuffer(&self, renderbuffer: gl::types::GLuint,
ctxt: &mut CommandContext)
{
self.purge_if(|a| a == &RawAttachment::RenderBuffer(renderbuffer), ctxt);
}
fn purge_if<F>(&self, condition: F, mut ctxt: &mut CommandContext)
where F: Fn(&RawAttachment) -> bool
{
let mut framebuffers = self.framebuffers.borrow_mut();
let mut attachments = Vec::with_capacity(0);
for (key, _) in framebuffers.iter() {
if key.color.iter().find(|&&(_, ref id)| condition(id)).is_some() {
attachments.push(key.clone());
continue;
}
if let Some(ref atch) = key.depth {
if condition(atch) {
attachments.push(key.clone());
continue;
}
}
if let Some(ref atch) = key.stencil {
if condition(atch) {
attachments.push(key.clone());
continue;
}
}
if let Some(ref atch) = key.depth_stencil {
if condition(atch) {
attachments.push(key.clone());
continue;
}
}
}
for atch in attachments.into_iter() {
framebuffers.remove(&atch).unwrap().destroy(ctxt);
}
}
pub fn cleanup(self, ctxt: &mut CommandContext) {
let mut other = HashMap::with_capacity(0);
mem::swap(&mut *self.framebuffers.borrow_mut(), &mut other);
for (_, obj) in other.into_iter() {
obj.destroy(ctxt);
}
}
pub fn get_framebuffer_for_drawing(&self, attachments: Option<&ValidatedAttachments>,
ctxt: &mut CommandContext) -> gl::types::GLuint
{
if let Some(attachments) = attachments {
self.get_framebuffer(attachments, ctxt)
} else {
0
}
}
pub fn get_framebuffer_for_reading(&self, attachment: &Attachment, ctxt: &mut CommandContext)
-> (gl::types::GLuint, gl::types::GLenum)
{
let attachments = FramebufferAttachments {
colors: vec![(0, attachment.clone())],
depth_stencil: FramebufferDepthStencilAttachments::None,
}.validate().unwrap();
let framebuffer = self.get_framebuffer_for_drawing(Some(&attachments), ctxt);
(framebuffer, gl::COLOR_ATTACHMENT0)
}
fn get_framebuffer(&self, attachments: &ValidatedAttachments,
ctxt: &mut CommandContext) -> gl::types::GLuint
{
let mut framebuffers = self.framebuffers.borrow_mut();
if let Some(value) = framebuffers.get(&attachments.raw) {
return value.id;
}
let new_fbo = FrameBufferObject::new(ctxt, &attachments.raw);
let new_fbo_id = new_fbo.id.clone();
framebuffers.insert(attachments.raw.clone(), new_fbo);
new_fbo_id
}
}
impl Drop for FramebuffersContainer {
fn drop(&mut self) {
if self.framebuffers.borrow().len() != 0 {
panic!()
}
}
}
impl FrameBufferObject {
fn new(mut ctxt: &mut CommandContext, attachments: &RawAttachments) -> FrameBufferObject {
if attachments.color.len() > ctxt.capabilities.max_draw_buffers as usize {
panic!("Trying to attach {} color buffers, but the hardware only supports {}",
attachments.color.len(), ctxt.capabilities.max_draw_buffers);
}
let id = unsafe {
let mut id = mem::uninitialized();
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.CreateFramebuffers(1, &mut id);
} else if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.version >= &Version(Api::GlEs, 2, 0)
{
ctxt.gl.GenFramebuffers(1, &mut id);
bind_framebuffer(&mut ctxt, id, true, false);
} else {
ctxt.gl.GenFramebuffersEXT(1, &mut id);
bind_framebuffer(&mut ctxt, id, true, false);
}
let mut raw_attachments: Vec<gl::types::GLenum> = Vec::new();
for &(slot, atchmnt) in attachments.color.iter() {
attach(&mut ctxt, gl::COLOR_ATTACHMENT0 + slot as u32, id, atchmnt);
raw_attachments.push(gl::COLOR_ATTACHMENT0 + slot as u32);
}
if let Some(depth) = attachments.depth {
attach(&mut ctxt, gl::DEPTH_ATTACHMENT, id, depth);
}
if let Some(stencil) = attachments.stencil {
attach(&mut ctxt, gl::STENCIL_ATTACHMENT, id, stencil);
}
if let Some(depth_stencil) = attachments.depth_stencil {
attach(&mut ctxt, gl::DEPTH_STENCIL_ATTACHMENT, id, depth_stencil);
}
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.NamedFramebufferDrawBuffers(id, raw_attachments.len()
as gl::types::GLsizei,
raw_attachments.as_ptr());
} else if ctxt.version >= &Version(Api::Gl, 2, 0) ||
ctxt.version >= &Version(Api::GlEs, 3, 0)
{
bind_framebuffer(&mut ctxt, id, true, false);
ctxt.gl.DrawBuffers(raw_attachments.len() as gl::types::GLsizei,
raw_attachments.as_ptr());
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
assert_eq!(raw_attachments, &[gl::COLOR_ATTACHMENT0]);
} else {
unimplemented!(); }
id
};
FrameBufferObject {
id: id,
current_read_buffer: gl::BACK,
}
}
fn destroy(self, mut ctxt: &mut CommandContext) {
unsafe {
if ctxt.version >= &Version(Api::Gl, 3, 0) {
if ctxt.state.draw_framebuffer == self.id && ctxt.state.read_framebuffer == self.id {
ctxt.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
ctxt.state.draw_framebuffer = 0;
ctxt.state.read_framebuffer = 0;
} else if ctxt.state.draw_framebuffer == self.id {
ctxt.gl.BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0);
ctxt.state.draw_framebuffer = 0;
} else if ctxt.state.read_framebuffer == self.id {
ctxt.gl.BindFramebuffer(gl::READ_FRAMEBUFFER, 0);
ctxt.state.read_framebuffer = 0;
}
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
if ctxt.state.draw_framebuffer == self.id || ctxt.state.read_framebuffer == self.id {
ctxt.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
ctxt.state.draw_framebuffer = 0;
ctxt.state.read_framebuffer = 0;
}
} else if ctxt.extensions.gl_ext_framebuffer_object {
if ctxt.state.draw_framebuffer == self.id || ctxt.state.read_framebuffer == self.id {
ctxt.gl.BindFramebufferEXT(gl::FRAMEBUFFER_EXT, 0);
ctxt.state.draw_framebuffer = 0;
ctxt.state.read_framebuffer = 0;
}
} else {
unreachable!();
}
if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.version >= &Version(Api::GlEs, 2, 0)
{
ctxt.gl.DeleteFramebuffers(1, [ self.id ].as_ptr());
} else if ctxt.extensions.gl_ext_framebuffer_object {
ctxt.gl.DeleteFramebuffersEXT(1, [ self.id ].as_ptr());
} else {
unreachable!();
}
}
}
}
impl GlObject for FrameBufferObject {
type Id = gl::types::GLuint;
fn get_id(&self) -> gl::types::GLuint {
self.id
}
}
pub fn bind_framebuffer(ctxt: &mut CommandContext, fbo_id: gl::types::GLuint,
draw: bool, read: bool)
{
if draw && ctxt.state.draw_framebuffer != fbo_id {
unsafe {
if ctxt.version >= &Version(Api::Gl, 3, 0) {
ctxt.gl.BindFramebuffer(gl::DRAW_FRAMEBUFFER, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
ctxt.gl.BindFramebuffer(gl::FRAMEBUFFER, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
} else {
ctxt.gl.BindFramebufferEXT(gl::FRAMEBUFFER_EXT, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
}
}
}
if read && ctxt.state.read_framebuffer != fbo_id {
unsafe {
if ctxt.version >= &Version(Api::Gl, 3, 0) {
ctxt.gl.BindFramebuffer(gl::READ_FRAMEBUFFER, fbo_id);
ctxt.state.read_framebuffer = fbo_id;
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
ctxt.gl.BindFramebuffer(gl::FRAMEBUFFER, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
} else {
ctxt.gl.BindFramebufferEXT(gl::FRAMEBUFFER_EXT, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
}
}
}
}
unsafe fn attach(ctxt: &mut CommandContext, slot: gl::types::GLenum,
id: gl::types::GLuint, attachment: RawAttachment)
{
if ctxt.version >= &Version(Api::Gl, 4, 5) || ctxt.extensions.gl_arb_direct_state_access {
match attachment {
RawAttachment::Texture { texture: tex_id, level, layer, .. } => {
if layer == 0 {
ctxt.gl.NamedFramebufferTexture(id, slot, tex_id,
level as gl::types::GLint);
} else {
ctxt.gl.NamedFramebufferTextureLayer(id, slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
}
},
RawAttachment::RenderBuffer(buf_id) => {
ctxt.gl.NamedFramebufferRenderbuffer(id, slot, gl::RENDERBUFFER,
buf_id);
},
}
} else if ctxt.extensions.gl_ext_direct_state_access &&
ctxt.extensions.gl_ext_geometry_shader4
{
match attachment {
RawAttachment::Texture { texture: tex_id, level, layer, .. } => {
if layer == 0 {
ctxt.gl.NamedFramebufferTextureEXT(id, slot, tex_id,
level as gl::types::GLint);
} else {
ctxt.gl.NamedFramebufferTextureLayerEXT(id, slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
}
},
RawAttachment::RenderBuffer(buf_id) => {
ctxt.gl.NamedFramebufferRenderbufferEXT(id, slot, gl::RENDERBUFFER,
buf_id);
},
}
} else if ctxt.version >= &Version(Api::Gl, 3, 2) {
bind_framebuffer(ctxt, id, true, false);
match attachment {
RawAttachment::Texture { texture: tex_id, level, layer, .. } => {
if layer == 0 {
ctxt.gl.FramebufferTexture(gl::DRAW_FRAMEBUFFER,
slot, tex_id, level as gl::types::GLint);
} else {
ctxt.gl.FramebufferTextureLayer(gl::DRAW_FRAMEBUFFER,
slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
}
},
RawAttachment::RenderBuffer(buf_id) => {
ctxt.gl.FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, buf_id);
},
}
} else if ctxt.version >= &Version(Api::Gl, 3, 0) {
bind_framebuffer(ctxt, id, true, false);
match attachment {
RawAttachment::Texture { bind_point, texture: tex_id, level, layer } => {
match bind_point {
gl::TEXTURE_1D | gl::TEXTURE_RECTANGLE => {
assert!(layer == 0);
ctxt.gl.FramebufferTexture1D(gl::DRAW_FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
gl::TEXTURE_2D | gl::TEXTURE_2D_MULTISAMPLE | gl::TEXTURE_1D_ARRAY => {
assert!(layer == 0);
ctxt.gl.FramebufferTexture2D(gl::DRAW_FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
gl::TEXTURE_3D | gl::TEXTURE_2D_ARRAY | gl::TEXTURE_2D_MULTISAMPLE_ARRAY => {
ctxt.gl.FramebufferTextureLayer(gl::DRAW_FRAMEBUFFER,
slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
},
_ => unreachable!()
}
},
RawAttachment::RenderBuffer(buf_id) => {
ctxt.gl.FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, buf_id);
},
}
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
bind_framebuffer(ctxt, id, true, true);
match attachment {
RawAttachment::Texture { bind_point, texture: tex_id, level, layer } => {
match bind_point {
gl::TEXTURE_2D => {
assert!(layer == 0);
ctxt.gl.FramebufferTexture2D(gl::FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
_ => unreachable!()
}
},
RawAttachment::RenderBuffer(buf_id) => {
ctxt.gl.FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, buf_id);
},
}
} else if ctxt.extensions.gl_ext_framebuffer_object {
bind_framebuffer(ctxt, id, true, true);
match attachment {
RawAttachment::Texture { bind_point, texture: tex_id, level, layer } => {
match bind_point {
gl::TEXTURE_1D | gl::TEXTURE_RECTANGLE => {
assert!(layer == 0);
ctxt.gl.FramebufferTexture1DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
gl::TEXTURE_2D | gl::TEXTURE_2D_MULTISAMPLE | gl::TEXTURE_1D_ARRAY => {
assert!(layer == 0);
ctxt.gl.FramebufferTexture2DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
gl::TEXTURE_3D | gl::TEXTURE_2D_ARRAY | gl::TEXTURE_2D_MULTISAMPLE_ARRAY => {
ctxt.gl.FramebufferTexture3DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
},
_ => unreachable!()
}
},
RawAttachment::RenderBuffer(buf_id) => {
ctxt.gl.FramebufferRenderbufferEXT(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, buf_id);
},
}
} else {
unreachable!();
}
}