use drmkit_core::{AtomicRequest, CoreError, Device, PropertyBlob, PropertyStore};
use crate::capabilities::CrtcCapabilities;
use crate::curves::{
ColorCtm, ColorLut, build_bt2020_to_bt709_ctm, build_hlg_oetf_inverse_lut, build_identity_ctm,
build_identity_lut, build_pq_eotf_lut, build_pq_oetf_lut,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Stage {
Degamma,
Ctm,
Gamma,
}
impl Stage {
#[must_use]
pub const fn property(self) -> &'static str {
match self {
Self::Degamma => "DEGAMMA_LUT",
Self::Ctm => "CTM",
Self::Gamma => "GAMMA_LUT",
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PipelineError {
#[error("this CRTC has no color pipeline at all")]
NoPipeline,
#[error("{} is a matrix, not a LUT", .0.property())]
NotALut(Stage),
#[error(transparent)]
Core(#[from] CoreError),
#[error("this CRTC has no {} stage", .0.property())]
NoSuchStage(Stage),
#[error("{} wants {expected} entries, not {given}", .stage.property())]
WrongLutSize {
stage: Stage,
expected: u32,
given: usize,
},
}
#[derive(Debug)]
pub struct CrtcColorPipeline<'a> {
device: &'a Device,
crtc_id: u32,
capabilities: CrtcCapabilities,
properties: StageProperties,
degamma: Option<PropertyBlob<'a>>,
ctm: Option<PropertyBlob<'a>>,
gamma: Option<PropertyBlob<'a>>,
}
#[derive(Debug, Clone, Copy, Default)]
struct StageProperties {
degamma: Option<u32>,
ctm: Option<u32>,
gamma: Option<u32>,
}
impl<'a> CrtcColorPipeline<'a> {
pub fn new(device: &'a Device, crtc_id: u32) -> Result<Self, PipelineError> {
let mut properties = PropertyStore::new();
properties.cache_properties(device, crtc_id, drmkit_core::ObjectType::Crtc)?;
let capabilities = CrtcCapabilities::from_properties(&properties, crtc_id);
if !capabilities.has_degamma_lut && !capabilities.has_ctm && !capabilities.has_gamma_lut {
return Err(PipelineError::NoPipeline);
}
let id = |stage: Stage| properties.property_id(crtc_id, stage.property()).ok();
Ok(Self {
device,
crtc_id,
capabilities,
properties: StageProperties {
degamma: id(Stage::Degamma),
ctm: id(Stage::Ctm),
gamma: id(Stage::Gamma),
},
degamma: None,
ctm: None,
gamma: None,
})
}
#[must_use]
pub const fn capabilities(&self) -> &CrtcCapabilities {
&self.capabilities
}
#[must_use]
pub fn blob_id(&self, stage: Stage) -> Option<u64> {
self.slot(stage).map(PropertyBlob::id)
}
pub fn set_identity(&mut self) -> Result<(), PipelineError> {
if self.capabilities.has_degamma_lut {
self.set_lut(Stage::Degamma, build_identity_lut)?;
}
if self.capabilities.has_ctm {
self.set_ctm_blob(build_identity_ctm())?;
}
if self.capabilities.has_gamma_lut {
self.set_lut(Stage::Gamma, build_identity_lut)?;
}
Ok(())
}
pub fn set_pq_to_linear(&mut self) -> Result<(), PipelineError> {
self.require(Stage::Degamma)?;
self.set_lut(Stage::Degamma, build_pq_eotf_lut)
}
pub fn set_hlg_to_linear(&mut self) -> Result<(), PipelineError> {
self.require(Stage::Degamma)?;
self.set_lut(Stage::Degamma, build_hlg_oetf_inverse_lut)
}
pub fn set_bt2020_to_bt709(&mut self) -> Result<(), PipelineError> {
self.require(Stage::Ctm)?;
self.set_ctm_blob(build_bt2020_to_bt709_ctm())
}
pub fn set_linear_to_pq(&mut self) -> Result<(), PipelineError> {
self.require(Stage::Gamma)?;
self.set_lut(Stage::Gamma, build_pq_oetf_lut)
}
pub fn set_custom_lut(&mut self, stage: Stage, lut: &[ColorLut]) -> Result<(), PipelineError> {
self.require(stage)?;
let expected = self.lut_size(stage).ok_or(PipelineError::NotALut(stage))?;
if lut.len() != expected as usize {
return Err(PipelineError::WrongLutSize {
stage,
expected,
given: lut.len(),
});
}
self.replace(stage, self.device.create_property_blob(lut)?);
Ok(())
}
pub fn set_custom_ctm(&mut self, ctm: ColorCtm) -> Result<(), PipelineError> {
self.require(Stage::Ctm)?;
self.set_ctm_blob(ctm)
}
pub fn apply(&self, request: &mut AtomicRequest) -> Result<(), PipelineError> {
for stage in [Stage::Degamma, Stage::Ctm, Stage::Gamma] {
let (Some(blob), Some(property)) = (self.blob_id(stage), self.property_id(stage))
else {
continue;
};
request.add_property(self.crtc_id, property, blob)?;
}
Ok(())
}
#[deprecated(
since = "0.1.3",
note = "use `pause` and `PausedPipeline::resume`, which rebind to the resumed device"
)]
pub fn forget_for_session_loss(&mut self) {
self.forget_blobs();
}
#[must_use]
pub fn pause(mut self) -> PausedPipeline {
self.forget_blobs();
PausedPipeline {
crtc_id: self.crtc_id,
capabilities: self.capabilities,
properties: self.properties,
}
}
fn forget_blobs(&mut self) {
for slot in [&mut self.degamma, &mut self.ctm, &mut self.gamma] {
if let Some(blob) = slot.take() {
blob.forget();
}
}
}
fn require(&self, stage: Stage) -> Result<(), PipelineError> {
let present = match stage {
Stage::Degamma => self.capabilities.has_degamma_lut,
Stage::Ctm => self.capabilities.has_ctm,
Stage::Gamma => self.capabilities.has_gamma_lut,
};
if present {
Ok(())
} else {
Err(PipelineError::NoSuchStage(stage))
}
}
const fn lut_size(&self, stage: Stage) -> Option<u32> {
match stage {
Stage::Degamma => Some(self.capabilities.degamma_lut_size),
Stage::Gamma => Some(self.capabilities.gamma_lut_size),
Stage::Ctm => None,
}
}
const fn property_id(&self, stage: Stage) -> Option<u32> {
match stage {
Stage::Degamma => self.properties.degamma,
Stage::Ctm => self.properties.ctm,
Stage::Gamma => self.properties.gamma,
}
}
const fn slot(&self, stage: Stage) -> Option<&PropertyBlob<'a>> {
match stage {
Stage::Degamma => self.degamma.as_ref(),
Stage::Ctm => self.ctm.as_ref(),
Stage::Gamma => self.gamma.as_ref(),
}
}
fn replace(&mut self, stage: Stage, blob: PropertyBlob<'a>) {
let slot = match stage {
Stage::Degamma => &mut self.degamma,
Stage::Ctm => &mut self.ctm,
Stage::Gamma => &mut self.gamma,
};
*slot = Some(blob);
}
fn set_lut(&mut self, stage: Stage, build: fn(&mut [ColorLut])) -> Result<(), PipelineError> {
let size = self.lut_size(stage).ok_or(PipelineError::NotALut(stage))? as usize;
let mut lut = vec![ColorLut::default(); size];
build(&mut lut);
let blob = self.device.create_property_blob(lut.as_slice())?;
self.replace(stage, blob);
Ok(())
}
fn set_ctm_blob(&mut self, ctm: ColorCtm) -> Result<(), PipelineError> {
let blob = self.device.create_property_blob(&ctm)?;
self.replace(Stage::Ctm, blob);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PausedPipeline {
crtc_id: u32,
capabilities: CrtcCapabilities,
properties: StageProperties,
}
impl PausedPipeline {
#[must_use]
pub const fn resume(self, device: &Device) -> CrtcColorPipeline<'_> {
CrtcColorPipeline {
device,
crtc_id: self.crtc_id,
capabilities: self.capabilities,
properties: self.properties,
degamma: None,
ctm: None,
gamma: None,
}
}
#[must_use]
pub const fn capabilities(&self) -> &CrtcCapabilities {
&self.capabilities
}
}