use std::collections::HashMap;
use std::mem;
use std::cell::RefCell;
use GlObject;
use gl;
use context::CommandContext;
use version::Version;
use version::Api;
#[derive(Hash, Clone, PartialEq, Eq)]
pub struct FramebufferAttachments {
pub colors: Vec<(u32, Attachment)>,
pub depth_stencil: FramebufferDepthStencilAttachments,
}
#[derive(Hash, Clone, PartialEq, Eq)]
pub enum FramebufferDepthStencilAttachments {
None,
DepthAttachment(Attachment),
StencilAttachment(Attachment),
DepthAndStencilAttachments(Attachment, Attachment),
DepthStencilAttachment(Attachment),
}
#[derive(Hash, Copy, Clone, PartialEq, Eq)]
pub enum Attachment {
Texture {
bind_point: gl::types::GLenum, id: gl::types::GLuint,
level: u32,
layer: u32,
},
RenderBuffer(gl::types::GLuint),
}
pub struct FramebuffersContainer {
framebuffers: RefCell<HashMap<FramebufferAttachments, 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) {
self.purge_if(|_| true, ctxt);
}
pub fn purge_texture(&self, texture: gl::types::GLuint, ctxt: &mut CommandContext) {
self.purge_if(|a| {
match a {
&Attachment::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 == &Attachment::RenderBuffer(renderbuffer), ctxt);
}
fn purge_if<F>(&self, condition: F, mut ctxt: &mut CommandContext)
where F: Fn(&Attachment) -> bool
{
let mut framebuffers = self.framebuffers.borrow_mut();
let mut attachments = Vec::new();
for (key, _) in framebuffers.iter() {
match key.depth_stencil {
FramebufferDepthStencilAttachments::None => (),
FramebufferDepthStencilAttachments::DepthAttachment(ref depth) => {
if condition(depth) {
attachments.push(key.clone());
continue;
}
},
FramebufferDepthStencilAttachments::StencilAttachment(ref stencil) => {
if condition(stencil) {
attachments.push(key.clone());
continue;
}
},
FramebufferDepthStencilAttachments::DepthAndStencilAttachments(ref depth,
ref stencil) =>
{
if condition(depth) || condition(stencil) {
attachments.push(key.clone());
continue;
}
},
FramebufferDepthStencilAttachments::DepthStencilAttachment(ref depth_stencil) => {
if condition(depth_stencil) {
attachments.push(key.clone());
continue;
}
}
}
if key.colors.iter().find(|&&(_, ref id)| condition(id)).is_some() {
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::new();
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<&FramebufferAttachments>,
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)
{
for (attachments, fbo) in self.framebuffers.borrow_mut().iter() {
for &(key, ref atc) in attachments.colors.iter() {
if atc == attachment {
return (fbo.get_id(), gl::COLOR_ATTACHMENT0 + key);
}
}
}
let attachments = FramebufferAttachments {
colors: vec![(0, attachment.clone())],
depth_stencil: FramebufferDepthStencilAttachments::None,
};
let framebuffer = self.get_framebuffer_for_drawing(Some(&attachments), ctxt);
(framebuffer, gl::COLOR_ATTACHMENT0)
}
fn get_framebuffer(&self, framebuffer: &FramebufferAttachments,
ctxt: &mut CommandContext) -> gl::types::GLuint
{
let mut framebuffers = self.framebuffers.borrow_mut();
if let Some(value) = framebuffers.get(framebuffer) {
return value.id;
}
let new_fbo = FrameBufferObject::new(ctxt, framebuffer);
let new_fbo_id = new_fbo.id.clone();
framebuffers.insert(framebuffer.clone(), new_fbo);
new_fbo_id
}
}
impl Drop for FramebuffersContainer {
fn drop(&mut self) {
if self.framebuffers.borrow_mut().len() != 0 {
panic!()
}
}
}
impl FrameBufferObject {
fn new(mut ctxt: &mut CommandContext, attachments: &FramebufferAttachments) -> FrameBufferObject {
if attachments.colors.len() > ctxt.capabilities.max_draw_buffers as usize {
panic!("Trying to attach {} color buffers, but the hardware only supports {}",
attachments.colors.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.colors.iter() {
attach(&mut ctxt, gl::COLOR_ATTACHMENT0 + slot as u32, id, atchmnt);
raw_attachments.push(gl::COLOR_ATTACHMENT0 + slot as u32);
}
match attachments.depth_stencil {
FramebufferDepthStencilAttachments::None => (),
FramebufferDepthStencilAttachments::DepthAttachment(depth) => {
attach(&mut ctxt, gl::DEPTH_ATTACHMENT, id, depth);
},
FramebufferDepthStencilAttachments::StencilAttachment(stencil) => {
attach(&mut ctxt, gl::STENCIL_ATTACHMENT, id, stencil);
},
FramebufferDepthStencilAttachments::DepthAndStencilAttachments(depth,
stencil) =>
{
attach(&mut ctxt, gl::DEPTH_ATTACHMENT, id, depth);
attach(&mut ctxt, gl::STENCIL_ATTACHMENT, id, stencil);
},
FramebufferDepthStencilAttachments::DepthStencilAttachment(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: Attachment)
{
if ctxt.version >= &Version(Api::Gl, 4, 5) || ctxt.extensions.gl_arb_direct_state_access {
match attachment {
Attachment::Texture { id: 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);
}
},
Attachment::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 {
Attachment::Texture { id: 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);
}
},
Attachment::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 {
Attachment::Texture { id: 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);
}
},
Attachment::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 {
Attachment::Texture { bind_point, id: 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!()
}
},
Attachment::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 {
Attachment::Texture { bind_point, id: 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!()
}
},
Attachment::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 {
Attachment::Texture { bind_point, id: 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!()
}
},
Attachment::RenderBuffer(buf_id) => {
ctxt.gl.FramebufferRenderbufferEXT(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, buf_id);
},
}
} else {
unreachable!();
}
}