use crate::context::{consts, Context};
use crate::core::*;
use crate::definition::*;
use crate::math::*;
use crate::ImageEffect;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ClearState {
pub red: Option<f32>,
pub green: Option<f32>,
pub blue: Option<f32>,
pub alpha: Option<f32>,
pub depth: Option<f32>,
}
impl ClearState {
pub const fn none() -> Self {
Self {
red: None,
green: None,
blue: None,
alpha: None,
depth: None,
}
}
pub const fn depth(depth: f32) -> Self {
Self {
red: None,
green: None,
blue: None,
alpha: None,
depth: Some(depth),
}
}
pub const fn color(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self {
red: Some(red),
green: Some(green),
blue: Some(blue),
alpha: Some(alpha),
depth: None,
}
}
pub const fn color_and_depth(red: f32, green: f32, blue: f32, alpha: f32, depth: f32) -> Self {
Self {
red: Some(red),
green: Some(green),
blue: Some(blue),
alpha: Some(alpha),
depth: Some(depth),
}
}
}
impl Default for ClearState {
fn default() -> Self {
Self::color_and_depth(0.0, 0.0, 0.0, 1.0, 1.0)
}
}
pub struct Screen {}
impl Screen {
pub fn write<F: FnOnce() -> Result<(), Error>>(
context: &Context,
clear_state: ClearState,
render: F,
) -> Result<(), Error> {
context.bind_framebuffer(consts::DRAW_FRAMEBUFFER, None);
clear(context, &clear_state);
render()?;
Ok(())
}
pub fn read_color(context: &Context, viewport: Viewport) -> Result<Vec<u8>, Error> {
let mut pixels = vec![0u8; viewport.width * viewport.height * 4];
context.bind_framebuffer(consts::READ_FRAMEBUFFER, None);
context.read_pixels_with_u8_data(
viewport.x as u32,
viewport.y as u32,
viewport.width as u32,
viewport.height as u32,
consts::RGBA,
consts::UNSIGNED_BYTE,
&mut pixels,
);
Ok(pixels)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn read_depth(context: &Context, viewport: Viewport) -> Result<Vec<f32>, Error> {
let mut pixels = vec![0f32; viewport.width * viewport.height];
context.bind_framebuffer(consts::READ_FRAMEBUFFER, None);
context.read_pixels_with_f32_data(
viewport.x as u32,
viewport.y as u32,
viewport.width as u32,
viewport.height as u32,
consts::DEPTH_COMPONENT,
consts::FLOAT,
&mut pixels,
);
Ok(pixels)
}
}
pub enum CopyDestination<'a, 'b, 'c, 'd, T: TextureValueType> {
Screen,
ColorTexture(&'d ColorTargetTexture2D<T>),
DepthTexture(&'d DepthTargetTexture2D),
RenderTarget(&'c RenderTarget<'a, 'b, T>),
}
pub struct RenderTarget<'a, 'b, T: TextureValueType> {
context: Context,
id: crate::context::Framebuffer,
color_texture: Option<&'a ColorTargetTexture2D<T>>,
depth_texture: Option<&'b DepthTargetTexture2D>,
}
impl<'a, 'b, T: TextureValueType> RenderTarget<'a, 'b, T> {
pub fn new(
context: &Context,
color_texture: &'a ColorTargetTexture2D<T>,
depth_texture: &'b DepthTargetTexture2D,
) -> Result<Self, Error> {
Ok(Self {
context: context.clone(),
id: new_framebuffer(context)?,
color_texture: Some(color_texture),
depth_texture: Some(depth_texture),
})
}
pub fn write<F: FnOnce() -> Result<(), Error>>(
&self,
clear_state: ClearState,
render: F,
) -> Result<(), Error> {
self.bind(consts::DRAW_FRAMEBUFFER)?;
clear(
&self.context,
&ClearState {
red: self.color_texture.and(clear_state.red),
green: self.color_texture.and(clear_state.green),
blue: self.color_texture.and(clear_state.blue),
alpha: self.color_texture.and(clear_state.alpha),
depth: self.depth_texture.and(clear_state.depth),
},
);
render()?;
if let Some(color_texture) = self.color_texture {
color_texture.generate_mip_maps();
}
Ok(())
}
pub fn copy_to(
&self,
destination: CopyDestination<T>,
viewport: Viewport,
write_mask: WriteMask,
) -> Result<(), Error> {
let copy = || {
let effect = get_copy_effect(&self.context)?;
if let Some(tex) = self.color_texture {
effect.use_texture(tex, "colorMap")?;
}
if let Some(tex) = self.depth_texture {
effect.use_texture(tex, "depthMap")?;
}
effect.apply(
RenderStates {
depth_test: DepthTestType::Always,
write_mask,
..Default::default()
},
viewport,
)?;
Ok(())
};
match destination {
CopyDestination::RenderTarget(other) => {
other.write(ClearState::none(), copy)?;
}
CopyDestination::Screen => {
Screen::write(&self.context, ClearState::none(), copy)?;
}
CopyDestination::ColorTexture(tex) => {
if self.color_texture.is_none() {
Err(Error::RenderTargetError {
message: "Cannot copy color from a depth texture.".to_owned(),
})?;
}
tex.write(ClearState::none(), copy)?;
}
CopyDestination::DepthTexture(tex) => {
if self.depth_texture.is_none() {
Err(Error::RenderTargetError {
message: "Cannot copy depth from a color texture.".to_owned(),
})?;
}
tex.write(None, copy)?;
}
}
Ok(())
}
pub(super) fn new_color(
context: &Context,
color_texture: &'a ColorTargetTexture2D<T>,
) -> Result<Self, Error> {
Ok(Self {
context: context.clone(),
id: new_framebuffer(context)?,
color_texture: Some(color_texture),
depth_texture: None,
})
}
pub(super) fn new_depth(
context: &Context,
depth_texture: &'b DepthTargetTexture2D,
) -> Result<Self, Error> {
Ok(Self {
context: context.clone(),
id: new_framebuffer(context)?,
color_texture: None,
depth_texture: Some(depth_texture),
})
}
pub(super) fn bind(&self, target: u32) -> Result<(), Error> {
self.context.bind_framebuffer(target, Some(&self.id));
if let Some(tex) = self.color_texture {
self.context.draw_buffers(&[consts::COLOR_ATTACHMENT0]);
tex.bind_as_color_target(0);
}
if let Some(tex) = self.depth_texture {
tex.bind_as_depth_target();
}
#[cfg(feature = "debug")]
check(&self.context)?;
Ok(())
}
}
impl<T: TextureValueType> Drop for RenderTarget<'_, '_, T> {
fn drop(&mut self) {
self.context.delete_framebuffer(Some(&self.id));
}
}
pub struct RenderTargetArray<'a, 'b, T: TextureValueType> {
context: Context,
id: crate::context::Framebuffer,
color_texture: Option<&'a ColorTargetTexture2DArray<T>>,
depth_texture: Option<&'b DepthTargetTexture2DArray>,
}
impl<'a, 'b, T: TextureValueType> RenderTargetArray<'a, 'b, T> {
pub fn new(
context: &Context,
color_texture: &'a ColorTargetTexture2DArray<T>,
depth_texture: &'b DepthTargetTexture2DArray,
) -> Result<Self, Error> {
Ok(Self {
context: context.clone(),
id: new_framebuffer(context)?,
color_texture: Some(color_texture),
depth_texture: Some(depth_texture),
})
}
pub(super) fn new_color(
context: &Context,
color_texture: &'a ColorTargetTexture2DArray<T>,
) -> Result<Self, Error> {
Ok(Self {
context: context.clone(),
id: new_framebuffer(context)?,
color_texture: Some(color_texture),
depth_texture: None,
})
}
pub(super) fn new_depth(
context: &Context,
depth_texture: &'b DepthTargetTexture2DArray,
) -> Result<Self, Error> {
Ok(Self {
context: context.clone(),
id: new_framebuffer(context)?,
color_texture: None,
depth_texture: Some(depth_texture),
})
}
pub fn write<F: FnOnce() -> Result<(), Error>>(
&self,
color_layers: &[usize],
depth_layer: usize,
clear_state: ClearState,
render: F,
) -> Result<(), Error> {
self.bind(Some(color_layers), Some(depth_layer))?;
clear(
&self.context,
&ClearState {
red: self.color_texture.and(clear_state.red),
green: self.color_texture.and(clear_state.green),
blue: self.color_texture.and(clear_state.blue),
alpha: self.color_texture.and(clear_state.alpha),
depth: self.depth_texture.and(clear_state.depth),
},
);
render()?;
if let Some(color_texture) = self.color_texture {
color_texture.generate_mip_maps();
}
Ok(())
}
pub fn copy_to(
&self,
color_layer: usize,
depth_layer: usize,
destination: CopyDestination<T>,
viewport: Viewport,
write_mask: WriteMask,
) -> Result<(), Error> {
let copy = || {
let effect = get_copy_array_effect(&self.context)?;
if let Some(tex) = self.color_texture {
effect.use_texture_array(tex, "colorMap")?;
effect.use_uniform_int("colorLayer", &(color_layer as i32))?;
}
if let Some(tex) = self.depth_texture {
effect.use_texture_array(tex, "depthMap")?;
effect.use_uniform_int("depthLayer", &(depth_layer as i32))?;
}
effect.apply(
RenderStates {
depth_test: DepthTestType::Always,
write_mask,
..Default::default()
},
viewport,
)?;
Ok(())
};
match destination {
CopyDestination::RenderTarget(other) => {
other.write(ClearState::none(), copy)?;
}
CopyDestination::Screen => {
Screen::write(&self.context, ClearState::none(), copy)?;
}
CopyDestination::ColorTexture(tex) => {
if self.color_texture.is_none() {
Err(Error::RenderTargetError {
message: "Cannot copy color from a depth texture.".to_owned(),
})?;
}
tex.write(ClearState::none(), copy)?;
}
CopyDestination::DepthTexture(tex) => {
if self.depth_texture.is_none() {
Err(Error::RenderTargetError {
message: "Cannot copy depth from a color texture.".to_owned(),
})?;
}
tex.write(None, copy)?;
}
}
Ok(())
}
fn bind(
&self,
color_layers: Option<&[usize]>,
depth_layer: Option<usize>,
) -> Result<(), Error> {
self.context
.bind_framebuffer(consts::DRAW_FRAMEBUFFER, Some(&self.id));
if let Some(color_texture) = self.color_texture {
if let Some(color_layers) = color_layers {
self.context.draw_buffers(
&(0..color_layers.len())
.map(|i| consts::COLOR_ATTACHMENT0 + i as u32)
.collect::<Vec<u32>>(),
);
for channel in 0..color_layers.len() {
color_texture.bind_as_color_target(color_layers[channel], channel);
}
}
}
if let Some(depth_texture) = self.depth_texture {
if let Some(depth_layer) = depth_layer {
depth_texture.bind_as_depth_target(depth_layer);
}
}
#[cfg(feature = "debug")]
check(&self.context)?;
Ok(())
}
}
impl<T: TextureValueType> Drop for RenderTargetArray<'_, '_, T> {
fn drop(&mut self) {
self.context.delete_framebuffer(Some(&self.id));
}
}
fn new_framebuffer(context: &Context) -> Result<crate::context::Framebuffer, Error> {
Ok(context
.create_framebuffer()
.ok_or_else(|| Error::RenderTargetError {
message: "Failed to create framebuffer".to_string(),
})?)
}
#[cfg(feature = "debug")]
fn check(context: &Context) -> Result<(), Error> {
context.check_framebuffer_status().or_else(|status| {
Err(Error::RenderTargetError {
message: format!("Failed to create frame buffer: {}", status),
})
})
}
fn clear(context: &Context, clear_state: &ClearState) {
Program::set_write_mask(
context,
WriteMask {
red: clear_state.red.is_some(),
green: clear_state.green.is_some(),
blue: clear_state.blue.is_some(),
alpha: clear_state.alpha.is_some(),
depth: clear_state.depth.is_some(),
},
);
let clear_color = clear_state.red.is_some()
|| clear_state.green.is_some()
|| clear_state.blue.is_some()
|| clear_state.alpha.is_some();
if clear_color {
context.clear_color(
clear_state.red.unwrap_or(0.0),
clear_state.green.unwrap_or(0.0),
clear_state.blue.unwrap_or(0.0),
clear_state.alpha.unwrap_or(1.0),
);
}
if let Some(depth) = clear_state.depth {
context.clear_depth(depth);
}
context.clear(if clear_color && clear_state.depth.is_some() {
consts::COLOR_BUFFER_BIT | consts::DEPTH_BUFFER_BIT
} else {
if clear_color {
consts::COLOR_BUFFER_BIT
} else {
consts::DEPTH_BUFFER_BIT
}
});
}
fn get_copy_effect(context: &Context) -> Result<&ImageEffect, Error> {
unsafe {
static mut COPY_EFFECT: Option<ImageEffect> = None;
if COPY_EFFECT.is_none() {
COPY_EFFECT = Some(ImageEffect::new(
context,
&"
uniform sampler2D colorMap;
uniform sampler2D depthMap;
in vec2 uv;
layout (location = 0) out vec4 color;
void main()
{
color = texture(colorMap, uv);
gl_FragDepth = texture(depthMap, uv).r;
}",
)?);
}
Ok(COPY_EFFECT.as_ref().unwrap())
}
}
fn get_copy_array_effect(context: &Context) -> Result<&ImageEffect, Error> {
unsafe {
static mut COPY_EFFECT: Option<ImageEffect> = None;
if COPY_EFFECT.is_none() {
COPY_EFFECT = Some(ImageEffect::new(
context,
&"
uniform sampler2DArray colorMap;
uniform sampler2DArray depthMap;
uniform int colorLayer;
uniform int depthLayer;
in vec2 uv;
layout (location = 0) out vec4 color;
void main()
{
color = texture(colorMap, vec3(uv, colorLayer));
gl_FragDepth = texture(depthMap, vec3(uv, depthLayer)).r;
}",
)?);
}
Ok(COPY_EFFECT.as_ref().unwrap())
}
}