summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-10-06 21:31:19 -0400
committerLLLL Colonq <llll@colonq>2026-10-06 21:31:19 -0400
commitb71e024a8ecd93a5ff1db38db68010af9775c679 (patch)
tree40f28724902e0deed7b29eda53e8a5319a816720
parent0bfb852f7a2784938f1b9724bc46d98bb5257f86 (diff)
Add texture sharing
-rw-r--r--Cargo.lock8
-rw-r--r--crates/teleia/Cargo.toml6
-rw-r--r--crates/teleia/src/lib.rs1
-rw-r--r--crates/teleia/src/net.rs3
-rw-r--r--crates/teleia/src/net/texture.rs154
-rw-r--r--crates/teleia/src/net/texture/ffi/egl.rs54
-rw-r--r--crates/teleia/src/net/texture/ffi/glfw.rs7
-rw-r--r--crates/teleia/src/net/texture/ffi/mod.rs2
-rw-r--r--crates/teleia/src/texture.rs3
-rw-r--r--flake.nix7
10 files changed, 233 insertions, 12 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0af34c1..0e37038 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -910,7 +910,7 @@ dependencies = [
[[package]]
name = "glfw"
version = "0.59.0"
-source = "git+https://github.com/lcolonq/glfw-rs#1ce13a2bcf3ef3bf6184bcb4c2a90d9034956841"
+source = "git+https://github.com/lcolonq/glfw-rs#e7904991ea1ef6d34501d9d31c2d6f27b3ca957e"
dependencies = [
"bitflags 1.3.2",
"objc2 0.5.2",
@@ -1177,9 +1177,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.184"
+version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
+checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]]
name = "libloading"
@@ -2376,7 +2376,6 @@ dependencies = [
"bincode",
"bitflags 2.6.0",
"byteorder",
- "bytes",
"console_error_panic_hook",
"console_log",
"directories",
@@ -2390,6 +2389,7 @@ dependencies = [
"image",
"js-sys",
"kira",
+ "libc",
"log",
"nalgebra",
"parry3d",
diff --git a/crates/teleia/Cargo.toml b/crates/teleia/Cargo.toml
index 26c08a9..1236b8c 100644
--- a/crates/teleia/Cargo.toml
+++ b/crates/teleia/Cargo.toml
@@ -14,7 +14,6 @@ tobj = "=4.0.1" # loader for .obj meshes loader
ahash = "=0.8.10" # dependency of tobj
gltf = { git = "https://github.com/lcolonq/gltf", features = ["extras", "import", "names", "utils"] } # loader for .gltf scenes
image = { version = "0.25.10", default-features = false, features = ["jpeg", "png"] } # texture loader
-# cosmic-text = { version = "*", default-features = false, features = ["std", "swash"] } # advanced text rendering
glam = {version = "0.29.2", features = ["serde"]} # linear algebra
log = "0.4.21" # logging
rand = { version = "=0.8.5", features = ["small_rng"] } # rng
@@ -23,8 +22,6 @@ serde_json = "1.0.128" # serialize JSON
bincode = { version = "2.0.1", features = ["serde"] } # binary serialization
enum-map = "2.7.3" # fast maps with enums as keys
bimap = "0.6.3" # bijective maps
-# reqwest = "*" # http requests
-bytes = "*" # bytes for http responses
bitflags = { version = "2.6.0", features = ["serde"] } # bitwise flags
simple-eyre = { version = "0.3.1", default-features = false } # error reporting and formatting
rapier3d = "0.23.1" # rigid-body physics
@@ -51,4 +48,5 @@ glfw = { git = "https://github.com/lcolonq/glfw-rs", features = ["serde"] } # wi
kira = { version = "=0.9.6", default-features = false, features = ["cpal", "ogg", "mp3", "wav"] } # audio
directories = { git = "https://github.com/lcolonq/directories-rs" } # standard system directories
polling = "3.11.0" # interface to epoll
-tungstenite = { version = "0.24.0", features = ["native-tls"] } # websockets \ No newline at end of file
+tungstenite = { version = "0.24.0", features = ["native-tls"] } # websockets
+libc = "0.2.190" # ffi \ No newline at end of file
diff --git a/crates/teleia/src/lib.rs b/crates/teleia/src/lib.rs
index 498085f..1426737 100644
--- a/crates/teleia/src/lib.rs
+++ b/crates/teleia/src/lib.rs
@@ -1,4 +1,5 @@
#![feature(try_blocks)]
+#![feature(unix_socket_ancillary_data)]
pub mod utils;
pub mod ui;
diff --git a/crates/teleia/src/net.rs b/crates/teleia/src/net.rs
index 0fee34d..d6c28db 100644
--- a/crates/teleia/src/net.rs
+++ b/crates/teleia/src/net.rs
@@ -2,3 +2,6 @@ pub mod ws;
#[cfg(not(target_arch = "wasm32"))]
pub mod fig;
+
+#[cfg(not(target_arch = "wasm32"))]
+pub mod texture;
diff --git a/crates/teleia/src/net/texture.rs b/crates/teleia/src/net/texture.rs
new file mode 100644
index 0000000..86d6988
--- /dev/null
+++ b/crates/teleia/src/net/texture.rs
@@ -0,0 +1,154 @@
+mod ffi;
+
+use byteorder::{LE, ReadBytesExt, WriteBytesExt};
+use simple_eyre::eyre::Context;
+
+use std::io::{IoSlice, IoSliceMut};
+// use std::io::Write;
+// use std::os::fd::FromRawFd;
+use std::os::unix::net::{AncillaryData, SocketAncillary};
+use std::sync::mpsc::{Receiver, channel};
+use std::{os::unix::net::UnixDatagram, thread::{spawn, JoinHandle}};
+
+// use glow::HasContext;
+use ffi::egl;
+use ffi::glfw;
+
+use crate::{context, texture, utils};
+
+#[derive(Debug)]
+struct ReceivedTexture {
+ fd: i32,
+ width: i32, height: i32,
+ fourcc: u32, offset: i32, stride: i32,
+}
+
+#[allow(dead_code)]
+pub struct Server {
+ pub tex: texture::Texture,
+ thread: JoinHandle<()>,
+ receiver: Receiver<ReceivedTexture>,
+}
+impl Server {
+ pub fn new(ctx: &context::Context, path: &str) -> Self {
+ let _ = std::fs::remove_file(path);
+ let sock = UnixDatagram::bind(path).expect("failed to bind texture server socket");
+
+ let (sender, receiver) = channel::<ReceivedTexture>();
+ let thread = spawn(move || {
+ let mut buf = [0; 1024];
+ let mut ancillary_buf = [0; 128];
+ let mut ancillary = SocketAncillary::new(&mut ancillary_buf);
+ loop {
+ match sock.recv_vectored_with_ancillary(&mut [IoSliceMut::new(&mut buf)], &mut ancillary) {
+ Ok(_) => {
+ let res: utils::Erm<()> = try {
+ let mut cur = std::io::Cursor::new(&buf);
+ let width = cur.read_i32::<LE>().wrap_err("failed to read width")?;
+ let height = cur.read_i32::<LE>().wrap_err("failed to read height")?;
+ let fourcc = cur.read_u32::<LE>().wrap_err("failed to read fourcc")?;
+ let offset = cur.read_i32::<LE>().wrap_err("failed to read offset")?;
+ let stride = cur.read_i32::<LE>().wrap_err("failed to read stride")?;
+ for ar in ancillary.messages() {
+ match ar {
+ Ok(AncillaryData::ScmRights(mut sr)) => {
+ if let Some(fd) = sr.next() {
+ let rt = ReceivedTexture {
+ fd, width, height,
+ fourcc, offset, stride
+ };
+ if let Err(e) = sender.send(rt) {
+ log::warn!("error on channel: {:?}", e);
+ break;
+ }
+ }
+ },
+ Ok(_) => log::info!("message is not SCM_RIGHTS"),
+ Err(e) => log::warn!("failed to read ancillary message: {:?}", e),
+ }
+ };
+ };
+ if let Err(e) = res {
+ log::warn!("error when receiving texture: {}", e);
+ }
+ },
+ Err(e) => log::warn!("texture server failed to receive: {}", e),
+ }
+ }
+ });
+ Self {
+ tex: texture::Texture::new_empty(ctx),
+ thread,
+ receiver,
+ }
+ }
+ pub fn update(&self, ctx: &context::Context) {
+ if let Ok(rt) = self.receiver.try_recv() {
+ log::info!("received file descriptor: {:?}", rt);
+ unsafe {
+ let window = ctx.window.borrow_mut().0.ptr;
+ let d = glfw::glfwGetEGLDisplay();
+ let c = glfw::glfwGetEGLContext(window);
+ log::info!("d: {:?}, c: {:?}", d, c);
+ let img = egl::eglCreateImage(d, std::ptr::null_mut(), egl::EGL_LINUX_DMA_BUF_EXT, std::ptr::null_mut(), [
+ egl::EGL_WIDTH as egl::EGLAttrib, rt.width as _,
+ egl::EGL_HEIGHT as _, rt.height as _,
+ egl::EGL_LINUX_DRM_FOURCC_EXT as _, rt.fourcc as _,
+ egl::EGL_DMA_BUF_PLANE0_FD_EXT as _, rt.fd as _,
+ egl::EGL_DMA_BUF_PLANE0_OFFSET_EXT as _, rt.offset as _,
+ egl::EGL_DMA_BUF_PLANE0_PITCH_EXT as _, rt.stride as _,
+ egl::EGL_NONE as _,
+ ].as_ptr());
+ let _ = libc::close(rt.fd);
+ log::info!("error here: {}", egl::eglGetError());
+ self.tex.bind(ctx);
+ egl::glEGLImageTargetTexture2DOES(glow::TEXTURE_2D, img);
+ log::info!("set up texture: {}", egl::eglGetError());
+ }
+ }
+ }
+}
+
+pub fn share_texture(ctx: &context::Context, path: &str, tex: &texture::Texture) -> utils::Erm<()> {
+ unsafe {
+ #[allow(non_snake_case)]
+ let eglExportDMABUFImageQueryMESA =
+ std::mem::transmute::<egl::MustCastToFunction, egl::PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC>(
+ egl::eglGetProcAddress(c"eglExportDMABUFImageQueryMESA".as_ptr())
+ );
+ #[allow(non_snake_case)]
+ let eglExportDMABUFImageMESA =
+ std::mem::transmute::<egl::MustCastToFunction, egl::PFNEGLEXPORTDMABUFIMAGEMESAPROC>(
+ egl::eglGetProcAddress(c"eglExportDMABUFImageMESA".as_ptr())
+ );
+ let window = ctx.window.borrow_mut().0.ptr;
+ let d = glfw::glfwGetEGLDisplay();
+ let c = glfw::glfwGetEGLContext(window);
+ let img = egl::eglCreateImage(d, c, egl::EGL_GL_TEXTURE_2D,
+ std::ptr::with_exposed_provenance_mut(tex.tex.0.get() as usize),
+ std::ptr::null_mut()
+ );
+ let mut imgfd: std::ffi::c_int = 0;
+ let mut fourcc: std::ffi::c_int = 0;
+ let mut offset: egl::EGLint = 0;
+ let mut stride: egl::EGLint = 0;
+ eglExportDMABUFImageQueryMESA(d, img, &mut fourcc as *mut _, std::ptr::null_mut(), 0);
+ eglExportDMABUFImageMESA(d, img,
+ &mut imgfd as *mut _, &mut stride as *mut _, &mut offset as *mut _
+ );
+ log::info!("fd: {imgfd}, fourcc: {fourcc}, offset: {offset}, stride: {stride}");
+ let mut buf: Vec<u8> = Vec::new();
+ buf.write_i32::<LE>(tex.width)?;
+ buf.write_i32::<LE>(tex.height)?;
+ buf.write_u32::<LE>(fourcc as u32)?;
+ buf.write_i32::<LE>(offset)?;
+ buf.write_i32::<LE>(stride)?;
+ let mut ancillary_buf = [0; 128];
+ let mut ancillary = SocketAncillary::new(&mut ancillary_buf[..]);
+ ancillary.add_fds(&[imgfd]);
+ let sock = UnixDatagram::unbound().expect("failed to create unix socket");
+ sock.send_vectored_with_ancillary_to(&[IoSlice::new(&buf)], &mut ancillary, path)?;
+ let _ = libc::close(imgfd);
+ Ok(())
+ }
+}
diff --git a/crates/teleia/src/net/texture/ffi/egl.rs b/crates/teleia/src/net/texture/ffi/egl.rs
new file mode 100644
index 0000000..32c5356
--- /dev/null
+++ b/crates/teleia/src/net/texture/ffi/egl.rs
@@ -0,0 +1,54 @@
+// from <EGL/egl.h>
+pub const EGL_NONE: EGLenum = 0x3038;
+pub const EGL_WIDTH: EGLenum = 0x3057;
+pub const EGL_HEIGHT: EGLenum = 0x3056;
+pub const EGL_GL_TEXTURE_2D: EGLenum = 0x30B1;
+
+// from <EGL/eglext.h>
+pub const EGL_LINUX_DMA_BUF_EXT: EGLenum = 0x3270;
+pub const EGL_LINUX_DRM_FOURCC_EXT: EGLenum = 0x3271;
+pub const EGL_DMA_BUF_PLANE0_FD_EXT: EGLenum = 0x3272;
+pub const EGL_DMA_BUF_PLANE0_OFFSET_EXT: EGLenum = 0x3273;
+pub const EGL_DMA_BUF_PLANE0_PITCH_EXT: EGLenum = 0x3274;
+
+#[repr(C)]
+pub struct EGLDisplayTarget(());
+pub type EGLDisplay = *mut EGLDisplayTarget;
+
+#[repr(C)]
+pub struct EGLContextTarget(());
+pub type EGLContext = *mut EGLContextTarget;
+
+#[repr(C)]
+pub struct EGLClientBufferTarget(());
+pub type EGLClientBuffer = *mut EGLClientBufferTarget;
+
+#[repr(C)]
+pub struct EGLImageTarget(());
+pub type EGLImage = *mut EGLImageTarget;
+
+#[repr(C)]
+pub struct MustCastToFunctionTarget(());
+pub type MustCastToFunction = *mut MustCastToFunctionTarget;
+
+pub type EGLenum = core::ffi::c_uint;
+pub type EGLBoolean = core::ffi::c_uint;
+
+pub type EGLint = core::ffi::c_int;
+
+pub type EGLAttrib = libc::intptr_t;
+
+pub type PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC = fn(EGLDisplay, EGLImage, *mut core::ffi::c_int, *mut core::ffi::c_int, u64) -> EGLBoolean;
+pub type PFNEGLEXPORTDMABUFIMAGEMESAPROC = fn(EGLDisplay, EGLImage, *mut core::ffi::c_int, *mut EGLint, *mut EGLint) -> EGLBoolean;
+
+#[link(name = "EGL")]
+unsafe extern "C" {
+ pub fn eglCreateImage(d: EGLDisplay, c: EGLContext, t: EGLenum, buf: EGLClientBuffer, attribs: *const EGLAttrib) -> EGLImage;
+ pub fn eglGetError() -> EGLint;
+ pub fn eglGetProcAddress(nm: *const core::ffi::c_char) -> MustCastToFunction;
+}
+
+#[link(name = "GL")]
+unsafe extern "C" {
+ pub fn glEGLImageTargetTexture2DOES(t: u32, img: EGLImage);
+}
diff --git a/crates/teleia/src/net/texture/ffi/glfw.rs b/crates/teleia/src/net/texture/ffi/glfw.rs
new file mode 100644
index 0000000..eed135f
--- /dev/null
+++ b/crates/teleia/src/net/texture/ffi/glfw.rs
@@ -0,0 +1,7 @@
+use super::egl::*;
+
+#[link(name = "glfw3")]
+unsafe extern "C" {
+ pub fn glfwGetEGLDisplay() -> EGLDisplay;
+ pub fn glfwGetEGLContext(w: *mut glfw::ffi::GLFWwindow) -> EGLContext;
+}
diff --git a/crates/teleia/src/net/texture/ffi/mod.rs b/crates/teleia/src/net/texture/ffi/mod.rs
new file mode 100644
index 0000000..a98fd71
--- /dev/null
+++ b/crates/teleia/src/net/texture/ffi/mod.rs
@@ -0,0 +1,2 @@
+pub mod egl;
+pub mod glfw;
diff --git a/crates/teleia/src/texture.rs b/crates/teleia/src/texture.rs
index 71a39d8..95cb914 100644
--- a/crates/teleia/src/texture.rs
+++ b/crates/teleia/src/texture.rs
@@ -49,7 +49,6 @@ impl Texture {
glow::UNSIGNED_BYTE,
Some(pixels),
);
- ctx.gl.generate_mipmap(glow::TEXTURE_2D);
Self {
tex,
width: rgba.width() as i32,
@@ -85,7 +84,6 @@ impl Texture {
glow::UNSIGNED_BYTE,
Some(data),
);
- ctx.gl.generate_mipmap(glow::TEXTURE_2D);
}
self.width = width;
self.height = height;
@@ -100,6 +98,7 @@ impl Texture {
pub fn set_anisotropic_filtering(&self, ctx: &context::Context) {
unsafe {
ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex));
+ ctx.gl.generate_mipmap(glow::TEXTURE_2D);
ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32);
ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32);
ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR_MIPMAP_LINEAR as i32);
diff --git a/flake.nix b/flake.nix
index 67474e2..34e0527 100644
--- a/flake.nix
+++ b/flake.nix
@@ -200,19 +200,22 @@
});
};
- shell = craneLib.devShell {
+ baseShell = craneLib.devShell {
packages = [
pkgs.trunk
pkgs.rust-analyzer
pkgs.cargo-flamegraph
pkgs.cmake
pkgs.mold
- ] ++ native.nativeBuildInputs ++ native.buildInputs;
+ ];
LIBRARY_PATH = "$LIBRARY_PATH:${pkgs.lib.makeLibraryPath native.buildInputs}";
RUSTFLAGS="-L ${glfw}/lib";
LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath native.buildInputs}";
inherit LIBCOLONQ_PIT_NATIVE LIBCOLONQ_PIT_WASM;
};
+ shell = baseShell.overrideAttrs (final: prev: {
+ buildInputs = prev.buildInputs ++ native.nativeBuildInputs ++ native.buildInputs;
+ });
in {
inherit shell native wasm windows;
devShells.${system} = {