servo/components/compositing/gl.rs
Delan Azabani 17f3c45d4f
Add initial support for offscreen rendering (#30767)
* Offscreen rendering

* shared memory case never actually rendered to backbuffer

* fix compile errors (in theory) when gl crate feature disabled

* update doc comments

* remove dark CentralPanel border covering edges of viewport

* clear to transparent, to avoid pink artifacts

* fix mouse input for browser being consumed by egui

* avoid destroying OpenGL resources unless resizing window

* clean up compositing::gl

* fix flickering around edges after resizing window

* unset invalidate_last_render_target after invalidating

* fix incorrect DRAW_FRAMEBUFFER name when blitting

* bind the widget surface fbo before painting egui

* make composite_specific_target take CompositeTarget, not Option

* compositing: remove cargo feature “gl”

* capitalise FBO in bind log message

Co-authored-by: Martin Robinson <mrobinson@igalia.com>

* capitalise FBO in drop log message

Co-authored-by: Martin Robinson <mrobinson@igalia.com>

* rename RenderTargetInfo fields and use OnceCell for next field

* rename RenderTargetInfo.read to read_back_from_gpu

* document servo_framebuffer_id in Minibrowser::update

* rename needs_fbo to use_offscreen_framebuffer

* capitalise FBO in unbind log message

* clarify the purpose of Minibrowser::on_event

* fix unused_must_use warning

* reduce nesting in Minibrowser::update

* use implicit format argument in panic

* store Minibrowser.widget_surface_fbo as glow type

* explain why servo_framebuffer_id is None in first call site

* rename output_framebuffer_id to offscreen_framebuffer_id

---------

Co-authored-by: Martin Robinson <mrobinson@igalia.com>
2023-12-13 02:49:25 +00:00

150 lines
4.4 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use gleam::gl::{self, Gl};
use image::RgbImage;
use log::trace;
use servo_geometry::FramebufferUintLength;
pub struct RenderTargetInfo {
gl: Rc<dyn Gl>,
framebuffer_ids: Vec<gl::GLuint>,
renderbuffer_ids: Vec<gl::GLuint>,
texture_ids: Vec<gl::GLuint>,
}
impl RenderTargetInfo {
pub fn new(
gl: Rc<dyn Gl>,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> Self {
let framebuffer_ids = gl.gen_framebuffers(1);
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
trace!("Configuring fbo {}", framebuffer_ids[0]);
let texture_ids = gl.gen_textures(1);
gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]);
gl.tex_image_2d(
gl::TEXTURE_2D,
0,
gl::RGB as gl::GLint,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
0,
gl::RGB,
gl::UNSIGNED_BYTE,
None,
);
gl.tex_parameter_i(
gl::TEXTURE_2D,
gl::TEXTURE_MAG_FILTER,
gl::NEAREST as gl::GLint,
);
gl.tex_parameter_i(
gl::TEXTURE_2D,
gl::TEXTURE_MIN_FILTER,
gl::NEAREST as gl::GLint,
);
gl.framebuffer_texture_2d(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture_ids[0],
0,
);
gl.bind_texture(gl::TEXTURE_2D, 0);
let renderbuffer_ids = gl.gen_renderbuffers(1);
let depth_rb = renderbuffer_ids[0];
gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
gl.renderbuffer_storage(
gl::RENDERBUFFER,
gl::DEPTH_COMPONENT24,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
);
gl.framebuffer_renderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_ATTACHMENT,
gl::RENDERBUFFER,
depth_rb,
);
Self {
gl,
framebuffer_ids,
renderbuffer_ids,
texture_ids,
}
}
pub fn framebuffer_id(&self) -> gl::GLuint {
*self.framebuffer_ids.first().expect("Guaranteed by new")
}
pub fn bind(&self) {
trace!("Binding FBO {}", self.framebuffer_id());
self.gl
.bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer_id());
}
pub fn unbind(&self) {
trace!("Unbinding FBO {}", self.framebuffer_id());
self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
}
pub fn read_back_from_gpu(
self,
x: i32,
y: i32,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> RgbImage {
let width = width.get() as usize;
let height = height.get() as usize;
// For some reason, OSMesa fails to render on the 3rd
// attempt in headless mode, under some conditions.
// I think this can only be some kind of synchronization
// bug in OSMesa, but explicitly un-binding any vertex
// array here seems to work around that bug.
// See https://github.com/servo/servo/issues/18606.
self.gl.bind_vertex_array(0);
let mut pixels = self.gl.read_pixels(
x,
y,
width as gl::GLsizei,
height as gl::GLsizei,
gl::RGB,
gl::UNSIGNED_BYTE,
);
// flip image vertically (texture is upside down)
let orig_pixels = pixels.clone();
let stride = width * 3;
for y in 0..height {
let dst_start = y * stride;
let src_start = (height - y - 1) * stride;
let src_slice = &orig_pixels[src_start..src_start + stride];
(&mut pixels[dst_start..dst_start + stride]).clone_from_slice(&src_slice[..stride]);
}
RgbImage::from_raw(width as u32, height as u32, pixels).expect("Flipping image failed!")
}
}
impl Drop for RenderTargetInfo {
fn drop(&mut self) {
trace!("Dropping FBO {}", self.framebuffer_id());
self.unbind();
self.gl.delete_textures(&self.texture_ids);
self.gl.delete_renderbuffers(&self.renderbuffer_ids);
self.gl.delete_framebuffers(&self.framebuffer_ids);
}
}