From c81f35bdf1cc6974016c3244368de934b74d24dd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 4 Sep 2024 21:42:58 +0200 Subject: [PATCH 1/3] vulkan: don't hardcode argb8888 in pipelines --- src/gfx_apis/vulkan/pipeline.rs | 33 +++---- src/gfx_apis/vulkan/renderer.rs | 153 +++++++++++++++++++++----------- 2 files changed, 119 insertions(+), 67 deletions(-) diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index 98ac2926..17845186 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -1,21 +1,21 @@ use { - crate::{ - format::ARGB8888, - gfx_apis::vulkan::{ - descriptor::VulkanDescriptorSetLayout, device::VulkanDevice, shaders::VulkanShader, - util::OnDrop, VulkanError, - }, + crate::gfx_apis::vulkan::{ + descriptor::VulkanDescriptorSetLayout, device::VulkanDevice, shaders::VulkanShader, + util::OnDrop, VulkanError, }, arrayvec::ArrayVec, - ash::vk::{ - BlendFactor, BlendOp, ColorComponentFlags, CullModeFlags, DynamicState, FrontFace, - GraphicsPipelineCreateInfo, Pipeline, PipelineCache, PipelineColorBlendAttachmentState, - PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo, - PipelineInputAssemblyStateCreateInfo, PipelineLayout, PipelineLayoutCreateInfo, - PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo, - PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo, - PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode, - PrimitiveTopology, PushConstantRange, SampleCountFlags, ShaderStageFlags, + ash::{ + vk, + vk::{ + BlendFactor, BlendOp, ColorComponentFlags, CullModeFlags, DynamicState, FrontFace, + GraphicsPipelineCreateInfo, Pipeline, PipelineCache, PipelineColorBlendAttachmentState, + PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo, + PipelineInputAssemblyStateCreateInfo, PipelineLayout, PipelineLayoutCreateInfo, + PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo, + PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo, + PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode, + PrimitiveTopology, PushConstantRange, SampleCountFlags, ShaderStageFlags, + }, }, std::{mem, rc::Rc, slice}, }; @@ -30,6 +30,7 @@ pub(super) struct VulkanPipeline { } pub(super) struct PipelineCreateInfo { + pub(super) format: vk::Format, pub(super) vert: Rc, pub(super) frag: Rc, pub(super) alpha: bool, @@ -128,7 +129,7 @@ impl VulkanDevice { .viewport_count(1) .scissor_count(1); let mut pipeline_rendering_create_info = PipelineRenderingCreateInfo::default() - .color_attachment_formats(slice::from_ref(&ARGB8888.vk_format)); + .color_attachment_formats(slice::from_ref(&info.format)); let create_info = GraphicsPipelineCreateInfo::default() .push_next(&mut pipeline_rendering_create_info) .stages(&stages) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 36716a36..53868dee 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -1,7 +1,7 @@ use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, - format::Format, + format::{Format, XRGB8888}, gfx_api::{ AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer, GfxTexture, ReleaseSync, SyncFile, @@ -9,6 +9,7 @@ use { gfx_apis::vulkan::{ allocator::VulkanAllocator, command::{VulkanCommandBuffer, VulkanCommandPool}, + descriptor::VulkanDescriptorSetLayout, device::VulkanDevice, fence::VulkanFence, image::{VulkanImage, VulkanImageMemory}, @@ -28,6 +29,7 @@ use { }, ahash::AHashMap, ash::{ + vk, vk::{ AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferImageCopy, BufferMemoryBarrier2, ClearColorValue, ClearValue, CommandBuffer, @@ -56,8 +58,7 @@ use { pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, - pub(super) fill_pipeline: Rc, - pub(super) tex_pipelines: EnumMap>>, + pub(super) pipelines: CopyHashMap>, pub(super) command_pool: Rc, pub(super) command_buffers: Stack>, pub(super) wait_semaphores: Stack>, @@ -70,6 +71,13 @@ pub struct VulkanRenderer { pub(super) buffer_resv_user: BufferResvUser, pub(super) eng: Rc, pub(super) ring: Rc, + pub(super) fill_vert_shader: Rc, + pub(super) fill_frag_shader: Rc, + pub(super) tex_vert_shader: Rc, + pub(super) tex_frag_shader: Rc, + pub(super) tex_frag_mult_opaque_shader: Rc, + pub(super) tex_frag_mult_alpha_shader: Rc, + pub(super) tex_descriptor_set_layout: Rc, } pub(super) struct UsedTexture { @@ -112,46 +120,25 @@ pub(super) struct PendingFrame { _release_fence: Option>, } +pub(super) struct VulkanFormatPipelines { + pub(super) fill: Rc, + pub(super) tex: EnumMap>>, +} + impl VulkanDevice { pub fn create_renderer( self: &Rc, eng: &Rc, ring: &Rc, ) -> Result, VulkanError> { - let fill_pipeline = self.create_pipeline::( - PipelineCreateInfo { - vert: self.create_shader(FILL_VERT)?, - frag: self.create_shader(FILL_FRAG)?, - alpha: true, - frag_descriptor_set_layout: None, - }, - )?; + let fill_vert_shader = self.create_shader(FILL_VERT)?; + let fill_frag_shader = self.create_shader(FILL_FRAG)?; let sampler = self.create_sampler()?; let tex_descriptor_set_layout = self.create_descriptor_set_layout(&sampler)?; let tex_vert_shader = self.create_shader(TEX_VERT)?; let tex_frag_shader = self.create_shader(TEX_FRAG)?; let tex_frag_mult_opaque_shader = self.create_shader(TEX_FRAG_MULT_OPAQUE)?; let tex_frag_mult_alpha_shader = self.create_shader(TEX_FRAG_MULT_ALPHA)?; - let create_tex_pipeline = |alpha| { - self.create_pipeline::(PipelineCreateInfo { - vert: tex_vert_shader.clone(), - frag: tex_frag_shader.clone(), - alpha, - frag_descriptor_set_layout: Some(tex_descriptor_set_layout.clone()), - }) - }; - let create_tex_mult_pipeline = |frag: &Rc| { - self.create_pipeline::(PipelineCreateInfo { - vert: tex_vert_shader.clone(), - frag: frag.clone(), - alpha: true, - frag_descriptor_set_layout: Some(tex_descriptor_set_layout.clone()), - }) - }; - let tex_opaque_pipeline = create_tex_pipeline(false)?; - let tex_alpha_pipeline = create_tex_pipeline(true)?; - let tex_mult_opaque_pipeline = create_tex_mult_pipeline(&tex_frag_mult_opaque_shader)?; - let tex_mult_alpha_pipeline = create_tex_mult_pipeline(&tex_frag_mult_alpha_shader)?; let command_pool = self.create_command_pool()?; let formats: AHashMap = self .formats @@ -178,20 +165,10 @@ impl VulkanDevice { }) .collect(); let allocator = self.create_allocator()?; - Ok(Rc::new(VulkanRenderer { + let render = Rc::new(VulkanRenderer { formats: Rc::new(formats), device: self.clone(), - fill_pipeline, - tex_pipelines: enum_map! { - TexCopyType::Identity => enum_map! { - TexSourceType::HasAlpha => tex_alpha_pipeline.clone(), - TexSourceType::Opaque => tex_opaque_pipeline.clone(), - }, - TexCopyType::Multiply => enum_map! { - TexSourceType::HasAlpha => tex_mult_alpha_pipeline.clone(), - TexSourceType::Opaque => tex_mult_opaque_pipeline.clone(), - }, - }, + pipelines: Default::default(), command_pool, command_buffers: Default::default(), wait_semaphores: Default::default(), @@ -204,11 +181,79 @@ impl VulkanDevice { buffer_resv_user: Default::default(), eng: eng.clone(), ring: ring.clone(), - })) + fill_vert_shader, + fill_frag_shader, + tex_vert_shader, + tex_frag_shader, + tex_frag_mult_opaque_shader, + tex_frag_mult_alpha_shader, + tex_descriptor_set_layout, + }); + render.get_or_create_pipelines(XRGB8888.vk_format)?; + Ok(render) } } impl VulkanRenderer { + fn get_or_create_pipelines( + &self, + format: vk::Format, + ) -> Result, VulkanError> { + if let Some(pl) = self.pipelines.get(&format) { + return Ok(pl); + } + let fill = self + .device + .create_pipeline::( + PipelineCreateInfo { + format, + vert: self.fill_vert_shader.clone(), + frag: self.fill_frag_shader.clone(), + alpha: true, + frag_descriptor_set_layout: None, + }, + )?; + let create_tex_pipeline = |alpha| { + self.device + .create_pipeline::(PipelineCreateInfo { + format, + vert: self.tex_vert_shader.clone(), + frag: self.tex_frag_shader.clone(), + alpha, + frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()), + }) + }; + let create_tex_mult_pipeline = |frag: &Rc| { + self.device + .create_pipeline::(PipelineCreateInfo { + format, + vert: self.tex_vert_shader.clone(), + frag: frag.clone(), + alpha: true, + frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()), + }) + }; + let tex_opaque = create_tex_pipeline(false)?; + let tex_alpha = create_tex_pipeline(true)?; + let tex_mult_opaque = create_tex_mult_pipeline(&self.tex_frag_mult_opaque_shader)?; + let tex_mult_alpha = create_tex_mult_pipeline(&self.tex_frag_mult_alpha_shader)?; + let pipelines = Rc::new(VulkanFormatPipelines { + fill, + tex: enum_map! { + TexCopyType::Identity => enum_map! { + TexSourceType::HasAlpha => tex_alpha.clone(), + TexSourceType::Opaque => tex_opaque.clone(), + }, + TexCopyType::Multiply => enum_map! { + TexSourceType::HasAlpha => tex_mult_alpha.clone(), + TexSourceType::Opaque => tex_mult_opaque.clone(), + }, + }, + }); + self.pipelines.set(format, pipelines.clone()); + Ok(pipelines) + } + pub(super) fn allocate_point(&self) -> u64 { self.last_point.fetch_add(1) + 1 } @@ -350,7 +395,13 @@ impl VulkanRenderer { } } - fn record_draws(&self, buf: CommandBuffer, opts: &[GfxApiOpt]) -> Result<(), VulkanError> { + fn record_draws( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + opts: &[GfxApiOpt], + ) -> Result<(), VulkanError> { + let pipelines = self.get_or_create_pipelines(fb.format.vk_format)?; let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -365,7 +416,7 @@ impl VulkanRenderer { match opt { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(r) => { - bind(&self.fill_pipeline); + bind(&pipelines.fill); let vert = FillVertPushConstants { pos: r.rect.to_points(), }; @@ -375,16 +426,16 @@ impl VulkanRenderer { unsafe { dev.cmd_push_constants( buf, - self.fill_pipeline.pipeline_layout, + pipelines.fill.pipeline_layout, ShaderStageFlags::VERTEX, 0, uapi::as_bytes(&vert), ); dev.cmd_push_constants( buf, - self.fill_pipeline.pipeline_layout, + pipelines.fill.pipeline_layout, ShaderStageFlags::FRAGMENT, - self.fill_pipeline.frag_push_offset, + pipelines.fill.frag_push_offset, uapi::as_bytes(&frag), ); dev.cmd_draw(buf, 4, 1, 0, 0); @@ -400,7 +451,7 @@ impl VulkanRenderer { true => TexSourceType::HasAlpha, false => TexSourceType::Opaque, }; - let pipeline = &self.tex_pipelines[copy_type][source_type]; + let pipeline = &pipelines.tex[copy_type][source_type]; bind(pipeline); let vert = TexVertPushConstants { pos: c.target.to_points(), @@ -944,7 +995,7 @@ impl VulkanRenderer { self.initial_barriers(buf.buffer, fb); self.begin_rendering(buf.buffer, fb, clear); self.set_viewport(buf.buffer, fb); - self.record_draws(buf.buffer, opts)?; + self.record_draws(buf.buffer, fb, opts)?; self.end_rendering(buf.buffer); self.copy_bridge_to_dmabuf(buf.buffer, fb); self.final_barriers(buf.buffer, fb); From 9bab4f7ce177752e275fdb1a78783f80e0cef481 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 4 Sep 2024 19:00:52 +0200 Subject: [PATCH 2/3] metal: preserve mode across reconnects --- src/backend.rs | 19 +-- src/backends/metal.rs | 10 +- src/backends/metal/video.rs | 138 +++++++++++--------- src/backends/x.rs | 10 +- src/compositor.rs | 2 +- src/config/handler.rs | 6 +- src/ifs/jay_randr.rs | 12 +- src/ifs/wl_output.rs | 20 ++- src/it/test_backend.rs | 10 +- src/it/tests/t0034_workspace_restoration.rs | 10 +- src/tasks/connector.rs | 18 +-- 11 files changed, 148 insertions(+), 107 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 9816bff3..838bc988 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -4,13 +4,16 @@ use { drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{GfxFramebuffer, SyncFile}, - ifs::wl_seat::{ - tablet::{ - PadButtonState, TabletInit, TabletPadId, TabletPadInit, TabletRingEventSource, - TabletStripEventSource, TabletToolChanges, TabletToolId, TabletToolInit, - ToolButtonState, + ifs::{ + wl_output::OutputId, + wl_seat::{ + tablet::{ + PadButtonState, TabletInit, TabletPadId, TabletPadInit, TabletRingEventSource, + TabletStripEventSource, TabletToolChanges, TabletToolId, TabletToolInit, + ToolButtonState, + }, + wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, }, - wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, }, libinput::consts::DeviceCapability, video::drm::{ConnectorType, DrmConnector, DrmError, DrmVersion}, @@ -64,9 +67,7 @@ pub struct Mode { #[derive(Clone, Debug)] pub struct MonitorInfo { pub modes: Vec, - pub manufacturer: String, - pub product: String, - pub serial_number: String, + pub output_id: Rc, pub initial_mode: Mode, pub width_mm: i32, pub height_mm: i32, diff --git a/src/backends/metal.rs b/src/backends/metal.rs index c40cd371..b1fd8d2c 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -11,12 +11,16 @@ use { }, backends::metal::video::{ MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, + PersistentDisplayData, }, dbus::{DbusError, SignalHandler}, drm_feedback::DrmFeedback, gfx_api::GfxError, - ifs::wl_seat::tablet::{ - TabletId, TabletInit, TabletPadGroupInit, TabletPadId, TabletPadInit, + ifs::{ + wl_output::OutputId, + wl_seat::tablet::{ + TabletId, TabletInit, TabletPadGroupInit, TabletPadId, TabletPadInit, + }, }, libinput::{ consts::{ @@ -144,6 +148,7 @@ pub struct MetalBackend { resume_handler: Cell>, ctx: CloneCell>>, default_feedback: CloneCell>>, + persistent_display_data: CopyHashMap, Rc>, } impl Debug for MetalBackend { @@ -317,6 +322,7 @@ pub async fn create(state: &Rc) -> Result, MetalError> { resume_handler: Default::default(), ctx: Default::default(), default_feedback: Default::default(), + persistent_display_data: Default::default(), }); metal.pause_handler.set(Some({ let mtl = metal.clone(); diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 45339187..53a6758a 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -15,7 +15,10 @@ use { AcquireSync, BufferResv, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile, }, - ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, + ifs::{ + wl_output::OutputId, + wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, + }, renderer::RenderResult, state::State, theme::Color, @@ -23,9 +26,8 @@ use { udev::UdevDevice, utils::{ asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell, - copyhashmap::CopyHashMap, debug_fn::debug_fn, errorfmt::ErrorFmt, numcell::NumCell, - on_change::OnChange, opaque_cell::OpaqueCell, oserror::OsError, - transform_ext::TransformExt, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange, + opaque_cell::OpaqueCell, oserror::OsError, transform_ext::TransformExt, }, video::{ dmabuf::DmaBufId, @@ -295,40 +297,36 @@ pub struct MetalDrmDeviceData { pub unprocessed_change: Cell, } +#[derive(Debug)] +pub struct PersistentDisplayData { + pub mode: RefCell>, + pub vrr_requested: Cell, +} + #[derive(Debug)] pub struct ConnectorDisplayData { pub crtc_id: MutableProperty, pub crtcs: AHashMap>, pub modes: Vec, pub mode: Option, + pub persistent: Rc, pub refresh: u32, pub non_desktop: bool, pub non_desktop_effective: bool, pub vrr_capable: bool, - pub vrr_requested: bool, - pub monitor_manufacturer: String, - pub monitor_name: String, - pub monitor_serial_number: String, + pub connector_id: ConnectorKernelId, + pub output_id: Rc, pub connection: ConnectorStatus, pub mm_width: u32, pub mm_height: u32, pub _subpixel: u32, - - pub connector_type: ConnectorType, - pub connector_type_id: u32, } impl ConnectorDisplayData { - fn is_same_monitor(&self, other: &Self) -> bool { - self.monitor_manufacturer == other.monitor_manufacturer - && self.monitor_name == other.monitor_name - && self.monitor_serial_number == other.monitor_serial_number - } - fn should_enable_vrr(&self) -> bool { - self.vrr_requested && self.vrr_capable + self.persistent.vrr_requested.get() && self.vrr_capable } } @@ -1276,11 +1274,7 @@ impl Connector for MetalConnector { } fn kernel_id(&self) -> ConnectorKernelId { - let dd = self.display.borrow_mut(); - ConnectorKernelId { - ty: dd.connector_type, - idx: dd.connector_type_id, - } + self.display.borrow().connector_id } fn event(&self) -> Option { @@ -1349,6 +1343,8 @@ impl Connector for MetalConnector { return; }; log::info!("Trying to change mode from {:?} to {:?}", prev, mode); + let persistent = dd.persistent.clone(); + *persistent.mode.borrow_mut() = Some(mode.clone()); dd.mode = Some(mode.clone()); drop(dd); let Err(e) = self.backend.handle_drm_change_(&dev, true) else { @@ -1356,6 +1352,7 @@ impl Connector for MetalConnector { return; }; log::warn!("Could not change mode: {}", ErrorFmt(&e)); + *persistent.mode.borrow_mut() = prev.clone(); self.display.borrow_mut().mode = prev; if let MetalError::Modeset(DrmError::Atomic(OsError(c::EACCES))) = e { log::warn!("Failed due to access denied. Resetting in memory only."); @@ -1396,7 +1393,7 @@ impl Connector for MetalConnector { } let dd = &mut *self.display.borrow_mut(); let old_enabled = dd.should_enable_vrr(); - dd.vrr_requested = enabled; + dd.persistent.vrr_requested.set(enabled); let new_enabled = dd.should_enable_vrr(); if old_enabled == new_enabled { return; @@ -1608,13 +1605,10 @@ fn create_connector_display_data( let mut name = String::new(); let mut manufacturer = String::new(); let mut serial_number = String::new(); - let mode = info.modes.first().cloned(); - let refresh = mode - .as_ref() - .map(|m| 1_000_000_000_000u64 / (m.refresh_rate_millihz() as u64)) - .unwrap_or(0) as u32; - let connector_type = ConnectorType::from_drm(info.connector_type); - let connector_name = debug_fn(|f| write!(f, "{}-{}", connector_type, info.connector_type_id)); + let connector_id = ConnectorKernelId { + ty: ConnectorType::from_drm(info.connector_type), + idx: info.connector_type_id, + }; 'fetch_edid: { if connection != ConnectorStatus::Connected { break 'fetch_edid; @@ -1624,7 +1618,7 @@ fn create_connector_display_data( _ => { log::warn!( "Connector {} is connected but has no EDID blob", - connector_name, + connector_id, ); break 'fetch_edid; } @@ -1634,7 +1628,7 @@ fn create_connector_display_data( Err(e) => { log::error!( "Could not fetch edid property of connector {}: {}", - connector_name, + connector_id, ErrorFmt(e) ); break 'fetch_edid; @@ -1645,7 +1639,7 @@ fn create_connector_display_data( Err(e) => { log::error!( "Could not parse edid property of connector {}: {}", - connector_name, + connector_id, ErrorFmt(e) ); break 'fetch_edid; @@ -1666,43 +1660,76 @@ fn create_connector_display_data( if name.is_empty() { log::warn!( "The display attached to connector {} does not have a product name descriptor", - connector_name, + connector_id, ); } if serial_number.is_empty() { log::warn!( "The display attached to connector {} does not have a serial number descriptor", - connector_name, + connector_id, ); serial_number = edid.base_block.id_serial_number.to_string(); } } - let props = collect_properties(&dev.master, connector)?; - let connector_type = ConnectorType::from_drm(info.connector_type); + let output_id = Rc::new(OutputId::new( + connector_id.to_string(), + manufacturer, + name, + serial_number, + )); + let desired_state = match dev.backend.persistent_display_data.get(&output_id) { + Some(ds) => { + log::info!("Reusing desired state for {:?}", output_id); + ds + } + None => { + let ds = Rc::new(PersistentDisplayData { + mode: RefCell::new(info.modes.first().cloned()), + vrr_requested: Default::default(), + }); + dev.backend + .persistent_display_data + .set(output_id.clone(), ds.clone()); + ds + } + }; + let mut mode_opt = desired_state.mode.borrow_mut(); + if let Some(mode) = &*mode_opt { + if !info.modes.contains(mode) { + log::warn!("Discarding previously desired mode"); + *mode_opt = None; + } + } + if mode_opt.is_none() { + *mode_opt = info.modes.first().cloned(); + } + let refresh = mode_opt + .as_ref() + .map(|m| 1_000_000_000_000u64 / (m.refresh_rate_millihz() as u64)) + .unwrap_or(0) as u32; let non_desktop = props.get("non-desktop")?.value.get() != 0; let vrr_capable = match props.get("vrr_capable") { Ok(c) => c.value.get() == 1, Err(_) => false, }; + let mode = mode_opt.clone(); + drop(mode_opt); Ok(ConnectorDisplayData { crtc_id: props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)), crtcs, modes: info.modes, mode, + persistent: desired_state, refresh, non_desktop, non_desktop_effective: non_desktop_override.unwrap_or(non_desktop), vrr_capable, - vrr_requested: false, - monitor_manufacturer: manufacturer, - monitor_name: name, - monitor_serial_number: serial_number, connection, mm_width: info.mm_width, mm_height: info.mm_height, _subpixel: info.subpixel, - connector_type, - connector_type_id: info.connector_type_id, + connector_id, + output_id, }) } @@ -2015,14 +2042,6 @@ impl MetalBackend { } }; let mut old = c.display.borrow_mut(); - if old.is_same_monitor(&dd) { - if let Some(mode) = &old.mode { - if dd.modes.contains(mode) { - dd.mode = Some(mode.clone()); - } - } - dd.vrr_requested = old.vrr_requested; - } mem::swap(old.deref_mut(), &mut dd); match c.frontend_state.get() { FrontState::Removed | FrontState::Disconnected => {} @@ -2042,7 +2061,7 @@ impl MetalBackend { // Disconnect if the connector is no longer connected. disconnect |= old.connection != ConnectorStatus::Connected; // Disconnect if the connected monitor changed. - disconnect |= !old.is_same_monitor(&dd); + disconnect |= old.output_id != dd.output_id; } if disconnect { c.tearing_requested.set(false); @@ -2103,9 +2122,7 @@ impl MetalBackend { } connector.send_event(ConnectorEvent::Connected(MonitorInfo { modes, - manufacturer: dd.monitor_manufacturer.clone(), - product: dd.monitor_name.clone(), - serial_number: dd.monitor_serial_number.clone(), + output_id: dd.output_id.clone(), initial_mode: dd.mode.clone().unwrap().to_backend(), width_mm: dd.mm_width as _, height_mm: dd.mm_height as _, @@ -3047,8 +3064,8 @@ impl MetalBackend { } fn start_connector(&self, connector: &Rc, log_mode: bool) { - let dd = connector.display.borrow_mut(); - self.send_connected(connector, &dd); + let dd = &*connector.display.borrow(); + self.send_connected(connector, dd); match connector.frontend_state.get() { FrontState::Connected { non_desktop: false } => {} FrontState::Connected { non_desktop: true } @@ -3058,9 +3075,8 @@ impl MetalBackend { } if log_mode { log::info!( - "Initialized connector {}-{} with mode {:?}", - dd.connector_type, - dd.connector_type_id, + "Initialized connector {} with mode {:?}", + dd.connector_id, dd.mode.as_ref().unwrap(), ); } diff --git a/src/backends/x.rs b/src/backends/x.rs index 082b89e5..9b1b54ed 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -11,6 +11,7 @@ use { fixed::Fixed, format::XRGB8888, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, + ifs::wl_output::OutputId, renderer::RenderResult, state::State, utils::{ @@ -565,9 +566,12 @@ impl XBackend { .push(BackendEvent::NewConnector(output.clone())); output.events.push(ConnectorEvent::Connected(MonitorInfo { modes: vec![], - manufacturer: "X.Org Foundation".to_string(), - product: format!("X-Window-{}", output.window), - serial_number: output.window.to_string(), + output_id: Rc::new(OutputId::new( + String::new(), + "X.Org Foundation".to_string(), + format!("X-Window-{}", output.window), + output.window.to_string(), + )), initial_mode: Mode { width: output.width.get(), height: output.height.get(), diff --git a/src/compositor.rs b/src/compositor.rs index d41ff96c..7bfd478a 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -425,7 +425,7 @@ fn init_fd_limit() { fn create_dummy_output(state: &Rc) { let output_id = Rc::new(OutputId { - connector: "jay-dummy-connector".to_string(), + connector: Some("jay-dummy-connector".to_string()), manufacturer: "jay".to_string(), model: "jay-dummy-output".to_string(), serial_number: "".to_string(), diff --git a/src/config/handler.rs b/src/config/handler.rs index a21fcea9..a991fc3d 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -973,7 +973,7 @@ impl ConfigProxyHandler { fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_output(connector)?; self.respond(Response::GetConnectorModel { - model: connector.monitor_info.product.clone(), + model: connector.monitor_info.output_id.model.clone(), }); Ok(()) } @@ -981,7 +981,7 @@ impl ConfigProxyHandler { fn handle_connector_manufacturer(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_output(connector)?; self.respond(Response::GetConnectorManufacturer { - manufacturer: connector.monitor_info.manufacturer.clone(), + manufacturer: connector.monitor_info.output_id.manufacturer.clone(), }); Ok(()) } @@ -989,7 +989,7 @@ impl ConfigProxyHandler { fn handle_connector_serial_number(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_output(connector)?; self.respond(Response::GetConnectorSerialNumber { - serial_number: connector.monitor_info.serial_number.clone(), + serial_number: connector.monitor_info.output_id.serial_number.clone(), }); Ok(()) } diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 76fffcd9..3906736e 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -80,9 +80,9 @@ impl JayRandr { None => { self.client.event(NonDesktopOutput { self_id: self.id, - manufacturer: &output.monitor_info.manufacturer, - product: &output.monitor_info.product, - serial_number: &output.monitor_info.serial_number, + manufacturer: &output.monitor_info.output_id.manufacturer, + product: &output.monitor_info.output_id.model, + serial_number: &output.monitor_info.output_id.serial_number, width_mm: output.monitor_info.width_mm, height_mm: output.monitor_info.height_mm, }); @@ -99,9 +99,9 @@ impl JayRandr { x: pos.x1(), y: pos.y1(), transform: global.persistent.transform.get().to_wl(), - manufacturer: &output.monitor_info.manufacturer, - product: &output.monitor_info.product, - serial_number: &output.monitor_info.serial_number, + manufacturer: &output.monitor_info.output_id.manufacturer, + product: &output.monitor_info.output_id.model, + serial_number: &output.monitor_info.output_id.serial_number, width_mm: global.width_mm, height_mm: global.height_mm, }); diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 74069f02..3ac09c68 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -96,14 +96,30 @@ pub struct PersistentOutputState { pub tearing_mode: Cell<&'static TearingMode>, } -#[derive(Eq, PartialEq, Hash)] +#[derive(Eq, PartialEq, Hash, Debug)] pub struct OutputId { - pub connector: String, + pub connector: Option, pub manufacturer: String, pub model: String, pub serial_number: String, } +impl OutputId { + pub fn new( + connector: String, + manufacturer: String, + model: String, + serial_number: String, + ) -> Self { + Self { + connector: serial_number.is_empty().then_some(connector), + manufacturer, + model, + serial_number, + } + } +} + impl WlOutputGlobal { pub fn clear(&self) { self.opt.clear(); diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index ded416bb..300387ce 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -13,6 +13,7 @@ use { fixed::Fixed, gfx_api::GfxError, gfx_apis::create_vulkan_allocator, + ifs::wl_output::OutputId, it::{ test_error::TestResult, test_gfx_api::TestGfxCtx, test_utils::test_expected_event::TEEH, }, @@ -115,9 +116,12 @@ impl TestBackend { }; let default_monitor_info = MonitorInfo { modes: vec![mode], - manufacturer: "jay".to_string(), - product: "TestConnector".to_string(), - serial_number: default_connector.id.to_string(), + output_id: Rc::new(OutputId { + connector: None, + manufacturer: "jay".to_string(), + model: "TestConnector".to_string(), + serial_number: default_connector.id.to_string(), + }), initial_mode: mode, width_mm: 80, height_mm: 60, diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 2bf9ce82..10098aab 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -1,6 +1,7 @@ use { crate::{ backend::{BackendEvent, ConnectorEvent, ConnectorKernelId, Mode, MonitorInfo}, + ifs::wl_output::OutputId, it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun}, video::drm::ConnectorType, }, @@ -32,9 +33,12 @@ async fn test(run: Rc) -> TestResult { }); let new_monitor_info = MonitorInfo { modes: vec![], - manufacturer: "jay".to_string(), - product: "jay second connector".to_string(), - serial_number: "".to_string(), + output_id: Rc::new(OutputId { + connector: None, + manufacturer: "jay".to_string(), + model: "jay second connector".to_string(), + serial_number: "".to_string(), + }), initial_mode: Mode { width: 400, height: 400, diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index ff1c839c..398ac710 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -2,7 +2,7 @@ use { crate::{ backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}, globals::GlobalName, - ifs::wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, + ifs::wl_output::{PersistentOutputState, WlOutputGlobal}, output_schedule::OutputSchedule, state::{ConnectorData, OutputData, State}, tree::{move_ws_to_output, OutputNode, OutputRenderData, WsMoveConfig}, @@ -86,27 +86,17 @@ impl ConnectorHandler { log::info!("Connector {} connected", self.data.connector.kernel_id()); self.data.connected.set(true); let name = self.state.globals.name(); - let output_id = Rc::new(OutputId { - connector: self.data.name.clone(), - manufacturer: info.manufacturer.clone(), - model: info.product.clone(), - serial_number: info.serial_number.clone(), - }); if info.non_desktop { self.handle_non_desktop_connected(info).await; } else { - self.handle_desktop_connected(info, name, output_id).await; + self.handle_desktop_connected(info, name).await; } self.data.connected.set(false); log::info!("Connector {} disconnected", self.data.connector.kernel_id()); } - async fn handle_desktop_connected( - &self, - info: MonitorInfo, - name: GlobalName, - output_id: Rc, - ) { + async fn handle_desktop_connected(&self, info: MonitorInfo, name: GlobalName) { + let output_id = info.output_id.clone(); let desired_state = match self.state.persistent_output_states.get(&output_id) { Some(ds) => ds, _ => { From b4ca15fec0867958d8646aad6e9e781a28b075e3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 4 Sep 2024 19:56:01 +0200 Subject: [PATCH 3/3] 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); + 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 buffers = Rc::new(self.create_scanout_buffers( - &connector.dev, - XRGB8888, - primary_modifiers, - mode.hdisplay as _, - mode.vdisplay as _, - ctx, - false, - )?); + 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) + ); + } + } + } + (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, +}