First commit. Version 0.1.0
This commit is contained in:
253
src/draw/gles.rs
Normal file
253
src/draw/gles.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
//! GLES (EGL + FBO) draw paths.
|
||||
//!
|
||||
//! The GPU counterparts to [`super::software::draw_surface_full`] and
|
||||
//! [`super::software::draw_surface_partial`]. Same DrawCtx flow; the
|
||||
//! differences are:
|
||||
//!
|
||||
//! * Canvas is `Canvas::new_gles` backed by a persistent FBO rather
|
||||
//! than a CPU pixmap → FBO pixels survive across frames naturally,
|
||||
//! so the partial path only needs a scissor.
|
||||
//! * Presentation goes through `egl_ctx.swap_buffers_with_damage`
|
||||
//! which attaches + damages + commits atomically. The partial path
|
||||
//! translates the top-left dirty rects into EGL's bottom-left
|
||||
//! convention before passing them in.
|
||||
//! * No `wl_shm` pool; no `pick_shm_format`; no byte-order swap.
|
||||
//!
|
||||
//! The Y-flip for the swap-with-damage call lives here (only the GPU
|
||||
//! path needs it — SHM buffers are already top-down).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use smithay_client_toolkit::compositor::CompositorState;
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
use crate::egl_context::EglContext;
|
||||
use crate::event_loop::SurfaceState;
|
||||
use crate::render::Canvas;
|
||||
use crate::types::{ Color, Rect };
|
||||
use crate::widget::Element;
|
||||
|
||||
use super::{ DrawCtx, layout_and_draw };
|
||||
use super::chrome::{ apply_input_region, draw_fallback_banner, draw_titlebar };
|
||||
|
||||
/// GPU full-redraw path. Mirrors [`super::software::draw_surface_full`]
|
||||
/// but writes into the EGL surface's persistent FBO instead of an SHM
|
||||
/// buffer:
|
||||
///
|
||||
/// 1. `eglMakeCurrent` so subsequent GL calls hit this surface.
|
||||
/// 2. (Lazily) build the [`Canvas::Gles`], or resize its FBO if the
|
||||
/// surface dimensions changed.
|
||||
/// 3. Clear, run layout+draw, blit the FBO to the default framebuffer.
|
||||
/// 4. Set buffer_scale + input_region (state attached to the implicit
|
||||
/// commit done by `eglSwapBuffers`).
|
||||
/// 5. `eglSwapBuffers` — performs the wl attach/damage/commit atomically.
|
||||
pub( crate ) fn draw_surface_full_gpu<Msg: Clone>(
|
||||
ss: &mut SurfaceState<Msg>,
|
||||
compositor: &CompositorState,
|
||||
egl_ctx: &Arc<EglContext>,
|
||||
view: &Element<Msg>,
|
||||
bg: Color,
|
||||
input_region: Option<&[Rect]>,
|
||||
debug_layout: bool,
|
||||
pw: u32,
|
||||
ph: u32,
|
||||
scale: u32,
|
||||
request_frame: &dyn Fn( &WlSurface ),
|
||||
)
|
||||
{
|
||||
let Some( es ) = ss.egl_surface.as_ref() else { return };
|
||||
if egl_ctx.make_current( es ).is_err() { return; }
|
||||
|
||||
let canvas = ss.canvas.get_or_insert_with( ||
|
||||
{
|
||||
let mut c = Canvas::new_gles(
|
||||
Arc::clone( egl_ctx.gl() ), egl_ctx.version, pw, ph,
|
||||
);
|
||||
c.set_dpi_scale( scale as f32 );
|
||||
if let Some( reg ) = crate::theme::build_font_registry()
|
||||
{
|
||||
c.set_font_registry( Arc::new( reg ) );
|
||||
}
|
||||
c
|
||||
} );
|
||||
if canvas.size() != ( pw, ph )
|
||||
{
|
||||
canvas.resize( pw, ph );
|
||||
canvas.set_dpi_scale( scale as f32 );
|
||||
}
|
||||
canvas.clear_clip();
|
||||
|
||||
let sf = scale as f32;
|
||||
let tb_h = ss.titlebar_height * sf;
|
||||
let screen_rect = Rect { x: 0.0, y: tb_h, width: pw as f32, height: ( ph as f32 - tb_h ).max( 0.0 ) };
|
||||
|
||||
if bg.a > 0.0 { canvas.fill( bg ); } else { canvas.clear(); }
|
||||
|
||||
ss.titlebar_close_rect = draw_titlebar( canvas, &ss.titlebar_title, pw, tb_h, sf );
|
||||
|
||||
let mut ctx: DrawCtx<Msg> = DrawCtx
|
||||
{
|
||||
focused_idx: ss.focused_idx,
|
||||
hovered_idx: ss.hovered_idx,
|
||||
pressed_idx: ss.gesture.pressed_idx,
|
||||
cursor_state: std::mem::take( &mut ss.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut ss.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout,
|
||||
scroll_offsets: std::mem::take( &mut ss.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut ss.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut ss.scroll_navigable_items ),
|
||||
previous_widget_rects: ss.widget_rects.clone(),
|
||||
};
|
||||
layout_and_draw::<Msg>( view, canvas, screen_rect, &mut ctx, 0 );
|
||||
|
||||
if ctx.debug_layout
|
||||
{
|
||||
for w in &ctx.widget_rects
|
||||
{
|
||||
canvas.stroke_rect( w.rect, Color::rgb( 1.0, 0.0, 0.0 ), 1.5, 0.0 );
|
||||
}
|
||||
}
|
||||
|
||||
if let Some( ref menu ) = ss.context_menu
|
||||
{
|
||||
super::draw_context_menu( canvas, menu );
|
||||
}
|
||||
|
||||
// Fallback-theme warning. Painted last so it sits on top of every
|
||||
// widget; no-op when the real theme is loaded.
|
||||
draw_fallback_banner( canvas, pw, sf );
|
||||
|
||||
canvas.present();
|
||||
|
||||
ss.prev_focused = ss.focused_idx;
|
||||
ss.prev_hovered = ss.hovered_idx;
|
||||
ss.prev_pressed = ss.gesture.pressed_idx;
|
||||
|
||||
ss.widget_rects = ctx.widget_rects;
|
||||
ss.scroll_rects = ctx.scroll_rects;
|
||||
ss.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
ss.cursor_state = ctx.cursor_state;
|
||||
ss.selection_anchor = ctx.selection_anchor;
|
||||
ss.scroll_offsets = ctx.scroll_offsets;
|
||||
ss.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
ss.content_dirty = false;
|
||||
|
||||
let wl_surface = ss.surface.wl_surface();
|
||||
apply_input_region( wl_surface, compositor, input_region );
|
||||
// Frame callback request must precede the implicit commit done by
|
||||
// `swap_buffers_with_damage`, so the compositor can attach it to this
|
||||
// frame and not the previous one.
|
||||
request_frame( wl_surface );
|
||||
// Full-surface damage. (0,0,w,h) is identical in EGL bottom-left and
|
||||
// top-left coords, so no Y-flip needed.
|
||||
let _ = egl_ctx.swap_buffers_with_damage( es, &[ ( 0, 0, pw as i32, ph as i32 ) ] );
|
||||
ss.frame_pending = true;
|
||||
}
|
||||
|
||||
/// GPU partial-redraw path. Same flow as
|
||||
/// [`super::software::draw_surface_partial`] (clip mask → repaint
|
||||
/// background + widgets → present), but the clip is implemented with
|
||||
/// `glScissor` (bbox union) inside the canvas, and presentation goes
|
||||
/// through `eglSwapBuffers`. The FBO is preserved between frames so
|
||||
/// the unclipped pixels naturally carry over.
|
||||
pub( crate ) fn draw_surface_partial_gpu<Msg: Clone>(
|
||||
ss: &mut SurfaceState<Msg>,
|
||||
compositor: &CompositorState,
|
||||
egl_ctx: &Arc<EglContext>,
|
||||
view: &Element<Msg>,
|
||||
bg: Color,
|
||||
input_region: Option<&[Rect]>,
|
||||
dirty_rects: Vec<Rect>,
|
||||
pw: u32,
|
||||
ph: u32,
|
||||
scale: u32,
|
||||
request_frame: &dyn Fn( &WlSurface ),
|
||||
)
|
||||
{
|
||||
let Some( es ) = ss.egl_surface.as_ref() else { return };
|
||||
if egl_ctx.make_current( es ).is_err() { return; }
|
||||
|
||||
let canvas = ss.canvas.as_mut().expect( "partial path requires existing canvas" );
|
||||
if canvas.size() != ( pw, ph )
|
||||
{
|
||||
canvas.resize( pw, ph );
|
||||
canvas.set_dpi_scale( scale as f32 );
|
||||
}
|
||||
canvas.set_clip_rects( &dirty_rects );
|
||||
|
||||
let sf = scale as f32;
|
||||
let tb_h = ss.titlebar_height * sf;
|
||||
let screen_rect = Rect { x: 0.0, y: tb_h, width: pw as f32, height: ( ph as f32 - tb_h ).max( 0.0 ) };
|
||||
|
||||
if bg.a > 0.0 { canvas.fill( bg ); }
|
||||
else
|
||||
{
|
||||
canvas.clear_rects_transparent( &dirty_rects );
|
||||
}
|
||||
|
||||
ss.titlebar_close_rect = draw_titlebar( canvas, &ss.titlebar_title, pw, tb_h, sf );
|
||||
|
||||
let mut ctx: DrawCtx<Msg> = DrawCtx
|
||||
{
|
||||
focused_idx: ss.focused_idx,
|
||||
hovered_idx: ss.hovered_idx,
|
||||
pressed_idx: ss.gesture.pressed_idx,
|
||||
cursor_state: std::mem::take( &mut ss.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut ss.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout: false,
|
||||
scroll_offsets: std::mem::take( &mut ss.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut ss.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut ss.scroll_navigable_items ),
|
||||
previous_widget_rects: ss.widget_rects.clone(),
|
||||
};
|
||||
layout_and_draw::<Msg>( view, canvas, screen_rect, &mut ctx, 0 );
|
||||
|
||||
if let Some( ref menu ) = ss.context_menu
|
||||
{
|
||||
super::draw_context_menu( canvas, menu );
|
||||
}
|
||||
|
||||
// Fallback-theme warning. Painted last so it sits on top of every
|
||||
// widget; no-op when the real theme is loaded.
|
||||
draw_fallback_banner( canvas, pw, sf );
|
||||
|
||||
canvas.clear_clip();
|
||||
canvas.present();
|
||||
|
||||
ss.prev_focused = ss.focused_idx;
|
||||
ss.prev_hovered = ss.hovered_idx;
|
||||
ss.prev_pressed = ss.gesture.pressed_idx;
|
||||
|
||||
ss.widget_rects = ctx.widget_rects;
|
||||
ss.scroll_rects = ctx.scroll_rects;
|
||||
ss.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
ss.cursor_state = ctx.cursor_state;
|
||||
ss.selection_anchor = ctx.selection_anchor;
|
||||
ss.scroll_offsets = ctx.scroll_offsets;
|
||||
ss.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
ss.content_dirty = false;
|
||||
|
||||
let wl_surface = ss.surface.wl_surface();
|
||||
apply_input_region( wl_surface, compositor, input_region );
|
||||
request_frame( wl_surface );
|
||||
// Convert top-left dirty rects to EGL bottom-left coords for the
|
||||
// swap-with-damage call.
|
||||
let damage: Vec<( i32, i32, i32, i32 )> = dirty_rects.iter().map( |r|
|
||||
{
|
||||
let x = r.x.floor() as i32;
|
||||
let w = r.width.ceil() as i32;
|
||||
let h = r.height.ceil() as i32;
|
||||
let y_top = r.y.floor() as i32;
|
||||
let y_bottom = ph as i32 - y_top - h;
|
||||
( x, y_bottom.max( 0 ), w.max( 0 ), h.max( 0 ) )
|
||||
} ).collect();
|
||||
let _ = egl_ctx.swap_buffers_with_damage( es, &damage );
|
||||
ss.frame_pending = true;
|
||||
}
|
||||
Reference in New Issue
Block a user