Files
2026-07-13 12:24:51 +08:00

611 lines
21 KiB
Rust

// 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<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,
level: i32,
cube_map_face: Option<CubeMapFace>,
) {
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<Attachment>,
color_attachments: Vec<Attachment>,
) -> Result<Self, FrameworkError> {
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::<GlTexture>()
.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::<GlTexture>()
.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<glow::Framebuffer> {
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::<GlTexture>()
.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::<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.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<Vec<u8>> {
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<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(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<i32>,
program: &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, 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::<u32>()) 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<i32>,
program: &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, 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::<u32>()) 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<glow::Framebuffer>,
server: &GlGraphicsServer,
viewport: Rect<i32>,
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::<GlProgram>().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::<GlTexture>().unwrap();
texture.bind(server, *shader_location as u32);
let sampler = sampler.as_any().downcast_ref::<GlSampler>().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::<GlBuffer>()
.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);
}
}
}