use crate::framebuffer::DrawCallStatistics;
use crate::{
buffer::{Buffer, BufferKind},
core::{color::Color, math::Rect},
error::FrameworkError,
framebuffer::{
Attachment, AttachmentKind, BufferDataUsage, BufferLocation, FrameBuffer,
ResourceBindGroup, ResourceBinding, TextureShaderLocation,
},
geometry_buffer::GeometryBuffer,
gl::{
buffer::GlBuffer, geometry_buffer::GlGeometryBuffer, program::GlProgram,
server::GlGraphicsServer, texture::GlTexture, ToGlConstant,
},
gpu_program::GpuProgram,
gpu_texture::{CubeMapFace, GpuTexture, GpuTextureKind, PixelElementKind},
ColorMask, DrawParameters, ElementRange,
};
use glow::HasContext;
use std::rc::Weak;
pub struct GlFrameBuffer {
state: Weak<GlGraphicsServer>,
fbo: Option<glow::Framebuffer>,
depth_attachment: Option<Attachment>,
color_attachments: Vec<Attachment>,
}
unsafe fn set_attachment(server: &GlGraphicsServer, gl_attachment_kind: u32, texture: &GlTexture) {
match texture.kind() {
GpuTextureKind::Line { .. } => {
server.gl.framebuffer_texture(
glow::FRAMEBUFFER,
gl_attachment_kind,
Some(texture.id()),
0,
);
}
GpuTextureKind::Rectangle { .. } => {
server.gl.framebuffer_texture_2d(
glow::FRAMEBUFFER,
gl_attachment_kind,
glow::TEXTURE_2D,
Some(texture.id()),
0,
);
}
GpuTextureKind::Cube { .. } => {
server.gl.framebuffer_texture_2d(
glow::FRAMEBUFFER,
gl_attachment_kind,
glow::TEXTURE_CUBE_MAP_POSITIVE_X,
Some(texture.id()),
0,
);
}
GpuTextureKind::Volume { .. } => {
server.gl.framebuffer_texture_3d(
glow::FRAMEBUFFER,
gl_attachment_kind,
glow::TEXTURE_3D,
Some(texture.id()),
0,
0,
);
}
}
}
impl GlFrameBuffer {
pub fn new(
server: &GlGraphicsServer,
depth_attachment: Option<Attachment>,
color_attachments: Vec<Attachment>,
) -> Result<Self, FrameworkError> {
unsafe {
let fbo = server.gl.create_framebuffer()?;
server.set_framebuffer(Some(fbo));
if let Some(depth_attachment) = depth_attachment.as_ref() {
let depth_attachment_kind = match depth_attachment.kind {
AttachmentKind::Color => {
panic!("Attempt to use color attachment as depth/stencil!")
}
AttachmentKind::DepthStencil => glow::DEPTH_STENCIL_ATTACHMENT,
AttachmentKind::Depth => glow::DEPTH_ATTACHMENT,
};
let guard = depth_attachment.texture.borrow();
let texture = guard.as_any().downcast_ref::<GlTexture>().unwrap();
set_attachment(server, depth_attachment_kind, texture);
}
let mut color_buffers = Vec::new();
for (i, color_attachment) in color_attachments.iter().enumerate() {
assert_eq!(color_attachment.kind, AttachmentKind::Color);
let color_attachment_kind = glow::COLOR_ATTACHMENT0 + i as u32;
let guard = color_attachment.texture.borrow();
let texture = guard.as_any().downcast_ref::<GlTexture>().unwrap();
set_attachment(server, color_attachment_kind, texture);
color_buffers.push(color_attachment_kind);
}
if color_buffers.is_empty() {
server.gl.draw_buffers(&[glow::NONE])
} else {
server.gl.draw_buffers(&color_buffers);
}
if server.gl.check_framebuffer_status(glow::FRAMEBUFFER) != glow::FRAMEBUFFER_COMPLETE {
return Err(FrameworkError::FailedToConstructFBO);
}
server.set_framebuffer(None);
Ok(Self {
state: server.weak(),
fbo: Some(fbo),
depth_attachment,
color_attachments,
})
}
}
pub fn backbuffer(server: &GlGraphicsServer) -> Self {
Self {
state: server.weak(),
fbo: None,
depth_attachment: None,
color_attachments: Default::default(),
}
}
pub fn id(&self) -> Option<glow::Framebuffer> {
self.fbo
}
}
impl FrameBuffer for GlFrameBuffer {
fn color_attachments(&self) -> &[Attachment] {
&self.color_attachments
}
fn depth_attachment(&self) -> Option<&Attachment> {
self.depth_attachment.as_ref()
}
fn set_cubemap_face(&mut self, attachment_index: usize, face: CubeMapFace) {
let server = self.state.upgrade().unwrap();
unsafe {
server.set_framebuffer(self.fbo);
let attachment = self.color_attachments.get(attachment_index).unwrap();
let guard = attachment.texture.borrow();
let texture = guard.as_any().downcast_ref::<GlTexture>().unwrap();
server.gl.framebuffer_texture_2d(
glow::FRAMEBUFFER,
glow::COLOR_ATTACHMENT0 + attachment_index as u32,
face.into_gl(),
Some(texture.id()),
0,
);
}
}
fn blit_to(
&self,
dest: &dyn FrameBuffer,
src_x0: i32,
src_y0: i32,
src_x1: i32,
src_y1: i32,
dst_x0: i32,
dst_y0: i32,
dst_x1: i32,
dst_y1: i32,
copy_color: bool,
copy_depth: bool,
copy_stencil: bool,
) {
let server = self.state.upgrade().unwrap();
let source = self;
let dest = dest.as_any().downcast_ref::<GlFrameBuffer>().unwrap();
let mut mask = 0;
if copy_color {
mask |= glow::COLOR_BUFFER_BIT;
}
if copy_depth {
mask |= glow::DEPTH_BUFFER_BIT;
}
if copy_stencil {
mask |= glow::STENCIL_BUFFER_BIT;
}
unsafe {
server
.gl
.bind_framebuffer(glow::READ_FRAMEBUFFER, source.id());
server
.gl
.bind_framebuffer(glow::DRAW_FRAMEBUFFER, dest.id());
server.gl.blit_framebuffer(
src_x0,
src_y0,
src_x1,
src_y1,
dst_x0,
dst_y0,
dst_x1,
dst_y1,
mask,
glow::NEAREST,
);
}
}
fn clear(
&mut self,
viewport: Rect<i32>,
color: Option<Color>,
depth: Option<f32>,
stencil: Option<i32>,
) {
let server = self.state.upgrade().unwrap();
server.set_scissor_test(false);
server.set_viewport(viewport);
server.set_framebuffer(self.id());
unsafe {
if self.fbo == Default::default() {
let mut mask = 0;
if let Some(color) = color {
server.set_color_write(ColorMask::default());
server.set_clear_color(color);
mask |= glow::COLOR_BUFFER_BIT;
}
if let Some(depth) = depth {
server.set_depth_write(true);
server.set_clear_depth(depth);
mask |= glow::DEPTH_BUFFER_BIT;
}
if let Some(stencil) = stencil {
server.set_stencil_mask(0xFFFF_FFFF);
server.set_clear_stencil(stencil);
mask |= glow::STENCIL_BUFFER_BIT;
}
server.gl.clear(mask);
}
if let Some(depth_stencil) = self.depth_attachment.as_ref() {
server.set_depth_write(true);
server.set_stencil_mask(0xFFFF_FFFF);
match depth_stencil.kind {
AttachmentKind::Color => unreachable!("depth cannot be color!"),
AttachmentKind::DepthStencil => match (depth, stencil) {
(Some(depth), Some(stencil)) => {
server.gl.clear_buffer_depth_stencil(
glow::DEPTH_STENCIL,
0,
depth,
stencil,
);
}
(Some(depth), None) => {
let values = [depth];
server.gl.clear_buffer_f32_slice(glow::DEPTH, 0, &values);
}
(None, Some(stencil)) => {
let values = [stencil];
server.gl.clear_buffer_i32_slice(glow::STENCIL, 0, &values);
}
(None, None) => {
}
},
AttachmentKind::Depth => {
if let Some(depth) = depth {
let values = [depth];
server.gl.clear_buffer_f32_slice(glow::DEPTH, 0, &values);
}
}
}
}
if let Some(color) = color {
server.set_color_write(ColorMask::default());
for (i, attachment) in self.color_attachments.iter().enumerate() {
match attachment.texture.borrow().pixel_kind().element_kind() {
PixelElementKind::Float | PixelElementKind::NormalizedUnsignedInteger => {
let fvalues = color.as_frgba();
server.gl.clear_buffer_f32_slice(
glow::COLOR,
i as u32,
&fvalues.data.0[0],
)
}
PixelElementKind::Integer => {
let values = [
color.r as i32,
color.g as i32,
color.b as i32,
color.a as i32,
];
server
.gl
.clear_buffer_i32_slice(glow::COLOR, i as u32, &values);
}
PixelElementKind::UnsignedInteger => {
let values = [
color.r as u32,
color.g as u32,
color.b as u32,
color.a as u32,
];
server
.gl
.clear_buffer_u32_slice(glow::COLOR, i as u32, &values);
}
}
}
}
}
}
fn draw(
&mut self,
geometry: &dyn GeometryBuffer,
viewport: Rect<i32>,
program: &dyn GpuProgram,
params: &DrawParameters,
resources: &[ResourceBindGroup],
element_range: ElementRange,
) -> Result<DrawCallStatistics, FrameworkError> {
let server = self.state.upgrade().unwrap();
let geometry = geometry
.as_any()
.downcast_ref::<GlGeometryBuffer>()
.unwrap();
pre_draw(self.id(), &server, viewport, program, params, resources);
let (offset, count) = match element_range {
ElementRange::Full => (0, geometry.element_count.get()),
ElementRange::Specific { offset, count } => (offset, count),
};
let last_triangle_index = offset + count;
if last_triangle_index > geometry.element_count.get() {
Err(FrameworkError::InvalidElementRange {
start: offset,
end: last_triangle_index,
total: geometry.element_count.get(),
})
} else {
let index_per_element = geometry.element_kind.index_per_element();
let start_index = offset * index_per_element;
let index_count = count * index_per_element;
unsafe {
if index_count > 0 {
server.set_vertex_array_object(Some(geometry.vertex_array_object));
let indices = (start_index * size_of::<u32>()) as i32;
server.gl.draw_elements(
geometry.mode(),
index_count as i32,
glow::UNSIGNED_INT,
indices,
);
}
}
Ok(DrawCallStatistics { triangles: count })
}
}
fn draw_instances(
&mut self,
count: usize,
geometry: &dyn GeometryBuffer,
viewport: Rect<i32>,
program: &dyn GpuProgram,
params: &DrawParameters,
resources: &[ResourceBindGroup],
) -> DrawCallStatistics {
let server = self.state.upgrade().unwrap();
let geometry = geometry
.as_any()
.downcast_ref::<GlGeometryBuffer>()
.unwrap();
pre_draw(self.id(), &server, viewport, program, params, resources);
let index_per_element = geometry.element_kind.index_per_element();
let index_count = geometry.element_count.get() * index_per_element;
if index_count > 0 {
unsafe {
server.set_vertex_array_object(Some(geometry.vertex_array_object));
server.gl.draw_elements_instanced(
geometry.mode(),
index_count as i32,
glow::UNSIGNED_INT,
0,
count as i32,
)
}
}
DrawCallStatistics {
triangles: geometry.element_count.get() * count,
}
}
}
fn pre_draw(
fbo: Option<glow::Framebuffer>,
server: &GlGraphicsServer,
viewport: Rect<i32>,
program: &dyn GpuProgram,
params: &DrawParameters,
resources: &[ResourceBindGroup],
) {
server.set_framebuffer(fbo);
server.set_viewport(viewport);
server.apply_draw_parameters(params);
let program = program.as_any().downcast_ref::<GlProgram>().unwrap();
server.set_program(Some(program.id));
let mut texture_unit = 0;
let mut automatic_binding_index = 0;
for bind_group in resources {
for binding in bind_group.bindings {
match binding {
ResourceBinding::Texture {
texture,
shader_location,
} => {
let texture = texture.borrow();
let texture = texture.as_any().downcast_ref::<GlTexture>().unwrap();
match shader_location {
TextureShaderLocation::Uniform(uniform) => {
unsafe { server.gl.uniform_1_i32(Some(&uniform.id), texture_unit) };
texture.bind(server, texture_unit as u32);
texture_unit += 1;
}
TextureShaderLocation::ExplicitBinding(binding) => {
texture.bind(server, *binding as u32);
}
}
}
ResourceBinding::Buffer {
buffer,
binding: shader_location,
data_usage: data_location,
} => {
let gl_buffer = buffer
.as_any()
.downcast_ref::<GlBuffer>()
.expect("Must be OpenGL buffer");
unsafe {
let actual_binding = match shader_location {
BufferLocation::Auto { .. } => automatic_binding_index,
BufferLocation::Explicit { binding } => *binding,
};
match data_location {
BufferDataUsage::UseSegment { offset, size } => {
assert_ne!(*size, 0);
server.gl.bind_buffer_range(
gl_buffer.kind().into_gl(),
actual_binding as u32,
Some(gl_buffer.id),
*offset as i32,
*size as i32,
);
}
BufferDataUsage::UseEverything => {
server.gl.bind_buffer_base(
gl_buffer.kind().into_gl(),
actual_binding as u32,
Some(gl_buffer.id),
);
}
}
if let BufferLocation::Auto { shader_location } = shader_location {
match gl_buffer.kind() {
BufferKind::Uniform => server.gl.uniform_block_binding(
program.id,
*shader_location as u32,
automatic_binding_index as u32,
),
BufferKind::Vertex
| BufferKind::Index
| BufferKind::PixelRead
| BufferKind::PixelWrite => {}
}
automatic_binding_index += 1;
}
}
}
}
}
}
}
impl Drop for GlFrameBuffer {
fn drop(&mut self) {
if let Some(state) = self.state.upgrade() {
unsafe {
if let Some(id) = self.fbo {
state.gl.delete_framebuffer(id);
}
}
}
}
}