diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-03-15 08:54:36 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-03-15 13:29:36 +0400 |
| commit | 6839a118bb691fc022c8ac9e2564717ae995b5ce (patch) | |
| tree | 5671dcf6fcf9d7d83f9d3d396a6286df01746be1 /src/backend | |
| parent | 9ae3cad82b6dfe901b40510a3d6ff0eb655a11df (diff) | |
| download | niri-6839a118bb691fc022c8ac9e2564717ae995b5ce.tar.gz niri-6839a118bb691fc022c8ac9e2564717ae995b5ce.tar.bz2 niri-6839a118bb691fc022c8ac9e2564717ae995b5ce.zip | |
Implement gamma adjustment via GAMMA_LUT property
Diffstat (limited to 'src/backend')
| -rw-r--r-- | src/backend/tty.rs | 212 |
1 files changed, 196 insertions, 16 deletions
diff --git a/src/backend/tty.rs b/src/backend/tty.rs index 6ac84ca5..4cc45b0c 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -2,6 +2,8 @@ use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::iter::zip; +use std::num::NonZeroU64; +use std::os::fd::AsFd; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::path::Path; use std::rc::Rc; @@ -10,6 +12,7 @@ use std::time::Duration; use std::{io, mem}; use anyhow::{anyhow, bail, ensure, Context}; +use bytemuck::cast_slice_mut; use libc::dev_t; use niri_config::Config; use smithay::backend::allocator::dmabuf::Dmabuf; @@ -165,6 +168,7 @@ struct Surface { name: String, compositor: GbmDrmCompositor, dmabuf_feedback: Option<SurfaceDmabufFeedback>, + gamma_props: Option<GammaProps>, /// Tracy frame that goes from vblank to vblank. vblank_frame: Option<tracy_client::Frame>, /// Frame name for the VBlank frame. @@ -181,6 +185,13 @@ pub struct SurfaceDmabufFeedback { pub scanout: DmabufFeedback, } +struct GammaProps { + crtc: crtc::Handle, + gamma_lut: property::Handle, + gamma_lut_size: property::Handle, + previous_blob: Option<NonZeroU64>, +} + impl Tty { pub fn new( config: Rc<RefCell<Config>>, @@ -703,8 +714,17 @@ impl Tty { Err(err) => debug!("error setting max bpc: {err:?}"), } - // Reset gamma to linear in case it was set before. - if let Err(err) = set_gamma_for_crtc(&device.drm, crtc, None) { + let mut gamma_props = GammaProps::new(&device.drm, crtc) + .map_err(|err| debug!("error getting gamma properties: {err:?}")) + .ok(); + + // Reset gamma in case it was set before. + let res = if let Some(gamma_props) = &mut gamma_props { + gamma_props.set_gamma(&device.drm, None) + } else { + set_gamma_for_crtc(&device.drm, crtc, None) + }; + if let Err(err) = res { debug!("error resetting gamma: {err:?}"); } @@ -818,6 +838,7 @@ impl Tty { name: output_name.clone(), compositor, dmabuf_feedback, + gamma_props, vblank_frame: None, vblank_frame_name, time_since_presentation_plot_name, @@ -1258,20 +1279,34 @@ impl Tty { .devices .get(&tty_state.node) .context("missing device")?; - let info = device - .drm - .get_crtc(crtc) - .context("error getting crtc info")?; - Ok(info.gamma_length()) + + let surface = device.surfaces.get(&crtc).context("missing surface")?; + if let Some(gamma_props) = &surface.gamma_props { + gamma_props.gamma_size(&device.drm) + } else { + let info = device + .drm + .get_crtc(crtc) + .context("error getting crtc info")?; + Ok(info.gamma_length()) + } } - pub fn set_gamma(&self, output: &Output, ramp: Option<&[u16]>) -> anyhow::Result<()> { + pub fn set_gamma(&mut self, output: &Output, ramp: Option<&[u16]>) -> anyhow::Result<()> { let tty_state = output.user_data().get::<TtyOutputState>().unwrap(); + let crtc = tty_state.crtc; + let device = self .devices - .get(&tty_state.node) + .get_mut(&tty_state.node) .context("missing device")?; - set_gamma_for_crtc(&device.drm, tty_state.crtc, ramp) + + let surface = device.surfaces.get_mut(&crtc).context("missing surface")?; + if let Some(gamma_props) = &mut surface.gamma_props { + gamma_props.set_gamma(&device.drm, ramp) + } else { + set_gamma_for_crtc(&device.drm, crtc, ramp) + } } fn refresh_ipc_outputs(&self) { @@ -1525,6 +1560,130 @@ impl Tty { } } +impl GammaProps { + fn new(device: &DrmDevice, crtc: crtc::Handle) -> anyhow::Result<Self> { + let mut gamma_lut = None; + let mut gamma_lut_size = None; + + let props = device + .get_properties(crtc) + .context("error getting properties")?; + for (prop, _) in props { + let Ok(info) = device.get_property(prop) else { + continue; + }; + + let Ok(name) = info.name().to_str() else { + continue; + }; + + match name { + "GAMMA_LUT" => { + ensure!( + matches!(info.value_type(), property::ValueType::Blob), + "wrong GAMMA_LUT value type" + ); + gamma_lut = Some(prop); + } + "GAMMA_LUT_SIZE" => { + ensure!( + matches!(info.value_type(), property::ValueType::UnsignedRange(_, _)), + "wrong GAMMA_LUT_SIZE value type" + ); + gamma_lut_size = Some(prop); + } + _ => (), + } + } + + let gamma_lut = gamma_lut.context("missing GAMMA_LUT property")?; + let gamma_lut_size = gamma_lut_size.context("missing GAMMA_LUT_SIZE property")?; + + Ok(Self { + crtc, + gamma_lut, + gamma_lut_size, + previous_blob: None, + }) + } + + fn gamma_size(&self, device: &DrmDevice) -> anyhow::Result<u32> { + let value = get_drm_property(device, self.crtc, self.gamma_lut_size) + .context("missing GAMMA_LUT_SIZE property")?; + Ok(value as u32) + } + + fn set_gamma(&mut self, device: &DrmDevice, gamma: Option<&[u16]>) -> anyhow::Result<()> { + let _span = tracy_client::span!("GammaProps::set_gamma"); + + let blob = if let Some(gamma) = gamma { + let gamma_size = self + .gamma_size(device) + .context("error getting gamma size")? as usize; + + ensure!(gamma.len() == gamma_size * 3, "wrong gamma length"); + + #[allow(non_camel_case_types)] + #[repr(C)] + #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] + pub struct drm_color_lut { + pub red: u16, + pub green: u16, + pub blue: u16, + pub reserved: u16, + } + + let (red, rest) = gamma.split_at(gamma_size); + let (blue, green) = rest.split_at(gamma_size); + let mut data = zip(zip(red, blue), green) + .map(|((&red, &green), &blue)| drm_color_lut { + red, + green, + blue, + reserved: 0, + }) + .collect::<Vec<_>>(); + let data = cast_slice_mut(&mut data); + + let blob = drm_ffi::mode::create_property_blob(device.as_fd(), data) + .context("error creating property blob")?; + NonZeroU64::new(u64::from(blob.blob_id)) + } else { + None + }; + + { + let _span = tracy_client::span!("set_property"); + + let blob = blob.map(NonZeroU64::get).unwrap_or(0); + device + .set_property( + self.crtc, + self.gamma_lut, + property::Value::Blob(blob).into(), + ) + .context("error setting GAMMA_LUT") + .map_err(|err| { + if blob != 0 { + // Destroy the blob we just allocated. + if let Err(err) = device.destroy_property_blob(blob) { + warn!("error destroying GAMMA_LUT property blob: {err:?}"); + } + } + err + })?; + } + + if let Some(blob) = mem::replace(&mut self.previous_blob, blob) { + if let Err(err) = device.destroy_property_blob(blob.get()) { + warn!("error destroying previous GAMMA_LUT blob: {err:?}"); + } + } + + Ok(()) + } +} + fn primary_node_from_config(config: &Config) -> Option<(DrmNode, DrmNode)> { let path = config.debug.render_drm_device.as_ref()?; debug!("attempting to use render node from config: {path:?}"); @@ -1606,7 +1765,11 @@ fn surface_dmabuf_feedback( Ok(SurfaceDmabufFeedback { render, scanout }) } -fn find_drm_property(drm: &DrmDevice, crtc: crtc::Handle, name: &str) -> Option<property::Handle> { +fn find_drm_property( + drm: &DrmDevice, + crtc: crtc::Handle, + name: &str, +) -> Option<(property::Handle, property::RawValue)> { let props = match drm.get_properties(crtc) { Ok(props) => props, Err(err) => { @@ -1615,17 +1778,34 @@ fn find_drm_property(drm: &DrmDevice, crtc: crtc::Handle, name: &str) -> Option< } }; - let (handles, _) = props.as_props_and_values(); - handles.iter().find_map(|handle| { - let info = drm.get_property(*handle).ok()?; + props.into_iter().find_map(|(handle, value)| { + let info = drm.get_property(handle).ok()?; let n = info.name().to_str().ok()?; - (n == name).then_some(*handle) + (n == name).then_some((handle, value)) }) } +fn get_drm_property( + drm: &DrmDevice, + crtc: crtc::Handle, + prop: property::Handle, +) -> Option<property::RawValue> { + let props = match drm.get_properties(crtc) { + Ok(props) => props, + Err(err) => { + warn!("error getting CRTC properties: {err:?}"); + return None; + } + }; + + props + .into_iter() + .find_map(|(handle, value)| (handle == prop).then_some(value)) +} + fn set_crtc_active(drm: &DrmDevice, crtc: crtc::Handle, active: bool) { - let Some(prop) = find_drm_property(drm, crtc, "ACTIVE") else { + let Some((prop, _)) = find_drm_property(drm, crtc, "ACTIVE") else { return; }; |
