feat(render): add output-specific DMA-BUF feedback (#53)
Co-authored-by: Samuel Onoja <[email protected]>
This commit is contained in:
1 parent
9251522a69
commit
c8dfb3074d
13 files changed
+913
-3
No files matched your search
@@ -8,6 +8,7 @@ use std::time::{Duration, Instant};
|
||||
pub(crate) mod capture;
|
||||
mod crtc_assignment;
|
||||
mod diagnostics;
|
||||
pub(crate) mod feedback;
|
||||
pub(crate) mod gamma;
|
||||
mod lid;
|
||||
mod mirror;
|
||||
@@ -22,14 +23,15 @@ use diagnostics::{Diagnostics, PrimaryOutcome};
|
||||
use output_power::{OutputPower, Power};
|
||||
use redraw::{AnimationFallback, SchedulingMetrics};
|
||||
use scheduling::*;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
|
||||
use smithay::backend::allocator::{Buffer, Fourcc};
|
||||
use smithay::backend::drm::compositor::{DrmCompositor, FrameFlags, PrimaryPlaneElement};
|
||||
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;
|
||||
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, NodeType};
|
||||
use smithay::backend::egl::{EGLContext, EGLDisplay};
|
||||
use smithay::backend::input::InputEvent;
|
||||
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
|
||||
use smithay::backend::renderer::element::{RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::backend::renderer::{ImportDma, ImportMemWl, Renderer};
|
||||
@@ -277,6 +279,7 @@ struct DirectOutput {
|
||||
capture_texture: Option<GlesTexture>,
|
||||
render_metrics: RenderMetrics,
|
||||
diagnostics: Diagnostics,
|
||||
dmabuf_feedback: feedback::OutputFeedback,
|
||||
frame_pending: bool,
|
||||
completion: recovery::Completion,
|
||||
completion_timer: Option<RegistrationToken>,
|
||||
@@ -413,6 +416,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
||||
output.gamma.suspend();
|
||||
cancel_output_timers(&handle, output);
|
||||
output.surface.reset_buffers();
|
||||
output.dmabuf_feedback.invalidate();
|
||||
output.primary_commit = None;
|
||||
output.capture_texture = None;
|
||||
output.frame_pending = false;
|
||||
@@ -971,6 +975,7 @@ fn open_device(state: &mut Ferese, path: &Path) -> Result<DrmNode, Box<dyn Error
|
||||
.dmabuf_state
|
||||
.create_global_with_default_feedback::<Ferese>(&display_handle, &feedback);
|
||||
state.dmabuf_imports.register(dmabuf_global);
|
||||
state.dmabuf_feedback.register(dmabuf_global, feedback);
|
||||
state
|
||||
.direct_backend
|
||||
.as_mut()
|
||||
@@ -1334,6 +1339,22 @@ fn render_output(
|
||||
)
|
||||
.inspect_err(|_| output.diagnostics.render_failed())?;
|
||||
|
||||
let primary_format = match &result.primary_element {
|
||||
PrimaryPlaneElement::Swapchain(primary) => Some(primary.buffer().format()),
|
||||
PrimaryPlaneElement::Element(element) => match element.underlying_storage(&mut device.renderer) {
|
||||
Some(UnderlyingStorage::Wayland(buffer)) => smithay::wayland::dmabuf::get_dmabuf(buffer)
|
||||
.ok()
|
||||
.map(|buffer| buffer.format()),
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
let feedback_candidates = feedback::candidates(
|
||||
&mut device.renderer,
|
||||
&elements,
|
||||
&output.output,
|
||||
flags.contains(FrameFlags::ALLOW_PRIMARY_PLANE_SCANOUT),
|
||||
);
|
||||
|
||||
if result.needs_sync()
|
||||
&& let PrimaryPlaneElement::Swapchain(primary) = &result.primary_element
|
||||
{
|
||||
@@ -1347,6 +1368,9 @@ fn render_output(
|
||||
.into_iter()
|
||||
.any(|cursor| state.has_pending_screencopy(&output.output, cursor))
|
||||
});
|
||||
output
|
||||
.dmabuf_feedback
|
||||
.observed_frame(primary_format, &result.states, feedback_candidates);
|
||||
if output.mirror_source.is_some() {
|
||||
capture::capture_mirror(state, &mut device.renderer, &scene_output, &frame, &mut output);
|
||||
} else {
|
||||
@@ -1420,6 +1444,9 @@ fn render_output(
|
||||
flags.contains(FrameFlags::ALLOW_PRIMARY_PLANE_SCANOUT),
|
||||
);
|
||||
capture::after_queue(queued, || {
|
||||
output
|
||||
.dmabuf_feedback
|
||||
.observed_frame(primary_format, &visibility, feedback_candidates);
|
||||
if output.mirror_source.is_none() {
|
||||
state.display_presentation.queued(&output.output, &visibility);
|
||||
}
|
||||
@@ -1663,6 +1690,7 @@ fn remove_device(state: &mut Ferese, node: DrmNode) {
|
||||
|
||||
state.loop_handle.remove(device.notifier);
|
||||
state.dmabuf_imports.remove(device.dmabuf_global);
|
||||
state.dmabuf_feedback.remove(device.dmabuf_global);
|
||||
state
|
||||
.dmabuf_state
|
||||
.disable_global::<Ferese>(&state.display_handle, &device.dmabuf_global);
|
||||
@@ -2252,6 +2280,12 @@ fn create_direct_output(
|
||||
surface.set_output_mode_source((&output).into());
|
||||
let render_metrics = RenderMetrics::from_environment(output.name());
|
||||
let diagnostics = Diagnostics::from_environment(output.name());
|
||||
let dmabuf_feedback = feedback::OutputFeedback::new(
|
||||
render_node.dev_id(),
|
||||
node.dev_id(),
|
||||
renderer.dmabuf_formats(),
|
||||
surface.surface().plane_info().formats.clone(),
|
||||
);
|
||||
|
||||
tracing::info!(?crtc, connector = %connector, "initialized DRM output");
|
||||
Ok(DirectOutput {
|
||||
@@ -2274,6 +2308,7 @@ fn create_direct_output(
|
||||
capture_texture: None,
|
||||
render_metrics,
|
||||
diagnostics,
|
||||
dmabuf_feedback,
|
||||
frame_pending: false,
|
||||
completion: Default::default(),
|
||||
completion_timer: None,
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
//! Conservative output feedback for the existing primary-plane policy.
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use smithay::backend::allocator::Format;
|
||||
use smithay::backend::allocator::format::FormatSet;
|
||||
use smithay::backend::renderer::element::{
|
||||
Element, Id, RenderElement, RenderElementPresentationState, RenderElementStates, RenderingReason,
|
||||
};
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::backend::renderer::utils::with_renderer_surface_state;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags;
|
||||
use smithay::utils::{Physical, Rectangle, Scale};
|
||||
use smithay::wayland::dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal};
|
||||
|
||||
use crate::Ferese;
|
||||
use crate::render::AnimatedWindowRenderElement;
|
||||
|
||||
fn compatible(render: &FormatSet, primary: &FormatSet, observed: Format) -> bool {
|
||||
// Do not equate implicit and explicit modifiers, alpha and opaque FourCCs,
|
||||
// or formats from cursor/overlay planes. Exact intersection preserves import.
|
||||
render.contains(&observed) && primary.contains(&observed)
|
||||
}
|
||||
|
||||
pub(super) struct OutputFeedback {
|
||||
render_device: libc::dev_t,
|
||||
scanout_device: libc::dev_t,
|
||||
render_formats: FormatSet,
|
||||
primary_formats: FormatSet,
|
||||
observed: Option<Format>,
|
||||
preferred: Option<DmabufFeedback>,
|
||||
visible: HashSet<Id>,
|
||||
candidates: HashSet<Id>,
|
||||
}
|
||||
|
||||
impl OutputFeedback {
|
||||
pub fn new(
|
||||
render_device: libc::dev_t,
|
||||
scanout_device: libc::dev_t,
|
||||
render_formats: FormatSet,
|
||||
primary_formats: FormatSet,
|
||||
) -> Self {
|
||||
Self {
|
||||
render_device,
|
||||
scanout_device,
|
||||
render_formats,
|
||||
primary_formats,
|
||||
observed: None,
|
||||
preferred: None,
|
||||
visible: HashSet::new(),
|
||||
candidates: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate(&mut self) {
|
||||
self.observed = None;
|
||||
self.preferred = None;
|
||||
self.visible.clear();
|
||||
self.candidates.clear();
|
||||
}
|
||||
|
||||
fn observe_format(&mut self, format: Option<Format>) {
|
||||
let Some(format) = format else {
|
||||
return;
|
||||
};
|
||||
if self.observed == Some(format) {
|
||||
return;
|
||||
}
|
||||
self.observed = Some(format);
|
||||
self.preferred = None;
|
||||
if compatible(&self.render_formats, &self.primary_formats, format) {
|
||||
// Restrict the preference to the observed primary format/modifier:
|
||||
// without ALLOW_PRIMARY_PLANE_SCANOUT_ANY Smithay compares the full
|
||||
// format with the swapchain. Renderer-only formats remain in fallback.
|
||||
match DmabufFeedbackBuilder::new(self.render_device, self.render_formats.iter().copied())
|
||||
.add_preference_tranche(self.scanout_device, Some(TrancheFlags::Scanout), [format])
|
||||
.build()
|
||||
{
|
||||
Ok(feedback) => self.preferred = Some(feedback),
|
||||
Err(error) => tracing::warn!(%error, "output DMA-BUF feedback unavailable; retaining render fallback"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn observed_frame(&mut self, format: Option<Format>, states: &RenderElementStates, candidates: HashSet<Id>) {
|
||||
self.observe_format(format);
|
||||
self.visible = states
|
||||
.states
|
||||
.iter()
|
||||
.filter(|(_, state)| crate::display_presentation::is_visible(**state))
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect();
|
||||
self.candidates = candidates
|
||||
.into_iter()
|
||||
.filter(|id| {
|
||||
states.states.get(id).is_some_and(|state| {
|
||||
crate::display_presentation::is_visible(*state)
|
||||
&& state.presentation_state
|
||||
!= RenderElementPresentationState::Rendering {
|
||||
reason: Some(RenderingReason::ScanoutFailed),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn candidates(
|
||||
renderer: &mut GlesRenderer,
|
||||
elements: &[AnimatedWindowRenderElement],
|
||||
output: &Output,
|
||||
allowed: bool,
|
||||
) -> HashSet<Id> {
|
||||
if !allowed {
|
||||
return HashSet::new();
|
||||
}
|
||||
let Some(mode) = output.current_mode() else {
|
||||
return HashSet::new();
|
||||
};
|
||||
let bounds = Rectangle::from_size(output.current_transform().transform_size(mode.size));
|
||||
let scale = output.current_scale().fractional_scale().into();
|
||||
elements
|
||||
.iter()
|
||||
.filter(|&element| {
|
||||
candidate_geometry(element, bounds, scale, || {
|
||||
element.underlying_storage(renderer).is_some()
|
||||
})
|
||||
})
|
||||
.map(|element| element.id().clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn candidate_geometry(
|
||||
element: &impl Element,
|
||||
bounds: Rectangle<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
has_storage: impl FnOnce() -> bool,
|
||||
) -> bool {
|
||||
let geometry = element.geometry(scale);
|
||||
if element.alpha() != 1.0 || !geometry.contains_rect(bounds) {
|
||||
return false;
|
||||
}
|
||||
// A single opaque rectangle covering the output is deliberately more
|
||||
// conservative than reconstructing an arbitrary union of client regions.
|
||||
element
|
||||
.opaque_regions(scale)
|
||||
.iter()
|
||||
.any(|region| Rectangle::new(region.loc + geometry.loc, region.size).contains_rect(bounds))
|
||||
&& has_storage()
|
||||
}
|
||||
|
||||
struct OutputView<'a> {
|
||||
global: DmabufGlobal,
|
||||
feedback: &'a OutputFeedback,
|
||||
eligible: Option<Id>,
|
||||
}
|
||||
|
||||
fn select<'a>(
|
||||
global: DmabufGlobal,
|
||||
surface: &Id,
|
||||
outputs: impl IntoIterator<Item = OutputView<'a>>,
|
||||
) -> Option<&'a DmabufFeedback> {
|
||||
let mut visible = outputs
|
||||
.into_iter()
|
||||
.filter(|output| output.feedback.visible.contains(surface));
|
||||
let output = visible.next()?;
|
||||
// A surface visible on multiple outputs gets render fallback rather than
|
||||
// whichever output happened to render last. Cross-GPU import is not assumed.
|
||||
if visible.next().is_some()
|
||||
|| output.global != global
|
||||
|| output.eligible.as_ref() != Some(surface)
|
||||
|| !output.feedback.candidates.contains(surface)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
output.feedback.preferred.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn refresh(state: &mut Ferese) {
|
||||
let mut preferred = HashMap::new();
|
||||
if let Some(backend) = state.direct_backend.as_ref() {
|
||||
for (surface, global) in state.dmabuf_feedback.requested() {
|
||||
let id = Id::from(surface);
|
||||
let outputs = backend
|
||||
.devices
|
||||
.values()
|
||||
.flat_map(|device| device.outputs.values().map(move |output| (device, output)))
|
||||
.filter(|(_, output)| state.output_id(&output.output).is_some());
|
||||
let views = outputs.map(|(device, output)| {
|
||||
let eligible = (backend.can_render()
|
||||
&& device.drm.is_active()
|
||||
&& output.power.can_render()
|
||||
&& output.mirror_source.is_none()
|
||||
&& !state.session_lock.active()
|
||||
&& !state.output_has_animations(&output.output)
|
||||
&& state.output_has_fullscreen(&output.output))
|
||||
.then(|| {
|
||||
state
|
||||
.output_id(&output.output)
|
||||
.and_then(|id| state.output_workspaces.active_workspace(id))
|
||||
.and_then(|workspace| state.workspaces.workspace(workspace))
|
||||
.and_then(|workspace| workspace.fullscreen)
|
||||
.filter(|id| {
|
||||
state
|
||||
.current_window_presentation(*id)
|
||||
.is_some_and(|presentation| presentation.alpha() == 1.0 && presentation.dim == 0.0)
|
||||
})
|
||||
.and_then(|id| state.windows.window(id))
|
||||
.and_then(|window| window.toplevel())
|
||||
.filter(|toplevel| {
|
||||
with_renderer_surface_state(toplevel.wl_surface(), |state| state.buffer().is_some())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|toplevel| Id::from(toplevel.wl_surface()))
|
||||
})
|
||||
.flatten();
|
||||
OutputView {
|
||||
global: device.dmabuf_global,
|
||||
feedback: &output.dmabuf_feedback,
|
||||
eligible,
|
||||
}
|
||||
});
|
||||
if let Some(feedback) = select(global, &id, views) {
|
||||
preferred.insert(surface.clone(), feedback.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
state.dmabuf_feedback.refresh(&preferred);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use smithay::backend::allocator::{Fourcc, Modifier};
|
||||
use smithay::backend::renderer::element::{RenderElementPresentationState, RenderElementState};
|
||||
use smithay::reexports::wayland_server::Display;
|
||||
use smithay::wayland::dmabuf::DmabufState;
|
||||
|
||||
fn format(code: Fourcc, modifier: Modifier) -> Format {
|
||||
Format { code, modifier }
|
||||
}
|
||||
fn formats(values: &[Format]) -> FormatSet {
|
||||
values.iter().copied().collect()
|
||||
}
|
||||
fn output(render: &[Format], primary: &[Format]) -> OutputFeedback {
|
||||
OutputFeedback::new(10, 20, formats(render), formats(primary))
|
||||
}
|
||||
fn create_globals() -> (Display<Ferese>, DmabufGlobal, DmabufGlobal) {
|
||||
let display = Display::new().unwrap();
|
||||
let mut state = DmabufState::new();
|
||||
let feedback = DmabufFeedbackBuilder::new(10, [format(Fourcc::Argb8888, Modifier::Linear)])
|
||||
.build()
|
||||
.unwrap();
|
||||
let global = state.create_global_with_default_feedback::<Ferese>(&display.handle(), &feedback);
|
||||
let other = state.create_global_with_default_feedback::<Ferese>(&display.handle(), &feedback);
|
||||
(display, global, other)
|
||||
}
|
||||
fn visible(output: &mut OutputFeedback, id: &Id, f: Format) {
|
||||
let mut states = RenderElementStates::default();
|
||||
states.states.insert(
|
||||
id.clone(),
|
||||
RenderElementState {
|
||||
visible_area: 100,
|
||||
presentation_state: RenderElementPresentationState::Rendering { reason: None },
|
||||
},
|
||||
);
|
||||
output.observed_frame(Some(f), &states, HashSet::from([id.clone()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferences_require_exact_renderer_and_primary_intersection() {
|
||||
let linear = format(Fourcc::Argb8888, Modifier::Linear);
|
||||
let implicit = format(Fourcc::Argb8888, Modifier::Invalid);
|
||||
let opaque = format(Fourcc::Xrgb8888, Modifier::Linear);
|
||||
assert!(compatible(&formats(&[linear, implicit]), &formats(&[linear]), linear));
|
||||
assert!(!compatible(&formats(&[linear]), &formats(&[implicit]), linear));
|
||||
assert!(!compatible(&formats(&[linear]), &formats(&[opaque]), linear));
|
||||
assert!(!compatible(&formats(&[linear]), &formats(&[implicit]), implicit));
|
||||
assert!(!compatible(&formats(&[opaque]), &formats(&[linear]), linear));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observed_format_limits_preference_and_retains_renderer_fallback() {
|
||||
let linear = format(Fourcc::Argb8888, Modifier::Linear);
|
||||
let other = format(Fourcc::Abgr8888, Modifier::Linear);
|
||||
let mut o = output(&[linear, other], &[linear, other]);
|
||||
assert!(o.preferred.is_none());
|
||||
o.observe_format(Some(linear));
|
||||
let expected = DmabufFeedbackBuilder::new(10, [linear, other])
|
||||
.add_preference_tranche(20, Some(TrancheFlags::Scanout), [linear])
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(o.preferred.as_ref(), Some(&expected));
|
||||
o.observe_format(Some(other));
|
||||
assert_ne!(o.preferred.as_ref(), Some(&expected));
|
||||
o.observe_format(Some(format(Fourcc::Rgb565, Modifier::Linear)));
|
||||
assert!(o.preferred.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_multi_output_cross_gpu_and_policy_changes_fall_back() {
|
||||
let (_display, global, other_global) = create_globals();
|
||||
let id = Id::new();
|
||||
let f = format(Fourcc::Argb8888, Modifier::Linear);
|
||||
let mut a = output(&[f], &[f]);
|
||||
let mut b = output(&[f], &[f]);
|
||||
visible(&mut a, &id, f);
|
||||
visible(&mut b, &id, f);
|
||||
fn view(global: DmabufGlobal, feedback: &OutputFeedback, eligible: Option<Id>) -> OutputView<'_> {
|
||||
OutputView {
|
||||
global,
|
||||
feedback,
|
||||
eligible,
|
||||
}
|
||||
}
|
||||
assert!(select(global, &id, [view(global, &a, Some(id.clone()))]).is_some());
|
||||
assert!(select(global, &id, [view(global, &a, None)]).is_none());
|
||||
assert!(
|
||||
select(
|
||||
global,
|
||||
&id,
|
||||
[view(global, &a, Some(id.clone())), view(global, &b, Some(id.clone()))]
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(select(other_global, &id, [view(global, &a, Some(id.clone()))]).is_none());
|
||||
a.invalidate();
|
||||
assert!(select(global, &id, [view(global, &a, Some(id.clone()))]).is_none());
|
||||
assert!(
|
||||
select(
|
||||
global,
|
||||
&id,
|
||||
[view(global, &a, Some(id.clone())), view(global, &b, Some(id.clone()))]
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
b.candidates.clear();
|
||||
assert!(select(global, &id, [view(global, &b, Some(id.clone()))]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reconfiguration_invalidates_observations_until_a_new_frame() {
|
||||
let (_display, global, _) = create_globals();
|
||||
let id = Id::new();
|
||||
let f = format(Fourcc::Argb8888, Modifier::Linear);
|
||||
let mut o = output(&[f], &[f]);
|
||||
visible(&mut o, &id, f);
|
||||
o.invalidate();
|
||||
assert!(o.observed.is_none() && o.preferred.is_none() && o.visible.is_empty() && o.candidates.is_empty());
|
||||
o.observe_format(None);
|
||||
assert!(o.preferred.is_none());
|
||||
visible(&mut o, &id, f);
|
||||
assert!(
|
||||
select(
|
||||
global,
|
||||
&id,
|
||||
[OutputView {
|
||||
global,
|
||||
feedback: &o,
|
||||
eligible: Some(id.clone())
|
||||
}]
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_geometry_requires_covering_opaque_content_and_storage() {
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::element::solid::SolidColorRenderElement;
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::utils::Transform;
|
||||
for transform in [Transform::Normal, Transform::_90] {
|
||||
let bounds = Rectangle::from_size(transform.transform_size((100, 60).into()));
|
||||
for scale in [1.0, 1.25, 2.0] {
|
||||
let solid = |geometry, alpha| {
|
||||
SolidColorRenderElement::new(
|
||||
Id::new(),
|
||||
geometry,
|
||||
CommitCounter::default(),
|
||||
[1., 1., 1., alpha],
|
||||
Kind::Unspecified,
|
||||
)
|
||||
};
|
||||
let opaque = solid(bounds, 1.0);
|
||||
assert!(candidate_geometry(&opaque, bounds, scale.into(), || true));
|
||||
assert!(
|
||||
!candidate_geometry(&opaque, bounds, scale.into(), || false),
|
||||
"clipped storage must remain hidden"
|
||||
);
|
||||
let faded = solid(bounds, 0.8);
|
||||
assert!(!candidate_geometry(&faded, bounds, scale.into(), || panic!(
|
||||
"faded elements must not probe storage"
|
||||
)));
|
||||
let mut partial = bounds;
|
||||
partial.size.w -= 1;
|
||||
assert!(!candidate_geometry(
|
||||
&solid(partial, 1.0),
|
||||
bounds,
|
||||
scale.into(),
|
||||
|| panic!("partial content must not probe storage")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_scanout_and_occlusion_withdraw_preferences_but_format_hints_can_recover() {
|
||||
let (_display, global, _) = create_globals();
|
||||
let id = Id::new();
|
||||
let f = format(Fourcc::Argb8888, Modifier::Linear);
|
||||
let mut o = output(&[f], &[f]);
|
||||
for (state, expected) in [
|
||||
(
|
||||
RenderElementState {
|
||||
visible_area: 100,
|
||||
presentation_state: RenderElementPresentationState::Rendering {
|
||||
reason: Some(RenderingReason::FormatUnsupported),
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
(
|
||||
RenderElementState {
|
||||
visible_area: 100,
|
||||
presentation_state: RenderElementPresentationState::Rendering {
|
||||
reason: Some(RenderingReason::ScanoutFailed),
|
||||
},
|
||||
},
|
||||
false,
|
||||
),
|
||||
(
|
||||
RenderElementState {
|
||||
visible_area: 0,
|
||||
presentation_state: RenderElementPresentationState::Skipped,
|
||||
},
|
||||
false,
|
||||
),
|
||||
(
|
||||
RenderElementState {
|
||||
visible_area: 100,
|
||||
presentation_state: RenderElementPresentationState::ZeroCopy,
|
||||
},
|
||||
true,
|
||||
),
|
||||
] {
|
||||
let states = RenderElementStates {
|
||||
states: HashMap::from([(id.clone(), state)]),
|
||||
};
|
||||
o.observed_frame(Some(f), &states, HashSet::from([id.clone()]));
|
||||
assert_eq!(
|
||||
select(
|
||||
global,
|
||||
&id,
|
||||
[OutputView {
|
||||
global,
|
||||
feedback: &o,
|
||||
eligible: Some(id.clone())
|
||||
}]
|
||||
)
|
||||
.is_some(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_failure_never_publishes_prepared_feedback_observations() {
|
||||
let f = format(Fourcc::Argb8888, Modifier::Linear);
|
||||
let id = Id::new();
|
||||
let mut o = output(&[f], &[f]);
|
||||
let failed = super::super::capture::after_queue(Err::<(), _>("queue failed"), || visible(&mut o, &id, f));
|
||||
assert!(failed.is_err());
|
||||
assert!(o.visible.is_empty() && o.preferred.is_none());
|
||||
super::super::capture::after_queue(Ok::<(), ()>(()), || visible(&mut o, &id, f)).unwrap();
|
||||
assert!(o.visible.contains(&id) && o.preferred.is_some());
|
||||
}
|
||||
}
|
||||
@@ -274,6 +274,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) {
|
||||
for (crtc, output) in &mut device.outputs {
|
||||
cancel_output_timers(&state.loop_handle, output);
|
||||
output.surface.reset_buffers();
|
||||
output.dmabuf_feedback.invalidate();
|
||||
output.primary_commit = None;
|
||||
output.capture_texture = None;
|
||||
output.frame_pending = false;
|
||||
|
||||
@@ -262,6 +262,7 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> {
|
||||
.surface
|
||||
.set_connectors(&[selection.connector.handle()])
|
||||
.map_err(|error| error.to_string())?;
|
||||
output.dmabuf_feedback.invalidate();
|
||||
prepare(
|
||||
&mut output.surface,
|
||||
&mut self.device.renderer,
|
||||
@@ -280,6 +281,7 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> {
|
||||
let replacement = self.staged.remove(&crtc).unwrap();
|
||||
let previous = self.device.outputs.get_mut(&crtc).unwrap();
|
||||
previous.surface = replacement.surface;
|
||||
previous.dmabuf_feedback = replacement.dmabuf_feedback;
|
||||
self.replaced.insert(crtc);
|
||||
}
|
||||
self.staged
|
||||
@@ -345,11 +347,13 @@ impl crate::output_transaction::HardwareTransaction for DeviceTransaction<'_> {
|
||||
.map_err(|error| error.to_string())?;
|
||||
let previous = self.device.outputs.get_mut(&crtc).unwrap();
|
||||
previous.surface = replacement.surface;
|
||||
previous.dmabuf_feedback = replacement.dmabuf_feedback;
|
||||
previous.surface.commit_frame().map_err(|error| error.to_string())?;
|
||||
}
|
||||
let Some(output) = self.device.outputs.get_mut(&crtc) else {
|
||||
return Ok(());
|
||||
};
|
||||
output.dmabuf_feedback.invalidate();
|
||||
output.surface.reset_state().map_err(|error| error.to_string())?;
|
||||
let restored = output
|
||||
.surface
|
||||
|
||||
Reference in new issue
Block a user