// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. pub use crate::{ buffer::GlBuffer, geometry_buffer::GlGeometryBuffer, program::GlProgram, sampler::GlSampler, server::GlGraphicsServer, texture::GlTexture, ToGlConstant, }; use crate::{server::FrameBufferBindingPoint, texture::PixelDescriptor}; use fyrox_graphics::{ buffer::GpuBufferTrait, core::{color::Color, math::Rect}, error::FrameworkError, framebuffer::ReadTarget, framebuffer::{ Attachment, AttachmentKind, BufferDataUsage, DrawCallStatistics, GpuFrameBuffer, GpuFrameBufferTrait, ResourceBindGroup, ResourceBinding, }, geometry_buffer::GpuGeometryBuffer, gpu_program::GpuProgram, gpu_texture::image_2d_size_bytes, gpu_texture::{CubeMapFace, GpuTextureKind, GpuTextureTrait, PixelElementKind}, ColorMask, DrawParameters, ElementRange, }; use glow::{HasContext, PixelPackData}; use std::rc::Weak; pub struct GlFrameBuffer { state: Weak, fbo: Option, depth_attachment: Option, color_attachments: Vec, } unsafe fn set_attachment( server: &GlGraphicsServer, gl_attachment_kind: u32, texture: &GlTexture, level: i32, cube_map_face: Option, ) { match texture.kind() { GpuTextureKind::Line { .. } => { server.gl.framebuffer_texture( glow::FRAMEBUFFER, gl_attachment_kind, Some(texture.id()), level, ); } GpuTextureKind::Rectangle { .. } => { server.gl.framebuffer_texture_2d( glow::FRAMEBUFFER, gl_attachment_kind, glow::TEXTURE_2D, Some(texture.id()), level, ); } GpuTextureKind::Cube { .. } => { server.gl.framebuffer_texture_2d( glow::FRAMEBUFFER, gl_attachment_kind, cube_map_face.map_or(glow::TEXTURE_CUBE_MAP_POSITIVE_X, |face| face.into_gl()), Some(texture.id()), level, ); } GpuTextureKind::Volume { .. } => { server.gl.framebuffer_texture_3d( glow::FRAMEBUFFER, gl_attachment_kind, glow::TEXTURE_3D, Some(texture.id()), level, 0, ); } } } impl GlFrameBuffer { pub fn new( server: &GlGraphicsServer, depth_attachment: Option, color_attachments: Vec, ) -> Result { unsafe { let fbo = server.gl.create_framebuffer()?; server.set_framebuffer(FrameBufferBindingPoint::ReadWrite, 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 texture = depth_attachment .texture .as_any() .downcast_ref::() .unwrap(); set_attachment( server, depth_attachment_kind, texture, depth_attachment.level() as i32, depth_attachment.cube_map_face(), ); } 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 texture = color_attachment .texture .as_any() .downcast_ref::() .unwrap(); set_attachment( server, color_attachment_kind, texture, color_attachment.level() as i32, color_attachment.cube_map_face(), ); 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(FrameBufferBindingPoint::ReadWrite, 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(), } } /// None is possible only for back buffer. pub fn id(&self) -> Option { self.fbo } } impl GpuFrameBufferTrait 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(&self, attachment_index: usize, face: CubeMapFace, level: usize) { let server = self.state.upgrade().unwrap(); unsafe { server.set_framebuffer(FrameBufferBindingPoint::ReadWrite, self.fbo); let attachment = &self.color_attachments[attachment_index]; attachment.set_cube_map_face(Some(face)); attachment.set_level(level); let texture = attachment .texture .as_any() .downcast_ref::() .unwrap(); server.gl.framebuffer_texture_2d( glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0 + attachment_index as u32, face.into_gl(), Some(texture.id()), level as i32, ); } } fn blit_to( &self, dest: &GpuFrameBuffer, 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::().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.set_framebuffer(FrameBufferBindingPoint::Read, source.id()); server.set_framebuffer(FrameBufferBindingPoint::Write, 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 read_pixels(&self, read_target: ReadTarget) -> Option> { let server = self.state.upgrade()?; server.set_framebuffer(FrameBufferBindingPoint::Read, self.id()); let texture = match read_target { ReadTarget::Depth | ReadTarget::Stencil => &self.depth_attachment.as_ref()?.texture, ReadTarget::Color(index) => { unsafe { server .gl .read_buffer(glow::COLOR_ATTACHMENT0 + index as u32); } &self.color_attachments.get(index)?.texture } }; if let GpuTextureKind::Rectangle { width, height } = texture.kind() { let pixel_kind = texture.pixel_kind(); let pixel_info = PixelDescriptor::from(pixel_kind); let mut buffer = vec![0; image_2d_size_bytes(pixel_kind, width, height)]; unsafe { server.gl.read_pixels( 0, 0, width as i32, height as i32, pixel_info.format, pixel_info.data_type, PixelPackData::Slice(Some(buffer.as_mut_slice())), ); } Some(buffer) } else { None } } fn clear( &self, viewport: Rect, color: Option, depth: Option, stencil: Option, ) { let server = self.state.upgrade().unwrap(); server.set_scissor_test(false); server.set_viewport(viewport); server.set_framebuffer(FrameBufferBindingPoint::ReadWrite, self.id()); unsafe { // Special route for default buffer. 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); } // Custom routes for specific frame buffer attachments. 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) => { // Nothing to do } }, 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.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( &self, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange, ) -> Result { let server = self.state.upgrade().unwrap(); let geometry = geometry .as_any() .downcast_ref::() .unwrap(); pre_draw(self.id(), &server, viewport, program, params, resources); let (offset, element_count) = match element_range { ElementRange::Full => (0, geometry.element_count.get()), ElementRange::Specific { offset, count } => (offset, count), }; let last_element_index = offset + element_count; if last_element_index > geometry.element_count.get() { Err(FrameworkError::InvalidElementRange { start: offset, end: last_element_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 = element_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::()) as i32; server.gl.draw_elements( geometry.mode(), index_count as i32, glow::UNSIGNED_INT, indices, ); } } Ok(DrawCallStatistics { triangles: element_count, }) } } fn draw_instances( &self, instance_count: usize, geometry: &GpuGeometryBuffer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], element_range: ElementRange, ) -> Result { let server = self.state.upgrade().unwrap(); let geometry = geometry .as_any() .downcast_ref::() .unwrap(); pre_draw(self.id(), &server, viewport, program, params, resources); let (offset, element_count) = match element_range { ElementRange::Full => (0, geometry.element_count.get()), ElementRange::Specific { offset, count } => (offset, count), }; let last_element_index = offset + element_count; if last_element_index > geometry.element_count.get() { Err(FrameworkError::InvalidElementRange { start: offset, end: last_element_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 = geometry.element_count.get() * index_per_element; unsafe { if index_count > 0 { server.set_vertex_array_object(Some(geometry.vertex_array_object)); let indices = (start_index * size_of::()) as i32; server.gl.draw_elements_instanced( geometry.mode(), index_count as i32, glow::UNSIGNED_INT, indices, instance_count as i32, ) } } Ok(DrawCallStatistics { triangles: geometry.element_count.get() * instance_count, }) } } } fn pre_draw( fbo: Option, server: &GlGraphicsServer, viewport: Rect, program: &GpuProgram, params: &DrawParameters, resources: &[ResourceBindGroup], ) { server.set_framebuffer(FrameBufferBindingPoint::ReadWrite, fbo); server.set_viewport(viewport); server.apply_draw_parameters(params); let program = program.as_any().downcast_ref::().unwrap(); server.set_program(Some(program.id)); for bind_group in resources { for binding in bind_group.bindings { match binding { ResourceBinding::Texture { texture, sampler, binding: shader_location, } => { let texture = texture.as_any().downcast_ref::().unwrap(); texture.bind(server, *shader_location as u32); let sampler = sampler.as_any().downcast_ref::().unwrap(); unsafe { server .gl .bind_sampler(*shader_location as u32, Some(sampler.id)) }; } ResourceBinding::Buffer { buffer, binding, data_usage: data_location, } => { let gl_buffer = buffer .as_any() .downcast_ref::() .expect("Must be OpenGL buffer"); unsafe { let actual_binding = *binding as u32; match data_location { BufferDataUsage::UseSegment { offset, size } => { assert_ne!(*size, 0); server.gl.bind_buffer_range( gl_buffer.kind().into_gl(), actual_binding, Some(gl_buffer.id), *offset as i32, *size as i32, ); } BufferDataUsage::UseEverything => { server.gl.bind_buffer_base( gl_buffer.kind().into_gl(), actual_binding, Some(gl_buffer.id), ); } } } } } } } } impl Drop for GlFrameBuffer { fn drop(&mut self) { if let Some(state) = self.state.upgrade() { state.delete_framebuffer(self.fbo); } } }