makepad_platform/os/
cx_stdin.rs

1#![allow(dead_code)]
2use {
3    std::cell::Cell,
4    crate::{
5        cx::Cx,
6        cursor::MouseCursor,
7        makepad_micro_serde::*,
8        makepad_math::dvec2,
9        window::CxWindowPool,
10        area::Area,
11        event::{
12            KeyEvent,
13            ScrollEvent,
14            MouseDownEvent,
15            MouseUpEvent,
16            MouseMoveEvent,
17        }
18    }
19};
20
21// HACK(eddyb) more or less `<[T; N]>::each_ref`, which is still unstable.
22fn ref_array_to_array_of_refs<T, const N: usize>(ref_array: &[T; N]) -> [&T; N] {
23    let mut out_refs = std::mem::MaybeUninit::<[&T; N]>::uninit();
24    for (i, ref_elem) in ref_array.iter().enumerate() {
25        unsafe { *out_refs.as_mut_ptr().cast::<&T>().add(i) = ref_elem; }
26    }
27    unsafe { out_refs.assume_init() }
28}
29
30pub const SWAPCHAIN_IMAGE_COUNT: usize = match () {
31    // HACK(eddyb) done like this so that we can override each target easily.
32    _ if cfg!(target_os = "linux")   => 3,
33    _ if cfg!(target_os = "macos")   => 1,
34    _ if cfg!(target_os = "windows") => 2,
35    _ => 2,
36};
37
38/// "Swapchains" group together some number (i.e. `SWAPCHAIN_IMAGE_COUNT` here)
39/// of "presentable images", to form a queue of render targets which can be
40/// "presented" (to a surface, like a display, window, etc.) independently of
41/// rendering being done onto *other* "presentable images" in the "swapchain".
42///
43/// Certain configurations of swapchains often have older/more specific names,
44/// e.g. "double buffering" for `SWAPCHAIN_IMAGE_COUNT == 2` (or "triple" etc.).
45#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
46pub struct Swapchain<I>
47    // HACK(eddyb) hint `{Ser,De}{Bin,Json}` derivers to add their own bounds.
48    where I: Sized
49{
50    pub alloc_width: u32,
51    pub alloc_height: u32,
52    pub presentable_images: [PresentableImage<I>; SWAPCHAIN_IMAGE_COUNT],
53}
54
55impl Swapchain<()> {
56    pub fn new(alloc_width: u32, alloc_height: u32) -> Self {
57        let presentable_images = [(); SWAPCHAIN_IMAGE_COUNT].map(|()| PresentableImage {
58            id: PresentableImageId::alloc(),
59            image: (),
60        });
61        Self { alloc_width, alloc_height, presentable_images }
62    }
63}
64
65impl<I> Swapchain<I> {
66    pub fn get_image(&self, id: PresentableImageId) -> Option<&PresentableImage<I>> {
67        self.presentable_images.iter().find(|pi| pi.id == id)
68    }
69    pub fn images_as_ref(&self) -> Swapchain<&I> {
70        let Swapchain { alloc_width, alloc_height, ref presentable_images } = *self;
71        let presentable_images = ref_array_to_array_of_refs(presentable_images)
72            .map(|&PresentableImage { id, ref image }| PresentableImage { id, image });
73        Swapchain { alloc_width, alloc_height, presentable_images }
74    }
75    pub fn images_map<I2>(self, mut f: impl FnMut(PresentableImage<I>) -> I2) -> Swapchain<I2> {
76        let Swapchain { alloc_width, alloc_height, presentable_images } = self;
77        let presentable_images = presentable_images
78            .map(|pi| PresentableImage { id: pi.id, image: f(pi) });
79        Swapchain { alloc_width, alloc_height, presentable_images }
80    }
81}
82
83/// One of the "presentable images" of a [`SharedSwapchain`].
84#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
85pub struct PresentableImage<I>
86    // HACK(eddyb) hint `{Ser,De}{Bin,Json}` derivers to add their own bounds.
87    where I: Sized
88{
89    pub id: PresentableImageId,
90    pub image: I,
91}
92
93/// Cross-process-unique (on best-effort) ID of a [`SharedPresentableImage`],
94/// such that multiple processes on the same system should be able to share
95/// swapchains with each-other and (effectively) never observe collisions.
96#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
97pub struct PresentableImageId {
98    /// PID of the originating process (which allocated this ID).
99    origin_pid: u32,
100
101    /// The atomically-acquired value of a (private) counter, during allocation,
102    /// in the originating process, which will guarantee that the same process
103    /// continuously generating new swapchains will not overlap with itself,
104    /// unless it generates billions of swapchains, mixing old and new ones.
105    per_origin_counter: u32,
106}
107
108impl PresentableImageId {
109    pub fn alloc() -> Self {
110        use std::sync::atomic::{AtomicU32, Ordering};
111
112        static COUNTER: AtomicU32 = AtomicU32::new(0);
113
114        Self {
115            origin_pid: std::process::id(),
116            per_origin_counter: COUNTER.fetch_add(1, Ordering::Relaxed),
117        }
118    }
119
120    pub fn as_u64(self) -> u64 {
121        let Self { origin_pid, per_origin_counter } = self;
122        (u64::from(origin_pid) << 32) | u64::from(per_origin_counter)
123    }
124
125    // NOT public intentionally! (while not too dangerous, this could be misused)
126    fn from_u64(pid_and_counter: u64) -> Self {
127        Self {
128            origin_pid: (pid_and_counter >> 32) as u32,
129            per_origin_counter: pid_and_counter as u32,
130        }
131    }
132}
133
134pub type SharedSwapchain = Swapchain<SharedPresentableImageOsHandle>;
135
136// FIXME(eddyb) move these type aliases into `os::{linux,apple,windows}`.
137
138/// [DMA-BUF](crate::os::linux::dma_buf)-backed image from `eglExportDMABUFImageMESA`.
139#[cfg(target_os = "linux")]
140pub type SharedPresentableImageOsHandle =
141    crate::os::linux::dma_buf::Image<aux_chan::AuxChannedImageFd>;
142
143// HACK(eddyb) the macOS helper XPC service (in `os/apple/metal_xpc.{m,rs}`)
144// doesn't need/want any form of "handle passing", as the `id` field contains
145// all the disambiguating information it may need (however, long-term it'd
146// probably be better to use something like `IOSurface` + mach ports).
147#[cfg(target_os = "macos")]
148#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
149pub struct SharedPresentableImageOsHandle {
150    // HACK(eddyb) non-`()` field working around deriving limitations.
151    pub _dummy_for_macos: Option<u32>,
152}
153
154/// DirectX 11 `HANDLE` from `IDXGIResource::GetSharedHandle`.
155#[cfg(target_os = "windows")]
156// FIXME(eddyb) actually use a newtype of `HANDLE` with manual trait impls.
157pub type SharedPresentableImageOsHandle = u64;
158
159// FIXME(eddyb) use `enum Foo {}` here ideally, when the derives are fixed.
160#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
161#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
162pub struct SharedPresentableImageOsHandle {
163    // HACK(eddyb) non-`()` field working around deriving limitations.
164    pub _dummy_for_unsupported: Option<u32>,
165}
166
167/// Auxiliary communication channel, besides stdin (only on Linux).
168#[cfg(target_os = "linux")]
169pub mod aux_chan {
170    use super::*;
171    use crate::os::linux::ipc::{self as linux_ipc, FixedSizeEncoding};
172    use std::{io, os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd}};
173
174    // HACK(eddyb) `io::Error::other` stabilization is too recent.
175    fn io_error_other(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
176        io::Error::new(io::ErrorKind::Other, error)
177    }
178
179    // Host->Client and Client->Host message types.
180    pub type H2C = (PresentableImageId, OwnedFd);
181    pub type C2H = linux_ipc::Never;
182
183    impl FixedSizeEncoding<{u64::BYTE_LEN}, 0> for PresentableImageId {
184        fn encode(&self) -> ([u8; Self::BYTE_LEN], [std::os::fd::BorrowedFd<'_>; 0]) {
185            let (bytes, []) = self.as_u64().encode();
186            (bytes, [])
187        }
188        fn decode(bytes: [u8; Self::BYTE_LEN], fds: [OwnedFd; 0]) -> Self {
189            Self::from_u64(u64::decode(bytes, fds))
190        }
191    }
192
193    pub type HostEndpoint = linux_ipc::Channel<H2C, C2H>;
194    pub type ClientEndpoint = linux_ipc::Channel<C2H, H2C>;
195    pub fn make_host_and_client_endpoint_pair() -> io::Result<(HostEndpoint, ClientEndpoint)> {
196        linux_ipc::channel()
197    }
198
199    pub type InheritableClientEndpoint = linux_ipc::InheritableChannel<C2H, H2C>;
200    impl InheritableClientEndpoint {
201        pub fn extra_args_for_client_spawning(&self) -> [String; 1] {
202            [format!("--stdin-loop-aux-chan-fd={}", self.as_fd().as_raw_fd())]
203        }
204        pub fn from_process_args_in_client() -> io::Result<Self> {
205            for arg in std::env::args() {
206                if let Some(fd) = arg.strip_prefix("--stdin-loop-aux-chan-fd=") {
207                    let raw_fd = fd.parse().map_err(io_error_other)?;
208                    let owned_fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
209                    return Ok(Self::from(owned_fd));
210                }
211            }
212            Err(io_error_other("missing --stdin-loop-aux-chan-fd argument"))
213        }
214    }
215
216    // HACK(eddyb) this type being serialized/deserialized doesn't really ensure
217    // anything in and of itself, it's only used here to guide correct usage
218    // through types - ideally host<->client (de)serialization itself would
219    // handle all the file descriptors passing necessary, but for now this helps.
220    #[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
221    pub struct AuxChannedImageFd {
222        // HACK(eddyb) non-`()` field working around deriving limitations.
223        _private: Option<u32>,
224    }
225    type PrDmaBufImg<FD> = PresentableImage<crate::os::linux::dma_buf::Image<FD>>;
226    impl PrDmaBufImg<OwnedFd> {
227        pub fn send_fds_to_aux_chan(self, host_endpoint: &HostEndpoint)
228            -> io::Result<PrDmaBufImg<AuxChannedImageFd>>
229        {
230            let Self { id, image } = self;
231            let mut plane_idx = 0;
232            let mut success = Ok(());
233            let image = image.planes_fd_map(|fd| {
234                assert_eq!(plane_idx, 0, "only images with one DMA-BUF plane are supported");
235                plane_idx += 1;
236                if success.is_ok() {
237                    success = host_endpoint.send((self.id, fd));
238                }
239                AuxChannedImageFd { _private: None }
240            });
241            success?;
242            Ok(PresentableImage { id, image })
243        }
244    }
245    impl PrDmaBufImg<AuxChannedImageFd> {
246        pub fn recv_fds_from_aux_chan(self, client_endpoint: &ClientEndpoint)
247            -> io::Result<PrDmaBufImg<OwnedFd>>
248        {
249            let Self { id, image } = self;
250            let mut plane_idx = 0;
251            let mut success = Ok(());
252            let image = image.planes_fd_map(|_| {
253                assert_eq!(plane_idx, 0, "only images with one DMA-BUF plane are supported");
254                plane_idx += 1;
255
256                client_endpoint.recv().and_then(|(recv_id, recv_fd)|
257                if recv_id != id {
258                    Err(io_error_other(format!(
259                        "recv_fds_from_aux_chan: ID mismatch \
260                         (expected {id:?}, got {recv_id:?}",
261                    )))
262                } else {
263                    Ok(recv_fd)
264                }).map_err(|err| if success.is_ok() { success = Err(err); })
265            });
266            success?;
267            Ok(PresentableImage {
268                id,
269                image: image.planes_fd_map(Result::unwrap)
270            })
271        }
272    }
273}
274#[cfg(not(target_os = "linux"))]
275pub mod aux_chan {
276    use std::io;
277
278    #[derive(Clone)]
279    pub struct HostEndpoint { _private: () }
280    pub struct ClientEndpoint { _private: () }
281    pub fn make_host_and_client_endpoint_pair() -> io::Result<(HostEndpoint, ClientEndpoint)> {
282        Ok((HostEndpoint { _private: () }, ClientEndpoint { _private: () }))
283    }
284
285    pub struct InheritableClientEndpoint(ClientEndpoint);
286    impl ClientEndpoint {
287        pub fn into_child_process_inheritable(
288            self,
289        ) -> io::Result<InheritableClientEndpoint> {
290            Ok(InheritableClientEndpoint(self))
291        }
292    }
293    impl InheritableClientEndpoint {
294        pub fn into_uninheritable(self) -> io::Result<ClientEndpoint> {
295            Ok(self.0)
296        }
297        pub fn extra_args_for_client_spawning(&self) -> [String; 0] {
298            []
299        }
300    }
301}
302
303#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
304pub struct StdinMouseDown{
305   pub button: usize,
306   pub x: f64,
307   pub y: f64,
308   pub time: f64,
309}
310
311impl From<StdinMouseDown> for MouseDownEvent {
312    fn from(v: StdinMouseDown) -> Self {
313        Self{
314            abs: dvec2(v.x, v.y),
315            button: v.button,
316            window_id: CxWindowPool::id_zero(),
317            modifiers: Default::default(),
318            time: v.time,
319            handled: Cell::new(Area::Empty),
320        }
321    }
322}
323
324#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
325pub struct StdinMouseMove{
326   pub time: f64,
327   pub x: f64,
328   pub y: f64
329}
330
331impl From<StdinMouseMove> for MouseMoveEvent {
332    fn from(v: StdinMouseMove) -> Self {
333        Self{
334            abs: dvec2(v.x, v.y),
335            window_id: CxWindowPool::id_zero(),
336            modifiers: Default::default(),
337            time: v.time,
338            handled: Cell::new(Area::Empty),
339        }
340    }
341}
342
343#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
344pub struct StdinMouseUp{
345   pub time: f64,
346   pub button: usize,
347   pub x: f64,
348   pub y: f64
349}
350
351impl From<StdinMouseUp> for MouseUpEvent {
352    fn from(v: StdinMouseUp) -> Self {
353        Self{
354            abs: dvec2(v.x, v.y),
355            button: v.button,
356            window_id: CxWindowPool::id_zero(),
357            modifiers: Default::default(),
358            time: v.time,
359        }
360    }
361}
362
363
364#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
365pub struct StdinScroll{
366   pub time: f64,
367   pub sx: f64,
368   pub sy: f64,
369   pub x: f64,
370   pub y: f64,
371   pub is_mouse: bool,
372}
373
374impl From<StdinScroll> for ScrollEvent {
375    fn from(v: StdinScroll) -> Self {
376        Self{
377            abs: dvec2(v.x, v.y),
378            scroll: dvec2(v.sx, v.sy),
379            window_id: CxWindowPool::id_zero(),
380            modifiers: Default::default(),
381            handled_x: Cell::new(false),
382            handled_y: Cell::new(false),
383            is_mouse: v.is_mouse,
384            time: v.time,
385        }
386    }
387}
388
389#[derive(Clone, Debug, SerBin, DeBin, SerJson, DeJson)]
390pub enum HostToStdin{
391    Swapchain(SharedSwapchain),
392    WindowGeomChange {
393        dpi_factor: f64,
394        // HACK(eddyb) `DVec` (like `WindowGeom`'s `inner_size` field) can't
395        // be used here due to it not implementing (de)serialization traits.
396        inner_width: f64,
397        inner_height: f64,
398    },
399    Tick{
400        buffer_id: u64,
401        frame: u64,
402        time: f64,
403    },
404    MouseDown(StdinMouseDown),
405    MouseUp(StdinMouseUp),
406    MouseMove(StdinMouseMove),
407    KeyDown(KeyEvent),
408    KeyUp(KeyEvent),
409    Scroll(StdinScroll),
410    ReloadFile{
411        file:String,
412        contents:String
413    },
414}
415
416/// After a successful client-side draw, all the host needs to know, so it can
417/// present the result, is the swapchain image used, and the sub-area within
418/// that image that was being used to draw the entire client window (with the
419/// whole allocated area rarely used, except just before needing a new swapchain).
420#[derive(Copy, Clone, Debug, SerBin, DeBin, SerJson, DeJson)]
421pub struct PresentableDraw {
422    pub target_id: PresentableImageId,
423    pub width: u32,
424    pub height: u32,
425}
426
427#[derive(Clone, Debug, SerBin, DeBin, SerJson, DeJson)]
428pub enum StdinToHost {
429    ReadyToStart,
430    SetCursor(MouseCursor),
431    // the client is done drawing, and the texture is completely updated
432    DrawCompleteAndFlip(PresentableDraw)
433}
434
435impl StdinToHost{
436    pub fn to_json(&self)->String{
437        let mut json = self.serialize_json();
438        json.push('\n');
439        json
440    }
441}
442
443impl HostToStdin{
444    pub fn to_json(&self)->String{
445        let mut json = self.serialize_json();
446        json.push('\n');
447        json
448    }
449}
450
451impl Cx {
452    
453}