From b4ca15fec0867958d8646aad6e9e781a28b075e3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 4 Sep 2024 19:56:01 +0200 Subject: [PATCH] metal: allow configuring framebuffer formats --- jay-config/src/_private/client.rs | 6 +- jay-config/src/_private/ipc.rs | 8 +- jay-config/src/video.rs | 40 +++++++ src/backend.rs | 5 + src/backends/metal/video.rs | 132 +++++++++++++++++++---- src/cli.rs | 13 ++- src/cli/randr.rs | 67 +++++++++++- src/config/handler.rs | 23 +++- src/format.rs | 89 ++++++++++----- src/ifs/jay_compositor.rs | 2 +- src/ifs/jay_randr.rs | 32 ++++++ src/ifs/wl_output.rs | 5 + src/it/test_backend.rs | 1 + src/tasks/connector.rs | 4 + src/tools/tool_client.rs | 2 +- toml-config/src/config.rs | 3 +- toml-config/src/config/parsers.rs | 1 + toml-config/src/config/parsers/format.rs | 59 ++++++++++ toml-config/src/config/parsers/output.rs | 19 +++- toml-config/src/lib.rs | 3 + toml-spec/spec/spec.generated.json | 38 +++++++ toml-spec/spec/spec.generated.md | 121 +++++++++++++++++++++ toml-spec/spec/spec.yaml | 88 +++++++++++++++ wire/jay_randr.txt | 10 ++ 24 files changed, 713 insertions(+), 58 deletions(-) create mode 100644 toml-config/src/config/parsers/format.rs diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index fa70777d..5170ab3a 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -25,7 +25,7 @@ use { timer::Timer, video::{ connector_type::{ConnectorType, CON_UNKNOWN}, - Connector, DrmDevice, GfxApi, Mode, TearingMode, Transform, VrrMode, + Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, Transform, VrrMode, }, Axis, Direction, ModifiedKeySym, PciId, Workspace, }, @@ -754,6 +754,10 @@ impl Client { self.send(&ClientMessage::ConnectorSetScale { connector, scale }); } + pub fn connector_set_format(&self, connector: Connector, format: Format) { + self.send(&ClientMessage::ConnectorSetFormat { connector, format }); + } + pub fn connector_get_scale(&self, connector: Connector) -> f64 { let res = self.send_with_response(&ClientMessage::ConnectorGetScale { connector }); get_response!(res, 1.0, ConnectorGetScale { scale }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 1a91eb66..2e847584 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -9,8 +9,8 @@ use { theme::{colors::Colorable, sized::Resizable, Color}, timer::Timer, video::{ - connector_type::ConnectorType, Connector, DrmDevice, GfxApi, TearingMode, Transform, - VrrMode, + connector_type::ConnectorType, Connector, DrmDevice, Format, GfxApi, TearingMode, + Transform, VrrMode, }, Axis, Direction, PciId, Workspace, _private::{PollableId, WireMode}, @@ -509,6 +509,10 @@ pub enum ClientMessage<'a> { SetEiSocketEnabled { enabled: bool, }, + ConnectorSetFormat { + connector: Connector, + format: Format, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 8a40e2d3..69ae7bf1 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -267,6 +267,11 @@ impl Connector { pub fn set_tearing_mode(self, mode: TearingMode) { get!().set_tearing_mode(Some(self), mode) } + + /// Sets the format to use for framebuffers. + pub fn set_format(self, format: Format) { + get!().connector_set_format(self, format); + } } /// Returns all available DRM devices. @@ -612,3 +617,38 @@ impl TearingMode { pub fn set_tearing_mode(mode: TearingMode) { get!().set_tearing_mode(None, mode) } + +/// A graphics format. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Format(pub u32); + +impl Format { + pub const ARGB8888: Self = Self(0); + pub const XRGB8888: Self = Self(1); + pub const ABGR8888: Self = Self(2); + pub const XBGR8888: Self = Self(3); + pub const R8: Self = Self(4); + pub const GR88: Self = Self(5); + pub const RGB888: Self = Self(6); + pub const BGR888: Self = Self(7); + pub const RGBA4444: Self = Self(8); + pub const RGBX4444: Self = Self(9); + pub const BGRA4444: Self = Self(10); + pub const BGRX4444: Self = Self(11); + pub const RGB565: Self = Self(12); + pub const BGR565: Self = Self(13); + pub const RGBA5551: Self = Self(14); + pub const RGBX5551: Self = Self(15); + pub const BGRA5551: Self = Self(16); + pub const BGRX5551: Self = Self(17); + pub const ARGB1555: Self = Self(18); + pub const XRGB1555: Self = Self(19); + pub const ARGB2101010: Self = Self(20); + pub const XRGB2101010: Self = Self(21); + pub const ABGR2101010: Self = Self(22); + pub const XBGR2101010: Self = Self(23); + pub const ABGR16161616: Self = Self(24); + pub const XBGR16161616: Self = Self(25); + pub const ABGR16161616F: Self = Self(26); + pub const XBGR16161616F: Self = Self(27); +} diff --git a/src/backend.rs b/src/backend.rs index 838bc988..ffeedc5a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -3,6 +3,7 @@ use { async_engine::SpawnedFuture, drm_feedback::DrmFeedback, fixed::Fixed, + format::Format, gfx_api::{GfxFramebuffer, SyncFile}, ifs::{ wl_output::OutputId, @@ -116,6 +117,9 @@ pub trait Connector { fn set_tearing_enabled(&self, enabled: bool) { let _ = enabled; } + fn set_fb_format(&self, format: &'static Format) { + let _ = format; + } } #[derive(Debug)] @@ -128,6 +132,7 @@ pub enum ConnectorEvent { Unavailable, Available, VrrChanged(bool), + FormatsChanged(Rc>, &'static Format), } pub trait HardwareCursor: Debug { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 53a6758a..25d49337 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -301,6 +301,7 @@ pub struct MetalDrmDeviceData { pub struct PersistentDisplayData { pub mode: RefCell>, pub vrr_requested: Cell, + pub format: Cell<&'static Format>, } #[derive(Debug)] @@ -415,6 +416,7 @@ pub struct MetalConnector { pub connector_id: ConnectorId, + pub buffer_format: Cell<&'static Format>, pub buffers: CloneCell>>, pub next_buffer: NumCell, @@ -460,6 +462,7 @@ pub struct MetalConnector { pub direct_scanout_active: Cell, pub tearing_requested: Cell, + pub try_switch_format: Cell, } impl Debug for MetalConnector { @@ -641,6 +644,25 @@ impl MetalConnector { } } + fn send_formats(&self) { + match self.frontend_state.get() { + FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable + | FrontState::Connected { non_desktop: true } => return, + FrontState::Connected { non_desktop: false } => {} + } + let mut formats = vec![]; + if let Some(plane) = self.primary_plane.get() { + formats = plane.formats.values().map(|f| f.format).collect(); + } + let formats = Rc::new(formats); + self.send_event(ConnectorEvent::FormatsChanged( + formats, + self.buffer_format.get(), + )); + } + fn send_hardware_cursor(self: &Rc) { match self.frontend_state.get() { FrontState::Removed @@ -1264,6 +1286,17 @@ impl MetalConnector { log::error!("Tried to send vrr-changed event in invalid state: {state:?}"); } }, + ConnectorEvent::FormatsChanged(_, _) => match state { + FrontState::Connected { non_desktop: false } => { + self.on_change.send_event(event); + } + FrontState::Connected { non_desktop: true } + | FrontState::Removed + | FrontState::Disconnected + | FrontState::Unavailable => { + log::error!("Tried to send format-changed event in invalid state: {state:?}"); + } + }, } } } @@ -1425,6 +1458,24 @@ impl Connector for MetalConnector { log::debug!("{msg} tearing on output {}", self.kernel_id()); } } + + fn set_fb_format(&self, format: &'static Format) { + { + let dd = self.display.borrow().persistent.clone(); + dd.format.set(format); + if format == self.buffer_format.get() { + self.try_switch_format.set(false); + return; + } + self.try_switch_format.set(true); + } + if let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) { + if let Err(e) = self.backend.handle_drm_change_(&dev, true) { + dev.unprocessed_change.set(true); + log::error!("Could not change format: {}", ErrorFmt(e)); + } + } + } } pub struct MetalCrtc { @@ -1544,6 +1595,7 @@ fn create_connector( dev: dev.clone(), backend: backend.clone(), connector_id: backend.state.connector_ids.next(), + buffer_format: Cell::new(XRGB8888), buffers: Default::default(), next_buffer: Default::default(), enabled: Cell::new(true), @@ -1576,6 +1628,7 @@ fn create_connector( direct_scanout_active: Cell::new(false), next_flip_nsec: Cell::new(0), tearing_requested: Cell::new(false), + try_switch_format: Cell::new(false), }); let futures = ConnectorFutures { _present: backend @@ -1686,6 +1739,7 @@ fn create_connector_display_data( let ds = Rc::new(PersistentDisplayData { mode: RefCell::new(info.modes.first().cloned()), vrr_requested: Default::default(), + format: Cell::new(XRGB8888), }); dev.backend .persistent_display_data @@ -2043,6 +2097,7 @@ impl MetalBackend { }; let mut old = c.display.borrow_mut(); mem::swap(old.deref_mut(), &mut dd); + let mut preserve_connector = false; match c.frontend_state.get() { FrontState::Removed | FrontState::Disconnected => {} FrontState::Connected { .. } | FrontState::Unavailable => { @@ -2074,10 +2129,16 @@ impl MetalBackend { } c.send_event(ConnectorEvent::Disconnected); } else if preserve_any { - preserve.connectors.insert(c.id); + preserve_connector = true; } } } + if c.try_switch_format.get() && old.persistent.format.get() != c.buffer_format.get() { + preserve_connector = false; + } + if preserve_connector { + preserve.connectors.insert(c.id); + } } for c in new_connectors { let (connector, future) = match create_connector(self, c, &dev.dev) { @@ -2131,6 +2192,7 @@ impl MetalBackend { })); connector.send_hardware_cursor(); connector.send_vrr_enabled(); + connector.send_formats(); } pub fn create_drm_device( @@ -2662,6 +2724,7 @@ impl MetalBackend { connector.send_hardware_cursor(); connector.send_vrr_enabled(); connector.update_drm_feedback(); + connector.send_formats(); } Ok(()) } @@ -2954,7 +3017,7 @@ impl MetalBackend { ctx: &MetalRenderContext, old_buffers: &mut Vec>, ) -> Result<(), MetalError> { - let dd = connector.display.borrow_mut(); + let dd = &mut *connector.display.borrow_mut(); let crtc = match connector.crtc.get() { Some(c) => c, _ => return Ok(()), @@ -2966,26 +3029,55 @@ impl MetalBackend { return Ok(()); } }; - let (primary_plane, primary_modifiers) = 'primary_plane: { - for plane in crtc.possible_planes.values() { - if plane.ty == PlaneType::Primary && !plane.assigned.get() && plane.lease.is_none() - { - if let Some(format) = plane.formats.get(&XRGB8888.drm) { - break 'primary_plane (plane.clone(), &format.modifiers); + let allocate_primary_plane = |format: &'static Format| { + let (primary_plane, primary_modifiers) = 'primary_plane: { + for plane in crtc.possible_planes.values() { + if plane.ty == PlaneType::Primary + && !plane.assigned.get() + && plane.lease.is_none() + { + if let Some(format) = plane.formats.get(&format.drm) { + break 'primary_plane (plane.clone(), &format.modifiers); + } + } + } + return Err(MetalError::NoPrimaryPlaneForConnector); + }; + let buffers = Rc::new(self.create_scanout_buffers( + &connector.dev, + format, + primary_modifiers, + mode.hdisplay as _, + mode.vdisplay as _, + ctx, + false, + )?); + Ok((primary_plane, buffers)) + }; + let primary_plane; + let buffers; + let buffer_format; + 'primary_plane: { + let format = dd.persistent.format.get(); + if format != XRGB8888 { + match allocate_primary_plane(format) { + Ok(v) => { + (primary_plane, buffers) = v; + buffer_format = format; + break 'primary_plane; + } + Err(e) => { + log::error!( + "Could not allocate framebuffer with requested format {}: {}", + format.name, + ErrorFmt(e) + ); } } } - return Err(MetalError::NoPrimaryPlaneForConnector); - }; - let buffers = Rc::new(self.create_scanout_buffers( - &connector.dev, - XRGB8888, - primary_modifiers, - mode.hdisplay as _, - mode.vdisplay as _, - ctx, - false, - )?); + (primary_plane, buffers) = allocate_primary_plane(XRGB8888)?; + buffer_format = XRGB8888; + } let mut cursor_plane = None; let mut cursor_modifiers = &IndexSet::new(); for plane in crtc.possible_planes.values() { @@ -3060,6 +3152,8 @@ impl MetalBackend { } connector.cursor_plane.set(cursor_plane); connector.cursor_enabled.set(false); + connector.buffer_format.set(buffer_format); + connector.try_switch_format.set(false); Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index 7d9366a8..4a25dbb5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,10 +17,11 @@ use { crate::{ cli::{damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs}, compositor::start_compositor, + format::{ref_formats, Format}, portal, }, ::log::Level, - clap::{Args, Parser, Subcommand, ValueEnum}, + clap::{builder::PossibleValue, Args, Parser, Subcommand, ValueEnum}, clap_complete::Shell, }; @@ -231,6 +232,16 @@ pub struct GenerateArgs { shell: Shell, } +impl ValueEnum for &'static Format { + fn value_variants<'a>() -> &'a [Self] { + ref_formats() + } + + fn to_possible_value(&self) -> Option { + Some(PossibleValue::new(self.name)) + } +} + pub fn main() { let cli = Jay::parse(); match cli.command { diff --git a/src/cli/randr.rs b/src/cli/randr.rs index 8de02db2..caf5a9e2 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -1,6 +1,7 @@ use { crate::{ cli::GlobalArgs, + format::{Format, XRGB8888}, scale::Scale, tools::tool_client::{with_tool_client, Handle, ToolClient}, utils::{errorfmt::ErrorFmt, transform_ext::TransformExt}, @@ -44,6 +45,9 @@ pub struct ShowArgs { /// Show all available modes. #[arg(long)] pub modes: bool, + /// Show all available formats. + #[arg(long)] + pub formats: bool, } #[derive(Args, Debug)] @@ -122,6 +126,8 @@ pub enum OutputCommand { Vrr(VrrArgs), /// Change tearing settings. Tearing(TearingArgs), + /// Change format settings. + Format(FormatSettings), } #[derive(ValueEnum, Debug, Clone)] @@ -177,6 +183,21 @@ pub struct CursorHzArgs { pub rate: String, } +#[derive(Args, Debug, Clone)] +pub struct FormatSettings { + #[clap(subcommand)] + pub command: FormatCommand, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum FormatCommand { + /// Sets the format of the framebuffer. + Set { + #[clap(value_enum)] + format: &'static Format, + }, +} + #[derive(Args, Debug, Clone)] pub struct TearingArgs { #[clap(subcommand)] @@ -318,6 +339,8 @@ struct Output { pub vrr_mode: VrrMode, pub vrr_cursor_hz: Option, pub tearing_mode: TearingMode, + pub formats: Vec, + pub format: Option, } #[derive(Copy, Clone, Debug)] @@ -546,6 +569,20 @@ impl Randr { } } } + OutputCommand::Format(a) => { + self.handle_error(randr, move |msg| { + eprintln!("Could not change the framebuffer format: {}", msg); + }); + match a.command { + FormatCommand::Set { format } => { + tc.send(jay_randr::SetFbFormat { + self_id: randr, + output: &args.output, + format: format.name, + }); + } + } + } } tc.round_trip().await; } @@ -609,7 +646,7 @@ impl Randr { .collect(); connectors.sort_by_key(|c| &c.name); for c in connectors { - self.print_connector(c, args.modes); + self.print_connector(c, args.modes, args.formats); } } { @@ -622,7 +659,7 @@ impl Randr { connectors.sort_by_key(|c| &c.name); println!("unbound connectors:"); for c in connectors { - self.print_connector(c, args.modes); + self.print_connector(c, args.modes, args.formats); } } } @@ -639,7 +676,7 @@ impl Randr { } } - fn print_connector(&self, connector: &Connector, modes: bool) { + fn print_connector(&self, connector: &Connector, modes: bool, formats: bool) { println!(" {}:", connector.name); let Some(o) = &connector.output else { if !connector.enabled { @@ -701,6 +738,11 @@ impl Randr { print!(" mode: "); self.print_mode(mode, false); } + if let Some(format) = &o.format { + if format != XRGB8888.name { + println!(" format: {format}"); + } + } if o.scale != 1.0 { println!(" scale: {}", o.scale); } @@ -724,6 +766,12 @@ impl Randr { self.print_mode(mode, true); } } + if o.formats.is_not_empty() && formats { + println!(" formats:"); + for format in &o.formats { + println!(" {format}"); + } + } } fn print_mode(&self, m: &Mode, print_current: bool) { @@ -788,6 +836,8 @@ impl Randr { vrr_mode: VrrMode::NEVER, vrr_cursor_hz: None, tearing_mode: TearingMode::NEVER, + formats: vec![], + format: None, }); }); jay_randr::NonDesktopOutput::handle(tc, randr, data.clone(), |data, msg| { @@ -813,6 +863,8 @@ impl Randr { vrr_mode: VrrMode::NEVER, vrr_cursor_hz: None, tearing_mode: TearingMode::NEVER, + formats: vec![], + format: None, }); }); jay_randr::VrrState::handle(tc, randr, data.clone(), |data, msg| { @@ -835,6 +887,15 @@ impl Randr { let output = c.output.as_mut().unwrap(); output.tearing_mode = TearingMode(msg.mode); }); + jay_randr::FbFormat::handle(tc, randr, data.clone(), |data, msg| { + let mut data = data.borrow_mut(); + let c = data.connectors.last_mut().unwrap(); + let output = c.output.as_mut().unwrap(); + output.formats.push(msg.name.to_string()); + if msg.current != 0 { + output.format = Some(msg.name.to_string()); + } + }); jay_randr::Mode::handle(tc, randr, data.clone(), |data, msg| { let mut data = data.borrow_mut(); let c = data.connectors.last_mut().unwrap(); diff --git a/src/config/handler.rs b/src/config/handler.rs index a991fc3d..104bd4f3 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -7,6 +7,7 @@ use { }, compositor::MAX_EXTENTS, config::ConfigProxy, + format::config_formats, ifs::wl_seat::{SeatId, WlSeatGlobal}, io_uring::TaskResultExt, output_schedule::map_cursor_hz, @@ -49,8 +50,8 @@ use { theme::{colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, video::{ - Connector, DrmDevice, GfxApi, TearingMode as ConfigTearingMode, Transform, - VrrMode as ConfigVrrMode, + Connector, DrmDevice, Format as ConfigFormat, GfxApi, TearingMode as ConfigTearingMode, + Transform, VrrMode as ConfigVrrMode, }, Axis, Direction, Workspace, }, @@ -1051,6 +1052,19 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_set_format( + &self, + connector: Connector, + format: ConfigFormat, + ) -> Result<(), CphError> { + let Some(&format) = config_formats().get(&format) else { + return Err(CphError::UnknownFormat(format)); + }; + let connector = self.get_connector(connector)?; + connector.connector.set_fb_format(format); + Ok(()) + } + fn handle_set_vrr_mode( &self, connector: Option, @@ -1919,6 +1933,9 @@ impl ConfigProxyHandler { ClientMessage::SetEiSocketEnabled { enabled } => { self.handle_set_ei_socket_enabled(enabled) } + ClientMessage::ConnectorSetFormat { connector, format } => self + .handle_connector_set_format(connector, format) + .wrn("connector_set_format")?, } Ok(()) } @@ -1986,6 +2003,8 @@ enum CphError { InvalidCursorHz(f64), #[error("Unknown tearing mode {0:?}")] UnknownTearingMode(ConfigTearingMode), + #[error("The format {0:?} is unknown")] + UnknownFormat(ConfigFormat), } trait WithRequestName { diff --git a/src/format.rs b/src/format.rs index 45d9d179..059a251e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -13,6 +13,7 @@ use { }, ahash::AHashMap, ash::vk, + jay_config::video::Format as ConfigFormat, once_cell::sync::Lazy, std::fmt::{Debug, Write}, }; @@ -36,9 +37,10 @@ pub struct Format { pub pipewire: SpaVideoFormat, pub opaque: Option<&'static Format>, pub shm_info: Option, + pub config: ConfigFormat, } -const fn default() -> Format { +const fn default(config: ConfigFormat) -> Format { Format { name: "", vk_format: vk::Format::UNDEFINED, @@ -49,6 +51,7 @@ const fn default() -> Format { pipewire: SPA_VIDEO_FORMAT_UNKNOWN, opaque: None, shm_info: None, + config, } } @@ -78,10 +81,30 @@ static PW_FORMATS_MAP: Lazy> = Lazy::n map }); +static FORMATS_REFS: Lazy> = Lazy::new(|| FORMATS.iter().collect()); + +static FORMATS_NAMES: Lazy> = Lazy::new(|| { + let mut map = AHashMap::new(); + for format in FORMATS { + assert!(map.insert(format.name, format).is_none()); + } + map +}); + +static FORMATS_CONFIG: Lazy> = Lazy::new(|| { + let mut map = AHashMap::new(); + for format in FORMATS { + assert!(map.insert(format.config, format).is_none()); + } + map +}); + #[test] fn formats_dont_panic() { formats(); pw_formats(); + named_formats(); + config_formats(); } pub fn formats() -> &'static AHashMap { @@ -92,6 +115,18 @@ pub fn pw_formats() -> &'static AHashMap { &PW_FORMATS_MAP } +pub fn ref_formats() -> &'static [&'static Format] { + &FORMATS_REFS +} + +pub fn named_formats() -> &'static AHashMap<&'static str, &'static Format> { + &FORMATS_NAMES +} + +pub fn config_formats() -> &'static AHashMap { + &FORMATS_CONFIG +} + const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 { (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24) } @@ -136,6 +171,7 @@ pub static ARGB8888: &Format = &Format { has_alpha: true, pipewire: SPA_VIDEO_FORMAT_BGRA, opaque: Some(XRGB8888), + config: ConfigFormat::ARGB8888, }; pub static XRGB8888: &Format = &Format { @@ -153,6 +189,7 @@ pub static XRGB8888: &Format = &Format { has_alpha: false, pipewire: SPA_VIDEO_FORMAT_BGRx, opaque: None, + config: ConfigFormat::XRGB8888, }; static ABGR8888: &Format = &Format { @@ -170,6 +207,7 @@ static ABGR8888: &Format = &Format { has_alpha: true, pipewire: SPA_VIDEO_FORMAT_RGBA, opaque: Some(XBGR8888), + config: ConfigFormat::ABGR8888, }; static XBGR8888: &Format = &Format { @@ -187,6 +225,7 @@ static XBGR8888: &Format = &Format { has_alpha: false, pipewire: SPA_VIDEO_FORMAT_RGBx, opaque: None, + config: ConfigFormat::XBGR8888, }; static R8: &Format = &Format { @@ -194,14 +233,14 @@ static R8: &Format = &Format { vk_format: vk::Format::R8_UNORM, drm: fourcc_code('R', '8', ' ', ' '), pipewire: SPA_VIDEO_FORMAT_GRAY8, - ..default() + ..default(ConfigFormat::R8) }; static GR88: &Format = &Format { name: "gr88", vk_format: vk::Format::R8G8_UNORM, drm: fourcc_code('G', 'R', '8', '8'), - ..default() + ..default(ConfigFormat::GR88) }; static RGB888: &Format = &Format { @@ -209,7 +248,7 @@ static RGB888: &Format = &Format { vk_format: vk::Format::B8G8R8_UNORM, drm: fourcc_code('R', 'G', '2', '4'), pipewire: SPA_VIDEO_FORMAT_BGR, - ..default() + ..default(ConfigFormat::RGB888) }; static BGR888: &Format = &Format { @@ -217,7 +256,7 @@ static BGR888: &Format = &Format { vk_format: vk::Format::R8G8B8_UNORM, drm: fourcc_code('B', 'G', '2', '4'), pipewire: SPA_VIDEO_FORMAT_RGB, - ..default() + ..default(ConfigFormat::BGR888) }; static RGBA4444: &Format = &Format { @@ -226,14 +265,14 @@ static RGBA4444: &Format = &Format { drm: fourcc_code('R', 'A', '1', '2'), has_alpha: true, opaque: Some(RGBX4444), - ..default() + ..default(ConfigFormat::RGBA4444) }; static RGBX4444: &Format = &Format { name: "rgbx4444", vk_format: vk::Format::R4G4B4A4_UNORM_PACK16, drm: fourcc_code('R', 'X', '1', '2'), - ..default() + ..default(ConfigFormat::RGBX4444) }; static BGRA4444: &Format = &Format { @@ -242,14 +281,14 @@ static BGRA4444: &Format = &Format { drm: fourcc_code('B', 'A', '1', '2'), has_alpha: true, opaque: Some(BGRX4444), - ..default() + ..default(ConfigFormat::BGRA4444) }; static BGRX4444: &Format = &Format { name: "bgrx4444", vk_format: vk::Format::B4G4R4A4_UNORM_PACK16, drm: fourcc_code('B', 'X', '1', '2'), - ..default() + ..default(ConfigFormat::BGRX4444) }; static RGB565: &Format = &Format { @@ -257,7 +296,7 @@ static RGB565: &Format = &Format { vk_format: vk::Format::R5G6B5_UNORM_PACK16, drm: fourcc_code('R', 'G', '1', '6'), pipewire: SPA_VIDEO_FORMAT_BGR16, - ..default() + ..default(ConfigFormat::RGB565) }; static BGR565: &Format = &Format { @@ -265,7 +304,7 @@ static BGR565: &Format = &Format { vk_format: vk::Format::B5G6R5_UNORM_PACK16, drm: fourcc_code('B', 'G', '1', '6'), pipewire: SPA_VIDEO_FORMAT_RGB16, - ..default() + ..default(ConfigFormat::BGR565) }; static RGBA5551: &Format = &Format { @@ -274,14 +313,14 @@ static RGBA5551: &Format = &Format { drm: fourcc_code('R', 'A', '1', '5'), has_alpha: true, opaque: Some(RGBX5551), - ..default() + ..default(ConfigFormat::RGBA5551) }; static RGBX5551: &Format = &Format { name: "rgbx5551", vk_format: vk::Format::R5G5B5A1_UNORM_PACK16, drm: fourcc_code('R', 'X', '1', '5'), - ..default() + ..default(ConfigFormat::RGBX5551) }; static BGRA5551: &Format = &Format { @@ -290,14 +329,14 @@ static BGRA5551: &Format = &Format { drm: fourcc_code('B', 'A', '1', '5'), has_alpha: true, opaque: Some(BGRX5551), - ..default() + ..default(ConfigFormat::BGRA5551) }; static BGRX5551: &Format = &Format { name: "bgrx5551", vk_format: vk::Format::B5G5R5A1_UNORM_PACK16, drm: fourcc_code('B', 'X', '1', '5'), - ..default() + ..default(ConfigFormat::BGRX5551) }; static ARGB1555: &Format = &Format { @@ -306,7 +345,7 @@ static ARGB1555: &Format = &Format { drm: fourcc_code('A', 'R', '1', '5'), has_alpha: true, opaque: Some(XRGB1555), - ..default() + ..default(ConfigFormat::ARGB1555) }; static XRGB1555: &Format = &Format { @@ -314,7 +353,7 @@ static XRGB1555: &Format = &Format { vk_format: vk::Format::A1R5G5B5_UNORM_PACK16, drm: fourcc_code('X', 'R', '1', '5'), pipewire: SPA_VIDEO_FORMAT_BGR15, - ..default() + ..default(ConfigFormat::XRGB1555) }; static ARGB2101010: &Format = &Format { @@ -324,7 +363,7 @@ static ARGB2101010: &Format = &Format { has_alpha: true, opaque: Some(XRGB2101010), pipewire: SPA_VIDEO_FORMAT_ARGB_210LE, - ..default() + ..default(ConfigFormat::ARGB2101010) }; static XRGB2101010: &Format = &Format { @@ -332,7 +371,7 @@ static XRGB2101010: &Format = &Format { vk_format: vk::Format::A2R10G10B10_UNORM_PACK32, drm: fourcc_code('X', 'R', '3', '0'), pipewire: SPA_VIDEO_FORMAT_xRGB_210LE, - ..default() + ..default(ConfigFormat::XRGB2101010) }; static ABGR2101010: &Format = &Format { @@ -342,7 +381,7 @@ static ABGR2101010: &Format = &Format { has_alpha: true, opaque: Some(XBGR2101010), pipewire: SPA_VIDEO_FORMAT_ABGR_210LE, - ..default() + ..default(ConfigFormat::ABGR2101010) }; static XBGR2101010: &Format = &Format { @@ -350,7 +389,7 @@ static XBGR2101010: &Format = &Format { vk_format: vk::Format::A2B10G10R10_UNORM_PACK32, drm: fourcc_code('X', 'B', '3', '0'), pipewire: SPA_VIDEO_FORMAT_xBGR_210LE, - ..default() + ..default(ConfigFormat::XBGR2101010) }; static ABGR16161616: &Format = &Format { @@ -359,14 +398,14 @@ static ABGR16161616: &Format = &Format { drm: fourcc_code('A', 'B', '4', '8'), has_alpha: true, opaque: Some(XBGR16161616), - ..default() + ..default(ConfigFormat::ABGR16161616) }; static XBGR16161616: &Format = &Format { name: "xbgr16161616", vk_format: vk::Format::R16G16B16A16_UNORM, drm: fourcc_code('X', 'B', '4', '8'), - ..default() + ..default(ConfigFormat::XBGR16161616) }; static ABGR16161616F: &Format = &Format { @@ -375,14 +414,14 @@ static ABGR16161616F: &Format = &Format { drm: fourcc_code('A', 'B', '4', 'H'), has_alpha: true, opaque: Some(XBGR16161616F), - ..default() + ..default(ConfigFormat::ABGR16161616F) }; static XBGR16161616F: &Format = &Format { name: "xbgr16161616f", vk_format: vk::Format::R16G16B16A16_SFLOAT, drm: fourcc_code('X', 'B', '4', 'H'), - ..default() + ..default(ConfigFormat::XBGR16161616F) }; pub static FORMATS: &[Format] = &[ diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 4f40d054..8b8fa725 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -70,7 +70,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 7 + 8 } fn required_caps(&self) -> ClientCaps { diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 3906736e..4a806d61 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -3,6 +3,7 @@ use { backend, client::{Client, ClientError}, compositor::MAX_EXTENTS, + format::named_formats, leaks::Tracker, object::{Object, Version}, scale::Scale, @@ -27,6 +28,7 @@ pub struct JayRandr { const VRR_CAPABLE_SINCE: Version = Version(2); const TEARING_SINCE: Version = Version(3); +const FORMAT_SINCE: Version = Version(8); impl JayRandr { pub fn new(id: JayRandrId, client: &Rc, version: Version) -> Self { @@ -125,6 +127,23 @@ impl JayRandr { mode: node.global.persistent.tearing_mode.get().to_config().0, }); } + if self.version >= FORMAT_SINCE { + let current = node.global.format.get(); + self.client.event(FbFormat { + self_id: self.id, + name: current.name, + current: 1, + }); + for &format in &*node.global.formats.get() { + if format != current { + self.client.event(FbFormat { + self_id: self.id, + name: format.name, + current: 0, + }); + } + } + } let current_mode = global.mode.get(); for mode in &global.modes { self.client.event(Mode { @@ -365,6 +384,17 @@ impl JayRandrRequestHandler for JayRandr { c.update_presentation_type(); return Ok(()); } + + fn set_fb_format(&self, req: SetFbFormat<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let Some(&format) = named_formats().get(req.format) else { + return Err(JayRandrError::UnknownFormat(req.format.to_string())); + }; + let Some(c) = self.get_output_node(req.output) else { + return Ok(()); + }; + c.global.connector.connector.set_fb_format(format); + Ok(()) + } } object_base! { @@ -384,5 +414,7 @@ pub enum JayRandrError { UnknownVrrMode(u32), #[error("Unknown tearing mode {0}")] UnknownTearingMode(u32), + #[error("Unknown format {0}")] + UnknownFormat(String), } efrom!(JayRandrError, ClientError); diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 3ac09c68..0b9f549c 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -4,6 +4,7 @@ use { crate::{ backend, client::{Client, ClientError, ClientId}, + format::{Format, XRGB8888}, globals::{Global, GlobalName}, ifs::{wl_surface::WlSurface, zxdg_output_v1::ZxdgOutputV1}, leaks::Tracker, @@ -57,6 +58,8 @@ pub struct WlOutputGlobal { pub output_id: Rc, pub mode: Cell, pub modes: Vec, + pub formats: CloneCell>>, + pub format: Cell<&'static Format>, pub width_mm: i32, pub height_mm: i32, pub bindings: RefCell>>>, @@ -152,6 +155,8 @@ impl WlOutputGlobal { output_id: output_id.clone(), mode: Cell::new(*mode), modes, + formats: CloneCell::new(Rc::new(vec![])), + format: Cell::new(XRGB8888), width_mm, height_mm, bindings: Default::default(), diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 300387ce..4c6f7768 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -11,6 +11,7 @@ use { compositor::TestFuture, drm_feedback::DrmFeedback, fixed::Fixed, + format::XRGB8888, gfx_api::GfxError, gfx_apis::create_vulkan_allocator, ifs::wl_output::OutputId, diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 398ac710..5dbf2290 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -250,6 +250,10 @@ impl ConnectorHandler { ConnectorEvent::VrrChanged(enabled) => { on.schedule.set_vrr_enabled(enabled); } + ConnectorEvent::FormatsChanged(formats, format) => { + on.global.formats.set(formats); + on.global.format.set(format); + } ev => unreachable!("received unexpected event {:?}", ev), } } diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 1b489374..b045a236 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -330,7 +330,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(6), + version: s.jay_compositor.1.min(8), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 5cee1953..b18281d8 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -22,7 +22,7 @@ use { logging::LogLevel, status::MessageFormat, theme::Color, - video::{GfxApi, TearingMode, Transform, VrrMode}, + video::{Format, GfxApi, TearingMode, Transform, VrrMode}, Axis, Direction, Workspace, }, std::{ @@ -208,6 +208,7 @@ pub struct Output { pub mode: Option, pub vrr: Option, pub tearing: Option, + pub format: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index f457fd55..b60776a2 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -15,6 +15,7 @@ mod drm_device; mod drm_device_match; mod env; pub mod exec; +mod format; mod gfx_api; mod idle; mod input; diff --git a/toml-config/src/config/parsers/format.rs b/toml-config/src/config/parsers/format.rs new file mode 100644 index 00000000..32c9494b --- /dev/null +++ b/toml-config/src/config/parsers/format.rs @@ -0,0 +1,59 @@ +use { + crate::{ + config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + toml::toml_span::{Span, SpannedExt}, + }, + jay_config::video::Format, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum FormatParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error("Unknown format {0}")] + UnknownFormat(String), +} + +pub struct FormatParser; + +impl Parser for FormatParser { + type Value = Format; + type Error = FormatParserError; + const EXPECTED: &'static [DataType] = &[DataType::String]; + + fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { + let format = match string { + "argb8888" => Format::ARGB8888, + "xrgb8888" => Format::XRGB8888, + "abgr8888" => Format::ABGR8888, + "xbgr8888" => Format::XBGR8888, + "r8" => Format::R8, + "gr88" => Format::GR88, + "rgb888" => Format::RGB888, + "bgr888" => Format::BGR888, + "rgba4444" => Format::RGBA4444, + "rgbx4444" => Format::RGBX4444, + "bgra4444" => Format::BGRA4444, + "bgrx4444" => Format::BGRX4444, + "rgb565" => Format::RGB565, + "bgr565" => Format::BGR565, + "rgba5551" => Format::RGBA5551, + "rgbx5551" => Format::RGBX5551, + "bgra5551" => Format::BGRA5551, + "bgrx5551" => Format::BGRX5551, + "argb1555" => Format::ARGB1555, + "xrgb1555" => Format::XRGB1555, + "argb2101010" => Format::ARGB2101010, + "xrgb2101010" => Format::XRGB2101010, + "abgr2101010" => Format::ABGR2101010, + "xbgr2101010" => Format::XBGR2101010, + "abgr16161616" => Format::ABGR16161616, + "xbgr16161616" => Format::XBGR16161616, + "abgr16161616f" => Format::ABGR16161616F, + "xbgr16161616f" => Format::XBGR16161616F, + _ => return Err(FormatParserError::UnknownFormat(string.to_string()).spanned(span)), + }; + Ok(format) + } +} diff --git a/toml-config/src/config/parsers/output.rs b/toml-config/src/config/parsers/output.rs index 4348cd47..4f14d7ae 100644 --- a/toml-config/src/config/parsers/output.rs +++ b/toml-config/src/config/parsers/output.rs @@ -5,6 +5,7 @@ use { extractor::{fltorint, opt, recover, s32, str, val, Extractor, ExtractorError}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ + format::FormatParser, mode::ModeParser, output_match::{OutputMatchParser, OutputMatchParserError}, tearing::TearingParser, @@ -48,8 +49,8 @@ impl<'a> Parser for OutputParser<'a> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.cx, span, table); - let (name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val) = - ext.extract(( + let (name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val, format_val) = ext + .extract(( opt(str("name")), val("match"), recover(opt(s32("x"))), @@ -59,6 +60,7 @@ impl<'a> Parser for OutputParser<'a> { opt(val("mode")), opt(val("vrr")), opt(val("tearing")), + opt(val("format")), ))?; let transform = match transform { None => None, @@ -119,6 +121,18 @@ impl<'a> Parser for OutputParser<'a> { } } } + let mut format = None; + if let Some(value) = format_val { + match value.parse(&mut FormatParser) { + Ok(v) => format = Some(v), + Err(e) => { + log::warn!( + "Could not parse framebuffer format setting: {}", + self.cx.error(e) + ); + } + } + } Ok(Output { name: name.despan().map(|v| v.to_string()), match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?, @@ -129,6 +143,7 @@ impl<'a> Parser for OutputParser<'a> { mode, vrr, tearing, + format, }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index fa7a8b11..8ed0bbec 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -573,6 +573,9 @@ impl Output { c.set_tearing_mode(mode); } } + if let Some(format) = self.format { + c.set_format(format); + } } } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 47228da7..80eefd17 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -757,6 +757,40 @@ } ] }, + "Format": { + "type": "string", + "description": "A graphics format.\n\nThese formats are documented in https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n format = \"rgb565\"\n ```\n", + "enum": [ + "argb8888", + "xrgb8888", + "abgr8888", + "xbgr8888", + "r8", + "gr88", + "rgb888", + "bgr888", + "rgba4444", + "rgbx4444", + "bgra4444", + "bgrx4444", + "rgb565", + "bgr565", + "rgba5551", + "rgbx5551", + "bgra5551", + "bgrx5551", + "argb1555", + "xrgb1555", + "argb2101010", + "xrgb2101010", + "abgr2101010", + "xbgr2101010", + "abgr16161616", + "xbgr16161616", + "abgr16161616f", + "xbgr16161616f" + ] + }, "GfxApi": { "type": "string", "description": "A graphics API used for rendering.", @@ -1066,6 +1100,10 @@ "tearing": { "description": "Configures the tearing settings of this output.\n\nBy default, the tearing mode is `variant3`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n tearing.mode = \"never\"\n ```\n", "$ref": "#/$defs/Tearing" + }, + "format": { + "description": "Configures the framebuffer format of this output.\n\nBy default, the format is `xrgb8888`.\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n format = \"rgb565\"\n ```\n", + "$ref": "#/$defs/Format" } }, "required": [ diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 6ea5b050..6157bfa2 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1507,6 +1507,111 @@ The table has the following fields: The value of this field should be a boolean. + +### `Format` + +A graphics format. + +These formats are documented in https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h + +- Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + format = "rgb565" + ``` + +Values of this type should be strings. + +The string should have one of the following values: + +- `argb8888`: + + +- `xrgb8888`: + + +- `abgr8888`: + + +- `xbgr8888`: + + +- `r8`: + + +- `gr88`: + + +- `rgb888`: + + +- `bgr888`: + + +- `rgba4444`: + + +- `rgbx4444`: + + +- `bgra4444`: + + +- `bgrx4444`: + + +- `rgb565`: + + +- `bgr565`: + + +- `rgba5551`: + + +- `rgbx5551`: + + +- `bgra5551`: + + +- `bgrx5551`: + + +- `argb1555`: + + +- `xrgb1555`: + + +- `argb2101010`: + + +- `xrgb2101010`: + + +- `abgr2101010`: + + +- `xbgr2101010`: + + +- `abgr16161616`: + + +- `xbgr16161616`: + + +- `abgr16161616f`: + + +- `xbgr16161616f`: + + + + ### `GfxApi` @@ -2283,6 +2388,22 @@ The table has the following fields: The value of this field should be a [Tearing](#types-Tearing). +- `format` (optional): + + Configures the framebuffer format of this output. + + By default, the format is `xrgb8888`. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + format = "rgb565" + ``` + + The value of this field should be a [Format](#types-Format). + ### `OutputMatch` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 746247ad..a8602ce1 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1606,6 +1606,21 @@ Output: match.serial-number = "33K03894SL0" tearing.mode = "never" ``` + format: + ref: Format + required: false + description: | + Configures the framebuffer format of this output. + + By default, the format is `xrgb8888`. + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + format = "rgb565" + ``` Transform: @@ -2490,3 +2505,76 @@ Libei: Even if the socket is disabled, application can still request access via the portal. The default is `false`. + + +Format: + description: | + A graphics format. + + These formats are documented in https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h + + - Example: + + ```toml + [[outputs]] + match.serial-number = "33K03894SL0" + format = "rgb565" + ``` + kind: string + values: + - value: argb8888 + description: "" + - value: xrgb8888 + description: "" + - value: abgr8888 + description: "" + - value: xbgr8888 + description: "" + - value: r8 + description: "" + - value: gr88 + description: "" + - value: rgb888 + description: "" + - value: bgr888 + description: "" + - value: rgba4444 + description: "" + - value: rgbx4444 + description: "" + - value: bgra4444 + description: "" + - value: bgrx4444 + description: "" + - value: rgb565 + description: "" + - value: bgr565 + description: "" + - value: rgba5551 + description: "" + - value: rgbx5551 + description: "" + - value: bgra5551 + description: "" + - value: bgrx5551 + description: "" + - value: argb1555 + description: "" + - value: xrgb1555 + description: "" + - value: argb2101010 + description: "" + - value: xrgb2101010 + description: "" + - value: abgr2101010 + description: "" + - value: xbgr2101010 + description: "" + - value: abgr16161616 + description: "" + - value: xbgr16161616 + description: "" + - value: abgr16161616f + description: "" + - value: xbgr16161616f + description: "" diff --git a/wire/jay_randr.txt b/wire/jay_randr.txt index cfd4b8a8..109e52f7 100644 --- a/wire/jay_randr.txt +++ b/wire/jay_randr.txt @@ -70,6 +70,11 @@ request set_tearing_mode (since = 3) { mode: u32, } +request set_fb_format (since = 8) { + output: str, + format: str, +} + # events event global { @@ -141,3 +146,8 @@ event vrr_cursor_hz (since = 2) { event tearing_state (since = 3) { mode: u32, } + +event fb_format (since = 8) { + name: str, + current: u32, +}