<commit_before>
//! Framebuffers are used to draw to off-screen render targets

use gl;
use std::fmt;
use std::error;
use gl::types::*;

use color::Color;
use texture::{Texture, TextureFormat};
use buffer::{VertexData, GlPrimitive};

/// Set to 8, which 97% of all cards support, acording to the [wildfiregames report][1]
/// [1]: http://feedback.wildfiregames.com/report/opengl/feature/GL_MAX_COLOR_ATTACHMENTS_EXT
pub const MAX_COLOR_ATTACHMENTS: usize = 8;

/// Utility to specify the format of a framebuffer before building it. If you expect to rebuild a
/// framebuffer occasionally (e.g. when the game window is resized) it could be beneficial to store
/// this struct alongside the framebuffer itself.
pub struct FramebufferProperties {
    /// Size in pixels
    pub width: u32,
    /// Size in pixels
    pub height: u32,
    /// The amount of multisampling to apply
    pub multisample: Option<usize>,
    /// The color formats in which color data is stored internally. The OpenGL spec states that at
    /// least 8 attachments will be supported, and in practice no card supports more than this.
    pub color_formats: [Option<TextureFormat>; MAX_COLOR_ATTACHMENTS],
    /// If `true` a depthbuffer will be added to framebuffers
    pub depth_buffer: bool,
}

impl FramebufferProperties {
    pub fn new(width: u32, height: u32) -> FramebufferProperties {
        FramebufferProperties {
            width: width,
            height: height,
            multisample: None,
            color_formats: [Some(TextureFormat::RGB_8), None, None, None, None, None, None, None],
            depth_buffer: false,
        }
    }

    /// Creates a new framebuffer with these properties
    pub fn build(&self) -> Result<Framebuffer, FramebufferError> {
        Framebuffer::new(&self)
    }
}

/// A OpenGL framebuffer that is ready to be used. Framebuffers are constructed from
/// [`FramebufferProperties`](struct.FramebufferProperties.html).
pub struct Framebuffer {
    framebuffer: GLuint,
    textures: [Option<Texture>; MAX_COLOR_ATTACHMENTS],
    depth_buffer: Option<GLuint>,
    sample_level: Option<usize>,
    pub width: u32,
    pub height: u32
}

impl Framebuffer {
    fn new(properties: &FramebufferProperties) -> Result<Framebuffer, FramebufferError> {
        let mut framebuffer: GLuint = 0;
        let mut textures: [Option<Texture>; MAX_COLOR_ATTACHMENTS] = Default::default();
        let mut depth_buffer: Option<GLuint> = None;

        let mut error: Option<FramebufferError> = None;

        unsafe {
            gl::GenFramebuffers(1, &mut framebuffer);
            gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer);

            let texture_target = if properties.multisample.is_none() { gl::TEXTURE_2D } else { gl::TEXTURE_2D_MULTISAMPLE };

            // Add draw buffers
            let mut draw_buffers: [GLenum; MAX_COLOR_ATTACHMENTS] = Default::default();
            for i in 0..MAX_COLOR_ATTACHMENTS {
                // Add a color attachment
                if let Some(format) = properties.color_formats[i] {
                    let attachment = gl::COLOR_ATTACHMENT0 + (i as GLenum);
                    draw_buffers[i] = attachment;

                    let mut texture = 0;
                    gl::GenTextures(1, &mut texture);
                    gl::BindTexture(texture_target, texture);
                    if let Some(level) = properties.multisample {
                        gl::TexImage2DMultisample(texture_target,
                                                  level as GLsizei,
                                                  format as GLuint,
                                                  properties.width as GLint, properties.height as GLint, //Size
                                                  true as GLboolean); // Fixed sample locations
                    } else {
                        gl::TexImage2D(texture_target,
                                       0, // Level
                                       format as GLint,
                                       properties.width as GLint, properties.height as GLint, 0, //Size and border
                                       format.unsized_format(), format.gl_primitive_enum(), 
                                       ::std::ptr::null()); // Data for texture
                        gl::TexParameteri(texture_target, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_BORDER as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_BORDER as GLint);
                    }

                    gl::FramebufferTexture(gl::FRAMEBUFFER, attachment, texture, 0);
                    textures[i] = Some(Texture::from_raw(texture, format, properties.width, properties.height));
                } else {
                    draw_buffers[i] = gl::NONE;
                }
                
            }

            gl::DrawBuffers(MAX_COLOR_ATTACHMENTS as GLsizei, draw_buffers.as_ptr());

            // Add depth buffer
            if properties.depth_buffer {
                let mut depth_buffer_handle = 0;
                gl::GenRenderbuffers(1, &mut depth_buffer_handle);
                gl::BindRenderbuffer(gl::RENDERBUFFER, depth_buffer_handle);
                if let Some(level) = properties.multisample {
                    gl::RenderbufferStorageMultisample(gl::RENDERBUFFER, level as GLsizei, gl::DEPTH_COMPONENT, 
                                                       properties.width as GLint, properties.height as GLint);
                } else {
                    gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH_COMPONENT,
                                            properties.width as GLint, properties.height as GLint);
                }
                gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, gl::DEPTH_ATTACHMENT, gl::RENDERBUFFER, depth_buffer_handle);
                depth_buffer = Some(depth_buffer_handle);
            }

            // Check if framebuffer was sucessfully constructed
            let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
            if status != gl::FRAMEBUFFER_COMPLETE {
                // Delete OpenGL data (textures are automatically deleted)
                gl::DeleteFramebuffers(1, &framebuffer);
                if let Some(depth_buffer) = depth_buffer {
                    gl::DeleteRenderbuffers(1, &depth_buffer);
                }
                error = Some(From::from(status));
            }

            gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
        }

        if let Some(error) = error {
            return Err(error);
        } else {
            return Ok(
                Framebuffer {
                    framebuffer: framebuffer,
                    textures: textures,
                    depth_buffer: depth_buffer,
                    sample_level: properties.multisample,
                    width: properties.width,
                    height: properties.height,
                }
            );
        }
    }

    /// Binds this framebuffer. Subsequent draw operations will modify this framebuffer
    /// rather than the backbuffer. Note that you probably want to modify the viewport
    /// to fit this framebuffers size.
    pub fn bind(&self) {
        unsafe {
            gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer);
        }
    }
    
    /// Binds framebuffer 0, resulting in draw operations drawing to the backbuffer.
    pub fn unbind(&self) {
        unsafe {
            gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
        }
    }

    /// Moves the contents of this framebuffer to the given framebuffer, resolving multisampling
    /// if present. Note that this also unbinds this framebuffer
    pub fn blit_to_framebuffer(&self, other: &Framebuffer, buffers: BlitBuffers) {
        self.blit_indexed(other.framebuffer, other.width, other.height, buffers);
    }

    /// Moves the contents of this framebuffer to the backbuffer, resolving multisampling
    /// if present. Note that this also unbinds this framebuffer. This will only partially
    /// cover the backbuffer if this framebuffer is smaller than the backbuffer. To upscale
    /// a framebuffer while blitting, use [`blit_with_size`](struct.Framebuffer.html#method.blit_with_size).
    pub fn blit(&self, buffers: BlitBuffers) {
        self.blit_indexed(0, self.width, self.height, buffers);
    }

    /// Moves the contents of this framebuffer to the backbuffer, resolving multisampling
    /// if present. Note that this also unbinds this framebuffer. This allows setting
    /// the size to which this framebuffer should be scaled while blitting. This should
    /// be used if the framebuffer is larger or smaller than the backbuffer.
    pub fn blit_with_size(&self, width: u32, height: u32, buffers: BlitBuffers) {
        self.blit_indexed(0, width, height, buffers);
    }

    fn blit_indexed(&self, target: GLuint, dst_width: u32, dst_height: u32, buffers: BlitBuffers) {
        let mut gl_flag = 0;
        if buffers.contains(BLIT_COLOR)   { gl_flag |= gl::COLOR_BUFFER_BIT }
        if buffers.contains(BLIT_DEPTH)   { gl_flag |= gl::DEPTH_BUFFER_BIT }
        if buffers.contains(BLIT_STENCIL) { gl_flag |= gl::STENCIL_BUFFER_BIT }

        unsafe {
            gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, target);
            gl::BindFramebuffer(gl::READ_FRAMEBUFFER, self.framebuffer);
            gl::BlitFramebuffer(0, 0, self.width as i32, self.height as i32,
                                0, 0, dst_width as i32, dst_height as i32,
                                gl_flag, gl::NEAREST);
        }
        self.unbind();
    }

    /// Retrieves the color attachment at the given index. There will be a color attachment at each
    /// index for which a `color_format`  was set in the [framebuffers properties][1] from which this
    /// framebuffer was built. If there is no color attachment at the given index, or the index is
    /// greater than [`MAX_COLOR_ATTACHMENTS`][2] this returns `None`.
    /// Note that if multisampling is enabled for this color attachment this function returns none,
    /// as multisampled textures can not be used like normal textures.
    ///
    /// [1]: struct.FramebufferProperties.html
    /// [2]: constant.MAX_COLOR_ATTACHMENTS.html
    pub fn get_color_attachment(&self, index: usize) -> Option<&Texture> {
        if self.sample_level.is_none() && index < self.textures.len() {
            if let Some(ref texture) = self.textures[index] {
                return Some(texture);
            }
        } 
        None
    }

    /// Clears the color attachment at the given index to the given color. This method panics if
    /// the index is not that of a valid color attachment.
    pub fn clear_color_attachment(&self, index: usize, color: Color) {
        if index > MAX_COLOR_ATTACHMENTS || self.textures[index].is_none() {
            panic!("Could not clear framebuffer color attachment. {} is not a valid color attachment index", index);
        }

        unsafe {
            use std::mem;
            gl::ClearBufferfv(gl::COLOR, index as GLint, mem::transmute(&color));
        }
    }

    /// Retrieves the pixels from the given region in the given color attachment. Returns all
    /// pixels in row-major order. Because a framebuffers attachments types are not strongly typed
    /// it is critical that `T` is a type which has the same format as the color attachment.
    ///
    /// # Panics
    ///
    ///  * If the region is outside of the bounds of this framebuffer. 
    ///  * If the index does not point to a valid color attachment.
    ///  * If T has a different number of primitives than the given color attachment.
    ///  * If T has a different primitive type than the given color attachment.
    pub fn get_pixel_data<T>(&self, index: usize, x: u32, y: u32, width: u32, height: u32) -> Vec<T> 
        where T: VertexData,
    {
        let mut data = Vec::<T>::with_capacity((width * height) as usize);

        if index > MAX_COLOR_ATTACHMENTS && self.textures[index].is_none() {
            panic!("Invalid call to get_pixel_data. {} is not a valid color attachment.", index);
        }
        let format = match self.textures[index] {
            Some(ref texture) => texture.format,
            None => unreachable!(), // We check if texture is None above
        };

        if T::primitives() != format.components() {
            panic!("Invalid call to get_pixel_data. T has a different number of primitives than {:?}.", format);
        }

        if T::Primitive::gl_enum() != format.gl_primitive_enum() {
            panic!("Invalid call to get_pixel_data. T has a different primitive type than color attachment {}. ({} vs {})",
                   index, T::Primitive::gl_name(), format.gl_primitive_enum_name());
        }

        if x + width >= self.width && y + height >= self.height {
            panic!("Invalid call to get_pixel_data, The rectangle (x: {}, y: {}, width: {}, height: {}) is outside of the\
                   region of the framebuffer (width: {}, height: {}).",
                   x, y, width, height, self.width, self.height);
        }

        unsafe {
            gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer);
            gl::ReadBuffer(gl::COLOR_ATTACHMENT0 + index as u32);
            gl::ReadPixels(x as GLint, y as GLint, width as GLsizei, height as GLsizei,
                           format.unsized_format(), format.gl_primitive_enum(), data.as_ptr() as *mut _);
            data.set_len((width * height) as usize);
            gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
        }

        data
    }
}

impl Drop for Framebuffer {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteFramebuffers(1, &self.framebuffer);
            if let Some(depth_buffer) = self.depth_buffer {
                gl::DeleteRenderbuffers(1, &depth_buffer);
            }
            // Textures are managed by the `Texture` struct, and are automatically deleted
        }
    }
}
<commit_msg>Fixed get_pixel_data out of bounds check<commit_after>
//! Framebuffers are used to draw to off-screen render targets

use gl;
use std::fmt;
use std::error;
use gl::types::*;

use color::Color;
use texture::{Texture, TextureFormat};
use buffer::{VertexData, GlPrimitive};

/// Set to 8, which 97% of all cards support, acording to the [wildfiregames report][1]
/// [1]: http://feedback.wildfiregames.com/report/opengl/feature/GL_MAX_COLOR_ATTACHMENTS_EXT
pub const MAX_COLOR_ATTACHMENTS: usize = 8;

/// Utility to specify the format of a framebuffer before building it. If you expect to rebuild a
/// framebuffer occasionally (e.g. when the game window is resized) it could be beneficial to store
/// this struct alongside the framebuffer itself.
pub struct FramebufferProperties {
    /// Size in pixels
    pub width: u32,
    /// Size in pixels
    pub height: u32,
    /// The amount of multisampling to apply
    pub multisample: Option<usize>,
    /// The color formats in which color data is stored internally. The OpenGL spec states that at
    /// least 8 attachments will be supported, and in practice no card supports more than this.
    pub color_formats: [Option<TextureFormat>; MAX_COLOR_ATTACHMENTS],
    /// If `true` a depthbuffer will be added to framebuffers
    pub depth_buffer: bool,
}

impl FramebufferProperties {
    pub fn new(width: u32, height: u32) -> FramebufferProperties {
        FramebufferProperties {
            width: width,
            height: height,
            multisample: None,
            color_formats: [Some(TextureFormat::RGB_8), None, None, None, None, None, None, None],
            depth_buffer: false,
        }
    }

    /// Creates a new framebuffer with these properties
    pub fn build(&self) -> Result<Framebuffer, FramebufferError> {
        Framebuffer::new(&self)
    }
}

/// A OpenGL framebuffer that is ready to be used. Framebuffers are constructed from
/// [`FramebufferProperties`](struct.FramebufferProperties.html).
pub struct Framebuffer {
    framebuffer: GLuint,
    textures: [Option<Texture>; MAX_COLOR_ATTACHMENTS],
    depth_buffer: Option<GLuint>,
    sample_level: Option<usize>,
    pub width: u32,
    pub height: u32
}

impl Framebuffer {
    fn new(properties: &FramebufferProperties) -> Result<Framebuffer, FramebufferError> {
        let mut framebuffer: GLuint = 0;
        let mut textures: [Option<Texture>; MAX_COLOR_ATTACHMENTS] = Default::default();
        let mut depth_buffer: Option<GLuint> = None;

        let mut error: Option<FramebufferError> = None;

        unsafe {
            gl::GenFramebuffers(1, &mut framebuffer);
            gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer);

            let texture_target = if properties.multisample.is_none() { gl::TEXTURE_2D } else { gl::TEXTURE_2D_MULTISAMPLE };

            // Add draw buffers
            let mut draw_buffers: [GLenum; MAX_COLOR_ATTACHMENTS] = Default::default();
            for i in 0..MAX_COLOR_ATTACHMENTS {
                // Add a color attachment
                if let Some(format) = properties.color_formats[i] {
                    let attachment = gl::COLOR_ATTACHMENT0 + (i as GLenum);
                    draw_buffers[i] = attachment;

                    let mut texture = 0;
                    gl::GenTextures(1, &mut texture);
                    gl::BindTexture(texture_target, texture);
                    if let Some(level) = properties.multisample {
                        gl::TexImage2DMultisample(texture_target,
                                                  level as GLsizei,
                                                  format as GLuint,
                                                  properties.width as GLint, properties.height as GLint, //Size
                                                  true as GLboolean); // Fixed sample locations
                    } else {
                        gl::TexImage2D(texture_target,
                                       0, // Level
                                       format as GLint,
                                       properties.width as GLint, properties.height as GLint, 0, //Size and border
                                       format.unsized_format(), format.gl_primitive_enum(), 
                                       ::std::ptr::null()); // Data for texture
                        gl::TexParameteri(texture_target, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_BORDER as GLint);
                        gl::TexParameteri(texture_target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_BORDER as GLint);
                    }

                    gl::FramebufferTexture(gl::FRAMEBUFFER, attachment, texture, 0);
                    textures[i] = Some(Texture::from_raw(texture, format, properties.width, properties.height));
                } else {
                    draw_buffers[i] = gl::NONE;
                }
                
            }

            gl::DrawBuffers(MAX_COLOR_ATTACHMENTS as GLsizei, draw_buffers.as_ptr());

            // Add depth buffer
            if properties.depth_buffer {
                let mut depth_buffer_handle = 0;
                gl::GenRenderbuffers(1, &mut depth_buffer_handle);
                gl::BindRenderbuffer(gl::RENDERBUFFER, depth_buffer_handle);
                if let Some(level) = properties.multisample {
                    gl::RenderbufferStorageMultisample(gl::RENDERBUFFER, level as GLsizei, gl::DEPTH_COMPONENT, 
                                                       properties.width as GLint, properties.height as GLint);
                } else {
                    gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH_COMPONENT,
                                            properties.width as GLint, properties.height as GLint);
                }
                gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, gl::DEPTH_ATTACHMENT, gl::RENDERBUFFER, depth_buffer_handle);
                depth_buffer = Some(depth_buffer_handle);
            }

            // Check if framebuffer was sucessfully constructed
            let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
            if status != gl::FRAMEBUFFER_COMPLETE {
                // Delete OpenGL data (textures are automatically deleted)
                gl::DeleteFramebuffers(1, &framebuffer);
                if let Some(depth_buffer) = depth_buffer {
                    gl::DeleteRenderbuffers(1, &depth_buffer);
                }
                error = Some(From::from(status));
            }

            gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
        }

        if let Some(error) = error {
            return Err(error);
        } else {
            return Ok(
                Framebuffer {
                    framebuffer: framebuffer,
                    textures: textures,
                    depth_buffer: depth_buffer,
                    sample_level: properties.multisample,
                    width: properties.width,
                    height: properties.height,
                }
            );
        }
    }

    /// Binds this framebuffer. Subsequent draw operations will modify this framebuffer
    /// rather than the backbuffer. Note that you probably want to modify the viewport
    /// to fit this framebuffers size.
    pub fn bind(&self) {
        unsafe {
            gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer);
        }
    }
    
    /// Binds framebuffer 0, resulting in draw operations drawing to the backbuffer.
    pub fn unbind(&self) {
        unsafe {
            gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
        }
    }

    /// Moves the contents of this framebuffer to the given framebuffer, resolving multisampling
    /// if present. Note that this also unbinds this framebuffer
    pub fn blit_to_framebuffer(&self, other: &Framebuffer, buffers: BlitBuffers) {
        self.blit_indexed(other.framebuffer, other.width, other.height, buffers);
    }

    /// Moves the contents of this framebuffer to the backbuffer, resolving multisampling
    /// if present. Note that this also unbinds this framebuffer. This will only partially
    /// cover the backbuffer if this framebuffer is smaller than the backbuffer. To upscale
    /// a framebuffer while blitting, use [`blit_with_size`](struct.Framebuffer.html#method.blit_with_size).
    pub fn blit(&self, buffers: BlitBuffers) {
        self.blit_indexed(0, self.width, self.height, buffers);
    }

    /// Moves the contents of this framebuffer to the backbuffer, resolving multisampling
    /// if present. Note that this also unbinds this framebuffer. This allows setting
    /// the size to which this framebuffer should be scaled while blitting. This should
    /// be used if the framebuffer is larger or smaller than the backbuffer.
    pub fn blit_with_size(&self, width: u32, height: u32, buffers: BlitBuffers) {
        self.blit_indexed(0, width, height, buffers);
    }

    fn blit_indexed(&self, target: GLuint, dst_width: u32, dst_height: u32, buffers: BlitBuffers) {
        let mut gl_flag = 0;
        if buffers.contains(BLIT_COLOR)   { gl_flag |= gl::COLOR_BUFFER_BIT }
        if buffers.contains(BLIT_DEPTH)   { gl_flag |= gl::DEPTH_BUFFER_BIT }
        if buffers.contains(BLIT_STENCIL) { gl_flag |= gl::STENCIL_BUFFER_BIT }

        unsafe {
            gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, target);
            gl::BindFramebuffer(gl::READ_FRAMEBUFFER, self.framebuffer);
            gl::BlitFramebuffer(0, 0, self.width as i32, self.height as i32,
                                0, 0, dst_width as i32, dst_height as i32,
                                gl_flag, gl::NEAREST);
        }
        self.unbind();
    }

    /// Retrieves the color attachment at the given index. There will be a color attachment at each
    /// index for which a `color_format`  was set in the [framebuffers properties][1] from which this
    /// framebuffer was built. If there is no color attachment at the given index, or the index is
    /// greater than [`MAX_COLOR_ATTACHMENTS`][2] this returns `None`.
    /// Note that if multisampling is enabled for this color attachment this function returns none,
    /// as multisampled textures can not be used like normal textures.
    ///
    /// [1]: struct.FramebufferProperties.html
    /// [2]: constant.MAX_COLOR_ATTACHMENTS.html
    pub fn get_color_attachment(&self, index: usize) -> Option<&Texture> {
        if self.sample_level.is_none() && index < self.textures.len() {
            if let Some(ref texture) = self.textures[index] {
                return Some(texture);
            }
        } 
        None
    }

    /// Clears the color attachment at the given index to the given color. This method panics if
    /// the index is not that of a valid color attachment.
    pub fn clear_color_attachment(&self, index: usize, color: Color) {
        if index > MAX_COLOR_ATTACHMENTS || self.textures[index].is_none() {
            panic!("Could not clear framebuffer color attachment. {} is not a valid color attachment index", index);
        }

        unsafe {
            use std::mem;
            gl::ClearBufferfv(gl::COLOR, index as GLint, mem::transmute(&color));
        }
    }

    /// Retrieves the pixels from the given region in the given color attachment. Returns all
    /// pixels in row-major order. Because a framebuffers attachments types are not strongly typed
    /// it is critical that `T` is a type which has the same format as the color attachment.
    ///
    /// # Panics
    ///
    ///  * If the region is outside of the bounds of this framebuffer. 
    ///  * If the index does not point to a valid color attachment.
    ///  * If T has a different number of primitives than the given color attachment.
    ///  * If T has a different primitive type than the given color attachment.
    pub fn get_pixel_data<T>(&self, index: usize, x: u32, y: u32, width: u32, height: u32) -> Vec<T> 
        where T: VertexData,
    {
        let mut data = Vec::<T>::with_capacity((width * height) as usize);

        if index > MAX_COLOR_ATTACHMENTS && self.textures[index].is_none() {
            panic!("Invalid call to get_pixel_data. {} is not a valid color attachment.", index);
        }
        let format = match self.textures[index] {
            Some(ref texture) => texture.format,
            None => unreachable!(), // We check if texture is None above
        };

        if T::primitives() != format.components() {
            panic!("Invalid call to get_pixel_data. T has a different number of primitives than {:?}.", format);
        }

        if T::Primitive::gl_enum() != format.gl_primitive_enum() {
            panic!("Invalid call to get_pixel_data. T has a different primitive type than color attachment {}. ({} vs {})",
                   index, T::Primitive::gl_name(), format.gl_primitive_enum_name());
        }

        if x + width > self.width || y + height > self.height {
            panic!("Invalid call to get_pixel_data, The rectangle (x: {}, y: {}, width: {}, height: {}) is outside of the \
                   region of the framebuffer (width: {}, height: {}).",
                   x, y, width, height, self.width, self.height);
        }

        unsafe {
            gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer);
            gl::ReadBuffer(gl::COLOR_ATTACHMENT0 + index as u32);
            gl::ReadPixels(x as GLint, y as GLint, width as GLsizei, height as GLsizei,
                           format.unsized_format(), format.gl_primitive_enum(), data.as_ptr() as *mut _);
            data.set_len((width * height) as usize);
            gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
        }

        data
    }
}

impl Drop for Framebuffer {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteFramebuffers(1, &self.framebuffer);
            if let Some(depth_buffer) = self.depth_buffer {
                gl::DeleteRenderbuffers(1, &depth_buffer);
            }
            // Textures are managed by the `Texture` struct, and are automatically deleted
        }
    }
}

{'fraction_non_alphanumeric': 0.09583013201089614, 'fraction_numerical': 0.005657609834462527, 'mean_word_length': 2.083342306449876, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 38, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '306882', 'n_tokens_mistral': 7846, 'n_tokens_neox': 7525, 'n_words': 2345}