style: cargo fmt --all (rustfmt 1.96.0 drift across the re-arch branch)
`cargo fmt --all --check` (ci.yml) was red on main: the client re-architecture
commits and origin's windows-shortcut commit landed with rustfmt violations
(e.g. a 104-char .with_context line in hyprland.rs, an unsorted mod block in
vdisplay.rs, the input.rs `{`-placement CI flagged). Reformat the tree so the
fmt gate passes; no functional changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,36 +66,37 @@ pub fn run(target: &str) -> u8 {
|
|||||||
overlay: Some(Box::new(overlay)),
|
overlay: Some(Box::new(overlay)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = pf_presenter::run_browse(opts, |action, gamepad, native, force_software, vulkan| {
|
let result =
|
||||||
match action {
|
pf_presenter::run_browse(opts, |action, gamepad, native, force_software, vulkan| {
|
||||||
OverlayAction::Launch { id, title } => {
|
match action {
|
||||||
// The carousel only renders for a paired host, so the pin exists; the
|
OverlayAction::Launch { id, title } => {
|
||||||
// guard keeps a logic slip from turning into a pinless connect.
|
// The carousel only renders for a paired host, so the pin exists; the
|
||||||
let Some(pin) = pin else {
|
// guard keeps a logic slip from turning into a pinless connect.
|
||||||
tracing::warn!("launch without a stored pin — refusing");
|
let Some(pin) = pin else {
|
||||||
return ActionOutcome::Handled;
|
tracing::warn!("launch without a stored pin — refusing");
|
||||||
};
|
return ActionOutcome::Handled;
|
||||||
tracing::info!(%id, %title, "launching from the library");
|
};
|
||||||
ActionOutcome::Start(Box::new(session_params(
|
tracing::info!(%id, %title, "launching from the library");
|
||||||
&settings,
|
ActionOutcome::Start(Box::new(session_params(
|
||||||
addr.clone(),
|
&settings,
|
||||||
port,
|
addr.clone(),
|
||||||
pin,
|
port,
|
||||||
identity.clone(),
|
pin,
|
||||||
Some(id),
|
identity.clone(),
|
||||||
gamepad,
|
Some(id),
|
||||||
native,
|
gamepad,
|
||||||
force_software,
|
native,
|
||||||
vulkan,
|
force_software,
|
||||||
)))
|
vulkan,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
OverlayAction::Retry => {
|
||||||
|
spawn_fetch(shared.clone(), addr.clone(), mgmt, identity.clone(), pin);
|
||||||
|
ActionOutcome::Handled
|
||||||
|
}
|
||||||
|
OverlayAction::Quit => ActionOutcome::Quit,
|
||||||
}
|
}
|
||||||
OverlayAction::Retry => {
|
});
|
||||||
spawn_fetch(shared.clone(), addr.clone(), mgmt, identity.clone(), pin);
|
|
||||||
ActionOutcome::Handled
|
|
||||||
}
|
|
||||||
OverlayAction::Quit => ActionOutcome::Quit,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => 0,
|
Ok(()) => 0,
|
||||||
|
|||||||
@@ -217,7 +217,10 @@ mod session_main {
|
|||||||
// connects silently; an unknown host is REFUSED — there is no dialog here, and a
|
// connects silently; an unknown host is REFUSED — there is no dialog here, and a
|
||||||
// silent TOFU would defeat the pinning model. Pair via the desktop client.
|
// silent TOFU would defeat the pinning model. Pair via the desktop client.
|
||||||
let known = trust::KnownHosts::load();
|
let known = trust::KnownHosts::load();
|
||||||
let known_host = known.hosts.iter().find(|h| h.addr == addr && h.port == port);
|
let known_host = known
|
||||||
|
.hosts
|
||||||
|
.iter()
|
||||||
|
.find(|h| h.addr == addr && h.port == port);
|
||||||
let pin = arg_value("--fp")
|
let pin = arg_value("--fp")
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(trust::parse_hex32)
|
.and_then(trust::parse_hex32)
|
||||||
@@ -285,7 +288,10 @@ mod session_main {
|
|||||||
json_line("ended", &reason, None);
|
json_line("ended", &reason, None);
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
Ok(pf_presenter::Outcome::ConnectFailed { msg, trust_rejected }) => {
|
Ok(pf_presenter::Outcome::ConnectFailed {
|
||||||
|
msg,
|
||||||
|
trust_rejected,
|
||||||
|
}) => {
|
||||||
json_line("error", &msg, Some(trust_rejected));
|
json_line("error", &msg, Some(trust_rejected));
|
||||||
if trust_rejected {
|
if trust_rejected {
|
||||||
EXIT_TRUST_REJECTED
|
EXIT_TRUST_REJECTED
|
||||||
@@ -299,7 +305,6 @@ mod session_main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|||||||
+43
-24
@@ -162,15 +162,16 @@ impl SimpleComponent for AppModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hosts = HostsPage::builder()
|
let hosts =
|
||||||
.launch(settings.clone())
|
HostsPage::builder()
|
||||||
.forward(sender.input_sender(), |out| match out {
|
.launch(settings.clone())
|
||||||
HostsOutput::Connect(req) => AppMsg::Connect(req),
|
.forward(sender.input_sender(), |out| match out {
|
||||||
HostsOutput::WakeConnect(req) => AppMsg::WakeConnect(req),
|
HostsOutput::Connect(req) => AppMsg::Connect(req),
|
||||||
HostsOutput::Pair(req) => AppMsg::Pair(req),
|
HostsOutput::WakeConnect(req) => AppMsg::WakeConnect(req),
|
||||||
HostsOutput::SpeedTest(req) => AppMsg::SpeedTest(req),
|
HostsOutput::Pair(req) => AppMsg::Pair(req),
|
||||||
HostsOutput::Library(req, mgmt) => AppMsg::OpenLibrary(req, mgmt),
|
HostsOutput::SpeedTest(req) => AppMsg::SpeedTest(req),
|
||||||
});
|
HostsOutput::Library(req, mgmt) => AppMsg::OpenLibrary(req, mgmt),
|
||||||
|
});
|
||||||
|
|
||||||
let nav = adw::NavigationView::new();
|
let nav = adw::NavigationView::new();
|
||||||
nav.add(hosts.widget());
|
nav.add(hosts.widget());
|
||||||
@@ -250,7 +251,12 @@ impl SimpleComponent for AppModel {
|
|||||||
});
|
});
|
||||||
} else if known.find_by_addr(&req.addr, req.port).is_some() {
|
} else if known.find_by_addr(&req.addr, req.port).is_some() {
|
||||||
self.toast("Host fingerprint changed — re-pair with a PIN to continue");
|
self.toast("Host fingerprint changed — re-pair with a PIN to continue");
|
||||||
crate::ui_trust::pin_dialog(&self.window, &sender, self.identity.clone(), req);
|
crate::ui_trust::pin_dialog(
|
||||||
|
&self.window,
|
||||||
|
&sender,
|
||||||
|
self.identity.clone(),
|
||||||
|
req,
|
||||||
|
);
|
||||||
} else if req.pair_optional {
|
} else if req.pair_optional {
|
||||||
crate::ui_trust::tofu_dialog(&self.window, &sender, req);
|
crate::ui_trust::tofu_dialog(&self.window, &sender, req);
|
||||||
} else {
|
} else {
|
||||||
@@ -265,7 +271,10 @@ impl SimpleComponent for AppModel {
|
|||||||
None => {
|
None => {
|
||||||
// Manual entry: a known address connects on its stored pin;
|
// Manual entry: a known address connects on its stored pin;
|
||||||
// an unknown one must pair — never silent TOFU.
|
// an unknown one must pair — never silent TOFU.
|
||||||
match known.find_by_addr(&req.addr, req.port).map(|k| k.fp_hex.clone()) {
|
match known
|
||||||
|
.find_by_addr(&req.addr, req.port)
|
||||||
|
.map(|k| k.fp_hex.clone())
|
||||||
|
{
|
||||||
Some(fp_hex) => sender.input(AppMsg::StartSession {
|
Some(fp_hex) => sender.input(AppMsg::StartSession {
|
||||||
req,
|
req,
|
||||||
fp_hex,
|
fp_hex,
|
||||||
@@ -307,7 +316,8 @@ impl SimpleComponent for AppModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.hosts.emit(HostsMsg::ClearError);
|
self.hosts.emit(HostsMsg::ClearError);
|
||||||
self.hosts.emit(HostsMsg::SetConnecting(Some(req.card_key())));
|
self.hosts
|
||||||
|
.emit(HostsMsg::SetConnecting(Some(req.card_key())));
|
||||||
let fullscreen = self.settings.borrow().fullscreen_on_stream;
|
let fullscreen = self.settings.borrow().fullscreen_on_stream;
|
||||||
if let Err(e) = spawn::spawn_session(
|
if let Err(e) = spawn::spawn_session(
|
||||||
sender.input_sender().clone(),
|
sender.input_sender().clone(),
|
||||||
@@ -361,7 +371,12 @@ impl SimpleComponent for AppModel {
|
|||||||
(_, Some((_, true)), _) if !tofu => {
|
(_, Some((_, true)), _) if !tofu => {
|
||||||
// The stored pin no longer matches (rotated cert or impostor).
|
// The stored pin no longer matches (rotated cert or impostor).
|
||||||
self.toast("Host fingerprint changed — re-pair with a PIN to continue");
|
self.toast("Host fingerprint changed — re-pair with a PIN to continue");
|
||||||
crate::ui_trust::pin_dialog(&self.window, &sender, self.identity.clone(), req);
|
crate::ui_trust::pin_dialog(
|
||||||
|
&self.window,
|
||||||
|
&sender,
|
||||||
|
self.identity.clone(),
|
||||||
|
req,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(_, Some((msg, _)), _) => self
|
(_, Some((msg, _)), _) => self
|
||||||
.hosts
|
.hosts
|
||||||
@@ -380,10 +395,15 @@ impl SimpleComponent for AppModel {
|
|||||||
}
|
}
|
||||||
AppMsg::ShowPreferences => {
|
AppMsg::ShowPreferences => {
|
||||||
let hosts = self.hosts.sender().clone();
|
let hosts = self.hosts.sender().clone();
|
||||||
crate::ui_settings::show(&self.window, self.settings.clone(), &self.gamepad, move || {
|
crate::ui_settings::show(
|
||||||
// The library toggle changes the saved cards' menu — re-render.
|
&self.window,
|
||||||
let _ = hosts.send(HostsMsg::Refresh);
|
self.settings.clone(),
|
||||||
});
|
&self.gamepad,
|
||||||
|
move || {
|
||||||
|
// The library toggle changes the saved cards' menu — re-render.
|
||||||
|
let _ = hosts.send(HostsMsg::Refresh);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
AppMsg::ShowShortcuts => shortcuts_window(&self.window).present(),
|
AppMsg::ShowShortcuts => shortcuts_window(&self.window).present(),
|
||||||
AppMsg::ShowAbout => crate::ui_settings::show_about(&self.window),
|
AppMsg::ShowAbout => crate::ui_settings::show_about(&self.window),
|
||||||
@@ -435,12 +455,12 @@ impl AppModel {
|
|||||||
},
|
},
|
||||||
CompositorPref::Auto,
|
CompositorPref::Auto,
|
||||||
GamepadPref::Auto,
|
GamepadPref::Auto,
|
||||||
0, // bitrate_kbps (host default)
|
0, // bitrate_kbps (host default)
|
||||||
0, // video_caps: probe connect, nothing presents
|
0, // video_caps: probe connect, nothing presents
|
||||||
2, // audio_channels: stereo
|
2, // audio_channels: stereo
|
||||||
crate::video::decodable_codecs(), // codecs (unused by the probe, but honest)
|
crate::video::decodable_codecs(), // codecs (unused by the probe, but honest)
|
||||||
0, // preferred_codec: no preference
|
0, // preferred_codec: no preference
|
||||||
None, // launch: probe connect, no game
|
None, // launch: probe connect, no game
|
||||||
pin,
|
pin,
|
||||||
Some(identity),
|
Some(identity),
|
||||||
std::time::Duration::from_secs(15),
|
std::time::Duration::from_secs(15),
|
||||||
@@ -530,8 +550,7 @@ pub fn run() -> glib::ExitCode {
|
|||||||
// Streams and the console library live in the session binary now — exec it,
|
// Streams and the console library live in the session binary now — exec it,
|
||||||
// forwarding the relevant argv (the Decky wrapper keeps working through the shell
|
// forwarding the relevant argv (the Decky wrapper keeps working through the shell
|
||||||
// until it's repointed).
|
// until it's repointed).
|
||||||
if crate::cli::arg_value("--connect").is_some() || crate::cli::arg_value("--browse").is_some()
|
if crate::cli::arg_value("--connect").is_some() || crate::cli::arg_value("--browse").is_some() {
|
||||||
{
|
|
||||||
return crate::cli::exec_session();
|
return crate::cli::exec_session();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,14 @@ pub fn run_shot(ctx: &ShotCtx, scene: &str) {
|
|||||||
// no-art placeholders, and one solid-color texture standing in for a poster.
|
// no-art placeholders, and one solid-color texture standing in for a poster.
|
||||||
"library" | "08-library" => {
|
"library" | "08-library" => {
|
||||||
let (games, art) = mock_library();
|
let (games, art) = mock_library();
|
||||||
crate::ui_library::open_mock(&ctx.nav, ctx.identity.clone(), sender, mock_req(), games, art);
|
crate::ui_library::open_mock(
|
||||||
|
&ctx.nav,
|
||||||
|
ctx.identity.clone(),
|
||||||
|
sender,
|
||||||
|
mock_req(),
|
||||||
|
games,
|
||||||
|
art,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
other => tracing::warn!("unknown PUNKTFUNK_SHOT_SCENE={other:?}; showing hosts only"),
|
other => tracing::warn!("unknown PUNKTFUNK_SHOT_SCENE={other:?}; showing hosts only"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,10 +115,7 @@ pub fn spawn_session(
|
|||||||
let stdout = child.stdout.take().expect("piped stdout");
|
let stdout = child.stdout.take().expect("piped stdout");
|
||||||
// Park the child where the cancel handle (and the reader, for the final reap) can
|
// Park the child where the cancel handle (and the reader, for the final reap) can
|
||||||
// reach it.
|
// reach it.
|
||||||
let slot = opts
|
let slot = opts.cancel.clone().unwrap_or_default();
|
||||||
.cancel
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default();
|
|
||||||
*slot.0.lock().unwrap() = Some(child);
|
*slot.0.lock().unwrap() = Some(child);
|
||||||
|
|
||||||
let persist_paired = opts.persist_paired;
|
let persist_paired = opts.persist_paired;
|
||||||
@@ -138,7 +135,10 @@ pub fn spawn_session(
|
|||||||
persist_paired,
|
persist_paired,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(ChildEvent::Error { msg, trust_rejected }) => {
|
Some(ChildEvent::Error {
|
||||||
|
msg,
|
||||||
|
trust_rejected,
|
||||||
|
}) => {
|
||||||
error = Some((msg, trust_rejected));
|
error = Some((msg, trust_rejected));
|
||||||
}
|
}
|
||||||
Some(ChildEvent::Ended(msg)) => ended = Some(msg),
|
Some(ChildEvent::Ended(msg)) => ended = Some(msg),
|
||||||
@@ -173,9 +173,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_the_stdout_contract() {
|
fn parses_the_stdout_contract() {
|
||||||
assert!(matches!(parse_line("{\"ready\":true}"), Some(ChildEvent::Ready)));
|
assert!(matches!(
|
||||||
|
parse_line("{\"ready\":true}"),
|
||||||
|
Some(ChildEvent::Ready)
|
||||||
|
));
|
||||||
match parse_line("{\"error\":\"no route\",\"trust_rejected\":false}") {
|
match parse_line("{\"error\":\"no route\",\"trust_rejected\":false}") {
|
||||||
Some(ChildEvent::Error { msg, trust_rejected }) => {
|
Some(ChildEvent::Error {
|
||||||
|
msg,
|
||||||
|
trust_rejected,
|
||||||
|
}) => {
|
||||||
assert_eq!(msg, "no route");
|
assert_eq!(msg, "no route");
|
||||||
assert!(!trust_rejected);
|
assert!(!trust_rejected);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,9 @@ impl relm4::factory::FactoryComponent for HostCard {
|
|||||||
l
|
l
|
||||||
};
|
};
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
CardKind::Saved { host: k, online, .. } => {
|
CardKind::Saved {
|
||||||
|
host: k, online, ..
|
||||||
|
} => {
|
||||||
// Presence pip + spelled-out state, then the trust pill.
|
// Presence pip + spelled-out state, then the trust pill.
|
||||||
let pip = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
let pip = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||||
pip.add_css_class("pf-pip");
|
pip.add_css_class("pf-pip");
|
||||||
@@ -232,11 +234,17 @@ impl relm4::factory::FactoryComponent for HostCard {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
let req = req.clone();
|
let req = req.clone();
|
||||||
add("speed", Box::new(move || CardOutput::SpeedTest(req.clone())));
|
add(
|
||||||
|
"speed",
|
||||||
|
Box::new(move || CardOutput::SpeedTest(req.clone())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let req = req.clone();
|
let req = req.clone();
|
||||||
add("library", Box::new(move || CardOutput::Library(req.clone())));
|
add(
|
||||||
|
"library",
|
||||||
|
Box::new(move || CardOutput::Library(req.clone())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let (fp, name) = (k.fp_hex.clone(), k.name.clone());
|
let (fp, name) = (k.fp_hex.clone(), k.name.clone());
|
||||||
@@ -344,7 +352,9 @@ struct PageWidgets {
|
|||||||
pub enum HostsMsg {
|
pub enum HostsMsg {
|
||||||
/// A resolved mDNS advert (also the CI scenes' injection path).
|
/// A resolved mDNS advert (also the CI scenes' injection path).
|
||||||
Advert(DiscoveredHost),
|
Advert(DiscoveredHost),
|
||||||
AdvertRemoved { fullname: String },
|
AdvertRemoved {
|
||||||
|
fullname: String,
|
||||||
|
},
|
||||||
/// Reload the disk store and re-render (fresh pairings, renames, the library gate).
|
/// Reload the disk store and re-render (fresh pairings, renames, the library gate).
|
||||||
Refresh,
|
Refresh,
|
||||||
/// Mark the card matching `ConnectRequest::card_key` as connecting; `None` restores.
|
/// Mark the card matching `ConnectRequest::card_key` as connecting; `None` restores.
|
||||||
@@ -453,7 +463,10 @@ impl SimpleComponent for HostsPage {
|
|||||||
content.append(&searching);
|
content.append(&searching);
|
||||||
content.append(discovered.widget());
|
content.append(discovered.widget());
|
||||||
|
|
||||||
let clamp = adw::Clamp::builder().maximum_size(1100).child(&content).build();
|
let clamp = adw::Clamp::builder()
|
||||||
|
.maximum_size(1100)
|
||||||
|
.child(&content)
|
||||||
|
.build();
|
||||||
let scrolled = gtk::ScrolledWindow::builder()
|
let scrolled = gtk::ScrolledWindow::builder()
|
||||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||||
.child(&clamp)
|
.child(&clamp)
|
||||||
@@ -545,10 +558,7 @@ impl SimpleComponent for HostsPage {
|
|||||||
};
|
};
|
||||||
model.rebuild();
|
model.rebuild();
|
||||||
|
|
||||||
ComponentParts {
|
ComponentParts { model, widgets: () }
|
||||||
model,
|
|
||||||
widgets: (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, msg: HostsMsg, sender: ComponentSender<Self>) {
|
fn update(&mut self, msg: HostsMsg, sender: ComponentSender<Self>) {
|
||||||
@@ -606,8 +616,7 @@ impl HostsPage {
|
|||||||
// A saved host is ONLINE iff a live advert matches it (fingerprint, or address
|
// A saved host is ONLINE iff a live advert matches it (fingerprint, or address
|
||||||
// when the advert carries no fp).
|
// when the advert carries no fp).
|
||||||
let matches = |k: &KnownHost, a: &DiscoveredHost| {
|
let matches = |k: &KnownHost, a: &DiscoveredHost| {
|
||||||
(!a.fp_hex.is_empty() && a.fp_hex == k.fp_hex)
|
(!a.fp_hex.is_empty() && a.fp_hex == k.fp_hex) || (a.addr == k.addr && a.port == k.port)
|
||||||
|| (a.addr == k.addr && a.port == k.port)
|
|
||||||
};
|
};
|
||||||
let most_recent = known
|
let most_recent = known
|
||||||
.hosts
|
.hosts
|
||||||
@@ -851,6 +860,10 @@ mod tests {
|
|||||||
|
|
||||||
flow.emit_by_name::<()>("child-activated", &[&child]);
|
flow.emit_by_name::<()>("child-activated", &[&child]);
|
||||||
|
|
||||||
assert_eq!(fired.get(), 1, "the per-card handler should fire exactly once");
|
assert_eq!(
|
||||||
|
fired.get(),
|
||||||
|
1,
|
||||||
|
"the per-card handler should fire exactly once"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,8 @@ fn toggle_fullscreen(hwnd: isize) {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
let mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
||||||
if GetWindowPlacement(hwnd, &mut wp).is_ok() && GetMonitorInfoW(mon, &mut mi).as_bool() {
|
if GetWindowPlacement(hwnd, &mut wp).is_ok() && GetMonitorInfoW(mon, &mut mi).as_bool()
|
||||||
|
{
|
||||||
*SAVED.lock().unwrap() = Some(wp);
|
*SAVED.lock().unwrap() = Some(wp);
|
||||||
SetWindowLongPtrW(hwnd, GWL_STYLE, style & !overlapped);
|
SetWindowLongPtrW(hwnd, GWL_STYLE, style & !overlapped);
|
||||||
let r = mi.rcMonitor;
|
let r = mi.rcMonitor;
|
||||||
|
|||||||
@@ -362,9 +362,7 @@ fn pump(
|
|||||||
// frame) measures true received→decode-complete at zero
|
// frame) measures true received→decode-complete at zero
|
||||||
// pipeline cost. Software/VAAPI keep the synchronous stamp.
|
// pipeline cost. Software/VAAPI keep the synchronous stamp.
|
||||||
let hw_fence = match &image {
|
let hw_fence = match &image {
|
||||||
DecodedImage::VkFrame(v) => {
|
DecodedImage::VkFrame(v) => Some((v.timeline_sem, v.decode_done_value)),
|
||||||
Some((v.timeline_sem, v.decode_done_value))
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let _ = frame_tx.force_send(DecodedFrame {
|
let _ = frame_tx.force_send(DecodedFrame {
|
||||||
|
|||||||
@@ -284,8 +284,10 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
None if choice == "vulkan" => {
|
None if choice == "vulkan" => {
|
||||||
bail!("PUNKTFUNK_DECODER=vulkan but the presenter's device can't (missing \
|
bail!(
|
||||||
video extensions/queue) — see the presenter log")
|
"PUNKTFUNK_DECODER=vulkan but the presenter's device can't (missing \
|
||||||
|
video extensions/queue) — see the presenter log"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
@@ -883,17 +885,15 @@ impl VulkanDecoder {
|
|||||||
store.f11.pNext = &mut store.f12 as *mut _ as *mut std::ffi::c_void;
|
store.f11.pNext = &mut store.f12 as *mut _ as *mut std::ffi::c_void;
|
||||||
store.f12.pNext = &mut store.f13 as *mut _ as *mut std::ffi::c_void;
|
store.f12.pNext = &mut store.f13 as *mut _ as *mut std::ffi::c_void;
|
||||||
|
|
||||||
(*hwctx).get_proc_addr =
|
(*hwctx).get_proc_addr = std::mem::transmute::<usize, pf_ffvk::PFN_vkGetInstanceProcAddr>(
|
||||||
std::mem::transmute::<usize, pf_ffvk::PFN_vkGetInstanceProcAddr>(
|
vk.get_instance_proc_addr,
|
||||||
vk.get_instance_proc_addr,
|
);
|
||||||
);
|
|
||||||
(*hwctx).inst = vk.instance as pf_ffvk::VkInstance;
|
(*hwctx).inst = vk.instance as pf_ffvk::VkInstance;
|
||||||
(*hwctx).phys_dev = vk.physical_device as pf_ffvk::VkPhysicalDevice;
|
(*hwctx).phys_dev = vk.physical_device as pf_ffvk::VkPhysicalDevice;
|
||||||
(*hwctx).act_dev = vk.device as pf_ffvk::VkDevice;
|
(*hwctx).act_dev = vk.device as pf_ffvk::VkDevice;
|
||||||
(*hwctx).device_features.sType =
|
(*hwctx).device_features.sType =
|
||||||
pf_ffvk::VkStructureType_VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
pf_ffvk::VkStructureType_VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||||
(*hwctx).device_features.pNext =
|
(*hwctx).device_features.pNext = &mut store.f11 as *mut _ as *mut std::ffi::c_void;
|
||||||
&mut store.f11 as *mut _ as *mut std::ffi::c_void;
|
|
||||||
(*hwctx).enabled_inst_extensions = store.inst_ptrs.as_ptr();
|
(*hwctx).enabled_inst_extensions = store.inst_ptrs.as_ptr();
|
||||||
(*hwctx).nb_enabled_inst_extensions = store.inst_ptrs.len() as i32;
|
(*hwctx).nb_enabled_inst_extensions = store.inst_ptrs.len() as i32;
|
||||||
(*hwctx).enabled_dev_extensions = store.dev_ptrs.as_ptr();
|
(*hwctx).enabled_dev_extensions = store.dev_ptrs.as_ptr();
|
||||||
@@ -950,10 +950,8 @@ impl VulkanDecoder {
|
|||||||
let gipa = (*hwctx)
|
let gipa = (*hwctx)
|
||||||
.get_proc_addr
|
.get_proc_addr
|
||||||
.expect("get_proc_addr was just set above");
|
.expect("get_proc_addr was just set above");
|
||||||
let gdpa: pf_ffvk::PFN_vkGetDeviceProcAddr = std::mem::transmute(gipa(
|
let gdpa: pf_ffvk::PFN_vkGetDeviceProcAddr =
|
||||||
(*hwctx).inst,
|
std::mem::transmute(gipa((*hwctx).inst, c"vkGetDeviceProcAddr".as_ptr()));
|
||||||
c"vkGetDeviceProcAddr".as_ptr(),
|
|
||||||
));
|
|
||||||
let wait_semaphores: pf_ffvk::PFN_vkWaitSemaphores = std::mem::transmute(gdpa
|
let wait_semaphores: pf_ffvk::PFN_vkWaitSemaphores = std::mem::transmute(gdpa
|
||||||
.expect("vkGetDeviceProcAddr resolvable")(
|
.expect("vkGetDeviceProcAddr resolvable")(
|
||||||
(*hwctx).act_dev,
|
(*hwctx).act_dev,
|
||||||
@@ -975,8 +973,8 @@ impl VulkanDecoder {
|
|||||||
(*ctx).get_format = Some(pick_vulkan);
|
(*ctx).get_format = Some(pick_vulkan);
|
||||||
(*ctx).flags |= ffi::AV_CODEC_FLAG_LOW_DELAY as i32;
|
(*ctx).flags |= ffi::AV_CODEC_FLAG_LOW_DELAY as i32;
|
||||||
(*ctx).thread_count = 1; // hwaccel: threads only add latency
|
(*ctx).thread_count = 1; // hwaccel: threads only add latency
|
||||||
// Same pool headroom rationale as VAAPI: the presenter pins the on-screen
|
// Same pool headroom rationale as VAAPI: the presenter pins the on-screen
|
||||||
// frame + the newest in flight past receive_frame.
|
// frame + the newest in flight past receive_frame.
|
||||||
(*ctx).extra_hw_frames = 4;
|
(*ctx).extra_hw_frames = 4;
|
||||||
let r = ffi::avcodec_open2(ctx, codec, ptr::null_mut());
|
let r = ffi::avcodec_open2(ctx, codec, ptr::null_mut());
|
||||||
if r < 0 {
|
if r < 0 {
|
||||||
|
|||||||
@@ -172,10 +172,22 @@ fn mat_mul(a: &[f64; 16], b: &[f64; 16]) -> [f64; 16] {
|
|||||||
/// `meshColors`: dark-violet corners sink the frame, the edges carry mid-tone violets, and
|
/// `meshColors`: dark-violet corners sink the frame, the edges carry mid-tone violets, and
|
||||||
/// the four interior points hold the bright brand family (warm pools left, cool right).
|
/// the four interior points hold the bright brand family (warm pools left, cool right).
|
||||||
pub const MESH_COLORS: [(f64, f64, f64); 16] = [
|
pub const MESH_COLORS: [(f64, f64, f64); 16] = [
|
||||||
(0.075, 0.060, 0.160), (0.34, 0.27, 0.72), (0.30, 0.26, 0.74), (0.075, 0.060, 0.160),
|
(0.075, 0.060, 0.160),
|
||||||
(0.42, 0.20, 0.54), (0.49, 0.39, 0.95), (0.28, 0.31, 0.84), (0.16, 0.26, 0.64),
|
(0.34, 0.27, 0.72),
|
||||||
(0.45, 0.23, 0.60), (0.53, 0.31, 0.75), (0.35, 0.35, 0.91), (0.19, 0.28, 0.70),
|
(0.30, 0.26, 0.74),
|
||||||
(0.075, 0.060, 0.160), (0.22, 0.18, 0.54), (0.24, 0.20, 0.58), (0.075, 0.060, 0.160),
|
(0.075, 0.060, 0.160),
|
||||||
|
(0.42, 0.20, 0.54),
|
||||||
|
(0.49, 0.39, 0.95),
|
||||||
|
(0.28, 0.31, 0.84),
|
||||||
|
(0.16, 0.26, 0.64),
|
||||||
|
(0.45, 0.23, 0.60),
|
||||||
|
(0.53, 0.31, 0.75),
|
||||||
|
(0.35, 0.35, 0.91),
|
||||||
|
(0.19, 0.28, 0.70),
|
||||||
|
(0.075, 0.060, 0.160),
|
||||||
|
(0.22, 0.18, 0.54),
|
||||||
|
(0.24, 0.20, 0.58),
|
||||||
|
(0.075, 0.060, 0.160),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// The four interior control points that wander; the 12 boundary points stay pinned to the
|
/// The four interior control points that wander; the 12 boundary points stay pinned to the
|
||||||
@@ -414,7 +426,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert!((pos - 3.0).abs() < 0.01, "{pos}");
|
assert!((pos - 3.0).abs() < 0.01, "{pos}");
|
||||||
let (p, v) = spring_advance(0.0, 0.0, 1.0, BUMP_K, BUMP_C, 0.05);
|
let (p, v) = spring_advance(0.0, 0.0, 1.0, BUMP_K, BUMP_C, 0.05);
|
||||||
assert!(p.is_finite() && v.is_finite() && p > 0.0 && p < 2.0, "{p}/{v}");
|
assert!(
|
||||||
|
p.is_finite() && v.is_finite() && p > 0.0 && p < 2.0,
|
||||||
|
"{p}/{v}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The focused card (angle 0, scale 1) maps its center to (cx, cy) exactly.
|
/// The focused card (angle 0, scale 1) maps its center to (cx, cy) exactly.
|
||||||
@@ -435,7 +450,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn side_card_inner_edge_recedes() {
|
fn side_card_inner_edge_recedes() {
|
||||||
let flat = card_matrix(900.0, 400.0, 0.0, 1.0, POSTER_W, POSTER_H, PERSPECTIVE);
|
let flat = card_matrix(900.0, 400.0, 0.0, 1.0, POSTER_W, POSTER_H, PERSPECTIVE);
|
||||||
let tilted = card_matrix(900.0, 400.0, -ROTATE_DEG, 1.0, POSTER_W, POSTER_H, PERSPECTIVE);
|
let tilted = card_matrix(
|
||||||
|
900.0,
|
||||||
|
400.0,
|
||||||
|
-ROTATE_DEG,
|
||||||
|
1.0,
|
||||||
|
POSTER_W,
|
||||||
|
POSTER_H,
|
||||||
|
PERSPECTIVE,
|
||||||
|
);
|
||||||
let project = |m: &[f32; 16], x: f32, y: f32| {
|
let project = |m: &[f32; 16], x: f32, y: f32| {
|
||||||
let px = m[0] * x + m[1] * y + m[3];
|
let px = m[0] * x + m[1] * y + m[3];
|
||||||
let pw = m[12] * x + m[13] * y + m[15];
|
let pw = m[12] * x + m[13] * y + m[15];
|
||||||
|
|||||||
@@ -5,15 +5,17 @@
|
|||||||
//! restack hack is gone), and every state renders in-scene (gamescope maps no dialogs).
|
//! restack hack is gone), and every state renders in-scene (gamescope maps no dialogs).
|
||||||
|
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
card_matrix, initials, mesh_sksl, spring_advance, step_cursor, store_label,
|
card_matrix, initials, mesh_sksl, spring_advance, step_cursor, store_label, LibraryGame,
|
||||||
LibraryGame, LibraryPhase, LibraryShared, StepResult, BUMP_C, BUMP_K, BUMP_PX, FOCUS_GAP,
|
LibraryPhase, LibraryShared, StepResult, BUMP_C, BUMP_K, BUMP_PX, FOCUS_GAP, JUMP, PERSPECTIVE,
|
||||||
JUMP, PERSPECTIVE, POSTER_H, POSTER_W, RECEDE_DIM, RECEDE_SCALE, ROTATE_DEG, SIDE_SPACING,
|
POSTER_H, POSTER_W, RECEDE_DIM, RECEDE_SCALE, ROTATE_DEG, SIDE_SPACING, SPRING_C, SPRING_K,
|
||||||
SPRING_C, SPRING_K, VISIBLE_RANGE,
|
VISIBLE_RANGE,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use pf_client_core::gamepad::{MenuDir, MenuEvent, MenuPulse};
|
use pf_client_core::gamepad::{MenuDir, MenuEvent, MenuPulse};
|
||||||
use pf_presenter::overlay::OverlayAction;
|
use pf_presenter::overlay::OverlayAction;
|
||||||
use skia_safe::textlayout::{FontCollection, ParagraphBuilder, ParagraphStyle, TextAlign, TextStyle};
|
use skia_safe::textlayout::{
|
||||||
|
FontCollection, ParagraphBuilder, ParagraphStyle, TextAlign, TextStyle,
|
||||||
|
};
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
Canvas, Color4f, Data, Font, FontStyle, Image, Paint, Point, RRect, Rect, RuntimeEffect,
|
Canvas, Color4f, Data, Font, FontStyle, Image, Paint, Point, RRect, Rect, RuntimeEffect,
|
||||||
Typeface, M44,
|
Typeface, M44,
|
||||||
@@ -80,11 +82,7 @@ impl LibraryUi {
|
|||||||
if self.shared.generation() != self.generation {
|
if self.shared.generation() != self.generation {
|
||||||
let (phase, games, generation) = self.shared.snapshot();
|
let (phase, games, generation) = self.shared.snapshot();
|
||||||
let fresh_games = self.games.len() != games.len()
|
let fresh_games = self.games.len() != games.len()
|
||||||
|| self
|
|| self.games.iter().zip(&games).any(|(a, b)| a.id != b.id);
|
||||||
.games
|
|
||||||
.iter()
|
|
||||||
.zip(&games)
|
|
||||||
.any(|(a, b)| a.id != b.id);
|
|
||||||
self.phase = phase;
|
self.phase = phase;
|
||||||
self.games = games;
|
self.games = games;
|
||||||
self.generation = generation;
|
self.generation = generation;
|
||||||
@@ -233,14 +231,8 @@ impl LibraryUi {
|
|||||||
.map_or(1.0 / 60.0, |t| (now - t).as_secs_f64().clamp(0.0, 0.05));
|
.map_or(1.0 / 60.0, |t| (now - t).as_secs_f64().clamp(0.0, 0.05));
|
||||||
if self.phase == LibraryPhase::Ready {
|
if self.phase == LibraryPhase::Ready {
|
||||||
let target = f64::from(self.cursor);
|
let target = f64::from(self.cursor);
|
||||||
(self.anim_pos, self.anim_vel) = spring_advance(
|
(self.anim_pos, self.anim_vel) =
|
||||||
self.anim_pos,
|
spring_advance(self.anim_pos, self.anim_vel, target, SPRING_K, SPRING_C, dt);
|
||||||
self.anim_vel,
|
|
||||||
target,
|
|
||||||
SPRING_K,
|
|
||||||
SPRING_C,
|
|
||||||
dt,
|
|
||||||
);
|
|
||||||
if (target - self.anim_pos).abs() < 0.001 && self.anim_vel.abs() < 0.01 {
|
if (target - self.anim_pos).abs() < 0.001 && self.anim_vel.abs() < 0.01 {
|
||||||
self.anim_pos = target;
|
self.anim_pos = target;
|
||||||
self.anim_vel = 0.0;
|
self.anim_vel = 0.0;
|
||||||
@@ -259,19 +251,75 @@ impl LibraryUi {
|
|||||||
LibraryPhase::Ready => self.draw_carousel(canvas, wf, hf, k, fonts),
|
LibraryPhase::Ready => self.draw_carousel(canvas, wf, hf, k, fonts),
|
||||||
LibraryPhase::Loading => {
|
LibraryPhase::Loading => {
|
||||||
self.draw_spinner(canvas, wf / 2.0, hf / 2.0 - 24.0 * k, 16.0 * k);
|
self.draw_spinner(canvas, wf / 2.0, hf / 2.0 - 24.0 * k, 16.0 * k);
|
||||||
fonts.centered(canvas, "Loading library…", 14.0 * k, DIM_TEXT, wf / 2.0, hf / 2.0 + 16.0 * k, wf * 0.8);
|
fonts.centered(
|
||||||
|
canvas,
|
||||||
|
"Loading library…",
|
||||||
|
14.0 * k,
|
||||||
|
DIM_TEXT,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 + 16.0 * k,
|
||||||
|
wf * 0.8,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
LibraryPhase::PairFirst => {
|
LibraryPhase::PairFirst => {
|
||||||
fonts.centered_bold(canvas, "Not paired with this host", 22.0 * k, WHITE, wf / 2.0, hf / 2.0 - 20.0 * k, wf * 0.8);
|
fonts.centered_bold(
|
||||||
fonts.centered(canvas, "Pair from the Punktfunk plugin first.", 14.0 * k, DIM_TEXT, wf / 2.0, hf / 2.0 + 12.0 * k, wf * 0.8);
|
canvas,
|
||||||
|
"Not paired with this host",
|
||||||
|
22.0 * k,
|
||||||
|
WHITE,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 - 20.0 * k,
|
||||||
|
wf * 0.8,
|
||||||
|
);
|
||||||
|
fonts.centered(
|
||||||
|
canvas,
|
||||||
|
"Pair from the Punktfunk plugin first.",
|
||||||
|
14.0 * k,
|
||||||
|
DIM_TEXT,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 + 12.0 * k,
|
||||||
|
wf * 0.8,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
LibraryPhase::Empty => {
|
LibraryPhase::Empty => {
|
||||||
fonts.centered_bold(canvas, "No games found", 22.0 * k, WHITE, wf / 2.0, hf / 2.0 - 20.0 * k, wf * 0.8);
|
fonts.centered_bold(
|
||||||
fonts.centered(canvas, "Install Steam titles or add custom entries in the host's web console.", 14.0 * k, DIM_TEXT, wf / 2.0, hf / 2.0 + 12.0 * k, wf * 0.8);
|
canvas,
|
||||||
|
"No games found",
|
||||||
|
22.0 * k,
|
||||||
|
WHITE,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 - 20.0 * k,
|
||||||
|
wf * 0.8,
|
||||||
|
);
|
||||||
|
fonts.centered(
|
||||||
|
canvas,
|
||||||
|
"Install Steam titles or add custom entries in the host's web console.",
|
||||||
|
14.0 * k,
|
||||||
|
DIM_TEXT,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 + 12.0 * k,
|
||||||
|
wf * 0.8,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
LibraryPhase::Error { title, body, .. } => {
|
LibraryPhase::Error { title, body, .. } => {
|
||||||
fonts.centered_bold(canvas, &title, 22.0 * k, WHITE, wf / 2.0, hf / 2.0 - 32.0 * k, wf * 0.8);
|
fonts.centered_bold(
|
||||||
fonts.centered(canvas, &body, 14.0 * k, DIM_TEXT, wf / 2.0, hf / 2.0 + 4.0 * k, (600.0 * k).min(wf * 0.85));
|
canvas,
|
||||||
|
&title,
|
||||||
|
22.0 * k,
|
||||||
|
WHITE,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 - 32.0 * k,
|
||||||
|
wf * 0.8,
|
||||||
|
);
|
||||||
|
fonts.centered(
|
||||||
|
canvas,
|
||||||
|
&body,
|
||||||
|
14.0 * k,
|
||||||
|
DIM_TEXT,
|
||||||
|
wf / 2.0,
|
||||||
|
hf / 2.0 + 4.0 * k,
|
||||||
|
(600.0 * k).min(wf * 0.85),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,17 +394,28 @@ impl LibraryUi {
|
|||||||
let sh = iw / card_aspect;
|
let sh = iw / card_aspect;
|
||||||
Rect::from_xywh(0.0, (ih - sh) / 2.0, iw, sh)
|
Rect::from_xywh(0.0, (ih - sh) / 2.0, iw, sh)
|
||||||
};
|
};
|
||||||
canvas.draw_image_rect(img, Some((&src, skia_safe::canvas::SrcRectConstraint::Fast)), rect, &Paint::default());
|
canvas.draw_image_rect(
|
||||||
|
img,
|
||||||
|
Some((&src, skia_safe::canvas::SrcRectConstraint::Fast)),
|
||||||
|
rect,
|
||||||
|
&Paint::default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Solid face, not glass: the side cards OVERLAP (GTK CSS note).
|
// Solid face, not glass: the side cards OVERLAP (GTK CSS note).
|
||||||
canvas.draw_rect(rect, &Paint::new(Color4f::new(0.118, 0.118, 0.145, 1.0), None));
|
canvas.draw_rect(
|
||||||
|
rect,
|
||||||
|
&Paint::new(Color4f::new(0.118, 0.118, 0.145, 1.0), None),
|
||||||
|
);
|
||||||
let mono = initials(&game.title);
|
let mono = initials(&game.title);
|
||||||
let font = fonts.sans_bold(38.0 * k);
|
let font = fonts.sans_bold(38.0 * k);
|
||||||
let tw = font.measure_str(&mono, None).0;
|
let tw = font.measure_str(&mono, None).0;
|
||||||
canvas.draw_str(
|
canvas.draw_str(
|
||||||
&mono,
|
&mono,
|
||||||
Point::new((card_w as f32 - tw) / 2.0, card_h as f32 / 2.0 + 13.0 * k as f32),
|
Point::new(
|
||||||
|
(card_w as f32 - tw) / 2.0,
|
||||||
|
card_h as f32 / 2.0 + 13.0 * k as f32,
|
||||||
|
),
|
||||||
&font,
|
&font,
|
||||||
&Paint::new(Color4f::new(1.0, 1.0, 1.0, 0.45), None),
|
&Paint::new(Color4f::new(1.0, 1.0, 1.0, 0.45), None),
|
||||||
);
|
);
|
||||||
@@ -384,7 +443,10 @@ impl LibraryUi {
|
|||||||
if prox > 0.0 {
|
if prox > 0.0 {
|
||||||
canvas.draw_rect(
|
canvas.draw_rect(
|
||||||
rect,
|
rect,
|
||||||
&Paint::new(Color4f::new(0.0, 0.0, 0.0, (prox * RECEDE_DIM) as f32), None),
|
&Paint::new(
|
||||||
|
Color4f::new(0.0, 0.0, 0.0, (prox * RECEDE_DIM) as f32),
|
||||||
|
None,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
@@ -392,7 +454,15 @@ impl LibraryUi {
|
|||||||
|
|
||||||
// Detail block: focused title + store, centered between strip and hints.
|
// Detail block: focused title + store, centered between strip and hints.
|
||||||
if let Some(g) = self.games.get(self.cursor as usize) {
|
if let Some(g) = self.games.get(self.cursor as usize) {
|
||||||
fonts.centered_bold(canvas, &g.title, 27.0 * k, WHITE, w / 2.0, h - 96.0 * k, w * 0.8);
|
fonts.centered_bold(
|
||||||
|
canvas,
|
||||||
|
&g.title,
|
||||||
|
27.0 * k,
|
||||||
|
WHITE,
|
||||||
|
w / 2.0,
|
||||||
|
h - 96.0 * k,
|
||||||
|
w * 0.8,
|
||||||
|
);
|
||||||
fonts.centered(
|
fonts.centered(
|
||||||
canvas,
|
canvas,
|
||||||
&store_label(&g.store).to_uppercase(),
|
&store_label(&g.store).to_uppercase(),
|
||||||
@@ -451,7 +521,9 @@ impl LibraryUi {
|
|||||||
} else {
|
} else {
|
||||||
match &self.phase {
|
match &self.phase {
|
||||||
LibraryPhase::Ready => "A Play B Quit L1 / R1 Jump".to_string(),
|
LibraryPhase::Ready => "A Play B Quit L1 / R1 Jump".to_string(),
|
||||||
LibraryPhase::Error { can_retry: true, .. } => "A Retry B Quit".to_string(),
|
LibraryPhase::Error {
|
||||||
|
can_retry: true, ..
|
||||||
|
} => "A Retry B Quit".to_string(),
|
||||||
_ => "B Quit".to_string(),
|
_ => "B Quit".to_string(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -120,11 +120,13 @@ impl Overlay for SkiaOverlay {
|
|||||||
skvk::GetProcOf::Instance(raw_instance, name) => entry
|
skvk::GetProcOf::Instance(raw_instance, name) => entry
|
||||||
.get_instance_proc_addr(avk::Instance::from_raw(raw_instance as _), name)
|
.get_instance_proc_addr(avk::Instance::from_raw(raw_instance as _), name)
|
||||||
.map_or(std::ptr::null(), |f| f as *const std::ffi::c_void),
|
.map_or(std::ptr::null(), |f| f as *const std::ffi::c_void),
|
||||||
skvk::GetProcOf::Device(raw_device, name) => (instance.fp_v1_0()
|
skvk::GetProcOf::Device(raw_device, name) => {
|
||||||
.get_device_proc_addr)(
|
(instance.fp_v1_0().get_device_proc_addr)(
|
||||||
avk::Device::from_raw(raw_device as _), name
|
avk::Device::from_raw(raw_device as _),
|
||||||
)
|
name,
|
||||||
.map_or(std::ptr::null(), |f| f as *const std::ffi::c_void),
|
)
|
||||||
|
.map_or(std::ptr::null(), |f| f as *const std::ffi::c_void)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -173,12 +175,8 @@ impl Overlay for SkiaOverlay {
|
|||||||
} = event
|
} = event
|
||||||
{
|
{
|
||||||
use sdl3::keyboard::Mod;
|
use sdl3::keyboard::Mod;
|
||||||
if !keymod.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD | Mod::LALTMOD | Mod::RALTMOD)
|
if !keymod.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD | Mod::LALTMOD | Mod::RALTMOD) {
|
||||||
{
|
return self.library.as_mut().is_some_and(|l| l.key(*sc, *repeat));
|
||||||
return self
|
|
||||||
.library
|
|
||||||
.as_mut()
|
|
||||||
.is_some_and(|l| l.key(*sc, *repeat));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,10 +229,10 @@ impl Overlay for SkiaOverlay {
|
|||||||
let slot = slots[next].as_mut().expect("just ensured");
|
let slot = slots[next].as_mut().expect("just ensured");
|
||||||
let lib = library.as_mut().expect("library_visible");
|
let lib = library.as_mut().expect("library_visible");
|
||||||
let fonts = fonts.as_mut().expect("init ran");
|
let fonts = fonts.as_mut().expect("init ran");
|
||||||
fonts.chip_text = Some(
|
fonts.chip_text = Some(ctx.pad.map_or(
|
||||||
ctx.pad
|
"No controller — keyboard works too".to_string(),
|
||||||
.map_or("No controller — keyboard works too".to_string(), str::to_owned),
|
str::to_owned,
|
||||||
);
|
));
|
||||||
lib.sync();
|
lib.sync();
|
||||||
lib.render(slot.surface.canvas(), ctx.width, ctx.height, fonts);
|
lib.render(slot.surface.canvas(), ctx.width, ctx.height, fonts);
|
||||||
gpu.context.flush_surface_with_texture_state(
|
gpu.context.flush_surface_with_texture_state(
|
||||||
@@ -269,15 +267,12 @@ impl Overlay for SkiaOverlay {
|
|||||||
};
|
};
|
||||||
if want == self.drawn {
|
if want == self.drawn {
|
||||||
// Unchanged — hand the presenter the already-rendered image.
|
// Unchanged — hand the presenter the already-rendered image.
|
||||||
return Ok(self
|
return Ok(self.slots[self.current].as_ref().map(|s| OverlayFrame {
|
||||||
.slots[self.current]
|
image: s.image,
|
||||||
.as_ref()
|
view: s.view,
|
||||||
.map(|s| OverlayFrame {
|
width: s.width,
|
||||||
image: s.image,
|
height: s.height,
|
||||||
view: s.view,
|
}));
|
||||||
width: s.width,
|
|
||||||
height: s.height,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render into the OTHER slot — the presenter may still be sampling the current
|
// Render into the OTHER slot — the presenter may still be sampling the current
|
||||||
@@ -335,10 +330,8 @@ impl SkiaOverlay {
|
|||||||
// alternates and the presenter waits its fence before each record).
|
// alternates and the presenter waits its fence before each record).
|
||||||
unsafe { gpu.device.destroy_image_view(old.view, None) };
|
unsafe { gpu.device.destroy_image_view(old.view, None) };
|
||||||
}
|
}
|
||||||
let info = skia_safe::ImageInfo::new_n32_premul(
|
let info =
|
||||||
(width.max(1) as i32, height.max(1) as i32),
|
skia_safe::ImageInfo::new_n32_premul((width.max(1) as i32, height.max(1) as i32), None);
|
||||||
None,
|
|
||||||
);
|
|
||||||
let mut surface = gpu::surfaces::render_target(
|
let mut surface = gpu::surfaces::render_target(
|
||||||
&mut gpu.context,
|
&mut gpu.context,
|
||||||
gpu::Budgeted::Yes,
|
gpu::Budgeted::Yes,
|
||||||
@@ -350,12 +343,11 @@ impl SkiaOverlay {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.context("Skia render-target surface")?;
|
.context("Skia render-target surface")?;
|
||||||
let texture =
|
let texture = gpu::surfaces::get_backend_texture(
|
||||||
gpu::surfaces::get_backend_texture(
|
&mut surface,
|
||||||
&mut surface,
|
skia_safe::surface::BackendHandleAccess::FlushRead,
|
||||||
skia_safe::surface::BackendHandleAccess::FlushRead,
|
)
|
||||||
)
|
.context("surface backend texture")?;
|
||||||
.context("surface backend texture")?;
|
|
||||||
let image_info = texture
|
let image_info = texture
|
||||||
.vulkan_image_info()
|
.vulkan_image_info()
|
||||||
.context("backend texture is not Vulkan")?;
|
.context("backend texture is not Vulkan")?;
|
||||||
@@ -412,10 +404,7 @@ fn draw_osd_panel(canvas: &Canvas, font: &Font, text: &str, x: f32, y: f32) {
|
|||||||
for (i, line) in lines.iter().enumerate() {
|
for (i, line) in lines.iter().enumerate() {
|
||||||
canvas.draw_str(
|
canvas.draw_str(
|
||||||
line,
|
line,
|
||||||
Point::new(
|
Point::new(x + pad_x, y + pad_y - metrics.ascent + line_h * i as f32),
|
||||||
x + pad_x,
|
|
||||||
y + pad_y - metrics.ascent + line_h * i as f32,
|
|
||||||
),
|
|
||||||
font,
|
font,
|
||||||
&text_paint,
|
&text_paint,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -326,11 +326,10 @@ pub(crate) fn build_fullscreen_pipeline(
|
|||||||
.dynamic_state(&dynamic_state)
|
.dynamic_state(&dynamic_state)
|
||||||
.layout(layout)
|
.layout(layout)
|
||||||
.render_pass(render_pass);
|
.render_pass(render_pass);
|
||||||
let pipeline = unsafe {
|
let pipeline =
|
||||||
device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None)
|
unsafe { device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None) }
|
||||||
}
|
.map_err(|(_, e)| e)
|
||||||
.map_err(|(_, e)| e)
|
.context("CSC pipeline");
|
||||||
.context("CSC pipeline");
|
|
||||||
unsafe {
|
unsafe {
|
||||||
device.destroy_shader_module(vert_mod, None);
|
device.destroy_shader_module(vert_mod, None);
|
||||||
device.destroy_shader_module(frag_mod, None);
|
device.destroy_shader_module(frag_mod, None);
|
||||||
@@ -444,7 +443,10 @@ mod tests {
|
|||||||
core::array::from_fn(|r| (0..3).map(|c| mat[c * 3 + r] * v[c]).sum());
|
core::array::from_fn(|r| (0..3).map(|c| mat[c * 3 + r] * v[c]).sum());
|
||||||
let ours = apply(&rows, yuv);
|
let ours = apply(&rows, yuv);
|
||||||
for (a, b) in gl.iter().zip(ours) {
|
for (a, b) in gl.iter().zip(ours) {
|
||||||
assert!((a - b).abs() < 1e-5, "{matrix}/{full}: gl {gl:?} rows {ours:?}");
|
assert!(
|
||||||
|
(a - b).abs() < 1e-5,
|
||||||
|
"{matrix}/{full}: gl {gl:?} rows {ours:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use crate::overlay::{FrameCtx, Overlay, OverlayAction, OverlayFrame, SessionPhas
|
|||||||
use crate::vk::{FrameInput, Presenter};
|
use crate::vk::{FrameInput, Presenter};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use pf_client_core::gamepad::GamepadService;
|
use pf_client_core::gamepad::GamepadService;
|
||||||
use pf_client_core::video::VulkanDecodeDevice;
|
|
||||||
use pf_client_core::session::{self, SessionEvent, SessionHandle, SessionParams, Stats};
|
use pf_client_core::session::{self, SessionEvent, SessionHandle, SessionParams, Stats};
|
||||||
|
use pf_client_core::video::VulkanDecodeDevice;
|
||||||
use pf_client_core::video::{DecodedFrame, DecodedImage};
|
use pf_client_core::video::{DecodedFrame, DecodedImage};
|
||||||
use punktfunk_core::client::NativeClient;
|
use punktfunk_core::client::NativeClient;
|
||||||
use punktfunk_core::config::Mode;
|
use punktfunk_core::config::Mode;
|
||||||
@@ -379,8 +379,8 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
|||||||
}
|
}
|
||||||
// F11 or Alt+Enter (some keyboards' Fn layer sends a media key for
|
// F11 or Alt+Enter (some keyboards' Fn layer sends a media key for
|
||||||
// plain F11 — the Moonlight-standard alias always exists).
|
// plain F11 — the Moonlight-standard alias always exists).
|
||||||
let alt_enter = sc == Scancode::Return
|
let alt_enter =
|
||||||
&& keymod.intersects(Mod::LALTMOD | Mod::RALTMOD);
|
sc == Scancode::Return && keymod.intersects(Mod::LALTMOD | Mod::RALTMOD);
|
||||||
if sc == Scancode::F11 || alt_enter {
|
if sc == Scancode::F11 || alt_enter {
|
||||||
fullscreen = !fullscreen;
|
fullscreen = !fullscreen;
|
||||||
tracing::debug!(fullscreen, "fullscreen toggle");
|
tracing::debug!(fullscreen, "fullscreen toggle");
|
||||||
@@ -535,9 +535,15 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
|||||||
println!("stats: {}", st.osd_text.replace('\n', " | "));
|
println!("stats: {}", st.osd_text.replace('\n', " | "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SessionEvent::Failed { msg, trust_rejected } => match &mode {
|
SessionEvent::Failed {
|
||||||
|
msg,
|
||||||
|
trust_rejected,
|
||||||
|
} => match &mode {
|
||||||
ModeCtl::Single(_) => {
|
ModeCtl::Single(_) => {
|
||||||
break 'main Some(Outcome::ConnectFailed { msg, trust_rejected })
|
break 'main Some(Outcome::ConnectFailed {
|
||||||
|
msg,
|
||||||
|
trust_rejected,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
ModeCtl::Browse(_) => {
|
ModeCtl::Browse(_) => {
|
||||||
tracing::warn!(%msg, "connect failed — back to the library");
|
tracing::warn!(%msg, "connect failed — back to the library");
|
||||||
|
|||||||
@@ -376,11 +376,11 @@ impl Presenter {
|
|||||||
.api_version(vk::API_VERSION_1_3);
|
.api_version(vk::API_VERSION_1_3);
|
||||||
// HDR10 presentation needs the extended colorspaces at the INSTANCE level.
|
// HDR10 presentation needs the extended colorspaces at the INSTANCE level.
|
||||||
let mut instance_extensions: Vec<String> = instance_extensions.to_vec();
|
let mut instance_extensions: Vec<String> = instance_extensions.to_vec();
|
||||||
let inst_available = unsafe { entry.enumerate_instance_extension_properties(None) }
|
let inst_available =
|
||||||
.unwrap_or_default();
|
unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default();
|
||||||
let has_colorspace_ext = inst_available.iter().any(|e| {
|
let has_colorspace_ext = inst_available
|
||||||
e.extension_name_as_c_str() == Ok(c"VK_EXT_swapchain_colorspace")
|
.iter()
|
||||||
});
|
.any(|e| e.extension_name_as_c_str() == Ok(c"VK_EXT_swapchain_colorspace"));
|
||||||
if has_colorspace_ext {
|
if has_colorspace_ext {
|
||||||
instance_extensions.push("VK_EXT_swapchain_colorspace".into());
|
instance_extensions.push("VK_EXT_swapchain_colorspace".into());
|
||||||
}
|
}
|
||||||
@@ -486,10 +486,8 @@ impl Presenter {
|
|||||||
ash::khr::video_decode_h265::NAME,
|
ash::khr::video_decode_h265::NAME,
|
||||||
c"VK_KHR_video_decode_av1",
|
c"VK_KHR_video_decode_av1",
|
||||||
];
|
];
|
||||||
let codec_exts: Vec<&std::ffi::CStr> = VIDEO_CODECS
|
let codec_exts: Vec<&std::ffi::CStr> =
|
||||||
.into_iter()
|
VIDEO_CODECS.into_iter().filter(|n| has(n)).collect();
|
||||||
.filter(|n| has(n))
|
|
||||||
.collect();
|
|
||||||
let video_ok = dev_is_13
|
let video_ok = dev_is_13
|
||||||
&& features_ok
|
&& features_ok
|
||||||
&& decode_family.is_some()
|
&& decode_family.is_some()
|
||||||
@@ -603,8 +601,7 @@ impl Presenter {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let (format, hdr10_format) =
|
let (format, hdr10_format) = pick_formats(&surface_i, pdev, surface, has_colorspace_ext)?;
|
||||||
pick_formats(&surface_i, pdev, surface, has_colorspace_ext)?;
|
|
||||||
let present_mode = pick_present_mode(&surface_i, pdev, surface)?;
|
let present_mode = pick_present_mode(&surface_i, pdev, surface)?;
|
||||||
tracing::info!(?format, ?hdr10_format, ?present_mode, "swapchain config");
|
tracing::info!(?format, ?hdr10_format, ?present_mode, "swapchain config");
|
||||||
let overlay_pipe = OverlayPipe::new(&device, format.format)?;
|
let overlay_pipe = OverlayPipe::new(&device, format.format)?;
|
||||||
@@ -730,8 +727,8 @@ impl Presenter {
|
|||||||
.present_mode(self.present_mode)
|
.present_mode(self.present_mode)
|
||||||
.clipped(true)
|
.clipped(true)
|
||||||
.old_swapchain(old);
|
.old_swapchain(old);
|
||||||
let swapchain = unsafe { self.swap_d.create_swapchain(&info, None) }
|
let swapchain =
|
||||||
.context("vkCreateSwapchainKHR")?;
|
unsafe { self.swap_d.create_swapchain(&info, None) }.context("vkCreateSwapchainKHR")?;
|
||||||
// The old swapchain (and everything tied to its images) is parked, not
|
// The old swapchain (and everything tied to its images) is parked, not
|
||||||
// destroyed: the presentation engine may still hold its final present's
|
// destroyed: the presentation engine may still hold its final present's
|
||||||
// semaphore wait, which no fence covers. It dies after the next present on
|
// semaphore wait, which no fence covers. It dies after the next present on
|
||||||
@@ -746,8 +743,12 @@ impl Presenter {
|
|||||||
self.swapchain = swapchain;
|
self.swapchain = swapchain;
|
||||||
self.images = unsafe { self.swap_d.get_swapchain_images(swapchain) }?;
|
self.images = unsafe { self.swap_d.get_swapchain_images(swapchain) }?;
|
||||||
self.extent = extent;
|
self.extent = extent;
|
||||||
self.overlay_pipe
|
self.overlay_pipe.rebuild_targets(
|
||||||
.rebuild_targets(&self.device, &self.images, self.format.format, extent)?;
|
&self.device,
|
||||||
|
&self.images,
|
||||||
|
self.format.format,
|
||||||
|
extent,
|
||||||
|
)?;
|
||||||
|
|
||||||
for _ in 0..self.images.len() {
|
for _ in 0..self.images.len() {
|
||||||
self.render_sems.push(unsafe {
|
self.render_sems.push(unsafe {
|
||||||
@@ -929,7 +930,8 @@ impl Presenter {
|
|||||||
tracing::info!(width = f.width, height = f.height, "video image (re)built");
|
tracing::info!(width = f.width, height = f.height, "video image (re)built");
|
||||||
}
|
}
|
||||||
// Safe while nothing in flight references the set — the fence wait above.
|
// Safe while nothing in flight references the set — the fence wait above.
|
||||||
self.csc.bind_planes(&self.device, f.luma_view, f.chroma_view);
|
self.csc
|
||||||
|
.bind_planes(&self.device, f.luma_view, f.chroma_view);
|
||||||
}
|
}
|
||||||
if let Some((f, views)) = &vk_frame {
|
if let Some((f, views)) = &vk_frame {
|
||||||
if self
|
if self
|
||||||
@@ -995,7 +997,13 @@ impl Presenter {
|
|||||||
height: v.height,
|
height: v.height,
|
||||||
};
|
};
|
||||||
let ten_bit = f.is_p010();
|
let ten_bit = f.is_p010();
|
||||||
self.record_csc(v.framebuffer, extent, f.color, if ten_bit { 10 } else { 8 }, ten_bit);
|
self.record_csc(
|
||||||
|
v.framebuffer,
|
||||||
|
extent,
|
||||||
|
f.color,
|
||||||
|
if ten_bit { 10 } else { 8 },
|
||||||
|
ten_bit,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vulkan-Video frame: the decoded image is already on THIS device. Read the
|
// Vulkan-Video frame: the decoded image is already on THIS device. Read the
|
||||||
@@ -1019,7 +1027,13 @@ impl Presenter {
|
|||||||
};
|
};
|
||||||
let ten_bit =
|
let ten_bit =
|
||||||
f.vk_format == vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16.as_raw();
|
f.vk_format == vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16.as_raw();
|
||||||
self.record_csc(v.framebuffer, extent, f.color, if ten_bit { 10 } else { 8 }, ten_bit);
|
self.record_csc(
|
||||||
|
v.framebuffer,
|
||||||
|
extent,
|
||||||
|
f.color,
|
||||||
|
if ten_bit { 10 } else { 8 },
|
||||||
|
ten_bit,
|
||||||
|
);
|
||||||
vk_sync = Some(sync);
|
vk_sync = Some(sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,7 +1221,10 @@ impl Presenter {
|
|||||||
if let Some(sync) = vk_sync.take() {
|
if let Some(sync) = vk_sync.take() {
|
||||||
let ok = submitted.is_ok();
|
let ok = submitted.is_ok();
|
||||||
unlock_vkframe(
|
unlock_vkframe(
|
||||||
vk_frame.as_ref().map(|(f, _)| f).expect("vk_sync implies vk_frame"),
|
vk_frame
|
||||||
|
.as_ref()
|
||||||
|
.map(|(f, _)| f)
|
||||||
|
.expect("vk_sync implies vk_frame"),
|
||||||
&sync,
|
&sync,
|
||||||
ok,
|
ok,
|
||||||
self.qfi,
|
self.qfi,
|
||||||
@@ -1217,10 +1234,11 @@ impl Presenter {
|
|||||||
self.submitted = true;
|
self.submitted = true;
|
||||||
// The hw frame is on the GPU now — park it until the fence proves the reads
|
// The hw frame is on the GPU now — park it until the fence proves the reads
|
||||||
// done (destroyed at the next present's fence wait, or in Drop).
|
// done (destroyed at the next present's fence wait, or in Drop).
|
||||||
self.retired_hw = hw_frame
|
self.retired_hw = hw_frame.take().map(Retired::Dmabuf).or_else(|| {
|
||||||
.take()
|
vk_frame
|
||||||
.map(Retired::Dmabuf)
|
.take()
|
||||||
.or_else(|| vk_frame.take().map(|(frame, views)| Retired::Vk { frame, views }));
|
.map(|(frame, views)| Retired::Vk { frame, views })
|
||||||
|
});
|
||||||
|
|
||||||
let swapchains = [self.swapchain];
|
let swapchains = [self.swapchain];
|
||||||
let indices = [index];
|
let indices = [index];
|
||||||
@@ -1242,7 +1260,6 @@ impl Presenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Record the NV12→RGBA CSC pass into the video image (framebuffer): fullscreen
|
/// Record the NV12→RGBA CSC pass into the video image (framebuffer): fullscreen
|
||||||
/// triangle, CICP-driven push-constant rows. Shared by the dmabuf and Vulkan-Video
|
/// triangle, CICP-driven push-constant rows. Shared by the dmabuf and Vulkan-Video
|
||||||
/// paths — only the plane views bound beforehand differ.
|
/// paths — only the plane views bound beforehand differ.
|
||||||
@@ -1306,7 +1323,11 @@ impl Presenter {
|
|||||||
let rows = csc_rows(color, depth, msb_packed);
|
let rows = csc_rows(color, depth, msb_packed);
|
||||||
// Mode 1 = PQ→SDR tonemap (a PQ stream without an HDR10 surface); mode 0
|
// Mode 1 = PQ→SDR tonemap (a PQ stream without an HDR10 surface); mode 0
|
||||||
// passes the transfer through (SDR as-is, or PQ onto the HDR10 swapchain).
|
// passes the transfer through (SDR as-is, or PQ onto the HDR10 swapchain).
|
||||||
let mode = if color.is_pq() && !self.hdr_active { 1.0f32 } else { 0.0 };
|
let mode = if color.is_pq() && !self.hdr_active {
|
||||||
|
1.0f32
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
let peak = std::env::var("PUNKTFUNK_TONEMAP_PEAK")
|
let peak = std::env::var("PUNKTFUNK_TONEMAP_PEAK")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse::<f32>().ok())
|
.and_then(|v| v.parse::<f32>().ok())
|
||||||
@@ -1332,8 +1353,7 @@ impl Presenter {
|
|||||||
/// exact sampling contract (the frames pool was created MUTABLE_FORMAT for this).
|
/// exact sampling contract (the frames pool was created MUTABLE_FORMAT for this).
|
||||||
/// 8-bit NV12 (R8 + R8G8) and 10-bit P010/X6 (R10X6 + R10X6G10X6).
|
/// 8-bit NV12 (R8 + R8G8) and 10-bit P010/X6 (R10X6 + R10X6G10X6).
|
||||||
fn vkframe_plane_views(&self, f: &VkVideoFrame) -> Result<[vk::ImageView; 2]> {
|
fn vkframe_plane_views(&self, f: &VkVideoFrame) -> Result<[vk::ImageView; 2]> {
|
||||||
let (luma_fmt, chroma_fmt) = if f.vk_format
|
let (luma_fmt, chroma_fmt) = if f.vk_format == vk::Format::G8_B8R8_2PLANE_420_UNORM.as_raw()
|
||||||
== vk::Format::G8_B8R8_2PLANE_420_UNORM.as_raw()
|
|
||||||
{
|
{
|
||||||
(vk::Format::R8_UNORM, vk::Format::R8G8_UNORM)
|
(vk::Format::R8_UNORM, vk::Format::R8G8_UNORM)
|
||||||
} else if f.vk_format == vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16.as_raw() {
|
} else if f.vk_format == vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16.as_raw() {
|
||||||
@@ -1348,9 +1368,10 @@ impl Presenter {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
// img[0] is creation-constant (only the sync fields need the frames lock).
|
// img[0] is creation-constant (only the sync fields need the frames lock).
|
||||||
let image = vk::Image::from_raw(
|
let image =
|
||||||
unsafe { (*(f.vkframe as *const pf_ffvk::AVVkFrame)).img[0] } as u64,
|
vk::Image::from_raw(
|
||||||
);
|
unsafe { (*(f.vkframe as *const pf_ffvk::AVVkFrame)).img[0] } as u64,
|
||||||
|
);
|
||||||
let make = |aspect: vk::ImageAspectFlags, format: vk::Format| {
|
let make = |aspect: vk::ImageAspectFlags, format: vk::Format| {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.create_image_view(
|
self.device.create_image_view(
|
||||||
@@ -1612,10 +1633,9 @@ fn pick_device(
|
|||||||
let families = unsafe { instance.get_physical_device_queue_family_properties(pdev) };
|
let families = unsafe { instance.get_physical_device_queue_family_properties(pdev) };
|
||||||
for (i, f) in families.iter().enumerate() {
|
for (i, f) in families.iter().enumerate() {
|
||||||
let graphics = f.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
let graphics = f.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
||||||
let present = unsafe {
|
let present =
|
||||||
surface_i.get_physical_device_surface_support(pdev, i as u32, surface)
|
unsafe { surface_i.get_physical_device_surface_support(pdev, i as u32, surface) }
|
||||||
}
|
.unwrap_or(false);
|
||||||
.unwrap_or(false);
|
|
||||||
if graphics && present {
|
if graphics && present {
|
||||||
return Ok((pdev, i as u32));
|
return Ok((pdev, i as u32));
|
||||||
}
|
}
|
||||||
@@ -1724,7 +1744,6 @@ fn subresource_range() -> vk::ImageSubresourceRange {
|
|||||||
.layer_count(1)
|
.layer_count(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The live sync state of an `AVVkFrame`, snapshotted under the frames lock.
|
/// The live sync state of an `AVVkFrame`, snapshotted under the frames lock.
|
||||||
struct VkFrameSync {
|
struct VkFrameSync {
|
||||||
image: u64,
|
image: u64,
|
||||||
@@ -1766,10 +1785,8 @@ fn unlock_vkframe(f: &VkVideoFrame, sync: &VkFrameSync, submitted: bool, graphic
|
|||||||
(*vkf).queue_family[0] = graphics_qf;
|
(*vkf).queue_family[0] = graphics_qf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let unlock: unsafe extern "C" fn(
|
let unlock: unsafe extern "C" fn(*mut pf_ffvk::AVHWFramesContext, *mut pf_ffvk::AVVkFrame) =
|
||||||
*mut pf_ffvk::AVHWFramesContext,
|
std::mem::transmute(f.unlock_frame);
|
||||||
*mut pf_ffvk::AVVkFrame,
|
|
||||||
) = std::mem::transmute(f.unlock_frame);
|
|
||||||
unlock(f.frames_ctx as *mut pf_ffvk::AVHWFramesContext, vkf);
|
unlock(f.frames_ctx as *mut pf_ffvk::AVHWFramesContext, vkf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1180,6 +1180,9 @@ pub(crate) mod identity;
|
|||||||
#[path = "vdisplay/admission.rs"]
|
#[path = "vdisplay/admission.rs"]
|
||||||
pub(crate) mod admission;
|
pub(crate) mod admission;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
#[path = "vdisplay/linux/hyprland.rs"]
|
||||||
|
mod hyprland;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
#[path = "vdisplay/linux/kwin.rs"]
|
#[path = "vdisplay/linux/kwin.rs"]
|
||||||
mod kwin;
|
mod kwin;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -1194,9 +1197,6 @@ pub(crate) mod pf_vdisplay;
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[path = "vdisplay/linux/wlroots.rs"]
|
#[path = "vdisplay/linux/wlroots.rs"]
|
||||||
mod wlroots;
|
mod wlroots;
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
#[path = "vdisplay/linux/hyprland.rs"]
|
|
||||||
mod hyprland;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
@@ -146,8 +146,9 @@ impl VirtualDisplay for HyprlandDisplay {
|
|||||||
preflight_once();
|
preflight_once();
|
||||||
|
|
||||||
let name = next_output_name();
|
let name = next_output_name();
|
||||||
hyprctl_dispatch(&["output", "create", "headless", &name])
|
hyprctl_dispatch(&["output", "create", "headless", &name]).with_context(|| {
|
||||||
.with_context(|| format!("hyprctl output create headless {name} (is hyprctl reachable?)"))?;
|
format!("hyprctl output create headless {name} (is hyprctl reachable?)")
|
||||||
|
})?;
|
||||||
// Own the output from here on so any later error (or drop) removes it.
|
// Own the output from here on so any later error (or drop) removes it.
|
||||||
let output = OutputGuard(name.clone());
|
let output = OutputGuard(name.clone());
|
||||||
wait_monitor_ready(&name, Duration::from_secs(5))
|
wait_monitor_ready(&name, Duration::from_secs(5))
|
||||||
|
|||||||
Reference in New Issue
Block a user