Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: offscreen rendering #957

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ async-std = ["iced_futures/async-std"]
smol = ["iced_futures/smol"]
# Enables advanced color conversion via `palette`
palette = ["iced_core/palette"]
# Enables offscreen rendering
offscreen = ["iced_glutin/offscreen", "iced_glow/offscreen", "iced_winit/offscreen", "iced_wgpu/offscreen"]

[badges]
maintenance = { status = "actively-developed" }
Expand Down
1 change: 1 addition & 0 deletions glow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/hecrj/iced"
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
offscreen = []
# Not supported yet!
image = []
svg = []
Expand Down
8 changes: 7 additions & 1 deletion glow/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::quad;
use crate::text;
use crate::triangle;
use crate::{Settings, Transformation, Viewport};
use glow::HasContext;
use iced_graphics::backend;
use iced_graphics::font;
use iced_graphics::Layer;
Expand Down Expand Up @@ -36,7 +37,7 @@ impl Backend {
}
}

/// Draws the provided primitives in the default framebuffer.
/// Draws the provided primitives in the target framebuffer.
///
/// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information.
Expand All @@ -46,6 +47,7 @@ impl Backend {
viewport: &Viewport,
(primitive, mouse_interaction): &(Primitive, mouse::Interaction),
overlay_text: &[T],
target: Option<glow::Framebuffer>,
) -> mouse::Interaction {
let viewport_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
Expand All @@ -54,6 +56,10 @@ impl Backend {
let mut layers = Layer::generate(primitive, viewport);
layers.push(Layer::overlay(overlay_text, viewport));

unsafe {
gl.bind_framebuffer(glow::RENDERBUFFER, target);
}

for layer in layers {
self.flush(
gl,
Expand Down
60 changes: 57 additions & 3 deletions glow/src/window/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport};

use core::ffi::c_void;
use glow::HasContext;
use iced_graphics::{Antialiasing, Size};
use iced_graphics::{Antialiasing, Rectangle, Size};
use iced_native::mouse;

/// A window graphics backend for iced powered by `glow`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
gl: glow::Context,
framebuffer: Option<glow::Framebuffer>,
}

impl iced_graphics::window::GLCompositor for Compositor {
Expand All @@ -17,6 +18,7 @@ impl iced_graphics::window::GLCompositor for Compositor {

unsafe fn new(
settings: Self::Settings,
viewport_size: Size<u32>,
loader_function: impl FnMut(&str) -> *const c_void,
) -> Result<(Self, Self::Renderer), Error> {
let gl = glow::Context::from_loader_function(loader_function);
Expand All @@ -33,7 +35,35 @@ impl iced_graphics::window::GLCompositor for Compositor {

let renderer = Renderer::new(Backend::new(&gl, settings));

Ok((Self { gl }, renderer))
// Allocate an offscreen framebuffer if needed
#[cfg(feature = "offscreen")]
let framebuffer = {
let renderbuffer = gl.create_renderbuffer().ok();
gl.bind_renderbuffer(glow::RENDERBUFFER, renderbuffer);
gl.renderbuffer_storage(
glow::RENDERBUFFER,
glow::RGB8,
viewport_size.width as i32,
viewport_size.height as i32,
);

let framebuffer = gl.create_framebuffer().ok();
gl.bind_framebuffer(glow::FRAMEBUFFER, framebuffer);
gl.framebuffer_renderbuffer(
glow::FRAMEBUFFER,
glow::COLOR_ATTACHMENT0,
glow::RENDERBUFFER,
renderbuffer,
);

framebuffer
};

// `glow` translates `None` as the reserved 'zero' system-shared framebuffer (so, `None` is the same as `Some(0)`)
#[cfg(not(feature = "offscreen"))]
let framebuffer = None;

Ok((Self { gl, framebuffer }, renderer))
}

fn sample_count(settings: &Settings) -> u32 {
Expand Down Expand Up @@ -71,6 +101,30 @@ impl iced_graphics::window::GLCompositor for Compositor {
gl.clear(glow::COLOR_BUFFER_BIT);
}

renderer.backend_mut().draw(gl, viewport, output, overlay)
renderer.backend_mut().draw(
gl,
viewport,
output,
overlay,
self.framebuffer,
)
}

fn read(&self, region: Rectangle<u32>, buffer: &mut [u8]) {
let gl = &self.gl;

// TODO: Validate the buffer
// assert_eq!(buffer.len(), 3 * region.width as usize * region.height as usize);
unsafe {
gl.read_pixels(
region.x as i32,
region.y as i32,
region.width as i32,
region.height as i32,
glow::RGB,
glow::UNSIGNED_BYTE,
glow::PixelPackData::Slice(buffer),
);
}
}
}
4 changes: 4 additions & 0 deletions glutin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ categories = ["gui"]

[features]
debug = ["iced_winit/debug"]
offscreen = []

[dependencies.glutin]
version = "0.27"
git = "https://github.com/iced-rs/glutin"
rev = "03437d8a1826d83c62017b2bb7bf18bfc9e352cc"

[dependencies.image]
version = "0.23"

[dependencies.iced_native]
version = "0.4"
path = "../native"
Expand Down
49 changes: 46 additions & 3 deletions glutin/src/application.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Create interactive, native cross-platform applications.
use crate::{mouse, Error, Executor, Runtime};

use crate::{mouse, Error, Executor, Runtime, Size};
pub use iced_winit::Application;

use iced_graphics::window;
Expand Down Expand Up @@ -82,9 +81,14 @@ where
}
};

let viewport_size = Size::new(
context.window().inner_size().width,
context.window().inner_size().height,
);

#[allow(unsafe_code)]
let (compositor, renderer) = unsafe {
C::new(compositor_settings, |address| {
C::new(compositor_settings, viewport_size, |address| {
context.get_proc_address(address)
})?
};
Expand Down Expand Up @@ -176,6 +180,15 @@ async fn run_instance<A, E, C>(
let mut events = Vec::new();
let mut messages = Vec::new();

#[cfg(feature = "offscreen")]
let (mut pixels, mut saved) = (
Vec::with_capacity(
3 * state.viewport().physical_width() as usize
* state.viewport().physical_height() as usize,
),
false,
);

debug.startup_finished();

while let Some(event) = receiver.next().await {
Expand Down Expand Up @@ -291,6 +304,36 @@ async fn run_instance<A, E, C>(
&debug.overlay(),
);

#[cfg(feature = "offscreen")]
{
// Really simple "save on first frame"
if !saved {
let region = iced_graphics::Rectangle {
width: state.viewport().physical_width(),
height: state.viewport().physical_height(),
..Default::default()
};

pixels.resize(
3 * region.width as usize * region.height as usize,
0,
);
compositor.read(region, &mut pixels);

let image = image::RgbImage::from_raw(
region.width,
region.height,
pixels.clone(),
)
.unwrap();
image::imageops::flip_vertical(&image)
.save("framebuffer.png")
.unwrap();

saved = true;
}
}

context.swap_buffers().expect("Swap buffers");

debug.render_finished();
Expand Down
6 changes: 5 additions & 1 deletion graphics/src/window/compositor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Color, Error, Viewport};
use crate::{Color, Error, Rectangle, Size, Viewport};
use iced_native::mouse;
use raw_window_handle::HasRawWindowHandle;

Expand All @@ -19,6 +19,7 @@ pub trait Compositor: Sized {
/// Creates a new [`Compositor`].
fn new<W: HasRawWindowHandle>(
settings: Self::Settings,
viewport_size: Size<u32>,
compatible_window: Option<&W>,
) -> Result<(Self, Self::Renderer), Error>;

Expand Down Expand Up @@ -53,4 +54,7 @@ pub trait Compositor: Sized {
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;

/// Reads the framebuffer pixels on the provided region into the provided buffer.
fn read(&self, buffer: &mut [u8]);
}
8 changes: 6 additions & 2 deletions graphics/src/window/gl_compositor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Color, Error, Size, Viewport};
use crate::{Color, Error, Rectangle, Size, Viewport};
use iced_native::mouse;

use core::ffi::c_void;
Expand Down Expand Up @@ -31,14 +31,15 @@ pub trait GLCompositor: Sized {
type Settings: Default;

/// Creates a new [`GLCompositor`] and [`Renderer`] with the given
/// [`Settings`] and an OpenGL address loader function.
/// [`Settings`], viewport size and an OpenGL address loader function.
///
/// [`Renderer`]: crate::Renderer
/// [`Backend`]: crate::Backend
/// [`Settings`]: Self::Settings
#[allow(unsafe_code)]
unsafe fn new(
settings: Self::Settings,
viewport_size: Size<u32>,
loader_function: impl FnMut(&str) -> *const c_void,
) -> Result<(Self, Self::Renderer), Error>;

Expand All @@ -60,4 +61,7 @@ pub trait GLCompositor: Sized {
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;

/// Reads the framebuffer pixels on the provided region into the provided buffer.
fn read(&self, region: Rectangle<u32>, buffer: &mut [u8]);
}
1 change: 1 addition & 0 deletions wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ farbfeld = ["image_rs/farbfeld"]
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
offscreen = []

[dependencies]
wgpu = "0.9"
Expand Down
Loading