use std::{ cmp, mem, fmt };
use std::error::Error;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::hash::BuildHasherDefault;
use std::collections::HashMap;
use fnv::FnvHasher;
use smallvec::SmallVec;
use CapabilitiesSource;
use GlObject;
use TextureExt;
use texture::CubeLayer;
use texture::TextureAnyImage;
use texture::TextureAnyMipmap;
use texture::TextureKind;
use framebuffer::RenderBufferAny;
use gl;
use context::CommandContext;
use version::Version;
use version::Api;
pub fn is_dimensions_mismatch_supported<C: ?Sized>(context: &C) -> bool where C: CapabilitiesSource {
context.get_version() >= &Version(Api::Gl, 3, 0) ||
context.get_version() >= &Version(Api::GlEs, 2, 0) ||
context.get_extensions().gl_arb_framebuffer_object
}
#[derive(Clone)]
pub enum FramebufferAttachments<'a> {
Regular(FramebufferSpecificAttachments<RegularAttachment<'a>>),
Layered(FramebufferSpecificAttachments<LayeredAttachment<'a>>),
Empty {
width: u32,
height: u32,
layers: Option<u32>,
samples: Option<u32>,
fixed_samples: bool,
},
}
#[derive(Copy, Clone)]
pub enum RegularAttachment<'a> {
Texture(TextureAnyImage<'a>),
RenderBuffer(&'a RenderBufferAny),
}
impl<'a> RegularAttachment<'a> {
#[inline]
pub fn kind(&self) -> TextureKind {
match self {
&RegularAttachment::Texture(t) => t.get_texture().kind(),
&RegularAttachment::RenderBuffer(rb) => rb.kind(),
}
}
}
#[derive(Copy, Clone)]
pub struct LayeredAttachment<'a>(TextureAnyMipmap<'a>);
#[derive(Copy, Clone)]
pub enum DepthStencilAttachments<T> {
None,
DepthAttachment(T),
StencilAttachment(T),
DepthAndStencilAttachments(T, T),
DepthStencilAttachment(T),
}
#[derive(Clone)]
pub struct FramebufferSpecificAttachments<T> {
pub colors: SmallVec<[(u32, T); 5]>,
pub depth_stencil: DepthStencilAttachments<T>,
}
impl<'a> FramebufferAttachments<'a> {
#[inline]
pub fn validate<C: ?Sized>(self, context: &C) -> Result<ValidatedAttachments<'a>, ValidationError>
where C: CapabilitiesSource
{
match self {
FramebufferAttachments::Regular(a) => FramebufferAttachments::validate_regular(context, a),
FramebufferAttachments::Layered(a) => FramebufferAttachments::validate_layered(context, a),
FramebufferAttachments::Empty { width, height, layers, samples, fixed_samples } => {
if context.get_version() >= &Version(Api::Gl, 4, 3) ||
context.get_version() >= &Version(Api::GlEs, 3, 1) ||
context.get_extensions().gl_arb_framebuffer_no_attachments
{
assert!(width >= 1);
assert!(height >= 1);
if let Some(layers) = layers { assert!(layers >= 1); }
if let Some(samples) = samples { assert!(samples >= 1); }
if width > context.get_capabilities().max_framebuffer_width.unwrap_or(0) as u32 ||
height > context.get_capabilities().max_framebuffer_height.unwrap_or(0) as u32 ||
samples.unwrap_or(0) > context.get_capabilities()
.max_framebuffer_samples.unwrap_or(0) as u32 ||
layers.unwrap_or(0) > context.get_capabilities()
.max_framebuffer_layers.unwrap_or(0) as u32
{
return Err(ValidationError::EmptyFramebufferUnsupportedDimensions);
}
Ok(ValidatedAttachments {
raw: RawAttachments {
color: Vec::new(),
depth: None,
stencil: None,
depth_stencil: None,
default_width: Some(width),
default_height: Some(height),
default_layers: if context.get_version() <= &Version(Api::GlEs, 3, 1) { None } else { Some(layers.unwrap_or(0)) },
default_samples: Some(samples.unwrap_or(0)),
default_samples_fixed: Some(fixed_samples),
},
dimensions: (width, height),
layers: layers,
depth_buffer_bits: None,
stencil_buffer_bits: None,
marker: PhantomData,
})
} else {
Err(ValidationError::EmptyFramebufferObjectsNotSupported)
}
},
}
}
fn validate_layered<C: ?Sized>(context: &C, FramebufferSpecificAttachments { colors, depth_stencil }:
FramebufferSpecificAttachments<LayeredAttachment<'a>>)
-> Result<ValidatedAttachments<'a>, ValidationError>
where C: CapabilitiesSource
{
macro_rules! handle_tex {
($tex:ident, $dim:ident, $samples:ident, $num_bits:ident) => ({
$num_bits = Some($tex.get_texture().get_internal_format()
.map(|f| f.get_total_bits()).ok().unwrap_or(24) as u16);
handle_tex!($tex, $dim, $samples)
});
($tex:ident, $dim:ident, $samples:ident) => ({
let context = $tex.get_texture().get_context();
match &mut $samples {
&mut Some(samples) => {
if samples != $tex.get_samples().unwrap_or(0) {
return Err(ValidationError::SamplesCountMismatch);
}
},
s @ &mut None => {
*s = Some($tex.get_samples().unwrap_or(0));
}
}
match &mut $dim {
&mut Some((ref mut w, ref mut h)) => {
let height = $tex.get_height().unwrap_or(1);
if *w != $tex.get_width() || *h != height {
*w = cmp::min(*w, $tex.get_width());
*h = cmp::min(*h, height);
if !is_dimensions_mismatch_supported(context) {
return Err(ValidationError::DimensionsMismatchNotSupported);
}
}
},
dim @ &mut None => {
*dim = Some(($tex.get_width(), $tex.get_height().unwrap_or(1)));
},
}
RawAttachment::Texture {
texture: $tex.get_texture().get_id(),
bind_point: $tex.get_texture().get_bind_point(),
layer: None,
level: $tex.get_level(),
cubemap_layer: None,
}
});
}
let max_color_attachments = context.get_capabilities().max_color_attachments;
if colors.len() > max_color_attachments as usize {
return Err(ValidationError::TooManyColorAttachments{
maximum: max_color_attachments as usize,
obtained: colors.len(),
});
}
let mut raw_attachments = RawAttachments {
color: Vec::with_capacity(colors.len()),
depth: None,
stencil: None,
depth_stencil: None,
default_width: None,
default_height: None,
default_layers: None,
default_samples: None,
default_samples_fixed: None,
};
let mut dimensions = None;
let mut depth_bits = None;
let mut stencil_bits = None;
let mut samples = None;
for &(index, LayeredAttachment(ref attachment)) in colors.iter() {
if index >= max_color_attachments as u32 {
return Err(ValidationError::TooManyColorAttachments{
maximum: max_color_attachments as usize,
obtained: index as usize,
});
}
raw_attachments.color.push((index, handle_tex!(attachment, dimensions, samples)));
}
match depth_stencil {
DepthStencilAttachments::None => (),
DepthStencilAttachments::DepthAttachment(LayeredAttachment(ref d)) => {
raw_attachments.depth = Some(handle_tex!(d, dimensions, samples, depth_bits));
},
DepthStencilAttachments::StencilAttachment(LayeredAttachment(ref s)) => {
raw_attachments.stencil = Some(handle_tex!(s, dimensions, samples, stencil_bits));
},
DepthStencilAttachments::DepthAndStencilAttachments(LayeredAttachment(ref d),
LayeredAttachment(ref s))
=> {
raw_attachments.depth = Some(handle_tex!(d, dimensions, samples, depth_bits));
raw_attachments.stencil = Some(handle_tex!(s, dimensions, samples, stencil_bits));
},
DepthStencilAttachments::DepthStencilAttachment(LayeredAttachment(ref ds)) => {
let depth_stencil_bits = ds.get_texture().get_depth_stencil_bits();
depth_bits = Some(depth_stencil_bits.0);
stencil_bits = Some(depth_stencil_bits.1);
raw_attachments.depth_stencil = Some(handle_tex!(ds, dimensions, samples));
},
}
let dimensions = if let Some(dimensions) = dimensions {
dimensions
} else {
return Err(ValidationError::EmptyFramebufferObjectsNotSupported);
};
Ok(ValidatedAttachments {
raw: raw_attachments,
dimensions: dimensions,
layers: None,
depth_buffer_bits: depth_bits,
stencil_buffer_bits: stencil_bits,
marker: PhantomData,
})
}
fn validate_regular<C: ?Sized>(context: &C, FramebufferSpecificAttachments { colors, depth_stencil }:
FramebufferSpecificAttachments<RegularAttachment<'a>>)
-> Result<ValidatedAttachments<'a>, ValidationError>
where C: CapabilitiesSource
{
macro_rules! handle_tex {
($tex:ident, $dim:ident, $samples:ident, $num_bits:ident) => ({
$num_bits = Some($tex.get_texture().get_internal_format()
.map(|f| f.get_total_bits()).ok().unwrap_or(24) as u16);
handle_tex!($tex, $dim, $samples)
});
($tex:ident, $dim:ident, $samples:ident) => ({
let context = $tex.get_texture().get_context();
match &mut $samples {
&mut Some(samples) => {
if samples != $tex.get_samples().unwrap_or(0) {
return Err(ValidationError::SamplesCountMismatch);
}
},
s @ &mut None => {
*s = Some($tex.get_samples().unwrap_or(0));
}
}
match &mut $dim {
&mut Some((ref mut w, ref mut h)) => {
let height = $tex.get_height().unwrap_or(1);
if *w != $tex.get_width() || *h != height {
*w = cmp::min(*w, $tex.get_width());
*h = cmp::min(*h, height);
if !is_dimensions_mismatch_supported(context) {
return Err(ValidationError::DimensionsMismatchNotSupported);
}
}
},
dim @ &mut None => {
*dim = Some(($tex.get_width(), $tex.get_height().unwrap_or(1)));
},
}
RawAttachment::Texture {
texture: $tex.get_texture().get_id(),
bind_point: $tex.get_texture().get_bind_point(),
layer: Some($tex.get_layer()),
level: $tex.get_level(),
cubemap_layer: $tex.get_cubemap_layer(),
}
});
}
macro_rules! handle_rb {
($rb:ident, $dim:ident, $samples:ident, $num_bits:ident) => ({
$num_bits = Some(24);
handle_rb!($rb, $dim, $samples)
});
($rb:ident, $dim:ident, $samples:ident) => ({
let context = $rb.get_context();
let dimensions = $rb.get_dimensions();
match &mut $samples {
&mut Some(samples) => {
if samples != $rb.get_samples().unwrap_or(0) {
return Err(ValidationError::SamplesCountMismatch);
}
},
s @ &mut None => {
*s = Some($rb.get_samples().unwrap_or(0));
}
}
match &mut $dim {
&mut Some((ref mut w, ref mut h)) => {
if *w != dimensions.0 || *h != dimensions.1 {
*w = cmp::min(*w, dimensions.0);
*h = cmp::min(*h, dimensions.1);
if !is_dimensions_mismatch_supported(context) {
return Err(ValidationError::DimensionsMismatchNotSupported);
}
}
},
dim @ &mut None => {
*dim = Some((dimensions.0, dimensions.1));
},
}
RawAttachment::RenderBuffer($rb.get_id())
});
}
macro_rules! handle_atch {
($atch:ident, $($t:tt)*) => (
match $atch {
&RegularAttachment::Texture(ref tex) => handle_tex!(tex, $($t)*),
&RegularAttachment::RenderBuffer(ref rb) => handle_rb!(rb, $($t)*),
}
);
}
let max_color_attachments = context.get_capabilities().max_color_attachments;
if colors.len() > max_color_attachments as usize {
return Err(ValidationError::TooManyColorAttachments{
maximum: max_color_attachments as usize,
obtained: colors.len(),
});
}
let mut raw_attachments = RawAttachments {
color: Vec::with_capacity(colors.len()),
depth: None,
stencil: None,
depth_stencil: None,
default_width: None,
default_height: None,
default_layers: None,
default_samples: None,
default_samples_fixed: None,
};
let mut dimensions = None;
let mut depth_bits = None;
let mut stencil_bits = None;
let mut samples = None;
for &(index, ref attachment) in colors.iter() {
if index >= max_color_attachments as u32 {
return Err(ValidationError::TooManyColorAttachments{
maximum: max_color_attachments as usize,
obtained: index as usize,
});
}
raw_attachments.color.push((index, handle_atch!(attachment, dimensions, samples)));
}
match depth_stencil {
DepthStencilAttachments::None => (),
DepthStencilAttachments::DepthAttachment(ref d) => {
raw_attachments.depth = Some(handle_atch!(d, dimensions, samples, depth_bits));
},
DepthStencilAttachments::StencilAttachment(ref s) => {
raw_attachments.stencil = Some(handle_atch!(s, dimensions, samples, stencil_bits));
},
DepthStencilAttachments::DepthAndStencilAttachments(ref d, ref s) => {
raw_attachments.depth = Some(handle_atch!(d, dimensions, samples, depth_bits));
raw_attachments.stencil = Some(handle_atch!(s, dimensions, samples, stencil_bits));
},
DepthStencilAttachments::DepthStencilAttachment(ref ds) => {
let depth_stencil_bits = match ds {
&RegularAttachment::Texture(ref tex) =>
tex.get_texture().get_depth_stencil_bits(),
&RegularAttachment::RenderBuffer(ref rb) =>
rb.get_depth_stencil_bits(),
};
depth_bits = Some(depth_stencil_bits.0);
stencil_bits = Some(depth_stencil_bits.1);
raw_attachments.depth_stencil = Some(handle_atch!(ds, dimensions, samples));
},
}
let dimensions = if let Some(dimensions) = dimensions {
dimensions
} else {
return Err(ValidationError::EmptyFramebufferObjectsNotSupported);
};
Ok(ValidatedAttachments {
raw: raw_attachments,
dimensions: dimensions,
layers: None,
depth_buffer_bits: depth_bits,
stencil_buffer_bits: stencil_bits,
marker: PhantomData,
})
}
}
#[derive(Clone)]
pub struct ValidatedAttachments<'a> {
raw: RawAttachments,
dimensions: (u32, u32),
layers: Option<u32>,
depth_buffer_bits: Option<u16>,
stencil_buffer_bits: Option<u16>,
marker: PhantomData<&'a ()>,
}
impl<'a> ValidatedAttachments<'a> {
#[inline]
pub fn is_layered(&self) -> bool {
self.layers.is_some()
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
self.dimensions
}
#[inline]
pub fn get_depth_buffer_bits(&self) -> Option<u16> {
self.depth_buffer_bits
}
#[inline]
pub fn get_stencil_buffer_bits(&self) -> Option<u16> {
self.stencil_buffer_bits
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ValidationError {
EmptyFramebufferObjectsNotSupported,
EmptyFramebufferUnsupportedDimensions,
DimensionsMismatchNotSupported,
SamplesCountMismatch,
TooManyColorAttachments {
maximum: usize,
obtained: usize,
},
}
impl fmt::Display for ValidationError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::ValidationError::*;
let desc = match self {
EmptyFramebufferObjectsNotSupported =>
"You requested an empty framebuffer object, but they are not supported",
EmptyFramebufferUnsupportedDimensions =>
"The requested characteristics of an empty framebuffer object are out of range",
DimensionsMismatchNotSupported =>
"The backend doesn't support attachments with various dimensions",
SamplesCountMismatch =>
"All attachments must have the same number of samples",
TooManyColorAttachments {..} =>
"Backends only support a certain number of color attachments",
};
match self {
TooManyColorAttachments{ ref maximum, ref obtained } =>
write!(fmt, "{}: found {}, maximum: {}", desc, obtained, maximum),
_ =>
fmt.write_str(desc),
}
}
}
impl Error for ValidationError {}
#[derive(Hash, Clone, Eq, PartialEq)]
struct RawAttachments {
color: Vec<(u32, RawAttachment)>,
depth: Option<RawAttachment>,
stencil: Option<RawAttachment>,
depth_stencil: Option<RawAttachment>,
default_width: Option<u32>,
default_height: Option<u32>,
default_layers: Option<u32>,
default_samples: Option<u32>,
default_samples_fixed: Option<bool>,
}
#[derive(Hash, Copy, Clone, Eq, PartialEq)]
enum RawAttachment {
Texture {
bind_point: gl::types::GLenum,
texture: gl::types::GLuint,
layer: Option<u32>,
level: u32,
cubemap_layer: Option<CubeLayer>,
},
RenderBuffer(gl::types::GLuint),
}
#[derive(Debug, Copy, Clone)]
pub enum ClearBufferData {
Float([f32; 4]),
Integral([i32; 4]),
Unsigned([u32; 4]),
Depth(f32),
Stencil(i32),
DepthStencil(f32, i32),
}
impl From<[f32; 4]> for ClearBufferData {
#[inline]
fn from(data: [f32; 4]) -> ClearBufferData {
ClearBufferData::Float(data)
}
}
impl From<[i32; 4]> for ClearBufferData {
#[inline]
fn from(data: [i32; 4]) -> ClearBufferData {
ClearBufferData::Integral(data)
}
}
impl From<[u32; 4]> for ClearBufferData {
#[inline]
fn from(data: [u32; 4]) -> ClearBufferData {
ClearBufferData::Unsigned(data)
}
}
pub struct FramebuffersContainer {
framebuffers: RefCell<HashMap<RawAttachments, FrameBufferObject, BuildHasherDefault<FnvHasher>>>,
}
impl FramebuffersContainer {
#[inline]
pub fn new() -> FramebuffersContainer {
FramebuffersContainer {
framebuffers: RefCell::new(HashMap::with_hasher(Default::default())),
}
}
pub fn purge_all(ctxt: &mut CommandContext) {
let mut other = HashMap::with_hasher(Default::default());
mem::swap(&mut *ctxt.framebuffer_objects.framebuffers.borrow_mut(), &mut other);
for (_, obj) in other.into_iter() {
obj.destroy(ctxt);
}
}
#[inline]
pub fn purge_texture(ctxt: &mut CommandContext, texture: gl::types::GLuint) {
FramebuffersContainer::purge_if(ctxt, |a| {
match a {
&RawAttachment::Texture { texture: id, .. } if id == texture => true,
_ => false
}
});
}
#[inline]
pub fn purge_renderbuffer(ctxt: &mut CommandContext, renderbuffer: gl::types::GLuint) {
FramebuffersContainer::purge_if(ctxt, |a| a == &RawAttachment::RenderBuffer(renderbuffer));
}
fn purge_if<F>(ctxt: &mut CommandContext, condition: F)
where F: Fn(&RawAttachment) -> bool
{
let mut framebuffers = ctxt.framebuffer_objects.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(ctxt: &mut CommandContext) {
let mut other = HashMap::with_hasher(Default::default());
mem::swap(&mut *ctxt.framebuffer_objects.framebuffers.borrow_mut(), &mut other);
for (_, obj) in other.into_iter() {
obj.destroy(ctxt);
}
}
#[inline]
pub fn get_framebuffer_for_drawing(ctxt: &mut CommandContext,
attachments: Option<&ValidatedAttachments>)
-> gl::types::GLuint
{
if let Some(attachments) = attachments {
FramebuffersContainer::get_framebuffer(ctxt, attachments)
} else {
0
}
}
#[inline]
pub fn bind_default_framebuffer_for_reading(ctxt: &mut CommandContext,
read_buffer: gl::types::GLenum)
{
unsafe { bind_framebuffer(ctxt, 0, false, true) };
unsafe { ctxt.gl.ReadBuffer(read_buffer) };
}
pub unsafe fn bind_framebuffer_for_reading(ctxt: &mut CommandContext, attachment: &RegularAttachment) {
let attachments = FramebufferAttachments::Regular(FramebufferSpecificAttachments {
colors: { let mut v = SmallVec::new(); v.push((0, attachment.clone())); v },
depth_stencil: DepthStencilAttachments::None,
}).validate(ctxt).unwrap();
let framebuffer = FramebuffersContainer::get_framebuffer_for_drawing(ctxt, Some(&attachments));
bind_framebuffer(ctxt, framebuffer, false, true);
ctxt.gl.ReadBuffer(gl::COLOR_ATTACHMENT0);
}
pub unsafe fn clear_buffer<D>(ctxt: &mut CommandContext, attachment: &RegularAttachment,
data: D)
where D: Into<ClearBufferData>
{
let data = data.into();
let fb = FramebufferAttachments::Regular(FramebufferSpecificAttachments {
colors: { let mut v = SmallVec::new(); v.push((0, attachment.clone())); v },
depth_stencil: DepthStencilAttachments::None,
}).validate(ctxt).unwrap();
let fb = FramebuffersContainer::get_framebuffer_for_drawing(ctxt, Some(&fb));
bind_framebuffer(ctxt, fb, true, false);
match (attachment.kind(), data) {
(TextureKind::Float, ClearBufferData::Float(data)) => {
ctxt.gl.ClearBufferfv(gl::COLOR, 0, data.as_ptr());
},
(TextureKind::Integral, ClearBufferData::Integral(data)) => {
ctxt.gl.ClearBufferiv(gl::COLOR, 0, data.as_ptr());
},
(TextureKind::Unsigned, ClearBufferData::Unsigned(data)) => {
ctxt.gl.ClearBufferuiv(gl::COLOR, 0, data.as_ptr());
},
(TextureKind::Depth, _) => {
unimplemented!()
},
(TextureKind::Stencil, _) => {
unimplemented!()
},
(TextureKind::DepthStencil, _) => {
unimplemented!()
},
_ => {
panic!("The data passed to `clear_buffer` does not match the kind of attachment");
}
}
}
fn get_framebuffer(ctxt: &mut CommandContext, attachments: &ValidatedAttachments)
-> gl::types::GLuint
{
let mut framebuffers = ctxt.framebuffer_objects.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 {
#[inline]
fn drop(&mut self) {
if self.framebuffers.borrow().len() != 0 {
panic!()
}
}
}
struct FrameBufferObject {
id: gl::types::GLuint,
current_read_buffer: gl::types::GLenum,
}
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 = 0;
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.extensions.gl_arb_framebuffer_object
{
ctxt.gl.GenFramebuffers(1, &mut id);
bind_framebuffer(&mut ctxt, id, true, false);
} else if ctxt.extensions.gl_ext_framebuffer_object {
ctxt.gl.GenFramebuffersEXT(1, &mut id);
bind_framebuffer(&mut ctxt, id, true, false);
} else {
unreachable!();
}
id
};
if let Some(width) = attachments.default_width {
unsafe { bind_framebuffer(&mut ctxt, id, true, false) };
if ctxt.version >= &Version(Api::Gl, 4, 3) || ctxt.version >= &Version(Api::GlEs, 3, 1) ||
ctxt.extensions.gl_arb_framebuffer_no_attachments
{
unsafe {
ctxt.gl.FramebufferParameteri(gl::DRAW_FRAMEBUFFER, gl::FRAMEBUFFER_DEFAULT_WIDTH,
width as gl::types::GLint);
}
} else {
unreachable!();
}
}
if let Some(height) = attachments.default_height {
unsafe { bind_framebuffer(&mut ctxt, id, true, false) };
if ctxt.version >= &Version(Api::Gl, 4, 3) || ctxt.version >= &Version(Api::GlEs, 3, 1) ||
ctxt.extensions.gl_arb_framebuffer_no_attachments
{
unsafe {
ctxt.gl.FramebufferParameteri(gl::DRAW_FRAMEBUFFER, gl::FRAMEBUFFER_DEFAULT_HEIGHT,
height as gl::types::GLint);
}
} else {
unreachable!();
}
}
if let Some(layers) = attachments.default_layers {
unsafe { bind_framebuffer(&mut ctxt, id, true, false) };
if ctxt.version >= &Version(Api::Gl, 4, 3) || ctxt.version >= &Version(Api::GlEs, 3, 2) ||
ctxt.extensions.gl_arb_framebuffer_no_attachments
{
unsafe {
ctxt.gl.FramebufferParameteri(gl::DRAW_FRAMEBUFFER, gl::FRAMEBUFFER_DEFAULT_LAYERS,
layers as gl::types::GLint);
}
} else {
unreachable!();
}
}
if let Some(samples) = attachments.default_samples {
unsafe { bind_framebuffer(&mut ctxt, id, true, false) };
if ctxt.version >= &Version(Api::Gl, 4, 3) || ctxt.version >= &Version(Api::GlEs, 3, 1) ||
ctxt.extensions.gl_arb_framebuffer_no_attachments
{
unsafe {
ctxt.gl.FramebufferParameteri(gl::DRAW_FRAMEBUFFER, gl::FRAMEBUFFER_DEFAULT_SAMPLES,
samples as gl::types::GLint);
}
} else {
unreachable!();
}
}
if let Some(samples_fixed) = attachments.default_samples_fixed {
unsafe { bind_framebuffer(&mut ctxt, id, true, false) };
if ctxt.version >= &Version(Api::Gl, 4, 3) || ctxt.version >= &Version(Api::GlEs, 3, 1) ||
ctxt.extensions.gl_arb_framebuffer_no_attachments
{
unsafe {
ctxt.gl.FramebufferParameteri(gl::DRAW_FRAMEBUFFER, gl::FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS,
if samples_fixed { 1 } else { 0 });
}
} else {
unreachable!();
}
}
let mut raw_attachments = Vec::with_capacity(attachments.color.len());
for (attachment_pos, &(pos_in_drawbuffers, atchmnt)) in attachments.color.iter().enumerate() {
if attachment_pos >= ctxt.capabilities.max_color_attachments as usize {
panic!("Trying to attach a color buffer to slot {}, but the hardware only supports {} bind points",
attachment_pos, ctxt.capabilities.max_color_attachments);
}
unsafe { attach(&mut ctxt, gl::COLOR_ATTACHMENT0 + attachment_pos as u32, id, atchmnt) };
while raw_attachments.len() <= pos_in_drawbuffers as usize { raw_attachments.push(gl::NONE); }
raw_attachments[pos_in_drawbuffers as usize] = gl::COLOR_ATTACHMENT0 + attachment_pos as u32;
}
if let Some(depth) = attachments.depth {
unsafe { attach(&mut ctxt, gl::DEPTH_ATTACHMENT, id, depth) };
}
if let Some(stencil) = attachments.stencil {
unsafe { attach(&mut ctxt, gl::STENCIL_ATTACHMENT, id, stencil) };
}
if let Some(depth_stencil) = attachments.depth_stencil {
unsafe { attach(&mut ctxt, gl::DEPTH_STENCIL_ATTACHMENT, id, depth_stencil) };
}
if raw_attachments != &[gl::COLOR_ATTACHMENT0] {
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
unsafe {
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)
{
unsafe {
bind_framebuffer(&mut ctxt, id, true, false);
ctxt.gl.DrawBuffers(raw_attachments.len() as gl::types::GLsizei,
raw_attachments.as_ptr());
}
} else if ctxt.extensions.gl_arb_draw_buffers {
unsafe {
bind_framebuffer(&mut ctxt, id, true, false);
ctxt.gl.DrawBuffersARB(raw_attachments.len() as gl::types::GLsizei,
raw_attachments.as_ptr());
}
} else if ctxt.extensions.gl_ati_draw_buffers {
unsafe {
bind_framebuffer(&mut ctxt, id, true, false);
ctxt.gl.DrawBuffersATI(raw_attachments.len() as gl::types::GLsizei,
raw_attachments.as_ptr());
}
} else {
panic!("Using more than one attachment is not supported by the backend");
}
}
FrameBufferObject {
id: id,
current_read_buffer: gl::BACK,
}
}
fn destroy(self, ctxt: &mut CommandContext) {
if ctxt.state.draw_framebuffer == self.id {
ctxt.state.draw_framebuffer = 0;
}
if ctxt.state.read_framebuffer == self.id {
ctxt.state.read_framebuffer = 0;
}
if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.version >= &Version(Api::GlEs, 2, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
unsafe { ctxt.gl.DeleteFramebuffers(1, [ self.id ].as_ptr()) };
} else if ctxt.extensions.gl_ext_framebuffer_object {
unsafe { ctxt.gl.DeleteFramebuffersEXT(1, [ self.id ].as_ptr()) };
} else {
unreachable!();
}
}
}
impl GlObject for FrameBufferObject {
type Id = gl::types::GLuint;
#[inline]
fn get_id(&self) -> gl::types::GLuint {
self.id
}
}
pub unsafe fn bind_framebuffer(ctxt: &mut CommandContext, fbo_id: gl::types::GLuint,
draw: bool, read: bool)
{
if draw && read {
if ctxt.state.draw_framebuffer != fbo_id || ctxt.state.read_framebuffer != fbo_id {
if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.version >= &Version(Api::GlEs, 2, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
ctxt.gl.BindFramebuffer(gl::FRAMEBUFFER, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
} else if ctxt.extensions.gl_ext_framebuffer_object {
ctxt.gl.BindFramebufferEXT(gl::FRAMEBUFFER_EXT, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
} else {
unreachable!();
}
}
} else {
if draw && ctxt.state.draw_framebuffer != fbo_id {
if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
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 if ctxt.extensions.gl_ext_framebuffer_object {
ctxt.gl.BindFramebufferEXT(gl::FRAMEBUFFER_EXT, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
} else {
unreachable!();
}
}
if read && ctxt.state.read_framebuffer != fbo_id {
if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
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 if ctxt.extensions.gl_ext_framebuffer_object {
ctxt.gl.BindFramebufferEXT(gl::FRAMEBUFFER_EXT, fbo_id);
ctxt.state.draw_framebuffer = fbo_id;
ctxt.state.read_framebuffer = fbo_id;
} else {
unreachable!();
}
}
}
}
unsafe fn attach(ctxt: &mut CommandContext, slot: gl::types::GLenum,
id: gl::types::GLuint, attachment: RawAttachment)
{
match attachment {
RawAttachment::Texture { texture: tex_id, level, layer, bind_point, cubemap_layer } => {
match bind_point {
gl::TEXTURE_2D | gl::TEXTURE_2D_MULTISAMPLE | gl::TEXTURE_1D |
gl::TEXTURE_RECTANGLE =>
{
assert_eq!(layer, Some(0));
debug_assert!(cubemap_layer.is_none());
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.NamedFramebufferTexture(id, slot, tex_id,
level as gl::types::GLint);
} else if ctxt.extensions.gl_ext_direct_state_access &&
ctxt.extensions.gl_ext_geometry_shader4
{
ctxt.gl.NamedFramebufferTextureEXT(id, slot, tex_id,
level as gl::types::GLint);
} else if ctxt.version >= &Version(Api::Gl, 3, 2) {
bind_framebuffer(ctxt, id, true, false);
ctxt.gl.FramebufferTexture(gl::DRAW_FRAMEBUFFER,
slot, tex_id, level as gl::types::GLint);
} else if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
bind_framebuffer(ctxt, id, true, false);
match bind_point {
gl::TEXTURE_1D | gl::TEXTURE_RECTANGLE => {
ctxt.gl.FramebufferTexture1D(gl::DRAW_FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
gl::TEXTURE_2D | gl::TEXTURE_2D_MULTISAMPLE => {
ctxt.gl.FramebufferTexture2D(gl::DRAW_FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
_ => unreachable!()
}
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
bind_framebuffer(ctxt, id, true, true);
assert!(bind_point == gl::TEXTURE_2D);
ctxt.gl.FramebufferTexture2D(gl::FRAMEBUFFER, slot, bind_point, tex_id,
level as gl::types::GLint);
} else if ctxt.extensions.gl_ext_framebuffer_object {
bind_framebuffer(ctxt, id, true, true);
match bind_point {
gl::TEXTURE_1D | gl::TEXTURE_RECTANGLE => {
ctxt.gl.FramebufferTexture1DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
gl::TEXTURE_2D | gl::TEXTURE_2D_MULTISAMPLE => {
ctxt.gl.FramebufferTexture2DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint);
},
_ => unreachable!()
}
} else {
unreachable!();
}
},
gl::TEXTURE_1D_ARRAY | gl::TEXTURE_2D_ARRAY | gl::TEXTURE_2D_MULTISAMPLE_ARRAY |
gl::TEXTURE_3D | gl::TEXTURE_CUBE_MAP_ARRAY if layer.is_some() =>
{
let layer = if bind_point == gl::TEXTURE_CUBE_MAP_ARRAY {
layer.unwrap() * 6 + cubemap_layer.unwrap().get_layer_index()
as gl::types::GLenum
} else {
layer.unwrap()
};
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.NamedFramebufferTextureLayer(id, slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
} else if ctxt.extensions.gl_ext_direct_state_access &&
ctxt.extensions.gl_ext_geometry_shader4
{
ctxt.gl.NamedFramebufferTextureLayerEXT(id, slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
} else if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
bind_framebuffer(ctxt, id, true, false);
match bind_point {
gl::TEXTURE_1D_ARRAY | 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);
},
gl::TEXTURE_3D => {
ctxt.gl.FramebufferTexture3D(gl::DRAW_FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
},
_ => unreachable!()
}
} else if ctxt.extensions.gl_ext_framebuffer_object &&
bind_point == gl::TEXTURE_3D
{
bind_framebuffer(ctxt, id, true, true);
ctxt.gl.FramebufferTexture3DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
} else if ctxt.extensions.gl_ext_texture_array &&
bind_point == gl::TEXTURE_1D_ARRAY ||
bind_point == gl::TEXTURE_2D_ARRAY ||
bind_point == gl::TEXTURE_2D_MULTISAMPLE_ARRAY
{
bind_framebuffer(ctxt, id, true, false);
ctxt.gl.FramebufferTextureLayerEXT(gl::DRAW_FRAMEBUFFER,
slot, tex_id,
level as gl::types::GLint,
layer as gl::types::GLint);
} else {
panic!("Attaching a texture array is not supported");
}
},
gl::TEXTURE_1D_ARRAY | gl::TEXTURE_2D_ARRAY | gl::TEXTURE_2D_MULTISAMPLE_ARRAY |
gl::TEXTURE_3D | gl::TEXTURE_CUBE_MAP_ARRAY if layer.is_none() =>
{
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.NamedFramebufferTexture(id, slot, tex_id,
level as gl::types::GLint);
} else if ctxt.extensions.gl_ext_direct_state_access &&
ctxt.extensions.gl_ext_geometry_shader4
{
ctxt.gl.NamedFramebufferTextureEXT(id, slot, tex_id,
level as gl::types::GLint);
} else if ctxt.version >= &Version(Api::Gl, 3, 2) {
bind_framebuffer(ctxt, id, true, false);
ctxt.gl.FramebufferTexture(gl::DRAW_FRAMEBUFFER,
slot, tex_id, level as gl::types::GLint);
} else {
panic!("Layered framebuffers are not supported");
}
},
gl::TEXTURE_CUBE_MAP if layer.is_some() => {
let bind_point = gl::TEXTURE_CUBE_MAP_POSITIVE_X +
cubemap_layer.unwrap().get_layer_index() as gl::types::GLenum;
if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
bind_framebuffer(ctxt, id, true, false);
ctxt.gl.FramebufferTexture2D(gl::DRAW_FRAMEBUFFER,
slot, bind_point, tex_id,
level as gl::types::GLint);
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
bind_framebuffer(ctxt, id, true, true);
ctxt.gl.FramebufferTexture2D(gl::FRAMEBUFFER, slot, bind_point, tex_id,
level as gl::types::GLint);
} else if ctxt.extensions.gl_ext_framebuffer_object {
bind_framebuffer(ctxt, id, true, true);
ctxt.gl.FramebufferTexture2DEXT(gl::FRAMEBUFFER_EXT,
slot, bind_point, tex_id,
level as gl::types::GLint);
} else {
unreachable!();
}
},
gl::TEXTURE_CUBE_MAP if layer.is_none() => {
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.NamedFramebufferTexture(id, slot, tex_id,
level as gl::types::GLint);
} else if ctxt.extensions.gl_ext_direct_state_access &&
ctxt.extensions.gl_ext_geometry_shader4
{
ctxt.gl.NamedFramebufferTextureEXT(id, slot, tex_id,
level as gl::types::GLint);
} else if ctxt.version >= &Version(Api::Gl, 3, 2) {
bind_framebuffer(ctxt, id, true, false);
ctxt.gl.FramebufferTexture(gl::DRAW_FRAMEBUFFER,
slot, tex_id, level as gl::types::GLint);
} else {
panic!("Layered framebuffers are not supported");
}
},
_ => unreachable!()
}
},
RawAttachment::RenderBuffer(renderbuffer) => {
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
ctxt.extensions.gl_arb_direct_state_access
{
ctxt.gl.NamedFramebufferRenderbuffer(id, slot, gl::RENDERBUFFER, renderbuffer);
} else if ctxt.extensions.gl_ext_direct_state_access &&
ctxt.extensions.gl_ext_geometry_shader4
{
ctxt.gl.NamedFramebufferRenderbufferEXT(id, slot, gl::RENDERBUFFER, renderbuffer);
} else if ctxt.version >= &Version(Api::Gl, 3, 0) ||
ctxt.extensions.gl_arb_framebuffer_object
{
bind_framebuffer(ctxt, id, true, false);
ctxt.gl.FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, renderbuffer);
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) {
bind_framebuffer(ctxt, id, true, true);
ctxt.gl.FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, renderbuffer);
} else if ctxt.extensions.gl_ext_framebuffer_object {
bind_framebuffer(ctxt, id, true, true);
ctxt.gl.FramebufferRenderbufferEXT(gl::DRAW_FRAMEBUFFER, slot,
gl::RENDERBUFFER, renderbuffer);
} else {
unreachable!();
}
},
}
}