feat: create native smoke window handles
This commit is contained in:
Generated
+1
@@ -767,6 +767,7 @@ dependencies = [
|
||||
name = "fparkan-vulkan-smoke"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fparkan-platform",
|
||||
"fparkan-platform-winit",
|
||||
"fparkan-render-vulkan",
|
||||
]
|
||||
|
||||
@@ -28,9 +28,12 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::dpi::PhysicalSize as WinitPhysicalSize;
|
||||
use winit::event::{Event, MouseButton, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::platform::scancode::PhysicalKeyExtScancode;
|
||||
use winit::window::Window;
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
static NEXT_WINDOW_HANDLE_ID: AtomicU64 = AtomicU64::new(1);
|
||||
const DEFAULT_SMOKE_WIDTH: u32 = 1280;
|
||||
@@ -184,8 +187,115 @@ impl WinitWindowPlan {
|
||||
}
|
||||
}
|
||||
|
||||
/// Native smoke window creation result.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WinitSmokeWindowProbe {
|
||||
/// Validated creation plan.
|
||||
pub plan: WinitWindowPlan,
|
||||
/// Captured window descriptor.
|
||||
pub window: WinitWindow,
|
||||
}
|
||||
|
||||
impl WinitSmokeWindowProbe {
|
||||
/// Returns raw native handles captured from the native window.
|
||||
#[must_use]
|
||||
pub fn native_handles(&self) -> Option<NativeWindowHandles> {
|
||||
self.window.native_handles()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a native smoke window, captures raw handles, then exits the event loop.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`PlatformError`] when the plan is invalid, the event loop/window
|
||||
/// cannot be created, or raw native handles are unavailable.
|
||||
pub fn probe_smoke_window() -> Result<WinitSmokeWindowProbe, PlatformError> {
|
||||
let plan = WinitWindowPlan::smoke().validate()?;
|
||||
let event_loop = EventLoop::new().map_err(|err| PlatformError::Backend {
|
||||
context: "winit event loop",
|
||||
message: err.to_string(),
|
||||
})?;
|
||||
let mut app = SmokeWindowApp::new(plan);
|
||||
event_loop
|
||||
.run_app(&mut app)
|
||||
.map_err(|err| PlatformError::Backend {
|
||||
context: "winit event loop",
|
||||
message: err.to_string(),
|
||||
})?;
|
||||
app.into_probe()
|
||||
}
|
||||
|
||||
struct SmokeWindowApp {
|
||||
plan: WinitWindowPlan,
|
||||
window: Option<WinitWindow>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
impl SmokeWindowApp {
|
||||
const fn new(plan: WinitWindowPlan) -> Self {
|
||||
Self {
|
||||
plan,
|
||||
window: None,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_probe(self) -> Result<WinitSmokeWindowProbe, PlatformError> {
|
||||
if let Some(message) = self.error {
|
||||
return Err(PlatformError::Backend {
|
||||
context: "winit smoke window",
|
||||
message,
|
||||
});
|
||||
}
|
||||
let window = self.window.ok_or_else(|| PlatformError::Backend {
|
||||
context: "winit smoke window",
|
||||
message: "event loop exited before creating a window".to_string(),
|
||||
})?;
|
||||
if self.plan.requires_native_handles && window.native_handles().is_none() {
|
||||
return Err(PlatformError::Backend {
|
||||
context: "winit smoke window",
|
||||
message: "native window/display handles are unavailable".to_string(),
|
||||
});
|
||||
}
|
||||
Ok(WinitSmokeWindowProbe {
|
||||
plan: self.plan,
|
||||
window,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for SmokeWindowApp {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if self.window.is_some() || self.error.is_some() {
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
let attributes = Window::default_attributes()
|
||||
.with_title("FParkan Vulkan smoke")
|
||||
.with_inner_size(WinitPhysicalSize::new(self.plan.width, self.plan.height));
|
||||
match event_loop.create_window(attributes) {
|
||||
Ok(window) => {
|
||||
self.window = Some(WinitWindow::from_window(&window));
|
||||
}
|
||||
Err(err) => {
|
||||
self.error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
event_loop.exit();
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
_event_loop: &ActiveEventLoop,
|
||||
_window_id: WindowId,
|
||||
_event: WindowEvent,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal window view over a `winit` window.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WinitWindow {
|
||||
handle: WindowHandle,
|
||||
width: u32,
|
||||
@@ -343,6 +453,36 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_window_app_requires_created_native_window() {
|
||||
let app = SmokeWindowApp::new(WinitWindowPlan::smoke());
|
||||
|
||||
assert!(matches!(
|
||||
app.into_probe(),
|
||||
Err(PlatformError::Backend {
|
||||
context: "winit smoke window",
|
||||
..
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_window_app_rejects_synthetic_window_without_native_handles() {
|
||||
let mut app = SmokeWindowApp::new(WinitWindowPlan::smoke());
|
||||
app.window = Some(WinitWindow::synthetic(
|
||||
DEFAULT_SMOKE_WIDTH,
|
||||
DEFAULT_SMOKE_HEIGHT,
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
app.into_probe(),
|
||||
Err(PlatformError::Backend {
|
||||
context: "winit smoke window",
|
||||
..
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_events_push_expected_platform_events() {
|
||||
let mut source = WinitEventSource::new();
|
||||
|
||||
@@ -6,6 +6,7 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fparkan-platform = { path = "../../crates/fparkan-platform" }
|
||||
fparkan-platform-winit = { path = "../../adapters/fparkan-platform-winit" }
|
||||
fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" }
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
#![allow(clippy::print_stderr, clippy::print_stdout)]
|
||||
//! Native Vulkan smoke runner entrypoint.
|
||||
|
||||
use fparkan_platform_winit::WinitWindowPlan;
|
||||
use fparkan_platform::{NativeWindowHandles, WindowPort};
|
||||
use fparkan_platform_winit::{probe_smoke_window, WinitWindowPlan};
|
||||
use fparkan_render_vulkan::{
|
||||
create_vulkan_instance_probe, plan_vulkan_surface, probe_vulkan_loader,
|
||||
triangle_shader_manifest, validate_shader_manifest, VulkanInstanceConfig,
|
||||
@@ -209,7 +210,18 @@ struct VulkanBootstrapProbe {
|
||||
impl VulkanBootstrapProbe {
|
||||
fn run(options: &SmokeOptions) -> Self {
|
||||
if !options.probes.vulkan.includes_loader() {
|
||||
return Self {
|
||||
return Self::skipped();
|
||||
}
|
||||
|
||||
let mut probe = Self::probe_loader();
|
||||
let window_handles = probe.probe_window(options);
|
||||
probe.probe_instance(options);
|
||||
probe.probe_surface(options, window_handles);
|
||||
probe
|
||||
}
|
||||
|
||||
const fn skipped() -> Self {
|
||||
Self {
|
||||
loader_status: VulkanLoaderStatus::Skipped,
|
||||
instance_api: None,
|
||||
loader_error: None,
|
||||
@@ -222,10 +234,11 @@ impl VulkanBootstrapProbe {
|
||||
window_error: None,
|
||||
surface_status: VulkanSurfaceStatus::Skipped,
|
||||
surface_error: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let mut probe = match probe_vulkan_loader() {
|
||||
fn probe_loader() -> Self {
|
||||
match probe_vulkan_loader() {
|
||||
Ok(report) => Self {
|
||||
loader_status: VulkanLoaderStatus::Available,
|
||||
instance_api: Some(format_api_version(report.instance_api_version)),
|
||||
@@ -254,51 +267,79 @@ impl VulkanBootstrapProbe {
|
||||
surface_status: VulkanSurfaceStatus::Skipped,
|
||||
surface_error: None,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if options.probes.window {
|
||||
fn probe_window(&mut self, options: &SmokeOptions) -> Option<NativeWindowHandles> {
|
||||
if options.probes.vulkan.includes_surface() {
|
||||
match probe_smoke_window() {
|
||||
Ok(window) => {
|
||||
self.window_status = WinitWindowStatus::Created;
|
||||
self.window_width = Some(window.window.drawable_size().width);
|
||||
self.window_height = Some(window.window.drawable_size().height);
|
||||
window.native_handles()
|
||||
}
|
||||
Err(err) => {
|
||||
self.window_status = WinitWindowStatus::Failed;
|
||||
self.window_error = Some(err.to_string());
|
||||
None
|
||||
}
|
||||
}
|
||||
} else if options.probes.window {
|
||||
match WinitWindowPlan::smoke().validate() {
|
||||
Ok(plan) => {
|
||||
probe.window_status = WinitWindowStatus::Planned;
|
||||
probe.window_width = Some(plan.width);
|
||||
probe.window_height = Some(plan.height);
|
||||
self.window_status = WinitWindowStatus::Planned;
|
||||
self.window_width = Some(plan.width);
|
||||
self.window_height = Some(plan.height);
|
||||
}
|
||||
Err(err) => {
|
||||
probe.window_status = WinitWindowStatus::Failed;
|
||||
probe.window_error = Some(err.to_string());
|
||||
self.window_status = WinitWindowStatus::Failed;
|
||||
self.window_error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_instance(&mut self, options: &SmokeOptions) {
|
||||
if options.probes.vulkan.includes_instance()
|
||||
&& probe.loader_status == VulkanLoaderStatus::Available
|
||||
&& self.loader_status == VulkanLoaderStatus::Available
|
||||
{
|
||||
let config = VulkanInstanceConfig::smoke("fparkan-vulkan-smoke");
|
||||
probe.portability_enumeration = config.enable_portability_enumeration;
|
||||
self.portability_enumeration = config.enable_portability_enumeration;
|
||||
match create_vulkan_instance_probe(&config) {
|
||||
Ok(instance) => {
|
||||
probe.instance_status = VulkanInstanceStatus::Created;
|
||||
probe.portability_enumeration = instance.report.create_flags != 0;
|
||||
self.instance_status = VulkanInstanceStatus::Created;
|
||||
self.portability_enumeration = instance.report.create_flags != 0;
|
||||
}
|
||||
Err(err) => {
|
||||
probe.instance_status = VulkanInstanceStatus::Failed;
|
||||
probe.instance_error = Some(err.to_string());
|
||||
self.instance_status = VulkanInstanceStatus::Failed;
|
||||
self.instance_error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_surface(
|
||||
&mut self,
|
||||
options: &SmokeOptions,
|
||||
window_handles: Option<NativeWindowHandles>,
|
||||
) {
|
||||
if options.probes.vulkan.includes_surface()
|
||||
&& probe.instance_status == VulkanInstanceStatus::Created
|
||||
&& self.instance_status == VulkanInstanceStatus::Created
|
||||
{
|
||||
match plan_vulkan_surface(None) {
|
||||
match plan_vulkan_surface(window_handles) {
|
||||
Ok(_) => {
|
||||
probe.surface_status = VulkanSurfaceStatus::Planned;
|
||||
self.surface_status = VulkanSurfaceStatus::Planned;
|
||||
}
|
||||
Err(err) => {
|
||||
probe.surface_status = VulkanSurfaceStatus::MissingWindowHandles;
|
||||
probe.surface_error = Some(err.to_string());
|
||||
self.surface_status = VulkanSurfaceStatus::MissingWindowHandles;
|
||||
self.surface_error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
probe
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +381,7 @@ impl VulkanInstanceStatus {
|
||||
enum WinitWindowStatus {
|
||||
Skipped,
|
||||
Planned,
|
||||
Created,
|
||||
Failed,
|
||||
}
|
||||
|
||||
@@ -348,6 +390,7 @@ impl WinitWindowStatus {
|
||||
match self {
|
||||
Self::Skipped => "skipped",
|
||||
Self::Planned => "planned",
|
||||
Self::Created => "created",
|
||||
Self::Failed => "failed",
|
||||
}
|
||||
}
|
||||
@@ -463,7 +506,7 @@ fn validate_smoke_options(
|
||||
"passed native smoke report requires successful --probe-instance".to_string(),
|
||||
);
|
||||
}
|
||||
if bootstrap.window_status != WinitWindowStatus::Planned {
|
||||
if bootstrap.window_status != WinitWindowStatus::Created {
|
||||
return Err(
|
||||
"passed native smoke report requires successful --probe-window".to_string(),
|
||||
);
|
||||
@@ -710,7 +753,7 @@ mod tests {
|
||||
instance_status: VulkanInstanceStatus::Created,
|
||||
instance_error: None,
|
||||
portability_enumeration: false,
|
||||
window_status: WinitWindowStatus::Planned,
|
||||
window_status: WinitWindowStatus::Created,
|
||||
window_width: Some(1280),
|
||||
window_height: Some(720),
|
||||
window_error: None,
|
||||
@@ -793,7 +836,7 @@ mod tests {
|
||||
instance_status: VulkanInstanceStatus::Created,
|
||||
instance_error: None,
|
||||
portability_enumeration: false,
|
||||
window_status: WinitWindowStatus::Planned,
|
||||
window_status: WinitWindowStatus::Created,
|
||||
window_width: Some(1280),
|
||||
window_height: Some(720),
|
||||
window_error: None,
|
||||
@@ -923,7 +966,7 @@ mod tests {
|
||||
instance_status: VulkanInstanceStatus::Created,
|
||||
instance_error: None,
|
||||
portability_enumeration: false,
|
||||
window_status: WinitWindowStatus::Planned,
|
||||
window_status: WinitWindowStatus::Created,
|
||||
window_width: Some(1280),
|
||||
window_height: Some(720),
|
||||
window_error: None,
|
||||
|
||||
@@ -29,6 +29,7 @@ S0-CLI-002 covered cargo test -p fparkan-cli --offline accepts_json_format_optio
|
||||
S0-PLAT-001 covered cargo test -p fparkan-platform-winit --offline window_port_reports_default_request_profile
|
||||
S0-PLAT-002 covered cargo clippy -p fparkan-platform -p fparkan-platform-winit --all-targets --all-features --locked -- -D warnings
|
||||
S0-PLAT-003 covered cargo test -p fparkan-platform-winit --offline smoke_window_plan_requires_native_handles_and_nonzero_extent smoke_window_plan_rejects_zero_extent
|
||||
S0-PLAT-004 covered cargo test -p fparkan-platform-winit --offline smoke_window_app_requires_created_native_window smoke_window_app_rejects_synthetic_window_without_native_handles
|
||||
S0-VK-001 covered cargo test -p fparkan-render-vulkan --offline backend_tracks_render_request_and_presents
|
||||
S0-VK-002 covered cargo test -p fparkan-render-vulkan --offline device_scoring_is_deterministic_and_prefers_discrete_unified_queue
|
||||
S0-VK-003 covered cargo test -p fparkan-render-vulkan --offline portability_subset_is_reported_and_enabled_when_exposed
|
||||
|
||||
|
@@ -29,6 +29,7 @@
|
||||
`S0-PLAT-001`
|
||||
`S0-PLAT-002`
|
||||
`S0-PLAT-003`
|
||||
`S0-PLAT-004`
|
||||
`S0-VK-001`
|
||||
`S0-VK-002`
|
||||
`S0-VK-003`
|
||||
|
||||
+2
-2
@@ -1527,7 +1527,7 @@ fn validate_native_smoke_report(
|
||||
"created",
|
||||
failures,
|
||||
);
|
||||
expect_string_field(platform, report, "window_status", "planned", failures);
|
||||
expect_string_field(platform, report, "window_status", "created", failures);
|
||||
expect_string_field(
|
||||
platform,
|
||||
report,
|
||||
@@ -2232,7 +2232,7 @@ mod tests {
|
||||
"shader_manifest_hash": "dd293e4ff08ffca1c037900d08b0ffd415db39f238b4fcdde46468fa049b679c",
|
||||
"vulkan_loader_status": "available",
|
||||
"vulkan_instance_status": "created",
|
||||
"window_status": "planned",
|
||||
"window_status": "created",
|
||||
"vulkan_surface_status": "planned"
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user