use anyhow::ensure;
use anyhow::Context as _;
use anyhow::Result;
use crate::sys;
use crate::sys::Gl as _;
use crate::Renderbuffer;
use crate::Texture;
#[non_exhaustive]
#[derive(Debug)]
pub enum Attachment<'attchmt> {
Texture(&'attchmt Texture),
Renderbuffer(&'attchmt Renderbuffer),
}
impl<'attchmt> From<&'attchmt Texture> for Attachment<'attchmt> {
fn from(texture: &'attchmt Texture) -> Self {
Self::Texture(texture)
}
}
impl<'attchmt> From<&'attchmt Renderbuffer> for Attachment<'attchmt> {
fn from(rbo: &'attchmt Renderbuffer) -> Self {
Self::Renderbuffer(rbo)
}
}
fn configure_framebuffer(
color: &Option<Attachment<'_>>,
depth: &Option<Attachment<'_>>,
context: &sys::Context,
) -> Result<()> {
if let Some(color) = color {
match color {
Attachment::Texture(texture) => context.set_framebuffer_texture(
sys::FramebufferAttachment::Color,
texture.target(),
texture,
),
Attachment::Renderbuffer(rbo) => {
context.set_framebuffer_renderbuffer(sys::FramebufferAttachment::Color, rbo)
},
}
}
if let Some(depth) = depth {
match depth {
Attachment::Texture(texture) => {
let () = context.set_framebuffer_texture(
sys::FramebufferAttachment::Depth,
texture.target(),
texture,
);
},
Attachment::Renderbuffer(rbo) => {
context.set_framebuffer_renderbuffer(sys::FramebufferAttachment::Depth, rbo)
},
}
}
if color.is_none() {
let () = context.unset_draw_buffer();
let () = context.unset_read_buffer();
}
let status = context.check_framebuffer_status();
ensure!(
status == sys::FramebufferStatus::Complete,
"failed to complete framebuffer: status: {status}"
);
Ok(())
}
#[derive(Debug, Default)]
pub struct Builder<'attchmt> {
color: Option<Attachment<'attchmt>>,
depth: Option<Attachment<'attchmt>>,
}
impl<'attchmt> Builder<'attchmt> {
pub fn set_color_attachment<A>(mut self, attachment: A) -> Self
where
A: 'attchmt,
Attachment<'attchmt>: From<A>,
{
self.color = Some(Attachment::from(attachment));
self
}
pub fn set_depth_attachment<A>(mut self, attachment: A) -> Self
where
A: 'attchmt,
Attachment<'attchmt>: From<A>,
{
self.depth = Some(Attachment::from(attachment));
self
}
pub fn build(&self, context: &sys::Context) -> Result<Framebuffer> {
let fbo = Framebuffer {
context: context.clone(),
fbo: context
.create_framebuffer()
.context("failed to create framebuffer object")?,
};
let Builder { color, depth } = self;
let () = fbo.bind();
let result = configure_framebuffer(color, depth, context);
let () = fbo.unbind();
let () = result?;
Ok(fbo)
}
}
#[derive(Debug)]
pub struct Framebuffer {
context: sys::Context,
fbo: sys::Framebuffer,
}
impl Framebuffer {
#[inline]
pub fn builder() -> Builder<'static> {
Builder::default()
}
pub fn bind(&self) {
let () = self.context.bind_framebuffer(Some(&self.fbo));
}
pub fn unbind(&self) {
let () = self.context.bind_framebuffer(None);
}
}
impl Drop for Framebuffer {
fn drop(&mut self) {
let () = self.context.delete_framebuffer(&self.fbo);
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_fork::fork;
use crate::winit::with_opengl_context;
#[fork]
#[test]
fn framebuffer_creation_color() {
with_opengl_context(|| {
let gl_context = sys::Context::default();
let rbo = Renderbuffer::new(sys::TextureInternalFormat::RGB8, 128, 128, &gl_context).unwrap();
let _framebuffer = Framebuffer::builder()
.set_color_attachment(&rbo)
.build(&gl_context)
.unwrap();
})
}
#[fork]
#[test]
fn framebuffer_creation_depth() {
with_opengl_context(|| {
let gl_context = sys::Context::default();
let depth_map = Texture::builder()
.set_context(&gl_context)
.new_depth_map(512, 512)
.unwrap();
let _framebuffer = Framebuffer::builder()
.set_depth_attachment(&depth_map)
.build(&gl_context)
.unwrap();
})
}
}