aboutsummaryrefslogtreecommitdiff
path: root/apps/fparkan-vulkan-smoke/src
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-23 22:38:09 +0300
committerValentin Popov <valentin@popov.link>2026-06-23 22:38:09 +0300
commitaa2133d82b2a9d92fdbdce2b60eec103536fe484 (patch)
treeddb395163d457ef2cafcb51fd752eacb42cb1fb8 /apps/fparkan-vulkan-smoke/src
parent71ead678c05d2165fc0b6a291ca6d1e25c2a11e0 (diff)
downloadfparkan-aa2133d82b2a9d92fdbdce2b60eec103536fe484.tar.xz
fparkan-aa2133d82b2a9d92fdbdce2b60eec103536fe484.zip
feat: add Vulkan surface preflight to smoke runner
Diffstat (limited to 'apps/fparkan-vulkan-smoke/src')
-rw-r--r--apps/fparkan-vulkan-smoke/src/main.rs134
1 files changed, 132 insertions, 2 deletions
diff --git a/apps/fparkan-vulkan-smoke/src/main.rs b/apps/fparkan-vulkan-smoke/src/main.rs
index 7e47581..e6fc800 100644
--- a/apps/fparkan-vulkan-smoke/src/main.rs
+++ b/apps/fparkan-vulkan-smoke/src/main.rs
@@ -12,8 +12,8 @@
//! Native Vulkan smoke runner entrypoint.
use fparkan_render_vulkan::{
- create_vulkan_instance_probe, probe_vulkan_loader, triangle_shader_manifest,
- validate_shader_manifest, VulkanInstanceConfig,
+ create_vulkan_instance_probe, plan_vulkan_surface, probe_vulkan_loader,
+ triangle_shader_manifest, validate_shader_manifest, VulkanInstanceConfig,
};
use std::path::PathBuf;
use std::process::Command;
@@ -59,6 +59,7 @@ struct SmokeOptions {
validation_error_count: Option<u32>,
probe_loader: bool,
probe_instance: bool,
+ probe_surface: bool,
reason: Option<String>,
}
@@ -72,6 +73,7 @@ impl SmokeOptions {
let mut validation_error_count = None;
let mut probe_loader = false;
let mut probe_instance = false;
+ let mut probe_surface = false;
let mut reason = None;
let mut iter = args.iter();
while let Some(arg) = iter.next() {
@@ -119,6 +121,11 @@ impl SmokeOptions {
probe_loader = true;
probe_instance = true;
}
+ "--probe-surface" => {
+ probe_loader = true;
+ probe_instance = true;
+ probe_surface = true;
+ }
"--reason" => {
let value = iter
.next()
@@ -137,6 +144,7 @@ impl SmokeOptions {
validation_error_count,
probe_loader,
probe_instance,
+ probe_surface,
reason,
})
}
@@ -156,6 +164,8 @@ struct VulkanBootstrapProbe {
instance_status: VulkanInstanceStatus,
instance_error: Option<String>,
portability_enumeration: bool,
+ surface_status: VulkanSurfaceStatus,
+ surface_error: Option<String>,
}
impl VulkanBootstrapProbe {
@@ -168,6 +178,8 @@ impl VulkanBootstrapProbe {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
};
}
@@ -179,6 +191,8 @@ impl VulkanBootstrapProbe {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
},
Err(err) => Self {
loader_status: VulkanLoaderStatus::Unavailable,
@@ -187,6 +201,8 @@ impl VulkanBootstrapProbe {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
},
};
@@ -204,6 +220,17 @@ impl VulkanBootstrapProbe {
}
}
}
+ if options.probe_surface && probe.instance_status == VulkanInstanceStatus::Created {
+ match plan_vulkan_surface(None) {
+ Ok(_) => {
+ probe.surface_status = VulkanSurfaceStatus::Planned;
+ }
+ Err(err) => {
+ probe.surface_status = VulkanSurfaceStatus::MissingWindowHandles;
+ probe.surface_error = Some(err.to_string());
+ }
+ }
+ }
probe
}
}
@@ -243,6 +270,23 @@ impl VulkanInstanceStatus {
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum VulkanSurfaceStatus {
+ Skipped,
+ Planned,
+ MissingWindowHandles,
+}
+
+impl VulkanSurfaceStatus {
+ const fn as_str(self) -> &'static str {
+ match self {
+ Self::Skipped => "skipped",
+ Self::Planned => "planned",
+ Self::MissingWindowHandles => "missing_window_handles",
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SmokePlatform {
Windows,
Linux,
@@ -329,6 +373,11 @@ fn validate_smoke_options(
"passed native smoke report requires successful --probe-instance".to_string(),
);
}
+ if bootstrap.surface_status != VulkanSurfaceStatus::Planned {
+ return Err(
+ "passed native smoke report requires successful --probe-surface".to_string(),
+ );
+ }
}
}
Ok(())
@@ -359,6 +408,10 @@ fn render_smoke_report_json(
.instance_error
.as_ref()
.map_or_else(|| "null".to_string(), |value| json_string(value));
+ let surface_error = bootstrap
+ .surface_error
+ .as_ref()
+ .map_or_else(|| "null".to_string(), |value| json_string(value));
Ok(format!(
concat!(
"{{\n",
@@ -377,6 +430,8 @@ fn render_smoke_report_json(
" \"vulkan_instance_status\": \"{}\",\n",
" \"vulkan_instance_error\": {},\n",
" \"vulkan_portability_enumeration\": {},\n",
+ " \"vulkan_surface_status\": \"{}\",\n",
+ " \"vulkan_surface_error\": {},\n",
" \"reason\": {}\n",
"}}\n"
),
@@ -399,6 +454,8 @@ fn render_smoke_report_json(
} else {
"false"
},
+ bootstrap.surface_status.as_str(),
+ surface_error,
reason
))
}
@@ -480,6 +537,8 @@ mod tests {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
},
)
}
@@ -512,6 +571,8 @@ mod tests {
instance_status: VulkanInstanceStatus::Created,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Planned,
+ surface_error: None,
},
),
Err("passed native smoke report requires --frames >= 300".to_string())
@@ -546,6 +607,8 @@ mod tests {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
},
),
Err("passed native smoke report requires successful --probe-loader".to_string())
@@ -581,6 +644,8 @@ mod tests {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
},
),
Err("passed native smoke report requires successful --probe-instance".to_string())
@@ -588,6 +653,43 @@ mod tests {
}
#[test]
+ fn rejects_passed_without_surface_probe() {
+ let options = SmokeOptions::parse(&strings(&[
+ "--platform",
+ "linux",
+ "--out",
+ "target/native.json",
+ "--status",
+ "passed",
+ "--frames",
+ "300",
+ "--resize-count",
+ "1",
+ "--validation-error-count",
+ "0",
+ "--probe-instance",
+ ]))
+ .expect("options");
+
+ assert_eq!(
+ validate_smoke_options(
+ &options,
+ &VulkanBootstrapProbe {
+ loader_status: VulkanLoaderStatus::Available,
+ instance_api: Some("1.3.0".to_string()),
+ loader_error: None,
+ instance_status: VulkanInstanceStatus::Created,
+ instance_error: None,
+ portability_enumeration: false,
+ surface_status: VulkanSurfaceStatus::Skipped,
+ surface_error: None,
+ },
+ ),
+ Err("passed native smoke report requires successful --probe-surface".to_string())
+ );
+ }
+
+ #[test]
fn blocked_report_includes_shader_manifest_and_bootstrap_status() -> Result<(), String> {
let options = SmokeOptions::parse(&strings(&[
"--platform",
@@ -609,6 +711,11 @@ mod tests {
instance_status: VulkanInstanceStatus::Skipped,
instance_error: None,
portability_enumeration: true,
+ surface_status: VulkanSurfaceStatus::MissingWindowHandles,
+ surface_error: Some(
+ "native window/display handles are required for Vulkan surface creation"
+ .to_string(),
+ ),
},
)?;
@@ -623,6 +730,10 @@ mod tests {
assert!(json.contains("\"vulkan_instance_status\": \"skipped\""));
assert!(json.contains("\"vulkan_instance_error\": null"));
assert!(json.contains("\"vulkan_portability_enumeration\": true"));
+ assert!(json.contains("\"vulkan_surface_status\": \"missing_window_handles\""));
+ assert!(json.contains(
+ "\"vulkan_surface_error\": \"native window/display handles are required for Vulkan surface creation\""
+ ));
assert!(json.contains("\"reason\": \"runner unavailable\""));
Ok(())
}
@@ -641,6 +752,25 @@ mod tests {
assert!(options.probe_loader);
assert!(options.probe_instance);
+ assert!(!options.probe_surface);
+ Ok(())
+ }
+
+ #[test]
+ fn parses_surface_probe_as_instance_probe() -> Result<(), String> {
+ let options = SmokeOptions::parse(&strings(&[
+ "--platform",
+ "linux",
+ "--out",
+ "target/native.json",
+ "--probe-surface",
+ "--reason",
+ "runner unavailable",
+ ]))?;
+
+ assert!(options.probe_loader);
+ assert!(options.probe_instance);
+ assert!(options.probe_surface);
Ok(())
}