Merge pull request #275 from mahkoh/jorth/vsync-min-max
wp-presentation: implement version 2
This commit is contained in:
commit
eb905c160f
8 changed files with 152 additions and 14 deletions
|
|
@ -163,7 +163,7 @@ Jay supports the following wayland protocols:
|
||||||
| wp_fifo_manager_v1 | 1 | |
|
| wp_fifo_manager_v1 | 1 | |
|
||||||
| wp_fractional_scale_manager_v1 | 1 | |
|
| wp_fractional_scale_manager_v1 | 1 | |
|
||||||
| wp_linux_drm_syncobj_manager_v1 | 1 | |
|
| wp_linux_drm_syncobj_manager_v1 | 1 | |
|
||||||
| wp_presentation | 1 | |
|
| wp_presentation | 2 | |
|
||||||
| wp_security_context_manager_v1 | 1 | |
|
| wp_security_context_manager_v1 | 1 | |
|
||||||
| wp_single_pixel_buffer_manager_v1 | 1 | |
|
| wp_single_pixel_buffer_manager_v1 | 1 | |
|
||||||
| wp_tearing_control_manager_v1 | 1 | |
|
| wp_tearing_control_manager_v1 | 1 | |
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use {
|
||||||
MetalBackend, MetalError,
|
MetalBackend, MetalError,
|
||||||
},
|
},
|
||||||
drm_feedback::DrmFeedback,
|
drm_feedback::DrmFeedback,
|
||||||
edid::Descriptor,
|
edid::{CtaDataBlock, Descriptor, EdidExtension},
|
||||||
format::{Format, ARGB8888, XRGB8888},
|
format::{Format, ARGB8888, XRGB8888},
|
||||||
gfx_api::{
|
gfx_api::{
|
||||||
needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync,
|
needs_render_usage, AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync,
|
||||||
|
|
@ -331,6 +331,7 @@ pub struct ConnectorDisplayData {
|
||||||
pub non_desktop: bool,
|
pub non_desktop: bool,
|
||||||
pub non_desktop_effective: bool,
|
pub non_desktop_effective: bool,
|
||||||
pub vrr_capable: bool,
|
pub vrr_capable: bool,
|
||||||
|
pub _vrr_refresh_max_nsec: u64,
|
||||||
|
|
||||||
pub connector_id: ConnectorKernelId,
|
pub connector_id: ConnectorKernelId,
|
||||||
pub output_id: Rc<OutputId>,
|
pub output_id: Rc<OutputId>,
|
||||||
|
|
@ -1129,6 +1130,7 @@ fn create_connector_display_data(
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
let mut manufacturer = String::new();
|
let mut manufacturer = String::new();
|
||||||
let mut serial_number = String::new();
|
let mut serial_number = String::new();
|
||||||
|
let mut vrr_refresh_max_nsec = u64::MAX;
|
||||||
let connector_id = ConnectorKernelId {
|
let connector_id = ConnectorKernelId {
|
||||||
ty: ConnectorType::from_drm(info.connector_type),
|
ty: ConnectorType::from_drm(info.connector_type),
|
||||||
idx: info.connector_type_id,
|
idx: info.connector_type_id,
|
||||||
|
|
@ -1194,6 +1196,28 @@ fn create_connector_display_data(
|
||||||
);
|
);
|
||||||
serial_number = edid.base_block.id_serial_number.to_string();
|
serial_number = edid.base_block.id_serial_number.to_string();
|
||||||
}
|
}
|
||||||
|
let min_vrr_hz = 'fetch_min_hz: {
|
||||||
|
for ext in &edid.extension_blocks {
|
||||||
|
if let EdidExtension::CtaV3(cta) = ext {
|
||||||
|
for data_block in &cta.data_blocks {
|
||||||
|
if let CtaDataBlock::VendorAmd(amd) = data_block {
|
||||||
|
break 'fetch_min_hz amd.minimum_refresh_hz as u64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for desc in &edid.base_block.descriptors {
|
||||||
|
if let Some(desc) = desc {
|
||||||
|
if let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc {
|
||||||
|
break 'fetch_min_hz timings.vertical_field_rate_min as u64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
};
|
||||||
|
if min_vrr_hz > 0 {
|
||||||
|
vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let output_id = Rc::new(OutputId::new(
|
let output_id = Rc::new(OutputId::new(
|
||||||
connector_id.to_string(),
|
connector_id.to_string(),
|
||||||
|
|
@ -1249,6 +1273,7 @@ fn create_connector_display_data(
|
||||||
non_desktop,
|
non_desktop,
|
||||||
non_desktop_effective: non_desktop_override.unwrap_or(non_desktop),
|
non_desktop_effective: non_desktop_override.unwrap_or(non_desktop),
|
||||||
vrr_capable,
|
vrr_capable,
|
||||||
|
_vrr_refresh_max_nsec: vrr_refresh_max_nsec,
|
||||||
connection,
|
connection,
|
||||||
mm_width: info.mm_width,
|
mm_width: info.mm_width,
|
||||||
mm_height: info.mm_height,
|
mm_height: info.mm_height,
|
||||||
|
|
@ -2004,17 +2029,14 @@ impl MetalBackend {
|
||||||
if connector.presentation_is_zero_copy.get() {
|
if connector.presentation_is_zero_copy.get() {
|
||||||
flags |= KIND_ZERO_COPY;
|
flags |= KIND_ZERO_COPY;
|
||||||
}
|
}
|
||||||
let refresh = match crtc.vrr_enabled.value.get() {
|
|
||||||
true => 0,
|
|
||||||
false => dd.refresh,
|
|
||||||
};
|
|
||||||
if let Some(g) = &global {
|
if let Some(g) = &global {
|
||||||
g.presented(
|
g.presented(
|
||||||
tv_sec as _,
|
tv_sec as _,
|
||||||
tv_usec * 1000,
|
tv_usec * 1000,
|
||||||
refresh,
|
dd.refresh,
|
||||||
connector.sequence.get(),
|
connector.sequence.get(),
|
||||||
flags,
|
flags,
|
||||||
|
crtc.vrr_enabled.value.get(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
105
src/edid.rs
105
src/edid.rs
|
|
@ -431,6 +431,16 @@ impl<'a> EdidParser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nest(&self, data: &'a [u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
pos: 0,
|
||||||
|
context: self.context.clone(),
|
||||||
|
saved_ctx: vec![],
|
||||||
|
errors: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn store_error(&mut self, error: EdidError) {
|
fn store_error(&mut self, error: EdidError) {
|
||||||
self.errors.push((error, self.saved_ctx.clone()));
|
self.errors.push((error, self.saved_ctx.clone()));
|
||||||
}
|
}
|
||||||
|
|
@ -449,6 +459,16 @@ impl<'a> EdidParser<'a> {
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_var_n(&mut self, n: usize) -> Result<&'a [u8], EdidError> {
|
||||||
|
let _ctx = self.push_ctx(EdidParseContext::ReadingBytes(n));
|
||||||
|
if self.data.len() - self.pos < n {
|
||||||
|
bail!(self, EdidError::UnexpectedEof);
|
||||||
|
}
|
||||||
|
let v = &self.data[self.pos..self.pos + n];
|
||||||
|
self.pos += n;
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_u8(&mut self) -> Result<u8, EdidError> {
|
fn read_u8(&mut self) -> Result<u8, EdidError> {
|
||||||
let &[a] = self.read_n()?;
|
let &[a] = self.read_n()?;
|
||||||
Ok(a)
|
Ok(a)
|
||||||
|
|
@ -1000,10 +1020,71 @@ impl<'a> EdidParser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_cta_amd_vendor_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
|
||||||
|
let _ = self.read_n::<2>()?;
|
||||||
|
Ok(CtaDataBlock::VendorAmd(CtaAmdVendorDataBlock {
|
||||||
|
minimum_refresh_hz: self.read_u8()?,
|
||||||
|
maximum_refresh_hz: self.read_u8()?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cta_vendor_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
|
||||||
|
match self.read_n::<3>()? {
|
||||||
|
[0x1A, 0x00, 0x00] => self.parse_cta_amd_vendor_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(),
|
||||||
|
_ => Ok(CtaDataBlock::Unknown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cta_extension_v3(&mut self) -> Result<EdidExtension, EdidError> {
|
||||||
|
let detailed_timing_descriptors_offset = self.read_u8()? as usize;
|
||||||
|
let _ = self.read_u8()?;
|
||||||
|
let mut data_blocks = vec![];
|
||||||
|
while self.pos < detailed_timing_descriptors_offset {
|
||||||
|
let b1 = self.read_u8()?;
|
||||||
|
let data = self.read_var_n(b1 as usize & 0x1f)?;
|
||||||
|
let mut parser = self.nest(data);
|
||||||
|
match parser.parse_cta_data_block(b1 >> 5) {
|
||||||
|
Ok(d) => data_blocks.push(d),
|
||||||
|
Err(e) => {
|
||||||
|
self.saved_ctx = parser.saved_ctx;
|
||||||
|
self.store_error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(EdidExtension::CtaV3(CtaExtensionV3 { data_blocks }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cta_extension(&mut self) -> Result<EdidExtension, EdidError> {
|
||||||
|
// https://web.archive.org/web/20171201033424/https://standards.cta.tech/kwspub/published_docs/CTA-861-G_FINAL_revised_2017.pdf
|
||||||
|
match self.read_u8()? {
|
||||||
|
0x3 => self.parse_cta_extension_v3(),
|
||||||
|
_ => Ok(EdidExtension::Unknown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_extension_impl(&mut self) -> Result<EdidExtension, EdidError> {
|
||||||
|
match self.read_u8()? {
|
||||||
|
0x2 => self.parse_cta_extension(),
|
||||||
|
_ => Ok(EdidExtension::Unknown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_extension(&mut self) -> Result<EdidExtension, EdidError> {
|
fn parse_extension(&mut self) -> Result<EdidExtension, EdidError> {
|
||||||
let _ctx = self.push_ctx(EdidParseContext::Extension);
|
let _ctx = self.push_ctx(EdidParseContext::Extension);
|
||||||
self.read_n::<128>()?;
|
let data = self.read_n::<128>()?;
|
||||||
Ok(EdidExtension::Unknown)
|
let mut parser = self.nest(data);
|
||||||
|
let res = parser.parse_extension_impl();
|
||||||
|
if res.is_err() {
|
||||||
|
self.saved_ctx = parser.saved_ctx;
|
||||||
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(&mut self) -> Result<EdidFile, EdidError> {
|
fn parse(&mut self) -> Result<EdidFile, EdidError> {
|
||||||
|
|
@ -1080,10 +1161,28 @@ pub struct EdidBaseBlock {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EdidExtension {
|
pub enum EdidExtension {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
CtaV3(CtaExtensionV3),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CtaExtensionV3 {
|
||||||
|
pub data_blocks: Vec<CtaDataBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CtaDataBlock {
|
||||||
|
Unknown,
|
||||||
|
VendorAmd(CtaAmdVendorDataBlock),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CtaAmdVendorDataBlock {
|
||||||
|
pub minimum_refresh_hz: u8,
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub maximum_refresh_hz: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[expect(dead_code)]
|
|
||||||
pub struct EdidFile {
|
pub struct EdidFile {
|
||||||
pub base_block: EdidBaseBlock,
|
pub base_block: EdidBaseBlock,
|
||||||
pub extension_blocks: Vec<EdidExtension>,
|
pub extension_blocks: Vec<EdidExtension>,
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,7 @@ impl PresentationListener for ExtImageCopyCaptureSessionV1 {
|
||||||
_refresh: u32,
|
_refresh: u32,
|
||||||
_seq: u64,
|
_seq: u64,
|
||||||
_flags: u32,
|
_flags: u32,
|
||||||
|
_vrr: bool,
|
||||||
) {
|
) {
|
||||||
self.presentation_listener.detach();
|
self.presentation_listener.detach();
|
||||||
let Some(frame) = self.frame.get() else {
|
let Some(frame) = self.frame.get() else {
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ use {
|
||||||
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
|
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
|
||||||
},
|
},
|
||||||
wp_content_type_v1::ContentType,
|
wp_content_type_v1::ContentType,
|
||||||
wp_presentation_feedback::WpPresentationFeedback,
|
wp_presentation_feedback::{WpPresentationFeedback, VRR_REFRESH_SINCE},
|
||||||
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
|
||||||
},
|
},
|
||||||
io_uring::IoUringError,
|
io_uring::IoUringError,
|
||||||
|
|
@ -2190,6 +2190,7 @@ impl PresentationListener for WlSurface {
|
||||||
refresh: u32,
|
refresh: u32,
|
||||||
seq: u64,
|
seq: u64,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
|
vrr: bool,
|
||||||
) {
|
) {
|
||||||
let bindings = output.global.bindings.borrow();
|
let bindings = output.global.bindings.borrow();
|
||||||
let bindings = bindings.get(&self.client.id);
|
let bindings = bindings.get(&self.client.id);
|
||||||
|
|
@ -2199,6 +2200,10 @@ impl PresentationListener for WlSurface {
|
||||||
pf.send_sync_output(binding);
|
pf.send_sync_output(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut refresh = refresh;
|
||||||
|
if vrr && pf.version < VRR_REFRESH_SINCE {
|
||||||
|
refresh = 0;
|
||||||
|
}
|
||||||
pf.send_presented(tv_sec, tv_nsec, refresh, seq, flags);
|
pf.send_presented(tv_sec, tv_nsec, refresh, seq, flags);
|
||||||
let _ = pf.client.remove_obj(&*pf);
|
let _ = pf.client.remove_obj(&*pf);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ impl Global for WpPresentationGlobal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> u32 {
|
fn version(&self) -> u32 {
|
||||||
1
|
2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ pub const KIND_HW_CLOCK: u32 = 0x2;
|
||||||
pub const KIND_HW_COMPLETION: u32 = 0x4;
|
pub const KIND_HW_COMPLETION: u32 = 0x4;
|
||||||
pub const KIND_ZERO_COPY: u32 = 0x8;
|
pub const KIND_ZERO_COPY: u32 = 0x8;
|
||||||
|
|
||||||
|
pub const VRR_REFRESH_SINCE: Version = Version(2);
|
||||||
|
|
||||||
impl WpPresentationFeedback {
|
impl WpPresentationFeedback {
|
||||||
pub fn send_sync_output(&self, output: &WlOutput) {
|
pub fn send_sync_output(&self, output: &WlOutput) {
|
||||||
self.client.event(SyncOutput {
|
self.client.event(SyncOutput {
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ pub trait PresentationListener {
|
||||||
refresh: u32,
|
refresh: u32,
|
||||||
seq: u64,
|
seq: u64,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
|
vrr: bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,9 +189,17 @@ impl OutputNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn presented(&self, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32) {
|
pub fn presented(
|
||||||
|
&self,
|
||||||
|
tv_sec: u64,
|
||||||
|
tv_nsec: u32,
|
||||||
|
refresh: u32,
|
||||||
|
seq: u64,
|
||||||
|
flags: u32,
|
||||||
|
vrr: bool,
|
||||||
|
) {
|
||||||
for listener in self.presentation_event.iter() {
|
for listener in self.presentation_event.iter() {
|
||||||
listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags);
|
listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue