embedded_graphics_gop/
lib.rs

1// Copyright (c) 2025 nytpu <alex [at] nytpu.com>
2// SPDX-License-Identifier: MPL-2.0
3// For more license details, see LICENSE or <https://www.mozilla.org/en-US/MPL/2.0/>.
4
5//! embedded-graphics `DrawTarget`s for the UEFI Graphics Output Protocol (GOP).
6//!
7//! Supports using the UEFI block transfer (BLT) routines or utilizing direct framebuffer access,
8//! and supports the common framebuffer pixel formats.  Naturally is `no_std`, and also supports
9//! no-`alloc` (with reduced functionality).
10//!
11//! BLT routines require `alloc` and require UEFI boot services; while the framebuffer routines
12//! don't require `alloc` in single-buffered mode, and (in practice) will still function even after
13//! exiting UEFI boot services.
14//!
15//! The BLT target allows on-the-fly switching between single- and double-buffered modes with one
16//! type while the framebuffer targets don't, because the BLT implementation always requires an
17//! allocated backbuffer and always needs to do damage tracking; it's just a matter of choosing
18//! when to transfer it to the primary framebuffer.  However, the framebuffer implementations can
19//! write directly into the framebuffer and as such don't need an allocated buffer, and they also
20//! don't normally need to perform damage tracking which significantly reduces per-pixel draw
21//! overhead.
22//!
23//! # See Also
24//!
25//! - [`uefi::proto::console::gop`]
26//! - [embedded-graphics](https://crates.io/crates/embedded-graphics)
27//! - [UEFI Specification § 12.9](https://uefi.org/specs/UEFI/2.11/12_Protocols_Console_Support.html#graphics-output-protocol)
28//!
29//! # Crate Features
30//!
31//! - `alloc` (**default**) — adds dependency on the `alloc` crate.  Enables `BltDrawTarget` and
32//!   `fb::FbDbDrawTarget`.
33//! - `nightly` — only used to enable the
34//!   [`doc_cfg` feature](https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html)
35//!   when documenting with a nightly toolchain.
36//!
37//! # Prior Art
38//!
39//! [uefi-graphics](https://crates.io/crates/uefi-graphics),
40//! [uefi-graphics-driver](https://crates.io/crates/uefi-graphics-driver), and
41//! [uefi-graphics2](https://crates.io/crates/uefi-graphics2) already exist and provide somewhat
42//! similar functionality.
43//!
44//! However, they all exclusively support the framebuffer and not BLT routines, and generally seem
45//! implemented with minimal regard for the actual content of the embedded-graphics documentation
46//! and the UEFI specification.  For instance, none of those check if the framebuffer's pixel
47//! format actually matches what they implicitly expect (and as such exclusively support
48//! `PixelFormat::Bgr` framebuffers and will otherwise silently write garbage to the framebuffer);
49//! and uefi-graphics returns an error when the pixel is out of bounds in `DrawTarget::draw_iter`,
50//! which one is specifically not supposed to do (the pixels *should* be silently ignored).
51//!
52//! They also all feel fairly hastily implemented: e.g. if double-buffering is supported, performing
53//! zero damage tracking and copying the entire framebuffer every commit; or all but uefi-graphics2
54//! wantonly convert integer types without any previously-performed range/bounds checks nor explicit
55//! checked conversions which could lead to out-of-bounds `ptr::write`s; or uefi-graphics2 doesn't
56//! respect the horizontal stride and will write garbage to the framebuffer; etc.
57
58#![cfg_attr(feature = "nightly", feature(doc_cfg))]
59#![no_std]
60
61#[cfg(feature = "alloc")]
62extern crate alloc;
63
64#[cfg(feature = "alloc")]
65mod blt;
66#[cfg(feature = "alloc")]
67pub use blt::BltDrawTarget;
68
69pub mod fb;
70
71#[cfg(feature = "alloc")]
72use embedded_graphics_core::primitives::Rectangle;
73
74/// Compute the union/bounding rectangle containing both the existing `current_damage` rectangle
75/// and the given new rectangle.
76#[cfg(feature = "alloc")]
77fn update_damage(current_damage: Rectangle, new_damage: Rectangle) -> Rectangle {
78	if current_damage.is_zero_sized() {
79		new_damage
80	} else if new_damage.is_zero_sized() {
81		current_damage
82	} else {
83		let current_bottom_right = current_damage
84			.bottom_right()
85			.expect("infallible, confirmed to be non-zero-sized");
86		let new_bottom_right = new_damage
87			.bottom_right()
88			.expect("infallible, confirmed to be non-zero-sized");
89		Rectangle::with_corners(
90			current_damage.top_left.component_min(new_damage.top_left),
91			current_bottom_right.component_max(new_bottom_right),
92		)
93	}
94}