feat: EGL/GLES 3 renderer for zoom
This commit is contained in:
1 parent
69399a45bb
commit
c1b8e5cabe
6 files changed
+383
-1
No files matched your search
Generated
+90
@@ -262,6 +262,15 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dlib"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
|
||||||
|
dependencies = [
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "downcast-rs"
|
name = "downcast-rs"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -363,6 +372,18 @@ dependencies = [
|
|||||||
"r-efi",
|
"r-efi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glow"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "263218b0ba2b0715c3370fb04674f5f8aa331594b521c94ff0a8e4a8472d1386"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"slotmap",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -460,6 +481,16 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "khronos-egl"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -472,6 +503,16 @@ version = "0.2.189"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -722,6 +763,12 @@ version = "1.0.23"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.229"
|
version = "1.0.229"
|
||||||
@@ -811,6 +858,15 @@ version = "0.4.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slotmap"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.16.2"
|
version = "1.16.2"
|
||||||
@@ -1052,8 +1108,11 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"glow",
|
||||||
"image",
|
"image",
|
||||||
|
"khronos-egl",
|
||||||
"libc",
|
"libc",
|
||||||
|
"libloading",
|
||||||
"niri-ipc",
|
"niri-ipc",
|
||||||
"rustix",
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1064,12 +1123,20 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
|
"wayland-egl",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-protocols-wlr",
|
"wayland-protocols-wlr",
|
||||||
"wl-clipboard-rs",
|
"wl-clipboard-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.129"
|
version = "0.2.129"
|
||||||
@@ -1124,6 +1191,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"rustix",
|
"rustix",
|
||||||
|
"scoped-tls",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
]
|
]
|
||||||
@@ -1162,6 +1230,16 @@ dependencies = [
|
|||||||
"xcursor",
|
"xcursor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-egl"
|
||||||
|
version = "0.32.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b97bdb7c49e5bd9b7562f38ff84f0dad47079fdc9e926f691a787f6dbc05451"
|
||||||
|
dependencies = [
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-protocols"
|
name = "wayland-protocols"
|
||||||
version = "0.32.13"
|
version = "0.32.13"
|
||||||
@@ -1230,9 +1308,21 @@ version = "0.31.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dlib",
|
||||||
|
"log",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.62.2"
|
version = "0.62.2"
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ license = "MIT"
|
|||||||
anyhow = "1.0.104"
|
anyhow = "1.0.104"
|
||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
clap = { version = "4.6.7", features = ["derive"] }
|
clap = { version = "4.6.7", features = ["derive"] }
|
||||||
|
glow = "0.18.0"
|
||||||
image = { version = "0.25.10", default-features = false, features = ["png"] }
|
image = { version = "0.25.10", default-features = false, features = ["png"] }
|
||||||
|
khronos-egl = { version = "6.0.0", features = ["dynamic"] }
|
||||||
libc = "0.2.189"
|
libc = "0.2.189"
|
||||||
|
libloading = "0.8"
|
||||||
niri-ipc = "=26.4.0"
|
niri-ipc = "=26.4.0"
|
||||||
rustix = { version = "1.1.5", features = ["event", "fs", "pipe", "process", "stdio"] }
|
rustix = { version = "1.1.5", features = ["event", "fs", "pipe", "process", "stdio"] }
|
||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
@@ -20,7 +23,9 @@ smithay-client-toolkit = "0.21.1"
|
|||||||
toml = "1.1.6"
|
toml = "1.1.6"
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||||
|
wayland-backend = { version = "0.3.17", features = ["client_system"] }
|
||||||
wayland-client = "0.31.15"
|
wayland-client = "0.31.15"
|
||||||
|
wayland-egl = "0.32.11"
|
||||||
wayland-protocols = { version = "0.32.13", features = ["client", "staging"] }
|
wayland-protocols = { version = "0.32.13", features = ["client", "staging"] }
|
||||||
wayland-protocols-wlr = { version = "0.3.12", features = ["client"] }
|
wayland-protocols-wlr = { version = "0.3.12", features = ["client"] }
|
||||||
wl-clipboard-rs = "0.9.4"
|
wl-clipboard-rs = "0.9.4"
|
||||||
|
|||||||
+4
-1
@@ -34,8 +34,11 @@
|
|||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
|
||||||
# Clicking a preview opens Satty. --suffix keeps a user's own Satty first.
|
# Clicking a preview opens Satty. --suffix keeps a user's own Satty first.
|
||||||
|
# Zoom loads libEGL.so.1 (libglvnd); Mesa's drivers come from the system.
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
wrapProgram $out/bin/valw --suffix PATH : ${lib.makeBinPath [ pkgs.satty ]}
|
wrapProgram $out/bin/valw \
|
||||||
|
--suffix PATH : ${lib.makeBinPath [ pkgs.satty ]} \
|
||||||
|
--suffix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ pkgs.libglvnd ]}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
|
|||||||
@@ -142,6 +142,20 @@ pub fn check_save_dir(dir: &Path) -> Check {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// EGL with an OpenGL ES 3 config; only zoom needs it.
|
||||||
|
pub fn check_egl(result: Result<String>) -> Check {
|
||||||
|
match result {
|
||||||
|
Ok(vendor) => check(Level::Ok, format!("EGL ({vendor}) with OpenGL ES 3 (zoom)")),
|
||||||
|
Err(e) => check(
|
||||||
|
Level::Warn,
|
||||||
|
format!(
|
||||||
|
"no OpenGL ES 3 through EGL: {} (zoom won't work)",
|
||||||
|
one_line(&e)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs every check and prints one line each. Fails if any check failed.
|
/// Runs every check and prints one line each. Fails if any check failed.
|
||||||
pub fn run() -> Result<()> {
|
pub fn run() -> Result<()> {
|
||||||
let mut checks = Vec::new();
|
let mut checks = Vec::new();
|
||||||
@@ -158,6 +172,7 @@ pub fn run() -> Result<()> {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
checks.push(check_egl(crate::zoom::gl::probe(&wl.conn)));
|
||||||
}
|
}
|
||||||
Err(e) => checks.push(check(Level::Fail, format!("wayland: {}", one_line(&e)))),
|
Err(e) => checks.push(check(Level::Fail, format!("wayland: {}", one_line(&e)))),
|
||||||
}
|
}
|
||||||
@@ -182,6 +197,16 @@ pub fn run() -> Result<()> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn egl_check_levels() {
|
||||||
|
let ok = check_egl(Ok("Mesa Project".into()));
|
||||||
|
assert_eq!(ok.level, Level::Ok);
|
||||||
|
assert!(ok.text.contains("Mesa Project"), "{}", ok.text);
|
||||||
|
let missing = check_egl(Err(anyhow::anyhow!("could not load libEGL.so.1")));
|
||||||
|
assert_eq!(missing.level, Level::Warn, "only zoom needs it");
|
||||||
|
assert!(missing.text.contains("zoom"), "{}", missing.text);
|
||||||
|
}
|
||||||
|
|
||||||
fn globals(list: &[(&str, u32)]) -> Vec<(String, u32)> {
|
fn globals(list: &[(&str, u32)]) -> Vec<(String, u32)> {
|
||||||
list.iter().map(|&(n, v)| (n.to_string(), v)).collect()
|
list.iter().map(|&(n, v)| (n.to_string(), v)).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
//! OpenGL ES 3 through EGL on a Wayland surface: just enough to show the
|
||||||
|
//! frozen frame zoomed in.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use glow::HasContext;
|
||||||
|
use khronos_egl as egl;
|
||||||
|
use wayland_client::{Connection, Proxy, protocol::wl_surface::WlSurface};
|
||||||
|
|
||||||
|
use super::view::View;
|
||||||
|
use crate::frame::Bgrx;
|
||||||
|
|
||||||
|
type Egl = egl::DynamicInstance<egl::EGL1_5>;
|
||||||
|
|
||||||
|
/// `EGL_PLATFORM_WAYLAND_KHR`.
|
||||||
|
const PLATFORM_WAYLAND: egl::Enum = 0x31D8;
|
||||||
|
|
||||||
|
const VERTEX: &str = "#version 300 es
|
||||||
|
// One triangle that covers the screen.
|
||||||
|
void main() {
|
||||||
|
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
||||||
|
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
const FRAGMENT: &str = "#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
uniform sampler2D frame;
|
||||||
|
uniform float scale;
|
||||||
|
// The frame pixel at the screen's top-left.
|
||||||
|
uniform vec2 origin;
|
||||||
|
// Centre and radius in screen px; radius 0 means off.
|
||||||
|
uniform vec3 flashlight;
|
||||||
|
out vec4 color;
|
||||||
|
void main() {
|
||||||
|
ivec2 size = textureSize(frame, 0);
|
||||||
|
// gl_FragCoord starts at the bottom-left; the frame at the top-left.
|
||||||
|
vec2 screen = vec2(gl_FragCoord.x, float(size.y) - gl_FragCoord.y);
|
||||||
|
ivec2 texel = clamp(ivec2(floor(origin + screen / scale)), ivec2(0), size - 1);
|
||||||
|
// The texture holds the frame's B, G, R, X bytes.
|
||||||
|
vec3 rgb = texelFetch(frame, texel, 0).bgr;
|
||||||
|
if (flashlight.z > 0.0) {
|
||||||
|
float d = distance(screen, flashlight.xy);
|
||||||
|
rgb *= mix(1.0, 0.25, smoothstep(flashlight.z - 1.5, flashlight.z + 1.5, d));
|
||||||
|
}
|
||||||
|
color = vec4(rgb, 1.0);
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
/// Loads libEGL and opens the compositor's EGL display.
|
||||||
|
fn open(conn: &Connection) -> Result<(Egl, egl::Display)> {
|
||||||
|
let lib =
|
||||||
|
unsafe { libloading::Library::new("libEGL.so.1") }.context("could not load libEGL.so.1")?;
|
||||||
|
let egl = unsafe { Egl::load_required_from(lib) }
|
||||||
|
.map_err(|e| anyhow!("libEGL.so.1 has no EGL 1.5: {e}"))?;
|
||||||
|
let display = unsafe {
|
||||||
|
egl.get_platform_display(
|
||||||
|
PLATFORM_WAYLAND,
|
||||||
|
conn.backend().display_ptr().cast(),
|
||||||
|
&[egl::ATTRIB_NONE],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.context("no EGL display for the Wayland connection")?;
|
||||||
|
egl.initialize(display)
|
||||||
|
.context("could not initialise EGL")?;
|
||||||
|
Ok((egl, display))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config(egl: &Egl, display: egl::Display) -> Result<egl::Config> {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let attributes = [
|
||||||
|
egl::SURFACE_TYPE, egl::WINDOW_BIT,
|
||||||
|
egl::RENDERABLE_TYPE, egl::OPENGL_ES3_BIT,
|
||||||
|
egl::RED_SIZE, 8, egl::GREEN_SIZE, 8, egl::BLUE_SIZE, 8,
|
||||||
|
egl::NONE,
|
||||||
|
];
|
||||||
|
egl.choose_first_config(display, &attributes)
|
||||||
|
.context("eglChooseConfig failed")?
|
||||||
|
.context("no EGL config for OpenGL ES 3 windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The EGL vendor, if a window config for OpenGL ES 3 exists.
|
||||||
|
pub fn probe(conn: &Connection) -> Result<String> {
|
||||||
|
let (egl, display) = open(conn)?;
|
||||||
|
let result = config(&egl, display).map(|_| {
|
||||||
|
egl.query_string(Some(display), egl::VENDOR)
|
||||||
|
.map(|v| v.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|_| "unknown vendor".into())
|
||||||
|
});
|
||||||
|
let _ = egl.terminate(display);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws the frozen frame on one surface.
|
||||||
|
pub struct Renderer {
|
||||||
|
gl: glow::Context,
|
||||||
|
scale: glow::UniformLocation,
|
||||||
|
origin: glow::UniformLocation,
|
||||||
|
flashlight: glow::UniformLocation,
|
||||||
|
egl: Egl,
|
||||||
|
display: egl::Display,
|
||||||
|
context: egl::Context,
|
||||||
|
surface: egl::Surface,
|
||||||
|
// Destroyed after the EGL surface that draws into it.
|
||||||
|
_window: wayland_egl::WlEglSurface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
/// A GL surface of `size` buffer px on `surface`, showing `frame`.
|
||||||
|
pub fn new(
|
||||||
|
conn: &Connection,
|
||||||
|
surface: &WlSurface,
|
||||||
|
size: (u32, u32),
|
||||||
|
frame: &Bgrx,
|
||||||
|
) -> Result<Renderer> {
|
||||||
|
let (egl, display) = open(conn)?;
|
||||||
|
egl.bind_api(egl::OPENGL_ES_API)
|
||||||
|
.context("no OpenGL ES in EGL")?;
|
||||||
|
let config = config(&egl, display)?;
|
||||||
|
let context = egl
|
||||||
|
.create_context(
|
||||||
|
display,
|
||||||
|
config,
|
||||||
|
None,
|
||||||
|
&[egl::CONTEXT_MAJOR_VERSION, 3, egl::NONE],
|
||||||
|
)
|
||||||
|
.context("could not create an OpenGL ES 3 context")?;
|
||||||
|
let window = wayland_egl::WlEglSurface::new(surface.id(), size.0 as i32, size.1 as i32)
|
||||||
|
.context("could not create a wl_egl_window")?;
|
||||||
|
let egl_surface = unsafe {
|
||||||
|
egl.create_window_surface(display, config, window.ptr() as egl::NativeWindowType, None)
|
||||||
|
}
|
||||||
|
.context("could not create the EGL window surface")?;
|
||||||
|
egl.make_current(display, Some(egl_surface), Some(egl_surface), Some(context))
|
||||||
|
.context("eglMakeCurrent failed")?;
|
||||||
|
// valw paces drawing with frame callbacks; swaps must not block.
|
||||||
|
egl.swap_interval(display, 0)
|
||||||
|
.context("eglSwapInterval failed")?;
|
||||||
|
|
||||||
|
let gl = unsafe {
|
||||||
|
glow::Context::from_loader_function(|name| {
|
||||||
|
egl.get_proc_address(name)
|
||||||
|
.map_or(std::ptr::null(), |f| f as *const _)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let (scale, origin, flashlight) = unsafe { setup(&gl, size, frame)? };
|
||||||
|
Ok(Renderer {
|
||||||
|
gl,
|
||||||
|
scale,
|
||||||
|
origin,
|
||||||
|
flashlight,
|
||||||
|
egl,
|
||||||
|
display,
|
||||||
|
context,
|
||||||
|
surface: egl_surface,
|
||||||
|
_window: window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws `view` and swaps (which commits the surface).
|
||||||
|
pub fn draw(&self, view: &View, flashlight: Option<(f64, f64, f64)>) -> Result<()> {
|
||||||
|
let (x, y, r) = flashlight.unwrap_or_default();
|
||||||
|
unsafe {
|
||||||
|
self.gl.uniform_1_f32(Some(&self.scale), view.scale as f32);
|
||||||
|
self.gl.uniform_2_f32(
|
||||||
|
Some(&self.origin),
|
||||||
|
view.origin.0 as f32,
|
||||||
|
view.origin.1 as f32,
|
||||||
|
);
|
||||||
|
self.gl
|
||||||
|
.uniform_3_f32(Some(&self.flashlight), x as f32, y as f32, r as f32);
|
||||||
|
self.gl.draw_arrays(glow::TRIANGLES, 0, 3);
|
||||||
|
}
|
||||||
|
self.egl
|
||||||
|
.swap_buffers(self.display, self.surface)
|
||||||
|
.context("eglSwapBuffers failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Renderer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.egl.make_current(self.display, None, None, None);
|
||||||
|
let _ = self.egl.destroy_surface(self.display, self.surface);
|
||||||
|
let _ = self.egl.destroy_context(self.display, self.context);
|
||||||
|
let _ = self.egl.terminate(self.display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compiles the program, uploads the frame and returns the uniforms.
|
||||||
|
unsafe fn setup(
|
||||||
|
gl: &glow::Context,
|
||||||
|
size: (u32, u32),
|
||||||
|
frame: &Bgrx,
|
||||||
|
) -> Result<(
|
||||||
|
glow::UniformLocation,
|
||||||
|
glow::UniformLocation,
|
||||||
|
glow::UniformLocation,
|
||||||
|
)> {
|
||||||
|
unsafe {
|
||||||
|
tracing::info!("zoom renderer: {}", gl.get_parameter_string(glow::RENDERER));
|
||||||
|
let program = gl.create_program().map_err(|e| anyhow!(e))?;
|
||||||
|
for (kind, source) in [
|
||||||
|
(glow::VERTEX_SHADER, VERTEX),
|
||||||
|
(glow::FRAGMENT_SHADER, FRAGMENT),
|
||||||
|
] {
|
||||||
|
let shader = gl.create_shader(kind).map_err(|e| anyhow!(e))?;
|
||||||
|
gl.shader_source(shader, source);
|
||||||
|
gl.compile_shader(shader);
|
||||||
|
if !gl.get_shader_compile_status(shader) {
|
||||||
|
anyhow::bail!("shader: {}", gl.get_shader_info_log(shader));
|
||||||
|
}
|
||||||
|
gl.attach_shader(program, shader);
|
||||||
|
}
|
||||||
|
gl.link_program(program);
|
||||||
|
if !gl.get_program_link_status(program) {
|
||||||
|
anyhow::bail!("program: {}", gl.get_program_info_log(program));
|
||||||
|
}
|
||||||
|
gl.use_program(Some(program));
|
||||||
|
// GLES 3 needs a bound vertex array even without attributes.
|
||||||
|
let vao = gl.create_vertex_array().map_err(|e| anyhow!(e))?;
|
||||||
|
gl.bind_vertex_array(Some(vao));
|
||||||
|
|
||||||
|
let texture = gl.create_texture().map_err(|e| anyhow!(e))?;
|
||||||
|
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
||||||
|
gl.tex_parameter_i32(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
glow::TEXTURE_MIN_FILTER,
|
||||||
|
glow::NEAREST as i32,
|
||||||
|
);
|
||||||
|
gl.tex_parameter_i32(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
glow::TEXTURE_MAG_FILTER,
|
||||||
|
glow::NEAREST as i32,
|
||||||
|
);
|
||||||
|
gl.tex_image_2d(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
glow::RGBA8 as i32,
|
||||||
|
frame.width() as i32,
|
||||||
|
frame.height() as i32,
|
||||||
|
0,
|
||||||
|
glow::RGBA,
|
||||||
|
glow::UNSIGNED_BYTE,
|
||||||
|
glow::PixelUnpackData::Slice(Some(frame.as_raw())),
|
||||||
|
);
|
||||||
|
gl.viewport(0, 0, size.0 as i32, size.1 as i32);
|
||||||
|
|
||||||
|
let uniform = |name: &str| {
|
||||||
|
gl.get_uniform_location(program, name)
|
||||||
|
.with_context(|| format!("no uniform {name}"))
|
||||||
|
};
|
||||||
|
gl.uniform_1_i32(uniform("frame").ok().as_ref(), 0);
|
||||||
|
Ok((
|
||||||
|
uniform("scale")?,
|
||||||
|
uniform("origin")?,
|
||||||
|
uniform("flashlight")?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
//! Zoom mode: the frozen focused output, zoomed and panned on the GPU.
|
//! Zoom mode: the frozen focused output, zoomed and panned on the GPU.
|
||||||
|
|
||||||
|
pub mod gl;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
Reference in new issue
Block a user