1#![allow(clippy::uninlined_format_args)]
7
8use crate::error::{InitError, SwResultExt};
9use crate::{Rect, SoftBufferError};
10use raw_window_handle::{
11 HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle,
12 XcbWindowHandle,
13};
14use rustix::{
15 fd::{AsFd, BorrowedFd, OwnedFd},
16 mm, shm as posix_shm,
17};
18
19use std::{
20 collections::HashSet,
21 fmt,
22 fs::File,
23 io, mem,
24 num::{NonZeroU16, NonZeroU32},
25 ptr::{null_mut, NonNull},
26 rc::Rc,
27 slice,
28};
29
30use as_raw_xcb_connection::AsRawXcbConnection;
31use x11rb::connection::{Connection, SequenceNumber};
32use x11rb::cookie::Cookie;
33use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};
34use x11rb::protocol::shm::{self, ConnectionExt as _};
35use x11rb::protocol::xproto::{self, ConnectionExt as _, ImageOrder, VisualClass, Visualid};
36use x11rb::xcb_ffi::XCBConnection;
37
38pub struct X11DisplayImpl<D: ?Sized> {
39 connection: Option<XCBConnection>,
41
42 is_shm_available: bool,
44
45 supported_visuals: HashSet<Visualid>,
47
48 _display: D,
54}
55
56impl<D: HasDisplayHandle + ?Sized> X11DisplayImpl<D> {
57 pub(crate) fn new(display: D) -> Result<Self, InitError<D>>
59 where
60 D: Sized,
61 {
62 let raw = display.display_handle()?.as_raw();
64 let xcb_handle = match raw {
65 RawDisplayHandle::Xcb(xcb_handle) => xcb_handle,
66 RawDisplayHandle::Xlib(xlib) => {
67 let connection = xlib.display.map(|display| {
69 unsafe {
72 let display = tiny_xlib::Display::from_ptr(display.as_ptr());
73 NonNull::new_unchecked(display.as_raw_xcb_connection()).cast()
74 }
75 });
76
77 XcbDisplayHandle::new(connection, xlib.screen)
79 }
80 _ => return Err(InitError::Unsupported(display)),
81 };
82
83 let connection = match xcb_handle.connection {
85 Some(connection) => {
86 let result =
89 unsafe { XCBConnection::from_raw_xcb_connection(connection.as_ptr(), false) };
90
91 result.swbuf_err("Failed to wrap XCB connection")?
92 }
93 None => {
94 log::info!("no XCB connection provided by the user, so spawning our own");
96 XCBConnection::connect(None)
97 .swbuf_err("Failed to spawn XCB connection")?
98 .0
99 }
100 };
101
102 let is_shm_available = is_shm_available(&connection);
103 if !is_shm_available {
104 log::warn!("SHM extension is not available. Performance may be poor.");
105 }
106
107 let supported_visuals = supported_visuals(&connection);
108
109 Ok(Self {
110 connection: Some(connection),
111 is_shm_available,
112 supported_visuals,
113 _display: display,
114 })
115 }
116}
117
118impl<D: ?Sized> X11DisplayImpl<D> {
119 fn connection(&self) -> &XCBConnection {
120 self.connection
121 .as_ref()
122 .expect("X11DisplayImpl::connection() called after X11DisplayImpl::drop()")
123 }
124}
125
126pub struct X11Impl<D: ?Sized, W: ?Sized> {
128 display: Rc<X11DisplayImpl<D>>,
130
131 window: xproto::Window,
133
134 gc: xproto::Gcontext,
136
137 depth: u8,
139
140 visual_id: u32,
142
143 buffer: Buffer,
145
146 buffer_presented: bool,
148
149 size: Option<(NonZeroU16, NonZeroU16)>,
151
152 window_handle: W,
154}
155
156enum Buffer {
158 Shm(ShmBuffer),
160
161 Wire(Vec<u32>),
163}
164
165struct ShmBuffer {
166 seg: Option<(ShmSegment, shm::Seg)>,
168
169 done_processing: Option<SequenceNumber>,
182}
183
184impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> X11Impl<D, W> {
185 pub(crate) fn new(window_src: W, display: Rc<X11DisplayImpl<D>>) -> Result<Self, InitError<W>> {
187 let raw = window_src.window_handle()?.as_raw();
189 let window_handle = match raw {
190 RawWindowHandle::Xcb(xcb) => xcb,
191 RawWindowHandle::Xlib(xlib) => {
192 let window = match NonZeroU32::new(xlib.window as u32) {
193 Some(window) => window,
194 None => return Err(SoftBufferError::IncompleteWindowHandle.into()),
195 };
196 let mut xcb_window_handle = XcbWindowHandle::new(window);
197 xcb_window_handle.visual_id = NonZeroU32::new(xlib.visual_id as u32);
198 xcb_window_handle
199 }
200 _ => {
201 return Err(InitError::Unsupported(window_src));
202 }
203 };
204
205 log::trace!("new: window_handle={:X}", window_handle.window);
206 let window = window_handle.window.get();
207
208 let display2 = display.clone();
210 let tokens = {
211 let geometry_token = display2
212 .connection()
213 .get_geometry(window)
214 .swbuf_err("Failed to send geometry request")?;
215 let window_attrs_token = if window_handle.visual_id.is_none() {
216 Some(
217 display2
218 .connection()
219 .get_window_attributes(window)
220 .swbuf_err("Failed to send window attributes request")?,
221 )
222 } else {
223 None
224 };
225
226 (geometry_token, window_attrs_token)
227 };
228
229 let gc = display
231 .connection()
232 .generate_id()
233 .swbuf_err("Failed to generate GC ID")?;
234 display
235 .connection()
236 .create_gc(
237 gc,
238 window,
239 &xproto::CreateGCAux::new().graphics_exposures(0),
240 )
241 .swbuf_err("Failed to send GC creation request")?
242 .check()
243 .swbuf_err("Failed to create GC")?;
244
245 let (geometry_reply, visual_id) = {
247 let (geometry_token, window_attrs_token) = tokens;
248 let geometry_reply = geometry_token
249 .reply()
250 .swbuf_err("Failed to get geometry reply")?;
251 let visual_id = match window_attrs_token {
252 None => window_handle.visual_id.unwrap().get(),
253 Some(window_attrs) => {
254 window_attrs
255 .reply()
256 .swbuf_err("Failed to get window attributes reply")?
257 .visual
258 }
259 };
260
261 (geometry_reply, visual_id)
262 };
263
264 if !display.supported_visuals.contains(&visual_id) {
265 return Err(SoftBufferError::PlatformError(
266 Some(format!(
267 "Visual 0x{visual_id:x} does not use softbuffer's pixel format and is unsupported"
268 )),
269 None,
270 )
271 .into());
272 }
273
274 let buffer = if display.is_shm_available {
276 Buffer::Shm(ShmBuffer {
278 seg: None,
279 done_processing: None,
280 })
281 } else {
282 Buffer::Wire(Vec::new())
284 };
285
286 Ok(Self {
287 display,
288 window,
289 gc,
290 depth: geometry_reply.depth,
291 visual_id,
292 buffer,
293 buffer_presented: false,
294 size: None,
295 window_handle: window_src,
296 })
297 }
298
299 #[inline]
301 pub fn window(&self) -> &W {
302 &self.window_handle
303 }
304
305 pub(crate) fn resize(
307 &mut self,
308 width: NonZeroU32,
309 height: NonZeroU32,
310 ) -> Result<(), SoftBufferError> {
311 log::trace!(
312 "resize: window={:X}, size={}x{}",
313 self.window,
314 width,
315 height
316 );
317
318 let width: NonZeroU16 = width
320 .try_into()
321 .or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
322 let height: NonZeroU16 = height.try_into().or(Err(SoftBufferError::SizeOutOfRange {
323 width: width.into(),
324 height,
325 }))?;
326
327 if self.size != Some((width, height)) {
328 self.buffer_presented = false;
329 self.buffer
330 .resize(self.display.connection(), width.get(), height.get())
331 .swbuf_err("Failed to resize X11 buffer")?;
332
333 self.size = Some((width, height));
335 }
336
337 Ok(())
338 }
339
340 pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {
342 log::trace!("buffer_mut: window={:X}", self.window);
343
344 self.buffer.finish_wait(self.display.connection())?;
346
347 Ok(BufferImpl(self))
349 }
350
351 pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
353 log::trace!("fetch: window={:X}", self.window);
354
355 let (width, height) = self
356 .size
357 .expect("Must set size of surface before calling `fetch()`");
358
359 let reply = self
361 .display
362 .connection()
363 .get_image(
364 xproto::ImageFormat::Z_PIXMAP,
365 self.window,
366 0,
367 0,
368 width.get(),
369 height.get(),
370 u32::MAX,
371 )
372 .swbuf_err("Failed to send image fetching request")?
373 .reply()
374 .swbuf_err("Failed to fetch image from window")?;
375
376 if reply.depth == self.depth && reply.visual == self.visual_id {
377 let mut out = vec![0u32; reply.data.len() / 4];
378 bytemuck::cast_slice_mut::<u32, u8>(&mut out).copy_from_slice(&reply.data);
379 Ok(out)
380 } else {
381 Err(SoftBufferError::PlatformError(
382 Some("Mismatch between reply and window data".into()),
383 None,
384 ))
385 }
386 }
387}
388
389pub struct BufferImpl<'a, D: ?Sized, W: ?Sized>(&'a mut X11Impl<D, W>);
390
391impl<'a, D: HasDisplayHandle + ?Sized, W: HasWindowHandle + ?Sized> BufferImpl<'a, D, W> {
392 #[inline]
393 pub fn pixels(&self) -> &[u32] {
394 unsafe { self.0.buffer.buffer() }
396 }
397
398 #[inline]
399 pub fn pixels_mut(&mut self) -> &mut [u32] {
400 unsafe { self.0.buffer.buffer_mut() }
402 }
403
404 pub fn age(&self) -> u8 {
405 if self.0.buffer_presented {
406 1
407 } else {
408 0
409 }
410 }
411
412 pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
414 let imp = self.0;
415
416 let (surface_width, surface_height) = imp
417 .size
418 .expect("Must set size of surface before calling `present_with_damage()`");
419
420 log::trace!("present: window={:X}", imp.window);
421
422 match imp.buffer {
423 Buffer::Wire(ref wire) => {
424 log::debug!("Falling back to non-SHM method for window drawing.");
426
427 imp.display
428 .connection()
429 .put_image(
430 xproto::ImageFormat::Z_PIXMAP,
431 imp.window,
432 imp.gc,
433 surface_width.get(),
434 surface_height.get(),
435 0,
436 0,
437 0,
438 imp.depth,
439 bytemuck::cast_slice(wire),
440 )
441 .map(|c| c.ignore_error())
442 .push_err()
443 .swbuf_err("Failed to draw image to window")?;
444 }
445
446 Buffer::Shm(ref mut shm) => {
447 if let Some((_, segment_id)) = shm.seg {
451 damage
452 .iter()
453 .try_for_each(|rect| {
454 let (src_x, src_y, dst_x, dst_y, width, height) = (|| {
455 Some((
456 u16::try_from(rect.x).ok()?,
457 u16::try_from(rect.y).ok()?,
458 i16::try_from(rect.x).ok()?,
459 i16::try_from(rect.y).ok()?,
460 u16::try_from(rect.width.get()).ok()?,
461 u16::try_from(rect.height.get()).ok()?,
462 ))
463 })(
464 )
465 .ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;
466 imp.display
467 .connection()
468 .shm_put_image(
469 imp.window,
470 imp.gc,
471 surface_width.get(),
472 surface_height.get(),
473 src_x,
474 src_y,
475 width,
476 height,
477 dst_x,
478 dst_y,
479 imp.depth,
480 xproto::ImageFormat::Z_PIXMAP.into(),
481 false,
482 segment_id,
483 0,
484 )
485 .push_err()
486 .map(|c| c.ignore_error())
487 .swbuf_err("Failed to draw image to window")
488 })
489 .and_then(|()| {
490 shm.begin_wait(imp.display.connection())
492 .swbuf_err("Failed to draw image to window")
493 })?;
494 }
495 }
496 }
497
498 imp.buffer_presented = true;
499
500 Ok(())
501 }
502
503 pub fn present(self) -> Result<(), SoftBufferError> {
504 let (width, height) = self
505 .0
506 .size
507 .expect("Must set size of surface before calling `present()`");
508 self.present_with_damage(&[Rect {
509 x: 0,
510 y: 0,
511 width: width.into(),
512 height: height.into(),
513 }])
514 }
515}
516
517impl Buffer {
518 fn resize(
520 &mut self,
521 conn: &impl Connection,
522 width: u16,
523 height: u16,
524 ) -> Result<(), PushBufferError> {
525 match self {
526 Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),
527 Buffer::Wire(wire) => {
528 wire.resize(total_len(width, height) / 4, 0);
529 Ok(())
530 }
531 }
532 }
533
534 fn finish_wait(&mut self, conn: &impl Connection) -> Result<(), SoftBufferError> {
536 if let Buffer::Shm(ref mut shm) = self {
537 shm.finish_wait(conn)
538 .swbuf_err("Failed to wait for X11 buffer")?;
539 }
540
541 Ok(())
542 }
543
544 #[inline]
550 unsafe fn buffer(&self) -> &[u32] {
551 match self {
552 Buffer::Shm(ref shm) => unsafe { shm.as_ref() },
553 Buffer::Wire(wire) => wire,
554 }
555 }
556
557 #[inline]
563 unsafe fn buffer_mut(&mut self) -> &mut [u32] {
564 match self {
565 Buffer::Shm(ref mut shm) => unsafe { shm.as_mut() },
566 Buffer::Wire(wire) => wire,
567 }
568 }
569}
570
571impl ShmBuffer {
572 fn alloc_segment(
574 &mut self,
575 conn: &impl Connection,
576 buffer_size: usize,
577 ) -> Result<(), PushBufferError> {
578 let size = buffer_size.next_power_of_two();
580
581 let needs_realloc = match self.seg {
583 Some((ref seg, _)) => seg.size() < size,
584 None => true,
585 };
586
587 if needs_realloc {
589 let new_seg = ShmSegment::new(size, buffer_size)?;
590 self.associate(conn, new_seg)?;
591 } else if let Some((ref mut seg, _)) = self.seg {
592 seg.set_buffer_size(buffer_size);
593 }
594
595 Ok(())
596 }
597
598 #[inline]
604 unsafe fn as_ref(&self) -> &[u32] {
605 match self.seg.as_ref() {
606 Some((seg, _)) => {
607 let buffer_size = seg.buffer_size();
608
609 bytemuck::cast_slice(unsafe { &seg.as_ref()[..buffer_size] })
611 }
612 None => {
613 &[]
615 }
616 }
617 }
618
619 #[inline]
625 unsafe fn as_mut(&mut self) -> &mut [u32] {
626 match self.seg.as_mut() {
627 Some((seg, _)) => {
628 let buffer_size = seg.buffer_size();
629
630 bytemuck::cast_slice_mut(unsafe { &mut seg.as_mut()[..buffer_size] })
632 }
633 None => {
634 &mut []
636 }
637 }
638 }
639
640 fn associate(
642 &mut self,
643 conn: &impl Connection,
644 seg: ShmSegment,
645 ) -> Result<(), PushBufferError> {
646 let new_id = conn.generate_id()?;
648 conn.shm_attach_fd(new_id, seg.as_fd().try_clone_to_owned().unwrap(), true)?
649 .ignore_error();
650
651 if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {
653 self.finish_wait(conn)?;
655
656 conn.shm_detach(old_id)?.ignore_error();
657
658 drop(old_seg);
660 }
661
662 Ok(())
663 }
664
665 fn begin_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {
667 let cookie = c.get_input_focus()?.sequence_number();
668 let old_cookie = self.done_processing.replace(cookie);
669 debug_assert!(old_cookie.is_none());
670 Ok(())
671 }
672
673 fn finish_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {
675 if let Some(done_processing) = self.done_processing.take() {
676 let cookie = Cookie::<_, xproto::GetInputFocusReply>::new(c, done_processing);
678 cookie.reply()?;
679 }
680
681 Ok(())
682 }
683}
684
685struct ShmSegment {
686 id: File,
687 ptr: NonNull<i8>,
688 size: usize,
689 buffer_size: usize,
690}
691
692impl ShmSegment {
693 fn new(size: usize, buffer_size: usize) -> io::Result<Self> {
695 assert!(size >= buffer_size);
696
697 let id = File::from(create_shm_id()?);
699
700 id.set_len(size as u64)?;
702
703 let ptr = unsafe {
705 let ptr = mm::mmap(
706 null_mut(),
707 size,
708 mm::ProtFlags::READ | mm::ProtFlags::WRITE,
709 mm::MapFlags::SHARED,
710 &id,
711 0,
712 )?;
713
714 match NonNull::new(ptr.cast()) {
715 Some(ptr) => ptr,
716 None => {
717 return Err(io::Error::new(
718 io::ErrorKind::Other,
719 "unexpected null when mapping SHM segment",
720 ));
721 }
722 }
723 };
724
725 Ok(Self {
726 id,
727 ptr,
728 size,
729 buffer_size,
730 })
731 }
732
733 unsafe fn as_ref(&self) -> &[i8] {
739 unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
740 }
741
742 unsafe fn as_mut(&mut self) -> &mut [i8] {
748 unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
749 }
750
751 fn set_buffer_size(&mut self, buffer_size: usize) {
753 assert!(self.size >= buffer_size);
754 self.buffer_size = buffer_size
755 }
756
757 fn buffer_size(&self) -> usize {
759 self.buffer_size
760 }
761
762 fn size(&self) -> usize {
764 self.size
765 }
766}
767
768impl AsFd for ShmSegment {
769 fn as_fd(&self) -> BorrowedFd<'_> {
770 self.id.as_fd()
771 }
772}
773
774impl Drop for ShmSegment {
775 fn drop(&mut self) {
776 unsafe {
777 mm::munmap(self.ptr.as_ptr().cast(), self.size).ok();
779 }
780 }
781}
782
783impl<D: ?Sized> Drop for X11DisplayImpl<D> {
784 fn drop(&mut self) {
785 self.connection = None;
787 }
788}
789
790impl<D: ?Sized, W: ?Sized> Drop for X11Impl<D, W> {
791 fn drop(&mut self) {
792 if let Buffer::Shm(mut shm) = mem::replace(&mut self.buffer, Buffer::Wire(Vec::new())) {
794 shm.finish_wait(self.display.connection()).ok();
796
797 if let Some((segment, seg_id)) = shm.seg.take() {
798 if let Ok(token) = self.display.connection().shm_detach(seg_id) {
799 token.ignore_error();
800 }
801
802 drop(segment);
804 }
805 }
806
807 if let Ok(token) = self.display.connection().free_gc(self.gc) {
809 token.ignore_error();
810 }
811 }
812}
813
814fn create_shm_id() -> io::Result<OwnedFd> {
816 use posix_shm::{Mode, ShmOFlags};
817
818 let mut rng = fastrand::Rng::new();
819 let mut name = String::with_capacity(23);
820
821 for i in 0..4 {
824 name.clear();
825 name.push_str("softbuffer-x11-");
826 name.extend(std::iter::repeat_with(|| rng.alphanumeric()).take(7));
827
828 match posix_shm::shm_open(
830 &name,
831 ShmOFlags::RDWR | ShmOFlags::CREATE | ShmOFlags::EXCL,
832 Mode::RWXU,
833 ) {
834 Ok(id) => {
835 posix_shm::shm_unlink(&name).ok();
836 return Ok(id);
837 }
838
839 Err(rustix::io::Errno::EXIST) => {
840 log::warn!("x11: SHM ID collision at {} on try number {}", name, i);
841 }
842
843 Err(e) => return Err(e.into()),
844 };
845 }
846
847 Err(io::Error::new(
848 io::ErrorKind::Other,
849 "failed to generate a non-existent SHM name",
850 ))
851}
852
853fn is_shm_available(c: &impl Connection) -> bool {
855 let seg = match ShmSegment::new(0x1000, 0x1000) {
857 Ok(seg) => seg,
858 Err(_) => return false,
859 };
860
861 let seg_id = match c.generate_id() {
863 Ok(id) => id,
864 Err(_) => return false,
865 };
866
867 let (attach, detach) = {
868 let attach = c.shm_attach_fd(seg_id, seg.as_fd().try_clone_to_owned().unwrap(), false);
869 let detach = c.shm_detach(seg_id);
870
871 match (attach, detach) {
872 (Ok(attach), Ok(detach)) => (attach, detach),
873 _ => return false,
874 }
875 };
876
877 matches!((attach.check(), detach.check()), (Ok(()), Ok(())))
879}
880
881fn supported_visuals(c: &impl Connection) -> HashSet<Visualid> {
883 if !c
885 .setup()
886 .pixmap_formats
887 .iter()
888 .any(|f| f.depth == 24 && f.bits_per_pixel == 32)
889 {
890 log::warn!("X11 server does not have a depth 24 format with 32 bits per pixel");
891 return HashSet::new();
892 }
893
894 #[cfg(target_endian = "little")]
896 let own_byte_order = ImageOrder::LSB_FIRST;
897 #[cfg(target_endian = "big")]
898 let own_byte_order = ImageOrder::MSB_FIRST;
899 let expected_masks = if c.setup().image_byte_order == own_byte_order {
900 (0xff0000, 0xff00, 0xff)
901 } else {
902 (0xff00, 0xff0000, 0xff000000)
904 };
905
906 c.setup()
907 .roots
908 .iter()
909 .flat_map(|screen| {
910 screen
911 .allowed_depths
912 .iter()
913 .filter(|depth| depth.depth == 24)
914 .flat_map(|depth| {
915 depth
916 .visuals
917 .iter()
918 .filter(|visual| {
919 visual.class == VisualClass::TRUE_COLOR
921 || visual.class == VisualClass::DIRECT_COLOR
922 })
923 .filter(|visual| {
924 expected_masks == (visual.red_mask, visual.green_mask, visual.blue_mask)
926 })
927 .map(|visual| visual.visual_id)
928 })
929 })
930 .collect()
931}
932
933#[derive(Debug)]
935enum PushBufferError {
936 X11(ReplyError),
938
939 XidExhausted,
941
942 System(io::Error),
944}
945
946impl fmt::Display for PushBufferError {
947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948 match self {
949 Self::X11(e) => write!(f, "X11 error: {}", e),
950 Self::XidExhausted => write!(f, "XID space exhausted"),
951 Self::System(e) => write!(f, "System error: {}", e),
952 }
953 }
954}
955
956impl std::error::Error for PushBufferError {}
957
958impl From<ConnectionError> for PushBufferError {
959 fn from(e: ConnectionError) -> Self {
960 Self::X11(ReplyError::ConnectionError(e))
961 }
962}
963
964impl From<ReplyError> for PushBufferError {
965 fn from(e: ReplyError) -> Self {
966 Self::X11(e)
967 }
968}
969
970impl From<ReplyOrIdError> for PushBufferError {
971 fn from(e: ReplyOrIdError) -> Self {
972 match e {
973 ReplyOrIdError::ConnectionError(e) => Self::X11(ReplyError::ConnectionError(e)),
974 ReplyOrIdError::X11Error(e) => Self::X11(ReplyError::X11Error(e)),
975 ReplyOrIdError::IdsExhausted => Self::XidExhausted,
976 }
977 }
978}
979
980impl From<io::Error> for PushBufferError {
981 fn from(e: io::Error) -> Self {
982 Self::System(e)
983 }
984}
985
986trait PushResultExt<T, E> {
988 fn push_err(self) -> Result<T, PushBufferError>;
989}
990
991impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {
992 fn push_err(self) -> Result<T, PushBufferError> {
993 self.map_err(Into::into)
994 }
995}
996
997#[inline(always)]
999fn total_len(width: u16, height: u16) -> usize {
1000 let width: usize = width.into();
1001 let height: usize = height.into();
1002
1003 width
1004 .checked_mul(height)
1005 .and_then(|len| len.checked_mul(4))
1006 .unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))
1007}