Merge pull request #405 from mahkoh/jorth/cm-5
metal: allow configuring color space and transfer function
This commit is contained in:
commit
ec862648c9
42 changed files with 1687 additions and 219 deletions
|
|
@ -25,7 +25,8 @@ use {
|
|||
theme::{Color, colors::Colorable, sized::Resizable},
|
||||
timer::Timer,
|
||||
video::{
|
||||
Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, Transform, VrrMode,
|
||||
ColorSpace, Connector, DrmDevice, Format, GfxApi, Mode, TearingMode, TransferFunction,
|
||||
Transform, VrrMode,
|
||||
connector_type::{CON_UNKNOWN, ConnectorType},
|
||||
},
|
||||
xwayland::XScalingMode,
|
||||
|
|
@ -781,6 +782,19 @@ impl Client {
|
|||
self.send(&ClientMessage::ConnectorSetFormat { connector, format });
|
||||
}
|
||||
|
||||
pub fn connector_set_colors(
|
||||
&self,
|
||||
connector: Connector,
|
||||
color_space: ColorSpace,
|
||||
transfer_function: TransferFunction,
|
||||
) {
|
||||
self.send(&ClientMessage::ConnectorSetColors {
|
||||
connector,
|
||||
color_space,
|
||||
transfer_function,
|
||||
});
|
||||
}
|
||||
|
||||
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 });
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use {
|
|||
theme::{Color, colors::Colorable, sized::Resizable},
|
||||
timer::Timer,
|
||||
video::{
|
||||
Connector, DrmDevice, Format, GfxApi, TearingMode, Transform, VrrMode,
|
||||
connector_type::ConnectorType,
|
||||
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction,
|
||||
Transform, VrrMode, connector_type::ConnectorType,
|
||||
},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
|
|
@ -533,6 +533,11 @@ pub enum ClientMessage<'a> {
|
|||
SetColorManagementEnabled {
|
||||
enabled: bool,
|
||||
},
|
||||
ConnectorSetColors {
|
||||
connector: Connector,
|
||||
color_space: ColorSpace,
|
||||
transfer_function: TransferFunction,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -272,6 +272,19 @@ impl Connector {
|
|||
pub fn set_format(self, format: Format) {
|
||||
get!().connector_set_format(self, format);
|
||||
}
|
||||
|
||||
/// Sets the color space and transfer function of the connector.
|
||||
///
|
||||
/// By default, the default values are used which usually means sRGB color space with
|
||||
/// sRGB transfer function.
|
||||
///
|
||||
/// If the output supports it, HDR10 can be enabled by setting the color space to
|
||||
/// BT.2020 and the transfer function to PQ.
|
||||
///
|
||||
/// Note that some displays might ignore incompatible settings.
|
||||
pub fn set_colors(self, color_space: ColorSpace, transfer_function: TransferFunction) {
|
||||
get!().connector_set_colors(self, color_space, transfer_function);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all available DRM devices.
|
||||
|
|
@ -662,3 +675,25 @@ impl Format {
|
|||
pub const ABGR16161616F: Self = Self(26);
|
||||
pub const XBGR16161616F: Self = Self(27);
|
||||
}
|
||||
|
||||
/// A color space.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ColorSpace(pub u32);
|
||||
|
||||
impl ColorSpace {
|
||||
/// The default color space (usually sRGB).
|
||||
pub const DEFAULT: Self = Self(0);
|
||||
/// The BT.2020 color space.
|
||||
pub const BT2020: Self = Self(1);
|
||||
}
|
||||
|
||||
/// A transfer function.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct TransferFunction(pub u32);
|
||||
|
||||
impl TransferFunction {
|
||||
/// The default transfer function (usually sRGB).
|
||||
pub const DEFAULT: Self = Self(0);
|
||||
/// The PQ transfer function.
|
||||
pub const PQ: Self = Self(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
observable. This should have no impact on performance in the common case.
|
||||
- Implement color-management-v1.
|
||||
- Implement cursor-shape-v1 version 2.
|
||||
- Outputs can now optionally use the BT.2020/PQ color space.
|
||||
|
||||
# 1.9.1 (2025-02-13)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
cmm::cmm_primaries::Primaries,
|
||||
drm_feedback::DrmFeedback,
|
||||
fixed::Fixed,
|
||||
format::Format,
|
||||
|
|
@ -17,9 +18,14 @@ use {
|
|||
},
|
||||
},
|
||||
libinput::consts::DeviceCapability,
|
||||
video::drm::{ConnectorType, DrmConnector, DrmError, DrmVersion},
|
||||
video::drm::{
|
||||
ConnectorType, DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT,
|
||||
DrmConnector, DrmError, DrmVersion, HDMI_EOTF_SMPTE_ST2084,
|
||||
HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
|
||||
},
|
||||
},
|
||||
jay_config::{input::SwitchEvent, video::GfxApi},
|
||||
linearize::Linearize,
|
||||
std::{
|
||||
any::Any,
|
||||
error::Error,
|
||||
|
|
@ -83,6 +89,12 @@ pub struct MonitorInfo {
|
|||
pub height_mm: i32,
|
||||
pub non_desktop: bool,
|
||||
pub vrr_capable: bool,
|
||||
pub transfer_functions: Vec<BackendTransferFunction>,
|
||||
pub transfer_function: BackendTransferFunction,
|
||||
pub color_spaces: Vec<BackendColorSpace>,
|
||||
pub color_space: BackendColorSpace,
|
||||
pub primaries: Primaries,
|
||||
pub luminance: Option<BackendLuminance>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
|
@ -129,6 +141,10 @@ pub trait Connector {
|
|||
fn set_fb_format(&self, format: &'static Format) {
|
||||
let _ = format;
|
||||
}
|
||||
fn set_colors(&self, bcs: BackendColorSpace, btf: BackendTransferFunction) {
|
||||
let _ = bcs;
|
||||
let _ = btf;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -142,6 +158,7 @@ pub enum ConnectorEvent {
|
|||
Available,
|
||||
VrrChanged(bool),
|
||||
FormatsChanged(Rc<Vec<&'static Format>>, &'static Format),
|
||||
ColorsChanged(BackendColorSpace, BackendTransferFunction),
|
||||
}
|
||||
|
||||
pub trait HardwareCursorUpdate {
|
||||
|
|
@ -477,3 +494,56 @@ pub trait BackendDrmLease {
|
|||
pub trait BackendDrmLessee {
|
||||
fn created(&self, lease: Rc<dyn BackendDrmLease>);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)]
|
||||
pub enum BackendTransferFunction {
|
||||
#[default]
|
||||
Default,
|
||||
Pq,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)]
|
||||
pub enum BackendColorSpace {
|
||||
#[default]
|
||||
Default,
|
||||
Bt2020,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct BackendLuminance {
|
||||
pub min: f64,
|
||||
pub max: f64,
|
||||
pub max_fall: f64,
|
||||
}
|
||||
|
||||
impl BackendTransferFunction {
|
||||
pub fn to_drm(self) -> u8 {
|
||||
match self {
|
||||
BackendTransferFunction::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
|
||||
BackendTransferFunction::Pq => HDMI_EOTF_SMPTE_ST2084,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
BackendTransferFunction::Default => "default",
|
||||
BackendTransferFunction::Pq => "pq",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendColorSpace {
|
||||
pub fn to_drm(self) -> u64 {
|
||||
match self {
|
||||
BackendColorSpace::Default => DRM_MODE_COLORIMETRY_DEFAULT,
|
||||
BackendColorSpace::Bt2020 => DRM_MODE_COLORIMETRY_BT2020_RGB,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
BackendColorSpace::Default => "default",
|
||||
BackendColorSpace::Bt2020 => "bt2020",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use {
|
|||
MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer,
|
||||
},
|
||||
},
|
||||
cmm::cmm_description::ColorDescription,
|
||||
gfx_api::{
|
||||
AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile,
|
||||
create_render_pass,
|
||||
|
|
@ -176,10 +177,13 @@ impl MetalConnector {
|
|||
};
|
||||
let buffer = &buffers[self.next_buffer.get() % buffers.len()];
|
||||
|
||||
let cd = node.global.color_description.get();
|
||||
let linear_cd = node.global.linear_color_description.get();
|
||||
|
||||
if self.has_damage.get() > 0 || self.cursor_damage.get() {
|
||||
node.schedule.commit_cursor();
|
||||
}
|
||||
self.latch_cursor(&node)?;
|
||||
self.latch_cursor(&node, &cd)?;
|
||||
let cursor_programming = self.compute_cursor_programming();
|
||||
let latched = self.latch(&node, buffer);
|
||||
node.latched(self.try_async_flip());
|
||||
|
|
@ -191,11 +195,11 @@ impl MetalConnector {
|
|||
let mut present_fb = None;
|
||||
let mut direct_scanout_id = None;
|
||||
if let Some(latched) = &latched {
|
||||
let fb = self.prepare_present_fb(buffer, &plane, latched, true)?;
|
||||
let fb = self.prepare_present_fb(&cd, &linear_cd, buffer, &plane, latched, true)?;
|
||||
direct_scanout_id = fb.direct_scanout_data.as_ref().map(|d| d.dma_buf_id);
|
||||
present_fb = Some(fb);
|
||||
}
|
||||
self.perform_screencopies(&present_fb, &node);
|
||||
self.perform_screencopies(&present_fb, &node, &cd);
|
||||
if let Some(sync_file) = self.cursor_sync_file.take() {
|
||||
if let Err(e) = self.state.ring.readable(&sync_file).await {
|
||||
log::error!(
|
||||
|
|
@ -214,8 +218,14 @@ impl MetalConnector {
|
|||
);
|
||||
if res.is_err() {
|
||||
if let Some(dsd_id) = direct_scanout_id {
|
||||
let fb =
|
||||
self.prepare_present_fb(buffer, &plane, latched.as_ref().unwrap(), false)?;
|
||||
let fb = self.prepare_present_fb(
|
||||
&cd,
|
||||
&linear_cd,
|
||||
buffer,
|
||||
&plane,
|
||||
latched.as_ref().unwrap(),
|
||||
false,
|
||||
)?;
|
||||
present_fb = Some(fb);
|
||||
self.await_present_fb(present_fb.as_mut()).await;
|
||||
res = self.program_connector(
|
||||
|
|
@ -432,7 +442,11 @@ impl MetalConnector {
|
|||
res.map_err(MetalError::Commit)
|
||||
}
|
||||
|
||||
fn latch_cursor(&self, node: &Rc<OutputNode>) -> Result<(), MetalError> {
|
||||
fn latch_cursor(
|
||||
&self,
|
||||
node: &Rc<OutputNode>,
|
||||
cd: &Rc<ColorDescription>,
|
||||
) -> Result<(), MetalError> {
|
||||
if !self.cursor_damage.take() {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -451,9 +465,7 @@ impl MetalConnector {
|
|||
};
|
||||
self.state.present_hardware_cursor(node, &mut c);
|
||||
if c.cursor_swap_buffer {
|
||||
c.sync_file = c
|
||||
.cursor_buffer
|
||||
.copy_to_dev(&self.state.color_manager, c.sync_file)?;
|
||||
c.sync_file = c.cursor_buffer.copy_to_dev(cd, c.sync_file)?;
|
||||
}
|
||||
self.cursor_swap_buffer.set(c.cursor_swap_buffer);
|
||||
if c.sync_file.is_some() {
|
||||
|
|
@ -544,6 +556,7 @@ impl MetalConnector {
|
|||
&self,
|
||||
pass: &GfxRenderPass,
|
||||
plane: &Rc<MetalPlane>,
|
||||
cd: &Rc<ColorDescription>,
|
||||
) -> Option<DirectScanoutData> {
|
||||
let ct = 'ct: {
|
||||
let mut ops = pass.ops.iter().rev();
|
||||
|
|
@ -560,8 +573,8 @@ impl MetalConnector {
|
|||
}
|
||||
return None;
|
||||
};
|
||||
if ct.cd.id != self.state.color_manager.srgb_srgb().id {
|
||||
// Direct scanout requires identical color descriptions.
|
||||
if !ct.cd.embeds_into(cd) {
|
||||
// Direct scanout requires embeddable color descriptions.
|
||||
return None;
|
||||
}
|
||||
if ct.alpha.is_some() {
|
||||
|
|
@ -717,6 +730,8 @@ impl MetalConnector {
|
|||
|
||||
fn prepare_present_fb(
|
||||
&self,
|
||||
cd: &Rc<ColorDescription>,
|
||||
linear_cd: &Rc<ColorDescription>,
|
||||
buffer: &RenderBuffer,
|
||||
plane: &Rc<MetalPlane>,
|
||||
latched: &Latched,
|
||||
|
|
@ -733,7 +748,7 @@ impl MetalConnector {
|
|||
&& self.dev.is_render_device();
|
||||
let mut direct_scanout_data = None;
|
||||
if try_direct_scanout {
|
||||
direct_scanout_data = self.prepare_direct_scanout(&latched.pass, plane);
|
||||
direct_scanout_data = self.prepare_direct_scanout(&latched.pass, plane, cd);
|
||||
}
|
||||
let direct_scanout_active = direct_scanout_data.is_some();
|
||||
if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active {
|
||||
|
|
@ -753,14 +768,14 @@ impl MetalConnector {
|
|||
.perform_render_pass(
|
||||
AcquireSync::Unnecessary,
|
||||
ReleaseSync::Explicit,
|
||||
self.state.color_manager.srgb_srgb(),
|
||||
cd,
|
||||
&latched.pass,
|
||||
&latched.damage,
|
||||
buffer.blend_buffer.as_ref(),
|
||||
self.state.color_manager.srgb_linear(),
|
||||
linear_cd,
|
||||
)
|
||||
.map_err(MetalError::RenderFrame)?;
|
||||
sync_file = buffer.copy_to_dev(&self.state.color_manager, sf)?;
|
||||
sync_file = buffer.copy_to_dev(cd, sf)?;
|
||||
fb = buffer.drm.clone();
|
||||
tex = buffer.render_tex.clone();
|
||||
}
|
||||
|
|
@ -783,7 +798,12 @@ impl MetalConnector {
|
|||
})
|
||||
}
|
||||
|
||||
fn perform_screencopies(&self, new_fb: &Option<PresentFb>, output: &OutputNode) {
|
||||
fn perform_screencopies(
|
||||
&self,
|
||||
new_fb: &Option<PresentFb>,
|
||||
output: &OutputNode,
|
||||
cd: &Rc<ColorDescription>,
|
||||
) {
|
||||
let active_fb;
|
||||
let fb = match &new_fb {
|
||||
Some(f) => f,
|
||||
|
|
@ -800,7 +820,7 @@ impl MetalConnector {
|
|||
None => {
|
||||
output.perform_screencopies(
|
||||
&fb.tex,
|
||||
self.state.color_manager.srgb_srgb(),
|
||||
cd,
|
||||
None,
|
||||
&AcquireSync::Unnecessary,
|
||||
ReleaseSync::None,
|
||||
|
|
@ -813,7 +833,7 @@ impl MetalConnector {
|
|||
Some(dsd) => {
|
||||
output.perform_screencopies(
|
||||
&dsd.tex,
|
||||
self.state.color_manager.srgb_srgb(),
|
||||
cd,
|
||||
dsd.resv.as_ref(),
|
||||
&dsd.acquire_sync,
|
||||
dsd.release_sync,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ use {
|
|||
allocator::BufferObject,
|
||||
async_engine::{Phase, SpawnedFuture},
|
||||
backend::{
|
||||
BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, Connector,
|
||||
ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor,
|
||||
HardwareCursorUpdate, Mode, MonitorInfo,
|
||||
BackendColorSpace, BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent,
|
||||
BackendLuminance, BackendTransferFunction, Connector, ConnectorEvent, ConnectorId,
|
||||
ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate, Mode,
|
||||
MonitorInfo,
|
||||
},
|
||||
backends::metal::{
|
||||
MetalBackend, MetalError,
|
||||
|
|
@ -14,7 +15,7 @@ use {
|
|||
POST_COMMIT_MARGIN_DELTA, PresentFb,
|
||||
},
|
||||
},
|
||||
cmm::cmm_manager::ColorManager,
|
||||
cmm::{cmm_description::ColorDescription, cmm_primaries::Primaries},
|
||||
drm_feedback::DrmFeedback,
|
||||
edid::{CtaDataBlock, Descriptor, EdidExtension},
|
||||
format::{ARGB8888, Format, XRGB8888},
|
||||
|
|
@ -33,7 +34,8 @@ use {
|
|||
utils::{
|
||||
asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell,
|
||||
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay,
|
||||
numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, oserror::OsError,
|
||||
numcell::NumCell, on_change::OnChange, opaque_cell::OpaqueCell, ordered_float::F64,
|
||||
oserror::OsError,
|
||||
},
|
||||
video::{
|
||||
INVALID_MODIFIER, Modifier,
|
||||
|
|
@ -43,7 +45,7 @@ use {
|
|||
DRM_MODE_ATOMIC_ALLOW_MODESET, DrmBlob, DrmConnector, DrmCrtc, DrmEncoder,
|
||||
DrmError, DrmEvent, DrmFramebuffer, DrmLease, DrmMaster, DrmModeInfo, DrmObject,
|
||||
DrmPlane, DrmProperty, DrmPropertyDefinition, DrmPropertyType, DrmVersion,
|
||||
PropBlob, drm_mode_modeinfo,
|
||||
PropBlob, drm_mode_modeinfo, hdr_output_metadata,
|
||||
},
|
||||
gbm::{GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT, GbmBo, GbmDevice},
|
||||
},
|
||||
|
|
@ -64,6 +66,7 @@ use {
|
|||
ops::DerefMut,
|
||||
rc::Rc,
|
||||
},
|
||||
thiserror::Error,
|
||||
uapi::{
|
||||
OwnedFd,
|
||||
c::{self, dev_t},
|
||||
|
|
@ -322,6 +325,8 @@ pub struct PersistentDisplayData {
|
|||
pub mode: RefCell<Option<DrmModeInfo>>,
|
||||
pub vrr_requested: Cell<bool>,
|
||||
pub format: Cell<&'static Format>,
|
||||
pub eotf: Cell<BackendTransferFunction>,
|
||||
pub color_space: Cell<BackendColorSpace>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -346,6 +351,15 @@ pub struct ConnectorDisplayData {
|
|||
pub mm_width: u32,
|
||||
pub mm_height: u32,
|
||||
pub _subpixel: u32,
|
||||
|
||||
pub supports_bt2020: bool,
|
||||
pub supports_pq: bool,
|
||||
pub primaries: Primaries,
|
||||
pub luminance: Option<BackendLuminance>,
|
||||
|
||||
pub colorspace: Option<MutableProperty<u64>>,
|
||||
pub hdr_metadata: Option<MutableProperty<DrmBlob>>,
|
||||
pub hdr_metadata_blob: Option<PropBlob>,
|
||||
}
|
||||
|
||||
impl ConnectorDisplayData {
|
||||
|
|
@ -653,6 +667,21 @@ impl MetalConnector {
|
|||
|
||||
pub fn send_event(&self, event: ConnectorEvent) {
|
||||
let state = self.frontend_state.get();
|
||||
macro_rules! desktop_event {
|
||||
($name:expr) => {
|
||||
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 {} event in invalid state: {state:?}", $name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
match &event {
|
||||
ConnectorEvent::Connected(ty) => match state {
|
||||
FrontState::Disconnected => {
|
||||
|
|
@ -665,21 +694,12 @@ impl MetalConnector {
|
|||
log::error!("Tried to send connected event in invalid state: {state:?}");
|
||||
}
|
||||
},
|
||||
ConnectorEvent::HardwareCursor(_) | ConnectorEvent::ModeChanged(_) => match state {
|
||||
FrontState::Connected { non_desktop: false } => {
|
||||
self.on_change.send_event(event);
|
||||
}
|
||||
FrontState::Connected { non_desktop: true }
|
||||
| FrontState::Removed
|
||||
| FrontState::Disconnected
|
||||
| FrontState::Unavailable => {
|
||||
let name = match &event {
|
||||
ConnectorEvent::HardwareCursor(_) => "hardware cursor",
|
||||
_ => "mode change",
|
||||
};
|
||||
log::error!("Tried to send {name} event in invalid state: {state:?}");
|
||||
}
|
||||
},
|
||||
ConnectorEvent::HardwareCursor(_) => {
|
||||
desktop_event!("hardware cursor");
|
||||
}
|
||||
ConnectorEvent::ModeChanged(_) => {
|
||||
desktop_event!("mode change");
|
||||
}
|
||||
ConnectorEvent::Disconnected => match state {
|
||||
FrontState::Connected { .. } | FrontState::Unavailable => {
|
||||
self.on_change.send_event(event);
|
||||
|
|
@ -720,28 +740,15 @@ impl MetalConnector {
|
|||
log::error!("Tried to send available event in invalid state: {state:?}");
|
||||
}
|
||||
},
|
||||
ConnectorEvent::VrrChanged(_) => 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 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:?}");
|
||||
}
|
||||
},
|
||||
ConnectorEvent::VrrChanged(_) => {
|
||||
desktop_event!("vrr-changed");
|
||||
}
|
||||
ConnectorEvent::FormatsChanged(_, _) => {
|
||||
desktop_event!("formats-changed");
|
||||
}
|
||||
ConnectorEvent::ColorsChanged(_, _) => {
|
||||
desktop_event!("colors-changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -765,6 +772,55 @@ impl MetalConnector {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn change_property(
|
||||
&self,
|
||||
name: &str,
|
||||
needs_change: impl FnOnce(&ConnectorDisplayData) -> bool,
|
||||
supports_change: impl FnOnce(&ConnectorDisplayData) -> bool,
|
||||
change: impl FnOnce(&ConnectorDisplayData),
|
||||
changed: impl FnOnce(),
|
||||
reset: impl FnOnce(&ConnectorDisplayData),
|
||||
) {
|
||||
match self.frontend_state.get() {
|
||||
FrontState::Connected { non_desktop: false } => {}
|
||||
FrontState::Connected { non_desktop: true }
|
||||
| FrontState::Removed
|
||||
| FrontState::Disconnected
|
||||
| FrontState::Unavailable => return,
|
||||
}
|
||||
let dd = self.display.borrow();
|
||||
if !needs_change(&dd) {
|
||||
return;
|
||||
}
|
||||
if !supports_change(&dd) {
|
||||
return;
|
||||
}
|
||||
if dd.connection != ConnectorStatus::Connected {
|
||||
log::warn!("Cannot change {name} of connector that is not connected");
|
||||
return;
|
||||
}
|
||||
let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) else {
|
||||
log::warn!("Cannot change {name} because underlying device does not exist?");
|
||||
return;
|
||||
};
|
||||
change(&dd);
|
||||
drop(dd);
|
||||
let Err(e) = self.backend.handle_drm_change_(&dev, true) else {
|
||||
changed();
|
||||
return;
|
||||
};
|
||||
log::warn!("Could not change {name}: {}", ErrorFmt(&e));
|
||||
reset(&self.display.borrow());
|
||||
if let MetalError::Modeset(DrmError::Atomic(OsError(c::EACCES))) = e {
|
||||
log::warn!("Failed due to access denied. Resetting in memory only.");
|
||||
return;
|
||||
}
|
||||
log::warn!("Trying to re-initialize the drm device");
|
||||
if let Err(e) = self.backend.handle_drm_change_(&dev, true) {
|
||||
log::warn!("Could not restore the previous {name}: {}", ErrorFmt(e));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Connector for MetalConnector {
|
||||
|
|
@ -942,6 +998,47 @@ impl Connector for MetalConnector {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_colors(&self, bcs: BackendColorSpace, btf: BackendTransferFunction) {
|
||||
let prev_bcs = Cell::new(bcs);
|
||||
let prev_btf = Cell::new(btf);
|
||||
self.change_property(
|
||||
"colors",
|
||||
|dd| {
|
||||
prev_bcs.set(dd.persistent.color_space.get());
|
||||
prev_btf.set(dd.persistent.eotf.get());
|
||||
prev_bcs.get() != bcs || prev_btf.get() != btf
|
||||
},
|
||||
|dd| {
|
||||
let cs = match bcs {
|
||||
BackendColorSpace::Default => true,
|
||||
BackendColorSpace::Bt2020 => dd.supports_bt2020,
|
||||
};
|
||||
if !cs {
|
||||
log::warn!("Display does not support color space {:?}", bcs);
|
||||
}
|
||||
let tf = match btf {
|
||||
BackendTransferFunction::Default => true,
|
||||
BackendTransferFunction::Pq => dd.supports_pq,
|
||||
};
|
||||
if !tf {
|
||||
log::warn!("Display does not support transfer function {:?}", btf);
|
||||
}
|
||||
cs && tf
|
||||
},
|
||||
|dd| {
|
||||
dd.persistent.color_space.set(bcs);
|
||||
dd.persistent.eotf.set(btf);
|
||||
},
|
||||
|| {
|
||||
self.send_event(ConnectorEvent::ColorsChanged(bcs, btf));
|
||||
},
|
||||
|dd| {
|
||||
dd.persistent.color_space.set(prev_bcs.get());
|
||||
dd.persistent.eotf.set(prev_btf.get());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MetalCrtc {
|
||||
|
|
@ -1203,6 +1300,10 @@ fn create_connector_display_data(
|
|||
ty: ConnectorType::from_drm(info.connector_type),
|
||||
idx: info.connector_type_id,
|
||||
};
|
||||
let mut supports_bt2020 = false;
|
||||
let mut supports_pq = false;
|
||||
let mut luminance = None;
|
||||
let mut primaries = Primaries::SRGB;
|
||||
'fetch_edid: {
|
||||
if connection != ConnectorStatus::Connected {
|
||||
break 'fetch_edid;
|
||||
|
|
@ -1286,6 +1387,40 @@ fn create_connector_display_data(
|
|||
if min_vrr_hz > 0 {
|
||||
vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz;
|
||||
}
|
||||
let cc = &edid.base_block.chromaticity_coordinates;
|
||||
let map = |c: u16| F64(c as f64 / 1024.0);
|
||||
primaries = Primaries {
|
||||
r: (map(cc.red_x), map(cc.red_y)),
|
||||
g: (map(cc.green_x), map(cc.green_y)),
|
||||
b: (map(cc.blue_x), map(cc.blue_y)),
|
||||
wp: (map(cc.white_x), map(cc.white_y)),
|
||||
};
|
||||
for ext in &edid.extension_blocks {
|
||||
if let EdidExtension::CtaV3(cta) = ext {
|
||||
for data_block in &cta.data_blocks {
|
||||
match data_block {
|
||||
CtaDataBlock::Colorimetry(c) => {
|
||||
if c.bt2020_rgb {
|
||||
supports_bt2020 = true;
|
||||
}
|
||||
}
|
||||
CtaDataBlock::StaticHdrMetadata(h) => {
|
||||
if h.smpte_st_2084 {
|
||||
supports_pq = true;
|
||||
}
|
||||
if let Some(max) = h.max_luminance {
|
||||
luminance = Some(BackendLuminance {
|
||||
min: h.min_luminance.unwrap_or(0.0),
|
||||
max,
|
||||
max_fall: h.max_luminance.unwrap_or(max),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let output_id = Rc::new(OutputId::new(
|
||||
connector_id.to_string(),
|
||||
|
|
@ -1295,7 +1430,9 @@ fn create_connector_display_data(
|
|||
));
|
||||
let desired_state = match dev.backend.persistent_display_data.get(&output_id) {
|
||||
Some(ds) => {
|
||||
log::info!("Reusing desired state for {:?}", output_id);
|
||||
if connection != ConnectorStatus::Disconnected {
|
||||
log::info!("Reusing desired state for {:?}", output_id);
|
||||
}
|
||||
ds
|
||||
}
|
||||
None => {
|
||||
|
|
@ -1303,6 +1440,8 @@ fn create_connector_display_data(
|
|||
mode: RefCell::new(info.modes.first().cloned()),
|
||||
vrr_requested: Default::default(),
|
||||
format: Cell::new(XRGB8888),
|
||||
eotf: Default::default(),
|
||||
color_space: Default::default(),
|
||||
});
|
||||
dev.backend
|
||||
.persistent_display_data
|
||||
|
|
@ -1331,12 +1470,30 @@ fn create_connector_display_data(
|
|||
};
|
||||
let mode = mode_opt.clone();
|
||||
drop(mode_opt);
|
||||
{
|
||||
let viable = match desired_state.eotf.get() {
|
||||
BackendTransferFunction::Default => true,
|
||||
BackendTransferFunction::Pq => supports_pq,
|
||||
};
|
||||
if !viable {
|
||||
log::warn!("Discarding previously desired transfer function");
|
||||
desired_state.eotf.set(BackendTransferFunction::Default);
|
||||
}
|
||||
}
|
||||
{
|
||||
let viable = match desired_state.color_space.get() {
|
||||
BackendColorSpace::Default => true,
|
||||
BackendColorSpace::Bt2020 => supports_bt2020,
|
||||
};
|
||||
if !viable {
|
||||
log::warn!("Discarding previously desired color space");
|
||||
desired_state.color_space.set(BackendColorSpace::Default);
|
||||
}
|
||||
}
|
||||
let default_properties = create_default_properties(
|
||||
&props,
|
||||
&[
|
||||
("Broadcast RGB", DefaultValue::Enum("Automatic")),
|
||||
("Colorspace", DefaultValue::Enum("Default")),
|
||||
("HDR_OUTPUT_METADATA", DefaultValue::Fixed(0)),
|
||||
("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)),
|
||||
("Output format", DefaultValue::Enum("Default")),
|
||||
("WRITEBACK_FB_ID", DefaultValue::Fixed(0)),
|
||||
|
|
@ -1363,8 +1520,18 @@ fn create_connector_display_data(
|
|||
mm_width: info.mm_width,
|
||||
mm_height: info.mm_height,
|
||||
_subpixel: info.subpixel,
|
||||
supports_bt2020,
|
||||
supports_pq,
|
||||
primaries,
|
||||
luminance,
|
||||
connector_id,
|
||||
output_id,
|
||||
colorspace: props.get("Colorspace").ok(),
|
||||
hdr_metadata: props
|
||||
.get("HDR_OUTPUT_METADATA")
|
||||
.ok()
|
||||
.map(|v| v.map(|v| DrmBlob(v as _))),
|
||||
hdr_metadata_blob: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1802,6 +1969,14 @@ impl MetalBackend {
|
|||
modes.push(mode);
|
||||
}
|
||||
}
|
||||
let mut transfer_functions = vec![];
|
||||
if dd.supports_pq {
|
||||
transfer_functions.push(BackendTransferFunction::Pq);
|
||||
}
|
||||
let mut color_spaces = vec![];
|
||||
if dd.supports_bt2020 {
|
||||
color_spaces.push(BackendColorSpace::Bt2020);
|
||||
}
|
||||
connector.send_event(ConnectorEvent::Connected(MonitorInfo {
|
||||
modes,
|
||||
output_id: dd.output_id.clone(),
|
||||
|
|
@ -1810,6 +1985,12 @@ impl MetalBackend {
|
|||
height_mm: dd.mm_height as _,
|
||||
non_desktop: dd.non_desktop_effective,
|
||||
vrr_capable: dd.vrr_capable,
|
||||
transfer_functions,
|
||||
transfer_function: dd.persistent.eotf.get(),
|
||||
color_spaces,
|
||||
color_space: dd.persistent.color_space.get(),
|
||||
primaries: dd.primaries,
|
||||
luminance: dd.luminance,
|
||||
}));
|
||||
connector.send_hardware_cursor();
|
||||
connector.send_vrr_enabled();
|
||||
|
|
@ -1971,9 +2152,16 @@ impl MetalBackend {
|
|||
for c in dev.connectors.lock().values() {
|
||||
let dd = &mut *c.display.borrow_mut();
|
||||
collect_untyped_properties(master, c.id, &mut dd.untyped_properties)?;
|
||||
let props = &dd.untyped_properties;
|
||||
dd.crtc_id
|
||||
.value
|
||||
.set(DrmCrtc(get(&dd.untyped_properties, dd.crtc_id.id)? as _));
|
||||
.set(DrmCrtc(get(props, dd.crtc_id.id)? as _));
|
||||
if let Some(meta) = &dd.hdr_metadata {
|
||||
meta.value.set(DrmBlob(get(props, meta.id)? as _));
|
||||
}
|
||||
if let Some(cs) = &dd.colorspace {
|
||||
cs.value.set(get(props, cs.id)?);
|
||||
}
|
||||
}
|
||||
for c in dev.dev.crtcs.values() {
|
||||
let props = &mut *c.untyped_properties.borrow_mut();
|
||||
|
|
@ -2229,9 +2417,21 @@ impl MetalBackend {
|
|||
connector.version.fetch_add(1);
|
||||
let dd = connector.display.borrow_mut();
|
||||
dd.crtc_id.value.set(DrmCrtc::NONE);
|
||||
if let Some(cs) = &dd.colorspace {
|
||||
cs.value.set(0);
|
||||
}
|
||||
if let Some(hdr) = &dd.hdr_metadata {
|
||||
hdr.value.set(DrmBlob(0));
|
||||
}
|
||||
changes.change_object(connector.id, |c| {
|
||||
c.change(dd.crtc_id.id, 0);
|
||||
})
|
||||
if let Some(cs) = &dd.colorspace {
|
||||
c.change(cs.id, 0);
|
||||
}
|
||||
if let Some(hdr) = &dd.hdr_metadata {
|
||||
c.change(hdr.id, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
for crtc in dev.dev.crtcs.values() {
|
||||
if preserve.crtcs.contains(&crtc.id) {
|
||||
|
|
@ -2310,6 +2510,16 @@ impl MetalBackend {
|
|||
fail!(c.id);
|
||||
}
|
||||
}
|
||||
if let Some(m) = &dd.colorspace {
|
||||
if m.value.get() != dd.persistent.color_space.get().to_drm() {
|
||||
log::debug!("Connector has wrong colorspace");
|
||||
fail!(c.id);
|
||||
}
|
||||
}
|
||||
if let Some(diff) = self.compare_hdr_metadata(&dev.dev, &dd) {
|
||||
log::debug!("{}", diff);
|
||||
fail!(c.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for c in remove_connectors {
|
||||
|
|
@ -2509,6 +2719,39 @@ impl MetalBackend {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn compare_hdr_metadata(
|
||||
&self,
|
||||
dev: &MetalDrmDevice,
|
||||
dd: &ConnectorDisplayData,
|
||||
) -> Option<HdrMetadataDiff> {
|
||||
let Some(m) = &dd.hdr_metadata else {
|
||||
return None;
|
||||
};
|
||||
match dd.persistent.eotf.get() {
|
||||
BackendTransferFunction::Default => {
|
||||
if m.value.get() != DrmBlob::NONE {
|
||||
return Some(HdrMetadataDiff::Undesired);
|
||||
}
|
||||
}
|
||||
eotf => {
|
||||
if m.value.get() == DrmBlob::NONE {
|
||||
return Some(HdrMetadataDiff::No);
|
||||
}
|
||||
let current_metadata =
|
||||
match dev.master.getblob::<hdr_output_metadata>(m.value.get()) {
|
||||
Ok(m) => m,
|
||||
_ => {
|
||||
return Some(HdrMetadataDiff::CouldNotRetrieve);
|
||||
}
|
||||
};
|
||||
if current_metadata != hdr_output_metadata::from_eotf(eotf.to_drm()) {
|
||||
return Some(HdrMetadataDiff::Incompatible);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn can_use_current_drm_mode(&self, dev: &Rc<MetalDrmDeviceData>) -> bool {
|
||||
let mut used_crtcs = AHashSet::new();
|
||||
let mut vrr_crtcs = AHashSet::new();
|
||||
|
|
@ -2532,6 +2775,16 @@ impl MetalBackend {
|
|||
if dd.should_enable_vrr() {
|
||||
vrr_crtcs.insert(crtc_id);
|
||||
}
|
||||
if let Some(m) = &dd.colorspace {
|
||||
if m.value.get() != dd.persistent.color_space.get().to_drm() {
|
||||
log::debug!("Connector has wrong colorspace");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(diff) = self.compare_hdr_metadata(&dev.dev, &dd) {
|
||||
log::debug!("{}", diff);
|
||||
return false;
|
||||
}
|
||||
let crtc = dev.dev.crtcs.get(&crtc_id).unwrap();
|
||||
connector.crtc.set(Some(crtc.clone()));
|
||||
connector.version.fetch_add(1);
|
||||
|
|
@ -2804,8 +3057,8 @@ impl MetalBackend {
|
|||
connector: &Rc<MetalConnector>,
|
||||
changes: &mut Change,
|
||||
) -> Result<(), MetalError> {
|
||||
let dd = connector.display.borrow_mut();
|
||||
if should_ignore(connector, &dd) {
|
||||
let dd = &mut *connector.display.borrow_mut();
|
||||
if should_ignore(connector, dd) {
|
||||
return Ok(());
|
||||
}
|
||||
let crtc = 'crtc: {
|
||||
|
|
@ -2820,9 +3073,23 @@ impl MetalBackend {
|
|||
Some(m) => m,
|
||||
_ => return Err(MetalError::NoModeForConnector),
|
||||
};
|
||||
let hdr_blob = match dd.persistent.eotf.get() {
|
||||
BackendTransferFunction::Default => None,
|
||||
eotf => {
|
||||
let m = hdr_output_metadata::from_eotf(eotf.to_drm());
|
||||
Some(connector.master.create_blob(&m)?)
|
||||
}
|
||||
};
|
||||
let hdr_blob_id = hdr_blob.as_ref().map(|b| b.id()).unwrap_or_default();
|
||||
let mode_blob = mode.create_blob(&connector.master)?;
|
||||
changes.change_object(connector.id, |c| {
|
||||
c.change(dd.crtc_id.id, crtc.id.0 as _);
|
||||
if let Some(meta) = &dd.hdr_metadata {
|
||||
c.change(meta.id, hdr_blob_id.0 as _);
|
||||
}
|
||||
if let Some(cs) = &dd.colorspace {
|
||||
c.change(cs.id, dd.persistent.color_space.get().to_drm());
|
||||
}
|
||||
});
|
||||
changes.change_object(crtc.id, |c| {
|
||||
c.change(crtc.active.id, 1);
|
||||
|
|
@ -2832,6 +3099,13 @@ impl MetalBackend {
|
|||
connector.crtc.set(Some(crtc.clone()));
|
||||
connector.version.fetch_add(1);
|
||||
dd.crtc_id.value.set(crtc.id);
|
||||
dd.hdr_metadata_blob = hdr_blob;
|
||||
if let Some(meta) = &dd.hdr_metadata {
|
||||
meta.value.set(hdr_blob_id);
|
||||
}
|
||||
if let Some(cs) = &dd.colorspace {
|
||||
cs.value.set(dd.persistent.color_space.get().to_drm());
|
||||
}
|
||||
crtc.connector.set(Some(connector.clone()));
|
||||
crtc.active.value.set(true);
|
||||
crtc.mode_id.value.set(mode_blob.id());
|
||||
|
|
@ -3041,7 +3315,7 @@ impl RenderBuffer {
|
|||
|
||||
pub fn copy_to_dev(
|
||||
&self,
|
||||
cm: &ColorManager,
|
||||
cd: &Rc<ColorDescription>,
|
||||
sync_file: Option<SyncFile>,
|
||||
) -> Result<Option<SyncFile>, MetalError> {
|
||||
let Some(tex) = &self.dev_tex else {
|
||||
|
|
@ -3051,9 +3325,9 @@ impl RenderBuffer {
|
|||
.copy_texture(
|
||||
AcquireSync::Unnecessary,
|
||||
ReleaseSync::Explicit,
|
||||
cm.srgb_srgb(),
|
||||
cd,
|
||||
tex,
|
||||
cm.srgb_srgb(),
|
||||
cd,
|
||||
None,
|
||||
AcquireSync::from_sync_file(sync_file),
|
||||
ReleaseSync::None,
|
||||
|
|
@ -3091,3 +3365,15 @@ fn should_ignore(connector: &MetalConnector, dd: &ConnectorDisplayData) -> bool
|
|||
|| dd.connection != ConnectorStatus::Connected
|
||||
|| dd.non_desktop_effective
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum HdrMetadataDiff {
|
||||
#[error("Connector has undesired HDR metadata")]
|
||||
Undesired,
|
||||
#[error("Connector has no HDR metadata")]
|
||||
No,
|
||||
#[error("Could not retrieve current HDR metadata of connector")]
|
||||
CouldNotRetrieve,
|
||||
#[error("Connector has incompatible HDR metadata")]
|
||||
Incompatible,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ use {
|
|||
allocator::BufferObject,
|
||||
async_engine::{Phase, SpawnedFuture},
|
||||
backend::{
|
||||
AXIS_120, AxisSource, Backend, BackendDrmDevice, BackendEvent, Connector,
|
||||
ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, DrmEvent, InputDevice,
|
||||
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent, KeyState,
|
||||
Mode, MonitorInfo, ScrollAxis, TransformMatrix,
|
||||
AXIS_120, AxisSource, Backend, BackendColorSpace, BackendDrmDevice, BackendEvent,
|
||||
BackendTransferFunction, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId,
|
||||
DrmDeviceId, DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
|
||||
InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix,
|
||||
},
|
||||
cmm::cmm_primaries::Primaries,
|
||||
fixed::Fixed,
|
||||
format::XRGB8888,
|
||||
gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync},
|
||||
|
|
@ -581,6 +582,12 @@ impl XBackend {
|
|||
height_mm: output.height.get(),
|
||||
non_desktop: false,
|
||||
vrr_capable: false,
|
||||
transfer_functions: vec![],
|
||||
transfer_function: BackendTransferFunction::Default,
|
||||
color_spaces: vec![],
|
||||
color_space: BackendColorSpace::Default,
|
||||
primaries: Primaries::SRGB,
|
||||
luminance: None,
|
||||
}));
|
||||
output.changed();
|
||||
self.present(output).await;
|
||||
|
|
|
|||
138
src/cli/randr.rs
138
src/cli/randr.rs
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{BackendColorSpace, BackendTransferFunction},
|
||||
cli::GlobalArgs,
|
||||
format::{Format, XRGB8888},
|
||||
scale::Scale,
|
||||
|
|
@ -7,9 +8,13 @@ use {
|
|||
utils::{errorfmt::ErrorFmt, transform_ext::TransformExt},
|
||||
wire::{JayRandrId, jay_compositor, jay_randr},
|
||||
},
|
||||
clap::{Args, Subcommand, ValueEnum},
|
||||
clap::{
|
||||
Args, Subcommand, ValueEnum,
|
||||
builder::{PossibleValue, PossibleValuesParser},
|
||||
},
|
||||
isnt::std_1::vec::IsntVecExt,
|
||||
jay_config::video::{TearingMode, Transform, VrrMode},
|
||||
linearize::LinearizeExt,
|
||||
std::{
|
||||
cell::RefCell,
|
||||
fmt::{Display, Formatter},
|
||||
|
|
@ -154,6 +159,8 @@ pub enum OutputCommand {
|
|||
Tearing(TearingArgs),
|
||||
/// Change format settings.
|
||||
Format(FormatSettings),
|
||||
/// Change color settings.
|
||||
Colors(ColorsSettings),
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
|
|
@ -315,6 +322,51 @@ pub enum TransformCmd {
|
|||
FlipRotate270,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ColorsSettings {
|
||||
#[clap(subcommand)]
|
||||
pub command: ColorsCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum ColorsCommand {
|
||||
/// Sets the color space and transfer function of the output.
|
||||
Set {
|
||||
/// The name of the color space.
|
||||
#[clap(value_parser = PossibleValuesParser::new(color_space_possible_values()))]
|
||||
color_space: String,
|
||||
/// The name of the transfer function.
|
||||
#[clap(value_parser = PossibleValuesParser::new(transfer_function_possible_values()))]
|
||||
transfer_function: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn color_space_possible_values() -> Vec<PossibleValue> {
|
||||
let mut res = vec![];
|
||||
for cs in BackendColorSpace::variants() {
|
||||
use BackendColorSpace::*;
|
||||
let help = match cs {
|
||||
Default => "The default color space (usually sRGB)",
|
||||
Bt2020 => "The BT.2020 color space",
|
||||
};
|
||||
res.push(PossibleValue::new(cs.name()).help(help));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn transfer_function_possible_values() -> Vec<PossibleValue> {
|
||||
let mut res = vec![];
|
||||
for cs in BackendTransferFunction::variants() {
|
||||
use BackendTransferFunction::*;
|
||||
let help = match cs {
|
||||
Default => "The default transfer function (usually sRGB)",
|
||||
Pq => "The PQ transfer function",
|
||||
};
|
||||
res.push(PossibleValue::new(cs.name()).help(help));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn main(global: GlobalArgs, args: RandrArgs) {
|
||||
with_tool_client(global.log_level.into(), |tc| async move {
|
||||
let idle = Rc::new(Randr { tc: tc.clone() });
|
||||
|
|
@ -368,6 +420,10 @@ struct Output {
|
|||
pub formats: Vec<String>,
|
||||
pub format: Option<String>,
|
||||
pub flip_margin_ns: Option<u64>,
|
||||
pub supported_color_spaces: Vec<String>,
|
||||
pub current_color_space: Option<String>,
|
||||
pub supported_transfer_functions: Vec<String>,
|
||||
pub current_transfer_function: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
|
@ -610,6 +666,24 @@ impl Randr {
|
|||
}
|
||||
}
|
||||
}
|
||||
OutputCommand::Colors(a) => {
|
||||
self.handle_error(randr, move |msg| {
|
||||
eprintln!("Could not change the colors: {}", msg);
|
||||
});
|
||||
match a.command {
|
||||
ColorsCommand::Set {
|
||||
color_space,
|
||||
transfer_function,
|
||||
} => {
|
||||
tc.send(jay_randr::SetColors {
|
||||
self_id: randr,
|
||||
output: &args.output,
|
||||
color_space: &color_space,
|
||||
transfer_function: &transfer_function,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tc.round_trip().await;
|
||||
}
|
||||
|
|
@ -806,6 +880,32 @@ impl Randr {
|
|||
);
|
||||
}
|
||||
}
|
||||
if o.supported_color_spaces.is_not_empty() {
|
||||
println!(" color spaces:");
|
||||
let handle_cs = |cs: &str| {
|
||||
let current = match Some(cs) == o.current_color_space.as_deref() {
|
||||
false => "",
|
||||
true => " (current)",
|
||||
};
|
||||
println!(" {cs}{current}");
|
||||
};
|
||||
handle_cs("default");
|
||||
o.supported_color_spaces.iter().for_each(|cs| handle_cs(cs));
|
||||
}
|
||||
if o.supported_transfer_functions.is_not_empty() {
|
||||
println!(" transfer functions:");
|
||||
let handle_tf = |tf: &str| {
|
||||
let current = match Some(tf) == o.current_transfer_function.as_deref() {
|
||||
false => "",
|
||||
true => " (current)",
|
||||
};
|
||||
println!(" {tf}{current}");
|
||||
};
|
||||
handle_tf("default");
|
||||
o.supported_transfer_functions
|
||||
.iter()
|
||||
.for_each(|tf| handle_tf(tf));
|
||||
}
|
||||
if o.modes.is_not_empty() && modes {
|
||||
println!(" modes:");
|
||||
for mode in &o.modes {
|
||||
|
|
@ -886,6 +986,10 @@ impl Randr {
|
|||
formats: vec![],
|
||||
format: None,
|
||||
flip_margin_ns: None,
|
||||
supported_color_spaces: vec![],
|
||||
current_color_space: None,
|
||||
supported_transfer_functions: vec![],
|
||||
current_transfer_function: None,
|
||||
});
|
||||
});
|
||||
jay_randr::NonDesktopOutput::handle(tc, randr, data.clone(), |data, msg| {
|
||||
|
|
@ -914,6 +1018,10 @@ impl Randr {
|
|||
formats: vec![],
|
||||
format: None,
|
||||
flip_margin_ns: None,
|
||||
supported_color_spaces: vec![],
|
||||
current_color_space: None,
|
||||
supported_transfer_functions: vec![],
|
||||
current_transfer_function: None,
|
||||
});
|
||||
});
|
||||
jay_randr::VrrState::handle(tc, randr, data.clone(), |data, msg| {
|
||||
|
|
@ -966,6 +1074,34 @@ impl Randr {
|
|||
}
|
||||
o.modes.push(mode);
|
||||
});
|
||||
jay_randr::SupportedColorSpace::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
|
||||
.supported_color_spaces
|
||||
.push(msg.color_space.to_string());
|
||||
});
|
||||
jay_randr::CurrentColorSpace::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.current_color_space = Some(msg.color_space.to_string());
|
||||
});
|
||||
jay_randr::SupportedTransferFunction::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
|
||||
.supported_transfer_functions
|
||||
.push(msg.transfer_function.to_string());
|
||||
});
|
||||
jay_randr::CurrentTransferFunction::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.current_transfer_function = Some(msg.transfer_function.to_string());
|
||||
});
|
||||
tc.round_trip().await;
|
||||
let x = data.borrow_mut().clone();
|
||||
x
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::{
|
||||
cmm_luminance::{Luminance, white_balance},
|
||||
cmm_luminance::{Luminance, TargetLuminance, white_balance},
|
||||
cmm_manager::Shared,
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_transfer_function::TransferFunction,
|
||||
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
|
||||
},
|
||||
utils::free_list::FreeList,
|
||||
utils::{free_list::FreeList, ordered_float::F64},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
|
@ -19,6 +19,12 @@ pub type ColorDescriptionIds = FreeList<ColorDescriptionId, 3>;
|
|||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ColorDescriptionId(u32);
|
||||
|
||||
impl ColorDescriptionId {
|
||||
pub fn raw(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for ColorDescriptionId {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
|
|
@ -38,6 +44,10 @@ pub struct LinearColorDescription {
|
|||
pub xyz_from_local: ColorMatrix<Xyz, Local>,
|
||||
pub local_from_xyz: ColorMatrix<Local, Xyz>,
|
||||
pub luminance: Luminance,
|
||||
pub target_primaries: Primaries,
|
||||
pub target_luminance: TargetLuminance,
|
||||
pub max_cll: Option<F64>,
|
||||
pub max_fall: Option<F64>,
|
||||
pub(super) shared: Rc<Shared>,
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +55,6 @@ pub struct LinearColorDescription {
|
|||
pub struct ColorDescription {
|
||||
pub id: ColorDescriptionId,
|
||||
pub linear: Rc<LinearColorDescription>,
|
||||
#[expect(dead_code)]
|
||||
pub named_primaries: Option<NamedPrimaries>,
|
||||
pub transfer_function: TransferFunction,
|
||||
pub(super) shared: Rc<Shared>,
|
||||
|
|
@ -62,6 +71,26 @@ impl LinearColorDescription {
|
|||
}
|
||||
mat * self.xyz_from_local
|
||||
}
|
||||
|
||||
pub fn embeds_into(&self, target: &Self) -> bool {
|
||||
if self.id == target.id {
|
||||
return true;
|
||||
}
|
||||
if self.primaries != target.primaries {
|
||||
return false;
|
||||
}
|
||||
if self.luminance != target.luminance {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDescription {
|
||||
pub fn embeds_into(&self, target: &Self) -> bool {
|
||||
self.transfer_function == target.transfer_function
|
||||
&& self.linear.embeds_into(&target.linear)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LinearColorDescription {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ pub struct Luminance {
|
|||
pub white: F64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct TargetLuminance {
|
||||
pub min: F64,
|
||||
pub max: F64,
|
||||
}
|
||||
|
||||
impl Luminance {
|
||||
pub const SRGB: Self = Self {
|
||||
min: F64(0.2),
|
||||
|
|
@ -46,6 +52,15 @@ impl Luminance {
|
|||
};
|
||||
}
|
||||
|
||||
impl Luminance {
|
||||
pub fn to_target(&self) -> TargetLuminance {
|
||||
TargetLuminance {
|
||||
min: self.min,
|
||||
max: self.max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Luminance {
|
||||
fn default() -> Self {
|
||||
Self::SRGB
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ use {
|
|||
ColorDescription, ColorDescriptionIds, LinearColorDescription,
|
||||
LinearColorDescriptionId, LinearColorDescriptionIds,
|
||||
},
|
||||
cmm_luminance::Luminance,
|
||||
cmm_luminance::{Luminance, TargetLuminance},
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_transfer_function::TransferFunction,
|
||||
},
|
||||
utils::{copyhashmap::CopyHashMap, numcell::NumCell},
|
||||
utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
|
||||
},
|
||||
std::rc::{Rc, Weak},
|
||||
};
|
||||
|
|
@ -35,6 +35,10 @@ pub(super) struct Shared {
|
|||
struct LinearDescriptionKey {
|
||||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
target_primaries: Primaries,
|
||||
target_luminance: TargetLuminance,
|
||||
max_cll: Option<F64>,
|
||||
max_fall: Option<F64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
|
@ -60,15 +64,16 @@ impl ColorManager {
|
|||
Primaries::SRGB,
|
||||
Luminance::SRGB,
|
||||
TransferFunction::Srgb,
|
||||
);
|
||||
let srgb_linear = get_description(
|
||||
&shared,
|
||||
&linear_descriptions,
|
||||
&complete_descriptions,
|
||||
&linear_ids,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Primaries::SRGB,
|
||||
Luminance::SRGB,
|
||||
Luminance::SRGB.to_target(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let srgb_linear = get_description2(
|
||||
&shared,
|
||||
&srgb_srgb.linear,
|
||||
&complete_descriptions,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
TransferFunction::Linear,
|
||||
);
|
||||
let windows_scrgb = get_description(
|
||||
|
|
@ -80,6 +85,10 @@ impl ColorManager {
|
|||
Primaries::SRGB,
|
||||
Luminance::WINDOWS_SCRGB,
|
||||
TransferFunction::Linear,
|
||||
Primaries::BT2020,
|
||||
Luminance::ST2084_PQ.to_target(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
Rc::new(Self {
|
||||
linear_ids,
|
||||
|
|
@ -110,6 +119,10 @@ impl ColorManager {
|
|||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
transfer_function: TransferFunction,
|
||||
target_primaries: Primaries,
|
||||
target_luminance: TargetLuminance,
|
||||
max_cll: Option<F64>,
|
||||
max_fall: Option<F64>,
|
||||
) -> Rc<ColorDescription> {
|
||||
get_description(
|
||||
&self.shared,
|
||||
|
|
@ -120,6 +133,24 @@ impl ColorManager {
|
|||
primaries,
|
||||
luminance,
|
||||
transfer_function,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_with_tf(
|
||||
self: &Rc<Self>,
|
||||
cd: &Rc<ColorDescription>,
|
||||
transfer_function: TransferFunction,
|
||||
) -> Rc<ColorDescription> {
|
||||
get_description2(
|
||||
&self.shared,
|
||||
&cd.linear,
|
||||
&self.complete_descriptions,
|
||||
cd.named_primaries,
|
||||
transfer_function,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -133,6 +164,10 @@ fn get_description(
|
|||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
transfer_function: TransferFunction,
|
||||
target_primaries: Primaries,
|
||||
target_luminance: TargetLuminance,
|
||||
max_cll: Option<F64>,
|
||||
max_fall: Option<F64>,
|
||||
) -> Rc<ColorDescription> {
|
||||
macro_rules! gc {
|
||||
($d:ident, $i:expr) => {
|
||||
|
|
@ -147,29 +182,20 @@ fn get_description(
|
|||
let key = LinearDescriptionKey {
|
||||
primaries,
|
||||
luminance,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
};
|
||||
if let Some(d) = linear_descriptions.get(&key) {
|
||||
if let Some(d) = d.upgrade() {
|
||||
let key = CompleteDescriptionKey {
|
||||
linear: d.id,
|
||||
return get_description2(
|
||||
shared,
|
||||
&d,
|
||||
complete_descriptions,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
};
|
||||
if let Some(d) = complete_descriptions.get(&key) {
|
||||
if let Some(d) = d.upgrade() {
|
||||
return d;
|
||||
}
|
||||
shared.dead_complete.fetch_sub(1);
|
||||
}
|
||||
let d = Rc::new(ColorDescription {
|
||||
id: shared.complete_ids.acquire(),
|
||||
linear: d,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
return d;
|
||||
);
|
||||
}
|
||||
shared.dead_linear.fetch_sub(1);
|
||||
}
|
||||
|
|
@ -180,6 +206,10 @@ fn get_description(
|
|||
xyz_from_local,
|
||||
local_from_xyz,
|
||||
luminance,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
linear_descriptions.set(key, Rc::downgrade(&d));
|
||||
|
|
@ -198,3 +228,32 @@ fn get_description(
|
|||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
d
|
||||
}
|
||||
|
||||
fn get_description2(
|
||||
shared: &Rc<Shared>,
|
||||
ld: &Rc<LinearColorDescription>,
|
||||
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
transfer_function: TransferFunction,
|
||||
) -> Rc<ColorDescription> {
|
||||
let key = CompleteDescriptionKey {
|
||||
linear: ld.id,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
};
|
||||
if let Some(d) = complete_descriptions.get(&key) {
|
||||
if let Some(d) = d.upgrade() {
|
||||
return d;
|
||||
}
|
||||
shared.dead_complete.fetch_sub(1);
|
||||
}
|
||||
let d = Rc::new(ColorDescription {
|
||||
id: shared.complete_ids.acquire(),
|
||||
linear: ld.clone(),
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
d
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,18 @@ mod transforms {
|
|||
|
||||
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
|
||||
let manager = ColorManager::new();
|
||||
let d = |p| manager.get_description(None, p, Luminance::SRGB, TransferFunction::Linear);
|
||||
let d = |p| {
|
||||
manager.get_description(
|
||||
None,
|
||||
p,
|
||||
Luminance::SRGB,
|
||||
TransferFunction::Linear,
|
||||
p,
|
||||
Luminance::SRGB.to_target(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
};
|
||||
let d1 = d(p1);
|
||||
let d2 = d(p2);
|
||||
let m = d1.linear.color_transform(&d2.linear);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use {
|
|||
crate::{
|
||||
acceptor::{Acceptor, AcceptorError},
|
||||
async_engine::{AsyncEngine, Phase, SpawnedFuture},
|
||||
backend::{self, Backend, Connector},
|
||||
backend::{self, Backend, BackendColorSpace, BackendTransferFunction, Connector},
|
||||
backends::{
|
||||
dummy::{DummyBackend, DummyOutput},
|
||||
metal, x,
|
||||
|
|
@ -12,7 +12,7 @@ use {
|
|||
cli::{CliBackend, GlobalArgs, RunArgs},
|
||||
client::{ClientId, Clients},
|
||||
clientmem::{self, ClientMemError},
|
||||
cmm::cmm_manager::ColorManager,
|
||||
cmm::{cmm_manager::ColorManager, cmm_primaries::Primaries},
|
||||
config::ConfigProxy,
|
||||
cpu_worker::{CpuWorker, CpuWorkerError},
|
||||
damage::{DamageVisualizer, visualize_damage},
|
||||
|
|
@ -574,6 +574,12 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
0,
|
||||
&output_id,
|
||||
&persistent_state,
|
||||
Vec::new(),
|
||||
BackendTransferFunction::Default,
|
||||
Vec::new(),
|
||||
BackendColorSpace::Default,
|
||||
Primaries::SRGB,
|
||||
None,
|
||||
)),
|
||||
jay_outputs: Default::default(),
|
||||
workspaces: Default::default(),
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use {
|
|||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
backend::{
|
||||
self, ConnectorId, DrmDeviceId, InputDeviceAccelProfile, InputDeviceCapability,
|
||||
InputDeviceId,
|
||||
self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId,
|
||||
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId,
|
||||
},
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
compositor::MAX_EXTENTS,
|
||||
|
|
@ -51,7 +51,8 @@ use {
|
|||
theme::{colors::Colorable, sized::Resizable},
|
||||
timer::Timer as JayTimer,
|
||||
video::{
|
||||
Connector, DrmDevice, Format as ConfigFormat, GfxApi, TearingMode as ConfigTearingMode,
|
||||
ColorSpace, Connector, DrmDevice, Format as ConfigFormat, GfxApi,
|
||||
TearingMode as ConfigTearingMode, TransferFunction as ConfigTransferFunction,
|
||||
Transform, VrrMode as ConfigVrrMode,
|
||||
},
|
||||
xwayland::XScalingMode,
|
||||
|
|
@ -1104,6 +1105,27 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_set_colors(
|
||||
&self,
|
||||
connector: Connector,
|
||||
color_space: ColorSpace,
|
||||
transfer_function: ConfigTransferFunction,
|
||||
) -> Result<(), CphError> {
|
||||
let bcs = match color_space {
|
||||
ColorSpace::DEFAULT => BackendColorSpace::Default,
|
||||
ColorSpace::BT2020 => BackendColorSpace::Bt2020,
|
||||
_ => return Err(CphError::UnknownColorSpace(color_space)),
|
||||
};
|
||||
let btf = match transfer_function {
|
||||
ConfigTransferFunction::DEFAULT => BackendTransferFunction::Default,
|
||||
ConfigTransferFunction::PQ => BackendTransferFunction::Pq,
|
||||
_ => return Err(CphError::UnknownTransferFunction(transfer_function)),
|
||||
};
|
||||
let connector = self.get_connector(connector)?;
|
||||
connector.connector.set_colors(bcs, btf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_vrr_mode(
|
||||
&self,
|
||||
connector: Option<Connector>,
|
||||
|
|
@ -1994,6 +2016,13 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::SetColorManagementEnabled { enabled } => {
|
||||
self.handle_set_color_management_enabled(enabled)
|
||||
}
|
||||
ClientMessage::ConnectorSetColors {
|
||||
connector,
|
||||
color_space,
|
||||
transfer_function,
|
||||
} => self
|
||||
.handle_connector_set_colors(connector, color_space, transfer_function)
|
||||
.wrn("connector_set_colors")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2065,6 +2094,10 @@ enum CphError {
|
|||
UnknownFormat(ConfigFormat),
|
||||
#[error("Unknown x scaling mode {0:?}")]
|
||||
UnknownXScalingMode(XScalingMode),
|
||||
#[error("Unknown color space {0:?}")]
|
||||
UnknownColorSpace(ColorSpace),
|
||||
#[error("Unknown transfer function {0:?}")]
|
||||
UnknownTransferFunction(ConfigTransferFunction),
|
||||
}
|
||||
|
||||
trait WithRequestName {
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@ impl CursorUser {
|
|||
let transform = output.global.persistent.transform.get();
|
||||
let render = output.hardware_cursor_needs_render.take();
|
||||
let scale = output.global.persistent.scale.get();
|
||||
let cd = output.global.color_description.get();
|
||||
if render {
|
||||
cursor.tick();
|
||||
}
|
||||
|
|
@ -507,7 +508,7 @@ impl CursorUser {
|
|||
&self.group.state,
|
||||
scale,
|
||||
transform,
|
||||
self.group.state.color_manager.srgb_srgb(),
|
||||
&cd,
|
||||
);
|
||||
match res {
|
||||
Ok(sync_file) => {
|
||||
|
|
|
|||
79
src/edid.rs
79
src/edid.rs
|
|
@ -75,7 +75,6 @@ pub struct ScreenDimensions {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[expect(dead_code)]
|
||||
pub struct ChromaticityCoordinates {
|
||||
pub red_x: u16,
|
||||
pub red_y: u16,
|
||||
|
|
@ -1035,9 +1034,59 @@ impl<'a> EdidParser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_cta_colorimetry_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
|
||||
let [lo, hi] = *self.read_n::<2>()?;
|
||||
Ok(CtaDataBlock::Colorimetry(CtaColorimetryDataBlock {
|
||||
bt2020_rgb: lo.contains(0x80),
|
||||
bt2020_ycc: lo.contains(0x40),
|
||||
bt2020_cycc: lo.contains(0x20),
|
||||
op_rgb: lo.contains(0x10),
|
||||
op_ycc_601601: lo.contains(0x08),
|
||||
s_ycc_601: lo.contains(0x04),
|
||||
xv_ycc_709: lo.contains(0x02),
|
||||
xv_ycc_601: lo.contains(0x01),
|
||||
dci_p3: hi.contains(0x80),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_cta_hdr_static_metadata_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
|
||||
let et = self.read_u8()?;
|
||||
let _ = self.read_u8()?;
|
||||
let mut read_luminance = |min: bool| {
|
||||
let v = self.read_u8().unwrap_or_default();
|
||||
if v == 0 {
|
||||
None
|
||||
} else if min {
|
||||
Some((v as f64 / 255.0).powi(2) / 100.0)
|
||||
} else {
|
||||
Some(50.0 * 2.0f64.powf(v as f64 / 32.0))
|
||||
}
|
||||
};
|
||||
Ok(CtaDataBlock::StaticHdrMetadata(
|
||||
CtaStaticHdrMetadataDataBlock {
|
||||
traditional_gamma_sdr_luminance: et.contains(0x01),
|
||||
traditional_gamma_hdr_luminance: et.contains(0x02),
|
||||
smpte_st_2084: et.contains(0x04),
|
||||
hlg: et.contains(0x08),
|
||||
max_luminance: read_luminance(false),
|
||||
max_frame_average_luminance: read_luminance(false),
|
||||
min_luminance: read_luminance(true),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_cta_extended_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
|
||||
match self.read_u8()? {
|
||||
0x5 => self.parse_cta_colorimetry_data_block(),
|
||||
0x6 => self.parse_cta_hdr_static_metadata_data_block(),
|
||||
_ => Ok(CtaDataBlock::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cta_data_block(&mut self, tag: u8) -> Result<CtaDataBlock, EdidError> {
|
||||
match tag {
|
||||
0x3 => self.parse_cta_vendor_data_block(),
|
||||
0x7 => self.parse_cta_extended_data_block(),
|
||||
_ => Ok(CtaDataBlock::Unknown),
|
||||
}
|
||||
}
|
||||
|
|
@ -1173,6 +1222,8 @@ pub struct CtaExtensionV3 {
|
|||
pub enum CtaDataBlock {
|
||||
Unknown,
|
||||
VendorAmd(CtaAmdVendorDataBlock),
|
||||
Colorimetry(CtaColorimetryDataBlock),
|
||||
StaticHdrMetadata(CtaStaticHdrMetadataDataBlock),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -1182,6 +1233,32 @@ pub struct CtaAmdVendorDataBlock {
|
|||
pub maximum_refresh_hz: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[expect(dead_code)]
|
||||
pub struct CtaColorimetryDataBlock {
|
||||
pub bt2020_rgb: bool,
|
||||
pub bt2020_ycc: bool,
|
||||
pub bt2020_cycc: bool,
|
||||
pub op_rgb: bool,
|
||||
pub op_ycc_601601: bool,
|
||||
pub s_ycc_601: bool,
|
||||
pub xv_ycc_709: bool,
|
||||
pub xv_ycc_601: bool,
|
||||
pub dci_p3: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[expect(dead_code)]
|
||||
pub struct CtaStaticHdrMetadataDataBlock {
|
||||
pub traditional_gamma_sdr_luminance: bool,
|
||||
pub traditional_gamma_hdr_luminance: bool,
|
||||
pub smpte_st_2084: bool,
|
||||
pub hlg: bool,
|
||||
pub max_luminance: Option<f64>,
|
||||
pub max_frame_average_luminance: Option<f64>,
|
||||
pub min_luminance: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EdidFile {
|
||||
pub base_block: EdidBaseBlock,
|
||||
|
|
|
|||
|
|
@ -2138,7 +2138,7 @@ impl ColorTransforms {
|
|||
src: &LinearColorDescription,
|
||||
dst: &ColorDescription,
|
||||
) -> Option<&mut ColorTransform> {
|
||||
if src.id == dst.linear.id {
|
||||
if src.embeds_into(&dst.linear) {
|
||||
return None;
|
||||
}
|
||||
let ct = match self.map.entry([src.id, dst.linear.id]) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::color_management::wp_image_description_v1::WpImageDescriptionV1,
|
||||
ifs::{
|
||||
color_management::{CAUSE_NO_OUTPUT, wp_image_description_v1::WpImageDescriptionV1},
|
||||
wl_output::OutputGlobalOpt,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{WpColorManagementOutputV1Id, wp_color_management_output_v1::*},
|
||||
|
|
@ -15,20 +18,29 @@ pub struct WpColorManagementOutputV1 {
|
|||
pub client: Rc<Client>,
|
||||
pub version: Version,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub output: Rc<OutputGlobalOpt>,
|
||||
}
|
||||
|
||||
impl WpColorManagementOutputV1 {
|
||||
#[expect(dead_code)]
|
||||
pub fn send_image_description_changed(&self) {
|
||||
self.client
|
||||
.event(ImageDescriptionChanged { self_id: self.id });
|
||||
}
|
||||
|
||||
fn detach(&self) {
|
||||
if let Some(output) = self.output.get() {
|
||||
output
|
||||
.color_description_listeners
|
||||
.remove(&(self.client.id, self.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WpColorManagementOutputV1RequestHandler for WpColorManagementOutputV1 {
|
||||
type Error = WpColorManagementOutputV1Error;
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.detach();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -43,11 +55,15 @@ impl WpColorManagementOutputV1RequestHandler for WpColorManagementOutputV1 {
|
|||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
description: self.client.state.color_manager.srgb_srgb().clone(),
|
||||
description: self.output.get().map(|o| o.color_description.get()),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
obj.send_ready();
|
||||
if obj.description.is_some() {
|
||||
obj.send_ready();
|
||||
} else {
|
||||
obj.send_failed(CAUSE_NO_OUTPUT, "the output no longer exists");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +73,11 @@ object_base! {
|
|||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for WpColorManagementOutputV1 {}
|
||||
impl Object for WpColorManagementOutputV1 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_obj!(WpColorManagementOutputV1);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::color_management::wp_image_description_v1::WpImageDescriptionV1,
|
||||
cmm::cmm_description::ColorDescription,
|
||||
ifs::{
|
||||
color_management::wp_image_description_v1::WpImageDescriptionV1, wl_surface::WlSurface,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{
|
||||
|
|
@ -18,6 +21,7 @@ pub struct WpColorManagementSurfaceFeedbackV1 {
|
|||
pub client: Rc<Client>,
|
||||
pub version: Version,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub surface: Rc<WlSurface>,
|
||||
}
|
||||
|
||||
impl WpColorManagementSurfaceFeedbackV1 {
|
||||
|
|
@ -30,13 +34,20 @@ impl WpColorManagementSurfaceFeedbackV1 {
|
|||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
description: self.client.state.color_manager.srgb_srgb().clone(),
|
||||
description: Some(self.surface.get_output().global.color_description.get()),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
obj.send_ready();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_preferred_changed(&self, cd: &ColorDescription) {
|
||||
self.client.event(PreferredChanged {
|
||||
self_id: self.id,
|
||||
identity: cd.id.raw(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl WpColorManagementSurfaceFeedbackV1RequestHandler for WpColorManagementSurfaceFeedbackV1 {
|
||||
|
|
@ -44,6 +55,7 @@ impl WpColorManagementSurfaceFeedbackV1RequestHandler for WpColorManagementSurfa
|
|||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.remove_obj(self)?;
|
||||
self.surface.remove_color_management_feedback(self);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use {
|
|||
globals::{Global, GlobalName},
|
||||
ifs::{
|
||||
color_management::{
|
||||
FEATURE_EXTENDED_TARGET_VOLUME, FEATURE_SET_MASTERING_DISPLAY_PRIMARIES,
|
||||
consts::{
|
||||
FEATURE_PARAMETRIC, FEATURE_SET_LUMINANCES, FEATURE_SET_PRIMARIES,
|
||||
FEATURE_WINDOWS_SCRGB, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020,
|
||||
|
|
@ -77,6 +78,8 @@ impl WpColorManagerV1 {
|
|||
self.send_supported_feature(FEATURE_PARAMETRIC);
|
||||
self.send_supported_feature(FEATURE_SET_PRIMARIES);
|
||||
self.send_supported_feature(FEATURE_SET_LUMINANCES);
|
||||
self.send_supported_feature(FEATURE_SET_MASTERING_DISPLAY_PRIMARIES);
|
||||
self.send_supported_feature(FEATURE_EXTENDED_TARGET_VOLUME);
|
||||
self.send_supported_feature(FEATURE_WINDOWS_SCRGB);
|
||||
self.send_supported_tf_named(TRANSFER_FUNCTION_BT1886);
|
||||
self.send_supported_tf_named(TRANSFER_FUNCTION_GAMMA22);
|
||||
|
|
@ -144,15 +147,21 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
|
|||
}
|
||||
|
||||
fn get_output(&self, req: GetOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let _ = self.client.lookup(req.output)?;
|
||||
let output = self.client.lookup(req.output)?;
|
||||
let obj = Rc::new(WpColorManagementOutputV1 {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
output: output.global.clone(),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
if let Some(global) = output.global.get() {
|
||||
global
|
||||
.color_description_listeners
|
||||
.set((self.client.id, req.id), obj);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -176,15 +185,17 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
|
|||
req: GetSurfaceFeedback,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let _ = self.client.lookup(req.surface)?;
|
||||
let surface = self.client.lookup(req.surface)?;
|
||||
let obj = Rc::new(WpColorManagementSurfaceFeedbackV1 {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
surface: surface.clone(),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
surface.add_color_management_feedback(&obj);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -209,6 +220,10 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
|
|||
tf: Default::default(),
|
||||
primaries: Default::default(),
|
||||
luminance: Default::default(),
|
||||
mastering_primaries: Default::default(),
|
||||
mastering_luminance: Default::default(),
|
||||
max_cll: Default::default(),
|
||||
max_fall: Default::default(),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
|
|
@ -225,7 +240,7 @@ impl WpColorManagerV1RequestHandler for WpColorManagerV1 {
|
|||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
description: self.client.state.color_manager.windows_scrgb().clone(),
|
||||
description: Some(self.client.state.color_manager.windows_scrgb().clone()),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use {
|
|||
crate::{
|
||||
client::{Client, ClientError},
|
||||
cmm::{
|
||||
cmm_luminance::Luminance,
|
||||
cmm_luminance::{Luminance, TargetLuminance},
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_transfer_function::TransferFunction,
|
||||
},
|
||||
|
|
@ -43,6 +43,10 @@ pub struct WpImageDescriptionCreatorParamsV1 {
|
|||
pub tf: Cell<Option<TransferFunction>>,
|
||||
pub primaries: Cell<Option<(Option<NamedPrimaries>, Primaries)>>,
|
||||
pub luminance: Cell<Option<Luminance>>,
|
||||
pub mastering_primaries: Cell<Option<Primaries>>,
|
||||
pub mastering_luminance: Cell<Option<TargetLuminance>>,
|
||||
pub max_cll: Cell<Option<F64>>,
|
||||
pub max_fall: Cell<Option<F64>>,
|
||||
}
|
||||
|
||||
impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreatorParamsV1 {
|
||||
|
|
@ -67,18 +71,27 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat
|
|||
if luminance.max.0 <= luminance.min.0 || luminance.white.0 <= luminance.min.0 {
|
||||
return Err(WpImageDescriptionCreatorParamsV1Error::MinLuminanceTooLow);
|
||||
}
|
||||
let target_primaries = self.mastering_primaries.get().unwrap_or(primaries);
|
||||
let target_luminance = self
|
||||
.mastering_luminance
|
||||
.get()
|
||||
.unwrap_or(luminance.to_target());
|
||||
let description = self.client.state.color_manager.get_description(
|
||||
named_primaries,
|
||||
primaries,
|
||||
luminance,
|
||||
transfer_function,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
self.max_cll.get(),
|
||||
self.max_fall.get(),
|
||||
);
|
||||
let obj = Rc::new(WpImageDescriptionV1 {
|
||||
id: req.image_description,
|
||||
client: self.client.clone(),
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
description,
|
||||
description: Some(description),
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
|
|
@ -174,25 +187,59 @@ impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreat
|
|||
|
||||
fn set_mastering_display_primaries(
|
||||
&self,
|
||||
_req: SetMasteringDisplayPrimaries,
|
||||
req: SetMasteringDisplayPrimaries,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
Err(WpImageDescriptionCreatorParamsV1Error::SetMasteringDisplayPrimariesNotSupported)
|
||||
let map = |n: i32| F64(n as f64 * PRIMARIES_MUL_INV);
|
||||
let primaries = Primaries {
|
||||
r: (map(req.r_x), map(req.r_y)),
|
||||
g: (map(req.g_x), map(req.g_y)),
|
||||
b: (map(req.b_x), map(req.b_y)),
|
||||
wp: (map(req.w_x), map(req.w_y)),
|
||||
};
|
||||
if self.mastering_primaries.replace(Some(primaries)).is_some() {
|
||||
return Err(WpImageDescriptionCreatorParamsV1Error::MasteringPrimariesAlreadySet);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mastering_luminance(
|
||||
&self,
|
||||
_req: SetMasteringLuminance,
|
||||
req: SetMasteringLuminance,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
Err(WpImageDescriptionCreatorParamsV1Error::SetMasteringLuminanceNotSupported)
|
||||
}
|
||||
|
||||
fn set_max_cll(&self, _req: SetMaxCll, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let luminance = TargetLuminance {
|
||||
min: F64(req.min_lum as f64 * MIN_LUM_MUL_INV),
|
||||
max: F64(req.max_lum as f64),
|
||||
};
|
||||
if luminance.max.0 <= luminance.min.0 {
|
||||
return Err(WpImageDescriptionCreatorParamsV1Error::MinMasteringLuminanceTooLow);
|
||||
}
|
||||
if self.mastering_luminance.replace(Some(luminance)).is_some() {
|
||||
return Err(WpImageDescriptionCreatorParamsV1Error::MasteringLuminancesAlreadySet);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_max_fall(&self, _req: SetMaxFall, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
fn set_max_cll(&self, req: SetMaxCll, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if self
|
||||
.max_cll
|
||||
.replace(Some(F64(req.max_cll as f64)))
|
||||
.is_some()
|
||||
{
|
||||
return Err(WpImageDescriptionCreatorParamsV1Error::MaxCllAlreadySet);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_max_fall(&self, req: SetMaxFall, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if self
|
||||
.max_fall
|
||||
.replace(Some(F64(req.max_fall as f64)))
|
||||
.is_some()
|
||||
{
|
||||
return Err(WpImageDescriptionCreatorParamsV1Error::MaxFallAlreadySet);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -210,10 +257,6 @@ simple_add_obj!(WpImageDescriptionCreatorParamsV1);
|
|||
pub enum WpImageDescriptionCreatorParamsV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("set_mastering_luminance is not supported")]
|
||||
SetMasteringLuminanceNotSupported,
|
||||
#[error("set_mastering_display_primaries is not supported")]
|
||||
SetMasteringDisplayPrimariesNotSupported,
|
||||
#[error("{} is not a supported named primary", .0)]
|
||||
UnsupportedPrimaries(u32),
|
||||
#[error("set_tf_power is not supported")]
|
||||
|
|
@ -232,5 +275,15 @@ pub enum WpImageDescriptionCreatorParamsV1Error {
|
|||
TfNotSet,
|
||||
#[error("The primaries were not set")]
|
||||
PrimariesNotSet,
|
||||
#[error("The mastering display primaries have already been set")]
|
||||
MasteringPrimariesAlreadySet,
|
||||
#[error("The mastering display luminances have already been set")]
|
||||
MasteringLuminancesAlreadySet,
|
||||
#[error("The minimum mastering luminance is too low")]
|
||||
MinMasteringLuminanceTooLow,
|
||||
#[error("The max CLL has already been set")]
|
||||
MaxCllAlreadySet,
|
||||
#[error("The max FALL has already been set")]
|
||||
MaxFallAlreadySet,
|
||||
}
|
||||
efrom!(WpImageDescriptionCreatorParamsV1Error, ClientError);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
use {
|
||||
crate::{
|
||||
client::Client,
|
||||
ifs::color_management::consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB},
|
||||
cmm::{
|
||||
cmm_description::ColorDescription, cmm_primaries::NamedPrimaries,
|
||||
cmm_transfer_function::TransferFunction,
|
||||
},
|
||||
ifs::color_management::{
|
||||
MIN_LUM_MUL, PRIMARIES_ADOBE_RGB, PRIMARIES_BT2020, PRIMARIES_CIE1931_XYZ,
|
||||
PRIMARIES_DCI_P3, PRIMARIES_DISPLAY_P3, PRIMARIES_GENERIC_FILM, PRIMARIES_MUL,
|
||||
PRIMARIES_NTSC, PRIMARIES_PAL, PRIMARIES_PAL_M, TRANSFER_FUNCTION_BT1886,
|
||||
TRANSFER_FUNCTION_EXT_LINEAR, TRANSFER_FUNCTION_EXT_SRGB, TRANSFER_FUNCTION_GAMMA22,
|
||||
TRANSFER_FUNCTION_GAMMA28, TRANSFER_FUNCTION_LOG_100, TRANSFER_FUNCTION_LOG_316,
|
||||
TRANSFER_FUNCTION_ST240, TRANSFER_FUNCTION_ST428, TRANSFER_FUNCTION_ST2084_PQ,
|
||||
consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB},
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::ordered_float::F64,
|
||||
wire::{WpImageDescriptionInfoV1Id, wp_image_description_info_v1::*},
|
||||
},
|
||||
std::{convert::Infallible, rc::Rc},
|
||||
|
|
@ -18,17 +31,46 @@ pub struct WpImageDescriptionInfoV1 {
|
|||
}
|
||||
|
||||
impl WpImageDescriptionInfoV1 {
|
||||
pub fn send_srgb(&self) {
|
||||
let red = [0.64, 0.33];
|
||||
let green = [0.3, 0.6];
|
||||
let blue = [0.15, 0.06];
|
||||
let white = [0.3127, 0.3290];
|
||||
self.send_primaries(red, green, blue, white);
|
||||
self.send_primaries_named(PRIMARIES_SRGB);
|
||||
self.send_tf_named(TRANSFER_FUNCTION_SRGB);
|
||||
self.send_luminances(0.2, 80.0, 80.0);
|
||||
self.send_target_primaries(red, green, blue, white);
|
||||
self.send_target_luminances(0.2, 80.0);
|
||||
pub fn send_description(&self, d: &ColorDescription) {
|
||||
let tf = match d.transfer_function {
|
||||
TransferFunction::Srgb => TRANSFER_FUNCTION_SRGB,
|
||||
TransferFunction::Linear => TRANSFER_FUNCTION_EXT_LINEAR,
|
||||
TransferFunction::St2084Pq => TRANSFER_FUNCTION_ST2084_PQ,
|
||||
TransferFunction::Bt1886 => TRANSFER_FUNCTION_BT1886,
|
||||
TransferFunction::Gamma22 => TRANSFER_FUNCTION_GAMMA22,
|
||||
TransferFunction::Gamma28 => TRANSFER_FUNCTION_GAMMA28,
|
||||
TransferFunction::St240 => TRANSFER_FUNCTION_ST240,
|
||||
TransferFunction::ExtSrgb => TRANSFER_FUNCTION_EXT_SRGB,
|
||||
TransferFunction::Log100 => TRANSFER_FUNCTION_LOG_100,
|
||||
TransferFunction::Log316 => TRANSFER_FUNCTION_LOG_316,
|
||||
TransferFunction::St428 => TRANSFER_FUNCTION_ST428,
|
||||
};
|
||||
self.send_primaries(&d.linear.primaries);
|
||||
if let Some(n) = d.named_primaries {
|
||||
let n = match n {
|
||||
NamedPrimaries::Srgb => PRIMARIES_SRGB,
|
||||
NamedPrimaries::PalM => PRIMARIES_PAL_M,
|
||||
NamedPrimaries::Pal => PRIMARIES_PAL,
|
||||
NamedPrimaries::Ntsc => PRIMARIES_NTSC,
|
||||
NamedPrimaries::GenericFilm => PRIMARIES_GENERIC_FILM,
|
||||
NamedPrimaries::Bt2020 => PRIMARIES_BT2020,
|
||||
NamedPrimaries::Cie1931Xyz => PRIMARIES_CIE1931_XYZ,
|
||||
NamedPrimaries::DciP3 => PRIMARIES_DCI_P3,
|
||||
NamedPrimaries::DisplayP3 => PRIMARIES_DISPLAY_P3,
|
||||
NamedPrimaries::AdobeRgb => PRIMARIES_ADOBE_RGB,
|
||||
};
|
||||
self.send_primaries_named(n);
|
||||
}
|
||||
self.send_tf_named(tf);
|
||||
self.send_luminances(&d.linear.luminance);
|
||||
self.send_target_primaries(&d.linear.target_primaries);
|
||||
self.send_target_luminances(&d.linear.target_luminance);
|
||||
if let Some(max_cll) = d.linear.max_cll {
|
||||
self.send_target_max_cll(max_cll.0);
|
||||
}
|
||||
if let Some(max_fall) = d.linear.max_fall {
|
||||
self.send_target_max_fall(max_fall.0);
|
||||
}
|
||||
self.send_done();
|
||||
}
|
||||
|
||||
|
|
@ -45,18 +87,18 @@ impl WpImageDescriptionInfoV1 {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn send_primaries(&self, r: [f64; 2], g: [f64; 2], b: [f64; 2], w: [f64; 2]) {
|
||||
let map = |c: f64| (c * 1_000_000.0) as i32;
|
||||
pub fn send_primaries(&self, p: &crate::cmm::cmm_primaries::Primaries) {
|
||||
let map = |c: F64| (c.0 * PRIMARIES_MUL) as i32;
|
||||
self.client.event(Primaries {
|
||||
self_id: self.id,
|
||||
r_x: map(r[0]),
|
||||
r_y: map(r[1]),
|
||||
g_x: map(g[0]),
|
||||
g_y: map(g[1]),
|
||||
b_x: map(b[0]),
|
||||
b_y: map(b[1]),
|
||||
w_x: map(w[0]),
|
||||
w_y: map(w[1]),
|
||||
r_x: map(p.r.0),
|
||||
r_y: map(p.r.1),
|
||||
g_x: map(p.g.0),
|
||||
g_y: map(p.g.1),
|
||||
b_x: map(p.b.0),
|
||||
b_y: map(p.b.1),
|
||||
w_x: map(p.wp.0),
|
||||
w_y: map(p.wp.1),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -82,39 +124,38 @@ impl WpImageDescriptionInfoV1 {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn send_luminances(&self, min_lum: f64, max_lum: f64, reference_lum: f64) {
|
||||
pub fn send_luminances(&self, l: &crate::cmm::cmm_luminance::Luminance) {
|
||||
self.client.event(Luminances {
|
||||
self_id: self.id,
|
||||
min_lum: (min_lum * 10_000.0) as u32,
|
||||
max_lum: max_lum as _,
|
||||
reference_lum: reference_lum as _,
|
||||
min_lum: (l.min.0 * MIN_LUM_MUL) as u32,
|
||||
max_lum: l.max.0 as _,
|
||||
reference_lum: l.white.0 as _,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_target_primaries(&self, r: [f64; 2], g: [f64; 2], b: [f64; 2], w: [f64; 2]) {
|
||||
let map = |c: f64| (c * 1_000_000.0) as i32;
|
||||
pub fn send_target_primaries(&self, p: &crate::cmm::cmm_primaries::Primaries) {
|
||||
let map = |c: F64| (c.0 * PRIMARIES_MUL) as i32;
|
||||
self.client.event(TargetPrimaries {
|
||||
self_id: self.id,
|
||||
r_x: map(r[0]),
|
||||
r_y: map(r[1]),
|
||||
g_x: map(g[0]),
|
||||
g_y: map(g[1]),
|
||||
b_x: map(b[0]),
|
||||
b_y: map(b[1]),
|
||||
w_x: map(w[0]),
|
||||
w_y: map(w[1]),
|
||||
r_x: map(p.r.0),
|
||||
r_y: map(p.r.1),
|
||||
g_x: map(p.g.0),
|
||||
g_y: map(p.g.1),
|
||||
b_x: map(p.b.0),
|
||||
b_y: map(p.b.1),
|
||||
w_x: map(p.wp.0),
|
||||
w_y: map(p.wp.1),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_target_luminances(&self, min_lum: f64, max_lum: f64) {
|
||||
pub fn send_target_luminances(&self, l: &crate::cmm::cmm_luminance::TargetLuminance) {
|
||||
self.client.event(TargetLuminance {
|
||||
self_id: self.id,
|
||||
min_lum: (min_lum * 10_000.0) as u32,
|
||||
max_lum: max_lum as _,
|
||||
min_lum: (l.min.0 * MIN_LUM_MUL) as u32,
|
||||
max_lum: l.max.0 as _,
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn send_target_max_cll(&self, max_cll: f64) {
|
||||
self.client.event(TargetMaxCll {
|
||||
self_id: self.id,
|
||||
|
|
@ -122,7 +163,6 @@ impl WpImageDescriptionInfoV1 {
|
|||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn send_target_max_fall(&self, max_fall: f64) {
|
||||
self.client.event(TargetMaxFall {
|
||||
self_id: self.id,
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@ pub struct WpImageDescriptionV1 {
|
|||
pub client: Rc<Client>,
|
||||
pub version: Version,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub description: Rc<ColorDescription>,
|
||||
pub description: Option<Rc<ColorDescription>>,
|
||||
}
|
||||
|
||||
impl WpImageDescriptionV1 {
|
||||
#[expect(dead_code)]
|
||||
pub fn send_failed(&self, cause: u32, msg: &str) {
|
||||
self.client.event(Failed {
|
||||
self_id: self.id,
|
||||
|
|
@ -32,7 +31,7 @@ impl WpImageDescriptionV1 {
|
|||
pub fn send_ready(&self) {
|
||||
self.client.event(Ready {
|
||||
self_id: self.id,
|
||||
identity: self.description.id.into(),
|
||||
identity: self.description.as_ref().unwrap().id.raw(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +45,9 @@ impl WpImageDescriptionV1RequestHandler for WpImageDescriptionV1 {
|
|||
}
|
||||
|
||||
fn get_information(&self, req: GetInformation, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let Some(desc) = &self.description else {
|
||||
return Err(WpImageDescriptionV1Error::NotReady);
|
||||
};
|
||||
let obj = Rc::new(WpImageDescriptionInfoV1 {
|
||||
id: req.information,
|
||||
client: self.client.clone(),
|
||||
|
|
@ -54,7 +56,7 @@ impl WpImageDescriptionV1RequestHandler for WpImageDescriptionV1 {
|
|||
});
|
||||
self.client.add_client_obj(&obj)?;
|
||||
track!(self.client, obj);
|
||||
obj.send_srgb();
|
||||
obj.send_description(desc);
|
||||
self.client.remove_obj(&*obj)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -77,5 +79,7 @@ dedicated_add_obj!(
|
|||
pub enum WpImageDescriptionV1Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("The description is not ready")]
|
||||
NotReady,
|
||||
}
|
||||
efrom!(WpImageDescriptionV1Error, ClientError);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl Global for JayCompositorGlobal {
|
|||
}
|
||||
|
||||
fn version(&self) -> u32 {
|
||||
14
|
||||
15
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
backend,
|
||||
backend::{self, BackendColorSpace, BackendTransferFunction},
|
||||
client::{Client, ClientError},
|
||||
compositor::MAX_EXTENTS,
|
||||
format::named_formats,
|
||||
|
|
@ -15,6 +15,7 @@ use {
|
|||
jay_config::video::{
|
||||
GfxApi, TearingMode as ConfigTearingMode, Transform, VrrMode as ConfigVrrMode,
|
||||
},
|
||||
linearize::LinearizeExt,
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
|
@ -30,6 +31,7 @@ const VRR_CAPABLE_SINCE: Version = Version(2);
|
|||
const TEARING_SINCE: Version = Version(3);
|
||||
const FORMAT_SINCE: Version = Version(8);
|
||||
const FLIP_MARGIN_SINCE: Version = Version(10);
|
||||
const COLORIMETRY_SINCE: Version = Version(15);
|
||||
|
||||
impl JayRandr {
|
||||
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
|
||||
|
|
@ -163,6 +165,28 @@ impl JayRandr {
|
|||
current: (mode == ¤t_mode) as _,
|
||||
});
|
||||
}
|
||||
if self.version >= COLORIMETRY_SINCE {
|
||||
for tf in &node.global.transfer_functions {
|
||||
self.client.event(SupportedTransferFunction {
|
||||
self_id: self.id,
|
||||
transfer_function: tf.name(),
|
||||
});
|
||||
}
|
||||
self.client.event(CurrentTransferFunction {
|
||||
self_id: self.id,
|
||||
transfer_function: node.global.btf.get().name(),
|
||||
});
|
||||
for cs in &node.global.color_spaces {
|
||||
self.client.event(SupportedColorSpace {
|
||||
self_id: self.id,
|
||||
color_space: cs.name(),
|
||||
});
|
||||
}
|
||||
self.client.event(CurrentColorSpace {
|
||||
self_id: self.id,
|
||||
color_space: node.global.bcs.get().name(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn send_error(&self, msg: &str) {
|
||||
|
|
@ -412,6 +436,34 @@ impl JayRandrRequestHandler for JayRandr {
|
|||
dev.dev.set_flip_margin(req.margin_ns);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_colors(&self, req: SetColors<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let cs = 'cs: {
|
||||
for cs in BackendColorSpace::variants() {
|
||||
if cs.name() == req.color_space {
|
||||
break 'cs cs;
|
||||
}
|
||||
}
|
||||
return Err(JayRandrError::UnknownColorSpace(
|
||||
req.color_space.to_string(),
|
||||
));
|
||||
};
|
||||
let tf = 'tf: {
|
||||
for tf in BackendTransferFunction::variants() {
|
||||
if tf.name() == req.transfer_function {
|
||||
break 'tf tf;
|
||||
}
|
||||
}
|
||||
return Err(JayRandrError::UnknownTransferFunction(
|
||||
req.transfer_function.to_string(),
|
||||
));
|
||||
};
|
||||
let Some(c) = self.get_connector(req.output) else {
|
||||
return Ok(());
|
||||
};
|
||||
c.connector.set_colors(cs, tf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
|
|
@ -433,5 +485,9 @@ pub enum JayRandrError {
|
|||
UnknownTearingMode(u32),
|
||||
#[error("Unknown format {0}")]
|
||||
UnknownFormat(String),
|
||||
#[error("Unknown color space {0}")]
|
||||
UnknownColorSpace(String),
|
||||
#[error("Unknown transfer function {0}")]
|
||||
UnknownTransferFunction(String),
|
||||
}
|
||||
efrom!(JayRandrError, ClientError);
|
||||
|
|
|
|||
|
|
@ -2,22 +2,31 @@ mod removed_output;
|
|||
|
||||
use {
|
||||
crate::{
|
||||
backend,
|
||||
backend::{self, BackendColorSpace, BackendLuminance, BackendTransferFunction},
|
||||
client::{Client, ClientError, ClientId},
|
||||
cmm::{
|
||||
cmm_description::ColorDescription,
|
||||
cmm_luminance::Luminance,
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_transfer_function::TransferFunction,
|
||||
},
|
||||
damage::DamageMatrix,
|
||||
format::{Format, XRGB8888},
|
||||
globals::{Global, GlobalName},
|
||||
ifs::{wl_surface::WlSurface, zxdg_output_v1::ZxdgOutputV1},
|
||||
ifs::{
|
||||
color_management::wp_color_management_output_v1::WpColorManagementOutputV1,
|
||||
wl_surface::WlSurface, zxdg_output_v1::ZxdgOutputV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
rect::Rect,
|
||||
state::{ConnectorData, State},
|
||||
tree::{OutputNode, TearingMode, VrrMode, calculate_logical_size},
|
||||
utils::{
|
||||
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, rc_eq::rc_eq,
|
||||
transform_ext::TransformExt,
|
||||
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, ordered_float::F64,
|
||||
rc_eq::rc_eq, transform_ext::TransformExt,
|
||||
},
|
||||
wire::{WlOutputId, ZxdgOutputV1Id, wl_output::*},
|
||||
wire::{WlOutputId, WpColorManagementOutputV1Id, ZxdgOutputV1Id, wl_output::*},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
jay_config::video::Transform,
|
||||
|
|
@ -67,12 +76,22 @@ pub struct WlOutputGlobal {
|
|||
pub format: Cell<&'static Format>,
|
||||
pub width_mm: i32,
|
||||
pub height_mm: i32,
|
||||
pub transfer_functions: Vec<BackendTransferFunction>,
|
||||
pub color_spaces: Vec<BackendColorSpace>,
|
||||
pub primaries: Primaries,
|
||||
pub luminance: Option<BackendLuminance>,
|
||||
pub bindings: RefCell<AHashMap<ClientId, AHashMap<WlOutputId, Rc<WlOutput>>>>,
|
||||
pub destroyed: Cell<bool>,
|
||||
pub legacy_scale: Cell<u32>,
|
||||
pub persistent: Rc<PersistentOutputState>,
|
||||
pub opt: Rc<OutputGlobalOpt>,
|
||||
pub damage_matrix: Cell<DamageMatrix>,
|
||||
pub btf: Cell<BackendTransferFunction>,
|
||||
pub bcs: Cell<BackendColorSpace>,
|
||||
pub color_description: CloneCell<Rc<ColorDescription>>,
|
||||
pub linear_color_description: CloneCell<Rc<ColorDescription>>,
|
||||
pub color_description_listeners:
|
||||
CopyHashMap<(ClientId, WpColorManagementOutputV1Id), Rc<WpColorManagementOutputV1>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -133,6 +152,7 @@ impl WlOutputGlobal {
|
|||
pub fn clear(&self) {
|
||||
self.opt.clear();
|
||||
self.bindings.borrow_mut().clear();
|
||||
self.color_description_listeners.clear();
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
|
|
@ -145,6 +165,12 @@ impl WlOutputGlobal {
|
|||
height_mm: i32,
|
||||
output_id: &Rc<OutputId>,
|
||||
persistent_state: &Rc<PersistentOutputState>,
|
||||
transfer_functions: Vec<BackendTransferFunction>,
|
||||
btf: BackendTransferFunction,
|
||||
color_spaces: Vec<BackendColorSpace>,
|
||||
bcs: BackendColorSpace,
|
||||
primaries: Primaries,
|
||||
luminance: Option<BackendLuminance>,
|
||||
) -> Self {
|
||||
let (x, y) = persistent_state.pos.get();
|
||||
let scale = persistent_state.scale.get();
|
||||
|
|
@ -166,14 +192,24 @@ impl WlOutputGlobal {
|
|||
format: Cell::new(XRGB8888),
|
||||
width_mm,
|
||||
height_mm,
|
||||
transfer_functions,
|
||||
color_spaces,
|
||||
primaries,
|
||||
luminance,
|
||||
bindings: Default::default(),
|
||||
destroyed: Cell::new(false),
|
||||
legacy_scale: Cell::new(scale.round_up()),
|
||||
persistent: persistent_state.clone(),
|
||||
opt: Default::default(),
|
||||
damage_matrix: Default::default(),
|
||||
btf: Cell::new(btf),
|
||||
bcs: Cell::new(bcs),
|
||||
color_description: CloneCell::new(state.color_manager.srgb_srgb().clone()),
|
||||
linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()),
|
||||
color_description_listeners: Default::default(),
|
||||
};
|
||||
global.update_damage_matrix();
|
||||
global.update_color_description();
|
||||
global
|
||||
}
|
||||
|
||||
|
|
@ -292,6 +328,46 @@ impl WlOutputGlobal {
|
|||
pub fn add_visualizer_damage(&self) {
|
||||
self.state.damage_visualizer.copy_damage(self);
|
||||
}
|
||||
|
||||
pub fn update_color_description(&self) -> bool {
|
||||
let mut luminance = Luminance::SRGB;
|
||||
let tf = match self.btf.get() {
|
||||
BackendTransferFunction::Default => TransferFunction::Srgb,
|
||||
BackendTransferFunction::Pq => {
|
||||
luminance = Luminance::ST2084_PQ;
|
||||
TransferFunction::St2084Pq
|
||||
}
|
||||
};
|
||||
let mut target_luminance = luminance.to_target();
|
||||
let mut max_cll = None;
|
||||
let mut max_fall = None;
|
||||
if let Some(l) = self.luminance {
|
||||
target_luminance.min = F64(l.min);
|
||||
target_luminance.max = F64(l.max);
|
||||
max_cll = Some(F64(l.max));
|
||||
max_fall = Some(F64(l.max_fall));
|
||||
}
|
||||
let primaries = match self.bcs.get() {
|
||||
BackendColorSpace::Default => NamedPrimaries::Srgb,
|
||||
BackendColorSpace::Bt2020 => NamedPrimaries::Bt2020,
|
||||
};
|
||||
let cd = self.state.color_manager.get_description(
|
||||
Some(primaries),
|
||||
primaries.primaries(),
|
||||
luminance,
|
||||
tf,
|
||||
self.primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
);
|
||||
let cd_linear = self
|
||||
.state
|
||||
.color_manager
|
||||
.get_with_tf(&cd, TransferFunction::Linear);
|
||||
self.linear_color_description.set(cd_linear.clone());
|
||||
self.color_description.set(cd.clone()).id != cd.id
|
||||
}
|
||||
}
|
||||
|
||||
global_base!(WlOutputGlobal, WlOutput, WlOutputError);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use {
|
|||
ReleaseSync, SampleRect, SyncFile,
|
||||
},
|
||||
ifs::{
|
||||
color_management::wp_color_management_surface_feedback_v1::WpColorManagementSurfaceFeedbackV1,
|
||||
wl_buffer::WlBuffer,
|
||||
wl_callback::WlCallback,
|
||||
wl_seat::{
|
||||
|
|
@ -89,8 +90,8 @@ use {
|
|||
drm::sync_obj::{SyncObj, SyncObjPoint},
|
||||
},
|
||||
wire::{
|
||||
WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, ZwpLinuxDmabufFeedbackV1Id,
|
||||
wl_surface::*,
|
||||
WlOutputId, WlSurfaceId, WpColorManagementSurfaceFeedbackV1Id, ZwpIdleInhibitorV1Id,
|
||||
ZwpLinuxDmabufFeedbackV1Id, wl_surface::*,
|
||||
},
|
||||
xwayland::XWaylandEvent,
|
||||
},
|
||||
|
|
@ -201,6 +202,14 @@ impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct SurfaceSendPreferredColorDescription;
|
||||
impl NodeVisitorBase for SurfaceSendPreferredColorDescription {
|
||||
fn visit_surface(&mut self, node: &Rc<WlSurface>) {
|
||||
node.send_preferred_color_description();
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
struct SurfaceBufferExplicitRelease {
|
||||
sync_obj: Rc<SyncObj>,
|
||||
point: SyncObjPoint,
|
||||
|
|
@ -336,6 +345,8 @@ pub struct WlSurface {
|
|||
before_latch_listener: EventListener<dyn BeforeLatchListener>,
|
||||
is_opaque: Cell<bool>,
|
||||
color_management_surface: CloneCell<Option<Rc<WpColorManagementSurfaceV1>>>,
|
||||
color_management_feedback:
|
||||
CopyHashMap<WpColorManagementSurfaceFeedbackV1Id, Rc<WpColorManagementSurfaceFeedbackV1>>,
|
||||
color_description: CloneCell<Option<Rc<ColorDescription>>>,
|
||||
}
|
||||
|
||||
|
|
@ -678,6 +689,7 @@ impl WlSurface {
|
|||
before_latch_listener: EventListener::new(slf.clone()),
|
||||
is_opaque: Cell::new(false),
|
||||
color_management_surface: Default::default(),
|
||||
color_management_feedback: Default::default(),
|
||||
color_description: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -699,7 +711,6 @@ impl WlSurface {
|
|||
Ok(ext.into_xsurface().unwrap())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn get_output(&self) -> Rc<OutputNode> {
|
||||
self.output.get()
|
||||
}
|
||||
|
|
@ -720,6 +731,9 @@ impl WlSurface {
|
|||
if old.global.persistent.transform.get() != output.global.persistent.transform.get() {
|
||||
self.send_preferred_buffer_transform();
|
||||
}
|
||||
if old.global.color_description.get().id != output.global.color_description.get().id {
|
||||
self.send_preferred_color_description();
|
||||
}
|
||||
let children = self.children.borrow_mut();
|
||||
if let Some(children) = &*children {
|
||||
for ss in children.subsurfaces.values() {
|
||||
|
|
@ -1682,6 +1696,24 @@ impl WlSurface {
|
|||
None => self.client.state.color_manager.srgb_srgb().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_color_management_feedback(&self, fb: &Rc<WpColorManagementSurfaceFeedbackV1>) {
|
||||
self.color_management_feedback.set(fb.id, fb.clone());
|
||||
}
|
||||
|
||||
pub fn remove_color_management_feedback(&self, fb: &WpColorManagementSurfaceFeedbackV1) {
|
||||
self.color_management_feedback.remove(&fb.id);
|
||||
}
|
||||
|
||||
pub fn send_preferred_color_description(&self) {
|
||||
if self.color_management_feedback.is_empty() {
|
||||
return;
|
||||
}
|
||||
let cd = self.output.get().global.color_description.get();
|
||||
for fb in self.color_management_feedback.lock().values() {
|
||||
fb.send_preferred_changed(&cd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
|
|
@ -1714,6 +1746,7 @@ impl Object for WlSurface {
|
|||
self.fifo.take();
|
||||
self.commit_timer.take();
|
||||
self.color_management_surface.take();
|
||||
self.color_management_feedback.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
|
|||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.surface.color_management_surface.take();
|
||||
self.surface.pending.borrow_mut().color_description = Some(None);
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -56,7 +57,10 @@ impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
|
|||
));
|
||||
}
|
||||
let desc = self.client.lookup(req.image_description)?;
|
||||
self.surface.pending.borrow_mut().color_description = Some(Some(desc.description.clone()));
|
||||
if desc.description.is_none() {
|
||||
return Err(WpColorManagementSurfaceV1Error::NotReady);
|
||||
}
|
||||
self.surface.pending.borrow_mut().color_description = Some(desc.description.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -87,5 +91,7 @@ pub enum WpColorManagementSurfaceV1Error {
|
|||
UnsupportedRenderIntent(u32),
|
||||
#[error("wl_surface already has a color-management extension")]
|
||||
HasSurface,
|
||||
#[error("The color description is not ready")]
|
||||
NotReady,
|
||||
}
|
||||
efrom!(WpColorManagementSurfaceV1Error, ClientError);
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ use {
|
|||
allocator::{Allocator, AllocatorError},
|
||||
async_engine::SpawnedFuture,
|
||||
backend::{
|
||||
AxisSource, Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId,
|
||||
ConnectorKernelId, DrmDeviceId, InputDevice, InputDeviceAccelProfile,
|
||||
InputDeviceCapability, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo,
|
||||
ScrollAxis, TransformMatrix,
|
||||
AxisSource, Backend, BackendColorSpace, BackendEvent, BackendTransferFunction,
|
||||
Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice,
|
||||
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent, KeyState,
|
||||
Mode, MonitorInfo, ScrollAxis, TransformMatrix,
|
||||
},
|
||||
cmm::cmm_primaries::Primaries,
|
||||
compositor::TestFuture,
|
||||
drm_feedback::DrmFeedback,
|
||||
fixed::Fixed,
|
||||
|
|
@ -129,6 +130,12 @@ impl TestBackend {
|
|||
height_mm: 60,
|
||||
non_desktop: false,
|
||||
vrr_capable: false,
|
||||
transfer_functions: vec![],
|
||||
transfer_function: BackendTransferFunction::Default,
|
||||
color_spaces: vec![],
|
||||
color_space: BackendColorSpace::Default,
|
||||
primaries: Primaries::SRGB,
|
||||
luminance: None,
|
||||
};
|
||||
Self {
|
||||
state: state.clone(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{BackendEvent, ConnectorEvent, ConnectorKernelId, Mode, MonitorInfo},
|
||||
backend::{
|
||||
BackendColorSpace, BackendEvent, BackendTransferFunction, ConnectorEvent,
|
||||
ConnectorKernelId, Mode, MonitorInfo,
|
||||
},
|
||||
cmm::cmm_primaries::Primaries,
|
||||
ifs::wl_output::OutputId,
|
||||
it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun},
|
||||
video::drm::ConnectorType,
|
||||
|
|
@ -48,6 +52,12 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
height_mm: 0,
|
||||
non_desktop: false,
|
||||
vrr_capable: false,
|
||||
transfer_functions: vec![],
|
||||
transfer_function: BackendTransferFunction::Default,
|
||||
color_spaces: vec![],
|
||||
color_space: BackendColorSpace::Default,
|
||||
primaries: Primaries::SRGB,
|
||||
luminance: None,
|
||||
};
|
||||
run.backend
|
||||
.state
|
||||
|
|
|
|||
|
|
@ -136,6 +136,12 @@ impl ConnectorHandler {
|
|||
info.height_mm,
|
||||
&output_id,
|
||||
&desired_state,
|
||||
info.transfer_functions.clone(),
|
||||
info.transfer_function,
|
||||
info.color_spaces.clone(),
|
||||
info.color_space,
|
||||
info.primaries,
|
||||
info.luminance,
|
||||
));
|
||||
let schedule = Rc::new(OutputSchedule::new(
|
||||
&self.state.ring,
|
||||
|
|
@ -270,6 +276,9 @@ impl ConnectorHandler {
|
|||
on.global.formats.set(formats);
|
||||
on.global.format.set(format);
|
||||
}
|
||||
ConnectorEvent::ColorsChanged(bcs, btf) => {
|
||||
on.update_btf_and_bcs(btf, bcs);
|
||||
}
|
||||
ev => unreachable!("received unexpected event {:?}", ev),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ impl ToolClient {
|
|||
self_id: s.registry,
|
||||
name: s.jay_compositor.0,
|
||||
interface: JayCompositor.name(),
|
||||
version: s.jay_compositor.1.min(14),
|
||||
version: s.jay_compositor.1.min(15),
|
||||
id: id.into(),
|
||||
});
|
||||
self.jay_compositor.set(Some(id));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{HardwareCursor, KeyState, Mode},
|
||||
backend::{BackendColorSpace, BackendTransferFunction, HardwareCursor, KeyState, Mode},
|
||||
client::ClientId,
|
||||
cmm::cmm_description::ColorDescription,
|
||||
cursor::KnownCursor,
|
||||
|
|
@ -18,7 +18,8 @@ use {
|
|||
wl_pointer::PendingScroll,
|
||||
},
|
||||
wl_surface::{
|
||||
SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor,
|
||||
SurfaceSendPreferredColorDescription, SurfaceSendPreferredScaleVisitor,
|
||||
SurfaceSendPreferredTransformVisitor,
|
||||
ext_session_lock_surface_v1::ExtSessionLockSurfaceV1,
|
||||
tray::DynTrayItem,
|
||||
zwlr_layer_surface_v1::{ExclusiveSize, ZwlrLayerSurfaceV1},
|
||||
|
|
@ -837,6 +838,25 @@ impl OutputNode {
|
|||
self.state.tree_changed();
|
||||
}
|
||||
|
||||
pub fn update_btf_and_bcs(&self, btf: BackendTransferFunction, bcs: BackendColorSpace) {
|
||||
let old_btf = self.global.btf.replace(btf);
|
||||
let old_bcs = self.global.bcs.replace(bcs);
|
||||
if (old_btf, old_bcs) == (btf, bcs) {
|
||||
return;
|
||||
}
|
||||
if self.global.update_color_description() {
|
||||
self.state.damage(self.global.position());
|
||||
if let Some(hc) = self.hardware_cursor.get() {
|
||||
self.hardware_cursor_needs_render.set(true);
|
||||
hc.damage();
|
||||
}
|
||||
for fb in self.global.color_description_listeners.lock().values() {
|
||||
fb.send_image_description_changed();
|
||||
}
|
||||
self.node_visit_children(&mut SurfaceSendPreferredColorDescription);
|
||||
}
|
||||
}
|
||||
|
||||
fn find_stacked_at(
|
||||
&self,
|
||||
stack: &LinkedList<Rc<dyn StackedNode>>,
|
||||
|
|
|
|||
131
src/video/drm.rs
131
src/video/drm.rs
|
|
@ -1,6 +1,7 @@
|
|||
pub mod sync_obj;
|
||||
mod sys;
|
||||
pub mod wait_for_sync_obj;
|
||||
pub use consts::*;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
|
|
@ -668,7 +669,7 @@ pub struct DrmPropertyDefinition {
|
|||
pub ty: DrmPropertyType,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmPropertyType {
|
||||
Range {
|
||||
_min: u64,
|
||||
|
|
@ -688,7 +689,7 @@ pub enum DrmPropertyType {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DrmPropertyEnumValue {
|
||||
pub value: u64,
|
||||
pub name: BString,
|
||||
|
|
@ -803,6 +804,132 @@ pub struct DrmVersion {
|
|||
pub desc: BString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct HdrMetadata {
|
||||
pub eotf: u8,
|
||||
pub metadata_type: u8,
|
||||
pub red: (u16, u16),
|
||||
pub green: (u16, u16),
|
||||
pub blue: (u16, u16),
|
||||
pub white: (u16, u16),
|
||||
pub max_display_mastering_luminance: u16,
|
||||
pub min_display_mastering_luminance: u16,
|
||||
pub max_cll: u16,
|
||||
pub max_fall: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub struct hdr_metadata_primary {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
union hdr_output_metadata_type {
|
||||
hdmi_metadata_type1: hdr_metadata_infoframe,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct hdr_output_metadata {
|
||||
metadata_type: u32,
|
||||
ty: hdr_output_metadata_type,
|
||||
}
|
||||
|
||||
impl hdr_output_metadata {
|
||||
pub fn new(infoframe: hdr_metadata_infoframe) -> Self {
|
||||
Self {
|
||||
metadata_type: 0,
|
||||
ty: hdr_output_metadata_type {
|
||||
hdmi_metadata_type1: infoframe,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_eotf(eotf: u8) -> Self {
|
||||
Self::new(hdr_metadata_infoframe {
|
||||
eotf,
|
||||
metadata_type: 0,
|
||||
..hdr_metadata_infoframe::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Pod for hdr_output_metadata {}
|
||||
|
||||
impl Debug for hdr_output_metadata {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("hdr_output_metadata");
|
||||
f.field("metadata_type", &self.metadata_type);
|
||||
match self.metadata_type {
|
||||
0 => unsafe {
|
||||
f.field("hdmi_metadata_type1", &self.ty.hdmi_metadata_type1)
|
||||
.finish()
|
||||
},
|
||||
_ => f.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for hdr_output_metadata {}
|
||||
|
||||
impl PartialEq for hdr_output_metadata {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.metadata_type != other.metadata_type {
|
||||
return false;
|
||||
}
|
||||
match self.metadata_type {
|
||||
0 => unsafe {
|
||||
self.ty
|
||||
.hdmi_metadata_type1
|
||||
.eq(&other.ty.hdmi_metadata_type1)
|
||||
},
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
mod consts {
|
||||
pub const HDMI_EOTF_TRADITIONAL_GAMMA_SDR: u8 = 0;
|
||||
pub const HDMI_EOTF_TRADITIONAL_GAMMA_HDR: u8 = 1;
|
||||
pub const HDMI_EOTF_SMPTE_ST2084: u8 = 2;
|
||||
pub const HDMI_EOTF_BT_2100_HLG: u8 = 3;
|
||||
|
||||
pub const DRM_MODE_COLORIMETRY_DEFAULT: u64 = 0;
|
||||
pub const DRM_MODE_COLORIMETRY_NO_DATA: u64 = 0;
|
||||
pub const DRM_MODE_COLORIMETRY_SMPTE_170M_YCC: u64 = 1;
|
||||
pub const DRM_MODE_COLORIMETRY_BT709_YCC: u64 = 2;
|
||||
pub const DRM_MODE_COLORIMETRY_XVYCC_601: u64 = 3;
|
||||
pub const DRM_MODE_COLORIMETRY_XVYCC_709: u64 = 4;
|
||||
pub const DRM_MODE_COLORIMETRY_SYCC_601: u64 = 5;
|
||||
pub const DRM_MODE_COLORIMETRY_OPYCC_601: u64 = 6;
|
||||
pub const DRM_MODE_COLORIMETRY_OPRGB: u64 = 7;
|
||||
pub const DRM_MODE_COLORIMETRY_BT2020_CYCC: u64 = 8;
|
||||
pub const DRM_MODE_COLORIMETRY_BT2020_RGB: u64 = 9;
|
||||
pub const DRM_MODE_COLORIMETRY_BT2020_YCC: u64 = 10;
|
||||
pub const DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65: u64 = 11;
|
||||
pub const DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER: u64 = 12;
|
||||
pub const DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED: u64 = 13;
|
||||
pub const DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT: u64 = 14;
|
||||
pub const DRM_MODE_COLORIMETRY_BT601_YCC: u64 = 15;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub struct hdr_metadata_infoframe {
|
||||
pub eotf: u8,
|
||||
pub metadata_type: u8,
|
||||
pub display_primaries: [hdr_metadata_primary; 3],
|
||||
pub white_point: hdr_metadata_primary,
|
||||
pub max_display_mastering_luminance: u16,
|
||||
pub min_display_mastering_luminance: u16,
|
||||
pub max_cll: u16,
|
||||
pub max_fall: u16,
|
||||
}
|
||||
|
||||
impl DrmModeInfo {
|
||||
pub fn create_blob(&self, master: &Rc<DrmMaster>) -> Result<PropBlob, DrmError> {
|
||||
let raw = self.to_raw();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use {
|
|||
logging::LogLevel,
|
||||
status::MessageFormat,
|
||||
theme::Color,
|
||||
video::{Format, GfxApi, TearingMode, Transform, VrrMode},
|
||||
video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
std::{
|
||||
|
|
@ -220,6 +220,8 @@ pub struct Output {
|
|||
pub vrr: Option<Vrr>,
|
||||
pub tearing: Option<Tearing>,
|
||||
pub format: Option<Format>,
|
||||
pub color_space: Option<ColorSpace>,
|
||||
pub transfer_function: Option<TransferFunction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use {
|
|||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::video::Transform,
|
||||
jay_config::video::{ColorSpace, TransferFunction, Transform},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -49,8 +49,11 @@ impl Parser for OutputParser<'_> {
|
|||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.cx, span, table);
|
||||
let (name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val, format_val) = ext
|
||||
.extract((
|
||||
let (
|
||||
(name, match_val, x, y, scale, transform, mode, vrr_val, tearing_val, format_val),
|
||||
(color_space, transfer_function),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(str("name")),
|
||||
val("match"),
|
||||
recover(opt(s32("x"))),
|
||||
|
|
@ -61,7 +64,12 @@ impl Parser for OutputParser<'_> {
|
|||
opt(val("vrr")),
|
||||
opt(val("tearing")),
|
||||
opt(val("format")),
|
||||
))?;
|
||||
),
|
||||
(
|
||||
recover(opt(str("color-space"))),
|
||||
recover(opt(str("transfer-function"))),
|
||||
),
|
||||
))?;
|
||||
let transform = match transform {
|
||||
None => None,
|
||||
Some(t) => match t.value {
|
||||
|
|
@ -79,6 +87,36 @@ impl Parser for OutputParser<'_> {
|
|||
}
|
||||
},
|
||||
};
|
||||
let color_space = match color_space {
|
||||
None => None,
|
||||
Some(cs) => match cs.value {
|
||||
"default" => Some(ColorSpace::DEFAULT),
|
||||
"bt2020" => Some(ColorSpace::BT2020),
|
||||
_ => {
|
||||
log::warn!(
|
||||
"Unknown color space {}: {}",
|
||||
cs.value,
|
||||
self.cx.error3(cs.span)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
let transfer_function = match transfer_function {
|
||||
None => None,
|
||||
Some(tf) => match tf.value {
|
||||
"default" => Some(TransferFunction::DEFAULT),
|
||||
"pq" => Some(TransferFunction::PQ),
|
||||
_ => {
|
||||
log::warn!(
|
||||
"Unknown transfer function {}: {}",
|
||||
tf.value,
|
||||
self.cx.error3(tf.span)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
let mode = match mode {
|
||||
Some(mode) => match mode.parse(&mut ModeParser(self.cx)) {
|
||||
Ok(m) => Some(m),
|
||||
|
|
@ -144,6 +182,8 @@ impl Parser for OutputParser<'_> {
|
|||
vrr,
|
||||
tearing,
|
||||
format,
|
||||
color_space,
|
||||
transfer_function,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ use {
|
|||
switch_to_vt,
|
||||
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
||||
video::{
|
||||
Connector, DrmDevice, connectors, drm_devices, on_connector_connected,
|
||||
on_connector_disconnected, on_graphics_initialized, on_new_connector,
|
||||
on_new_drm_device, set_direct_scanout_enabled, set_gfx_api, set_tearing_mode,
|
||||
set_vrr_cursor_hz, set_vrr_mode,
|
||||
ColorSpace, Connector, DrmDevice, TransferFunction, connectors, drm_devices,
|
||||
on_connector_connected, on_connector_disconnected, on_graphics_initialized,
|
||||
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
|
||||
set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode,
|
||||
},
|
||||
xwayland::set_x_scaling_mode,
|
||||
},
|
||||
|
|
@ -588,6 +588,11 @@ impl Output {
|
|||
if let Some(format) = self.format {
|
||||
c.set_format(format);
|
||||
}
|
||||
if self.color_space.is_some() || self.transfer_function.is_some() {
|
||||
let cs = self.color_space.unwrap_or(ColorSpace::DEFAULT);
|
||||
let tf = self.transfer_function.unwrap_or(TransferFunction::DEFAULT);
|
||||
c.set_colors(cs, tf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -438,6 +438,14 @@
|
|||
},
|
||||
"required": []
|
||||
},
|
||||
"ColorSpace": {
|
||||
"type": "string",
|
||||
"description": "The color space of an output.\n",
|
||||
"enum": [
|
||||
"default",
|
||||
"bt2020"
|
||||
]
|
||||
},
|
||||
"ComplexShortcut": {
|
||||
"description": "Describes a complex shortcut.\n\n- Example:\n\n ```toml\n [complex-shortcuts.XF86AudioRaiseVolume]\n mod-mask = \"alt\"\n action = { type = \"exec\", exec = [\"pactl\", \"set-sink-volume\", \"0\", \"+10%\"] }\n ```\n",
|
||||
"type": "object",
|
||||
|
|
@ -1152,6 +1160,14 @@
|
|||
"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"
|
||||
},
|
||||
"color-space": {
|
||||
"description": "The color space of the output.\n",
|
||||
"$ref": "#/$defs/ColorSpace"
|
||||
},
|
||||
"transfer-function": {
|
||||
"description": "The transfer function of the output.\n",
|
||||
"$ref": "#/$defs/TransferFunction"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1372,6 +1388,14 @@
|
|||
},
|
||||
"required": []
|
||||
},
|
||||
"TransferFunction": {
|
||||
"type": "string",
|
||||
"description": "The transfer function of an output.\n",
|
||||
"enum": [
|
||||
"default",
|
||||
"pq"
|
||||
]
|
||||
},
|
||||
"Transform": {
|
||||
"type": "string",
|
||||
"description": "An output transformation.",
|
||||
|
|
|
|||
|
|
@ -617,6 +617,25 @@ The table has the following fields:
|
|||
The value of this field should be a boolean.
|
||||
|
||||
|
||||
<a name="types-ColorSpace"></a>
|
||||
### `ColorSpace`
|
||||
|
||||
The color space of an output.
|
||||
|
||||
Values of this type should be strings.
|
||||
|
||||
The string should have one of the following values:
|
||||
|
||||
- `default`:
|
||||
|
||||
The default color space (usually sRGB).
|
||||
|
||||
- `bt2020`:
|
||||
|
||||
The BT.2020 color space.
|
||||
|
||||
|
||||
|
||||
<a name="types-ComplexShortcut"></a>
|
||||
### `ComplexShortcut`
|
||||
|
||||
|
|
@ -2530,6 +2549,18 @@ The table has the following fields:
|
|||
|
||||
The value of this field should be a [Format](#types-Format).
|
||||
|
||||
- `color-space` (optional):
|
||||
|
||||
The color space of the output.
|
||||
|
||||
The value of this field should be a [ColorSpace](#types-ColorSpace).
|
||||
|
||||
- `transfer-function` (optional):
|
||||
|
||||
The transfer function of the output.
|
||||
|
||||
The value of this field should be a [TransferFunction](#types-TransferFunction).
|
||||
|
||||
|
||||
<a name="types-OutputMatch"></a>
|
||||
### `OutputMatch`
|
||||
|
|
@ -3050,6 +3081,25 @@ The table has the following fields:
|
|||
The value of this field should be a string.
|
||||
|
||||
|
||||
<a name="types-TransferFunction"></a>
|
||||
### `TransferFunction`
|
||||
|
||||
The transfer function of an output.
|
||||
|
||||
Values of this type should be strings.
|
||||
|
||||
The string should have one of the following values:
|
||||
|
||||
- `default`:
|
||||
|
||||
The default transfer function (usually sRGB).
|
||||
|
||||
- `pq`:
|
||||
|
||||
The PQ transfer function.
|
||||
|
||||
|
||||
|
||||
<a name="types-Transform"></a>
|
||||
### `Transform`
|
||||
|
||||
|
|
|
|||
|
|
@ -1631,6 +1631,16 @@ Output:
|
|||
match.serial-number = "33K03894SL0"
|
||||
format = "rgb565"
|
||||
```
|
||||
color-space:
|
||||
ref: ColorSpace
|
||||
required: false
|
||||
description: |
|
||||
The color space of the output.
|
||||
transfer-function:
|
||||
ref: TransferFunction
|
||||
required: false
|
||||
description: |
|
||||
The transfer function of the output.
|
||||
|
||||
|
||||
Transform:
|
||||
|
|
@ -2762,3 +2772,25 @@ ColorManagement:
|
|||
The default is `false`.
|
||||
kind: boolean
|
||||
required: false
|
||||
|
||||
|
||||
ColorSpace:
|
||||
description: |
|
||||
The color space of an output.
|
||||
kind: string
|
||||
values:
|
||||
- value: default
|
||||
description: The default color space (usually sRGB).
|
||||
- value: bt2020
|
||||
description: The BT.2020 color space.
|
||||
|
||||
|
||||
TransferFunction:
|
||||
description: |
|
||||
The transfer function of an output.
|
||||
kind: string
|
||||
values:
|
||||
- value: default
|
||||
description: The default transfer function (usually sRGB).
|
||||
- value: pq
|
||||
description: The PQ transfer function.
|
||||
|
|
|
|||
|
|
@ -80,6 +80,12 @@ request set_flip_margin (since = 10) {
|
|||
margin_ns: pod(u64),
|
||||
}
|
||||
|
||||
request set_colors (since = 15) {
|
||||
output: str,
|
||||
color_space: str,
|
||||
transfer_function: str,
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event global {
|
||||
|
|
@ -160,3 +166,19 @@ event fb_format (since = 8) {
|
|||
event flip_margin (since = 10) {
|
||||
margin_ns: pod(u64),
|
||||
}
|
||||
|
||||
event supported_color_space (since = 15) {
|
||||
color_space: str,
|
||||
}
|
||||
|
||||
event current_color_space (since = 15) {
|
||||
color_space: str,
|
||||
}
|
||||
|
||||
event supported_transfer_function (since = 15) {
|
||||
transfer_function: str,
|
||||
}
|
||||
|
||||
event current_transfer_function (since = 15) {
|
||||
transfer_function: str,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue