refactor(vulkan-reporting): remove manual json assembly

This commit is contained in:
2026-06-25 05:23:41 +04:00
parent 5c4fbff2af
commit 0a78fc2460
3 changed files with 197 additions and 239 deletions
Generated
+2
View File
@@ -630,6 +630,8 @@ dependencies = [
"fparkan-binary", "fparkan-binary",
"fparkan-platform", "fparkan-platform",
"fparkan-render", "fparkan-render",
"serde",
"serde_json",
] ]
[[package]] [[package]]
@@ -11,6 +11,8 @@ ash-window = "0.13"
fparkan-binary = { path = "../../crates/fparkan-binary" } fparkan-binary = { path = "../../crates/fparkan-binary" }
fparkan-platform = { path = "../../crates/fparkan-platform" } fparkan-platform = { path = "../../crates/fparkan-platform" }
fparkan-render = { path = "../../crates/fparkan-render" } fparkan-render = { path = "../../crates/fparkan-render" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[lints.rust] [lints.rust]
unsafe_code = "allow" unsafe_code = "allow"
+193 -239
View File
@@ -37,6 +37,7 @@ use fparkan_render::{
canonical_capture, validate_command_list, FrameOutput, RenderBackend, RenderCommand, canonical_capture, validate_command_list, FrameOutput, RenderBackend, RenderCommand,
RenderCommandList, RenderError, RenderCommandList, RenderError,
}; };
use serde::Serialize;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::os::raw::c_char; use std::os::raw::c_char;
@@ -461,7 +462,7 @@ const TRIANGLE_FRAGMENT_VALIDATE_COMMAND: &str =
"spirv-val --target-env vulkan1.0 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv"; "spirv-val --target-env vulkan1.0 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv";
/// Shader tool metadata pinned in the Stage 0 manifest. /// Shader tool metadata pinned in the Stage 0 manifest.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct VulkanShaderToolManifest { pub struct VulkanShaderToolManifest {
/// Tool executable name. /// Tool executable name.
pub name: &'static str, pub name: &'static str,
@@ -472,7 +473,8 @@ pub struct VulkanShaderToolManifest {
} }
/// Vulkan shader stage. /// Vulkan shader stage.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum VulkanShaderStage { pub enum VulkanShaderStage {
/// Vertex stage. /// Vertex stage.
Vertex, Vertex,
@@ -480,15 +482,6 @@ pub enum VulkanShaderStage {
Fragment, Fragment,
} }
impl VulkanShaderStage {
const fn as_str(self) -> &'static str {
match self {
Self::Vertex => "vertex",
Self::Fragment => "fragment",
}
}
}
/// Offline SPIR-V shader manifest entry. /// Offline SPIR-V shader manifest entry.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanShaderModuleManifest { pub struct VulkanShaderModuleManifest {
@@ -534,7 +527,7 @@ pub struct VulkanShaderManifestReport {
} }
/// Shader module validation report. /// Shader module validation report.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct VulkanShaderModuleReport { pub struct VulkanShaderModuleReport {
/// Logical shader name. /// Logical shader name.
pub name: &'static str, pub name: &'static str,
@@ -3064,18 +3057,19 @@ fn live_surface_capabilities(
/// Renders a deterministic JSON Vulkan surface plan. /// Renders a deterministic JSON Vulkan surface plan.
#[must_use] #[must_use]
pub fn render_surface_plan_json(plan: &VulkanSurfacePlan) -> String { pub fn render_surface_plan_json(plan: &VulkanSurfacePlan) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct SurfacePlanJson<'a> {
out.push_str(&plan.schema.to_string()); schema: u32,
out.push_str(",\"required_instance_extensions\":["); required_instance_extensions: &'a [String],
for (index, extension) in plan.required_instance_extensions.iter().enumerate() {
if index > 0 {
out.push(',');
}
push_json_string(&mut out, extension);
} }
out.push_str("]}");
out serialize_json_or_fallback(
&SurfacePlanJson {
schema: plan.schema,
required_instance_extensions: &plan.required_instance_extensions,
},
"{\"schema\":0,\"required_instance_extensions\":[]}",
)
} }
fn extension_name(extension: *const c_char) -> Result<String, VulkanSurfaceError> { fn extension_name(extension: *const c_char) -> Result<String, VulkanSurfaceError> {
@@ -3243,26 +3237,23 @@ fn validation_layer_cstrings(
/// Renders a deterministic JSON Vulkan instance plan. /// Renders a deterministic JSON Vulkan instance plan.
#[must_use] #[must_use]
pub fn render_instance_plan_json(plan: &VulkanInstancePlan) -> String { pub fn render_instance_plan_json(plan: &VulkanInstancePlan) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct InstancePlanJson<'a> {
out.push_str(&plan.schema.to_string()); schema: u32,
out.push_str(",\"create_flags\":"); create_flags: u32,
out.push_str(&plan.create_flags.to_string()); validation_requested: bool,
out.push_str(",\"validation_requested\":"); enabled_extensions: &'a [String],
out.push_str(if plan.validation_requested {
"true"
} else {
"false"
});
out.push_str(",\"enabled_extensions\":[");
for (index, extension) in plan.enabled_extensions.iter().enumerate() {
if index > 0 {
out.push(',');
}
push_json_string(&mut out, extension);
} }
out.push_str("]}");
out serialize_json_or_fallback(
&InstancePlanJson {
schema: plan.schema,
create_flags: plan.create_flags,
validation_requested: plan.validation_requested,
enabled_extensions: &plan.enabled_extensions,
},
"{\"schema\":0,\"create_flags\":0,\"validation_requested\":false,\"enabled_extensions\":[]}",
)
} }
fn cstring_vec(values: &[String]) -> Result<Vec<CString>, VulkanInstanceError> { fn cstring_vec(values: &[String]) -> Result<Vec<CString>, VulkanInstanceError> {
@@ -3348,19 +3339,21 @@ pub fn vulkan_entry_symbol_name() -> &'static CStr {
/// Renders a deterministic JSON Vulkan loader report. /// Renders a deterministic JSON Vulkan loader report.
#[must_use] #[must_use]
pub fn render_loader_probe_report_json(report: &VulkanLoaderProbeReport) -> String { pub fn render_loader_probe_report_json(report: &VulkanLoaderProbeReport) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct LoaderProbeReportJson {
out.push_str(&report.schema.to_string()); schema: u32,
out.push_str(",\"loader_available\":"); loader_available: bool,
out.push_str(if report.loader_available { instance_api: String,
"true" }
} else {
"false" serialize_json_or_fallback(
}); &LoaderProbeReportJson {
out.push_str(",\"instance_api\":\""); schema: report.schema,
out.push_str(&format_api_version(report.instance_api_version)); loader_available: report.loader_available,
out.push_str("\"}"); instance_api: format_api_version(report.instance_api_version),
out },
"{\"schema\":0,\"loader_available\":false,\"instance_api\":\"0.0.0\"}",
)
} }
/// Returns the built-in Stage 0 indexed-triangle shader manifest. /// Returns the built-in Stage 0 indexed-triangle shader manifest.
@@ -3445,16 +3438,23 @@ pub fn validate_shader_manifest(
} }
fn shader_interface_hash(module: &VulkanShaderModuleManifest) -> String { fn shader_interface_hash(module: &VulkanShaderModuleManifest) -> String {
let mut normalized = String::new(); #[derive(Serialize)]
normalized.push_str("{\"stage\":\""); struct ShaderInterfaceHashJson<'a> {
normalized.push_str(module.stage.as_str()); stage: VulkanShaderStage,
normalized.push_str("\",\"entry_point\":"); entry_point: &'a str,
push_json_string(&mut normalized, module.entry_point); descriptor_sets: u32,
normalized.push_str(",\"descriptor_sets\":"); push_constant_bytes: u32,
normalized.push_str(&module.descriptor_sets.to_string()); }
normalized.push_str(",\"push_constant_bytes\":");
normalized.push_str(&module.push_constant_bytes.to_string()); let normalized = serialize_json_or_fallback(
normalized.push('}'); &ShaderInterfaceHashJson {
stage: module.stage,
entry_point: module.entry_point,
descriptor_sets: module.descriptor_sets,
push_constant_bytes: module.push_constant_bytes,
},
"{\"stage\":\"vertex\",\"entry_point\":\"main\",\"descriptor_sets\":0,\"push_constant_bytes\":0}",
);
sha256_hex(&sha256(normalized.as_bytes())) sha256_hex(&sha256(normalized.as_bytes()))
} }
@@ -3493,85 +3493,61 @@ fn spirv_words_to_bytes(words: &[u32]) -> Vec<u8> {
/// Renders a deterministic JSON shader manifest report. /// Renders a deterministic JSON shader manifest report.
#[must_use] #[must_use]
pub fn render_shader_manifest_report_json(report: &VulkanShaderManifestReport) -> String { pub fn render_shader_manifest_report_json(report: &VulkanShaderManifestReport) -> String {
let mut out = render_shader_manifest_without_hash_json(&report.modules); #[derive(Serialize)]
out.push_str(",\"manifest_hash\":"); struct ShaderManifestReportJson<'a> {
push_json_string(&mut out, &report.manifest_hash); schema: u32,
out.push('}'); target_env: &'a str,
out compiler: &'a VulkanShaderToolManifest,
validator: &'a VulkanShaderToolManifest,
modules: &'a [VulkanShaderModuleReport],
manifest_hash: &'a str,
}
serialize_json_or_fallback(
&ShaderManifestReportJson {
schema: report.schema,
target_env: report.target_env,
compiler: &report.compiler,
validator: &report.validator,
modules: &report.modules,
manifest_hash: &report.manifest_hash,
},
"{\"schema\":0,\"target_env\":\"unknown\",\"compiler\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"validator\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"modules\":[],\"manifest_hash\":\"unknown\"}",
)
} }
fn render_shader_manifest_without_hash_json(modules: &[VulkanShaderModuleReport]) -> String { fn render_shader_manifest_without_hash_json(modules: &[VulkanShaderModuleReport]) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct ShaderManifestWithoutHashJson<'a> {
out.push_str(&SHADER_MANIFEST_SCHEMA.to_string()); schema: u32,
out.push_str(",\"target_env\":"); target_env: &'a str,
push_json_string(&mut out, SHADER_TARGET_ENV); compiler: VulkanShaderToolManifest,
out.push_str(",\"compiler\":"); validator: VulkanShaderToolManifest,
out.push_str(&render_shader_tool_json(&VulkanShaderToolManifest { modules: &'a [VulkanShaderModuleReport],
name: SHADER_COMPILER_NAME,
version: SHADER_COMPILER_VERSION,
binary_sha256: SHADER_COMPILER_BINARY_SHA256,
}));
out.push_str(",\"validator\":");
out.push_str(&render_shader_tool_json(&VulkanShaderToolManifest {
name: SPIRV_VALIDATOR_NAME,
version: SPIRV_VALIDATOR_VERSION,
binary_sha256: SPIRV_VALIDATOR_BINARY_SHA256,
}));
out.push_str(",\"modules\":");
out.push_str(&render_shader_modules_json(modules));
out
}
fn render_shader_modules_json(modules: &[VulkanShaderModuleReport]) -> String {
let mut out = String::new();
out.push('[');
for (index, module) in modules.iter().enumerate() {
if index > 0 {
out.push(',');
}
out.push_str("{\"name\":");
push_json_string(&mut out, module.name);
out.push_str(",\"stage\":\"");
out.push_str(module.stage.as_str());
out.push_str("\",\"entry_point\":");
push_json_string(&mut out, module.entry_point);
out.push_str(",\"source_path\":");
push_json_string(&mut out, module.source_path);
out.push_str(",\"source_sha256\":");
push_json_string(&mut out, module.source_sha256);
out.push_str(",\"spirv_path\":");
push_json_string(&mut out, module.spirv_path);
out.push_str(",\"word_count\":");
out.push_str(&module.word_count.to_string());
out.push_str(",\"sha256\":");
push_json_string(&mut out, &module.sha256);
out.push_str(",\"descriptor_sets\":");
out.push_str(&module.descriptor_sets.to_string());
out.push_str(",\"push_constant_bytes\":");
out.push_str(&module.push_constant_bytes.to_string());
out.push_str(",\"compile_command\":");
push_json_string(&mut out, module.compile_command);
out.push_str(",\"validate_command\":");
push_json_string(&mut out, module.validate_command);
out.push_str(",\"interface_hash\":");
push_json_string(&mut out, &module.interface_hash);
out.push('}');
} }
out.push(']');
out
}
fn render_shader_tool_json(tool: &VulkanShaderToolManifest) -> String { let json = serialize_json_or_fallback(
let mut out = String::new(); &ShaderManifestWithoutHashJson {
out.push_str("{\"name\":"); schema: SHADER_MANIFEST_SCHEMA,
push_json_string(&mut out, tool.name); target_env: SHADER_TARGET_ENV,
out.push_str(",\"version\":"); compiler: VulkanShaderToolManifest {
push_json_string(&mut out, tool.version); name: SHADER_COMPILER_NAME,
out.push_str(",\"binary_sha256\":"); version: SHADER_COMPILER_VERSION,
push_json_string(&mut out, tool.binary_sha256); binary_sha256: SHADER_COMPILER_BINARY_SHA256,
out.push('}'); },
out validator: VulkanShaderToolManifest {
name: SPIRV_VALIDATOR_NAME,
version: SPIRV_VALIDATOR_VERSION,
binary_sha256: SPIRV_VALIDATOR_BINARY_SHA256,
},
modules,
},
"{\"schema\":0,\"target_env\":\"unknown\",\"compiler\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"validator\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"modules\":[]}",
);
match json.strip_suffix('}') {
Some(stripped) => stripped.to_string(),
None => json,
}
} }
/// Vulkan backend migration readiness. /// Vulkan backend migration readiness.
@@ -3728,7 +3704,7 @@ pub struct VulkanSwapchainRecreationReport {
} }
/// Deterministic frame submission plan for command buffers and sync objects. /// Deterministic frame submission plan for command buffers and sync objects.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct VulkanFrameSubmissionPlan { pub struct VulkanFrameSubmissionPlan {
/// Report schema version. /// Report schema version.
pub schema: u32, pub schema: u32,
@@ -4131,102 +4107,99 @@ fn compare_reports(
/// Renders a deterministic JSON capability report. /// Renders a deterministic JSON capability report.
#[must_use] #[must_use]
pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String { pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct CapabilityReportJson<'a> {
out.push_str(&report.schema.to_string()); schema: u32,
out.push_str(",\"vulkan_api\":\""); vulkan_api: String,
out.push_str(&format_api_version(report.vulkan_api_version)); device_name: &'a str,
out.push_str("\",\"device_name\":"); score: i32,
push_json_string(&mut out, &report.device_name); graphics_queue_family: u32,
out.push_str(",\"score\":"); present_queue_family: u32,
out.push_str(&report.score.to_string()); portability_subset: bool,
out.push_str(",\"graphics_queue_family\":"); enabled_extensions: &'a [String],
out.push_str(&report.graphics_queue_family.to_string());
out.push_str(",\"present_queue_family\":");
out.push_str(&report.present_queue_family.to_string());
out.push_str(",\"portability_subset\":");
out.push_str(if report.portability_subset {
"true"
} else {
"false"
});
out.push_str(",\"enabled_extensions\":[");
for (index, extension) in report.enabled_extensions.iter().enumerate() {
if index > 0 {
out.push(',');
}
push_json_string(&mut out, extension);
} }
out.push_str("]}");
out serialize_json_or_fallback(
&CapabilityReportJson {
schema: report.schema,
vulkan_api: format_api_version(report.vulkan_api_version),
device_name: &report.device_name,
score: report.score,
graphics_queue_family: report.graphics_queue_family,
present_queue_family: report.present_queue_family,
portability_subset: report.portability_subset,
enabled_extensions: &report.enabled_extensions,
},
"{\"schema\":0,\"vulkan_api\":\"0.0.0\",\"device_name\":\"unknown\",\"score\":0,\"graphics_queue_family\":0,\"present_queue_family\":0,\"portability_subset\":false,\"enabled_extensions\":[]}",
)
} }
/// Renders a deterministic JSON swapchain plan. /// Renders a deterministic JSON swapchain plan.
#[must_use] #[must_use]
pub fn render_swapchain_plan_json(plan: &VulkanSwapchainPlan) -> String { pub fn render_swapchain_plan_json(plan: &VulkanSwapchainPlan) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct SwapchainPlanJson {
out.push_str(&plan.schema.to_string()); schema: u32,
out.push_str(",\"extent\":["); extent: [u32; 2],
out.push_str(&plan.extent.0.to_string()); format: i32,
out.push(','); color_space: i32,
out.push_str(&plan.extent.1.to_string()); present_mode: i32,
out.push_str("],\"format\":"); image_count: u32,
out.push_str(&plan.format.format.to_string()); }
out.push_str(",\"color_space\":");
out.push_str(&plan.format.color_space.to_string()); serialize_json_or_fallback(
out.push_str(",\"present_mode\":"); &SwapchainPlanJson {
out.push_str(&plan.present_mode.to_string()); schema: plan.schema,
out.push_str(",\"image_count\":"); extent: [plan.extent.0, plan.extent.1],
out.push_str(&plan.image_count.to_string()); format: plan.format.format,
out.push('}'); color_space: plan.format.color_space,
out present_mode: plan.present_mode,
image_count: plan.image_count,
},
"{\"schema\":0,\"extent\":[0,0],\"format\":0,\"color_space\":0,\"present_mode\":0,\"image_count\":0}",
)
} }
/// Renders a deterministic JSON swapchain recreation report. /// Renders a deterministic JSON swapchain recreation report.
#[must_use] #[must_use]
pub fn render_swapchain_recreation_report_json(report: &VulkanSwapchainRecreationReport) -> String { pub fn render_swapchain_recreation_report_json(report: &VulkanSwapchainRecreationReport) -> String {
let mut out = String::new(); #[derive(Serialize)]
out.push_str("{\"schema\":"); struct SwapchainRecreationReportJson<'a> {
out.push_str(&report.schema.to_string()); schema: u32,
out.push_str(",\"reason\":\""); reason: &'a str,
out.push_str(match report.reason { previous_extent: [u32; 2],
VulkanSwapchainRecreationReason::Resize => "resize", next_extent: [u32; 2],
VulkanSwapchainRecreationReason::OutOfDate => "out_of_date", }
VulkanSwapchainRecreationReason::Suboptimal => "suboptimal",
}); serialize_json_or_fallback(
out.push_str("\",\"previous_extent\":["); &SwapchainRecreationReportJson {
out.push_str(&report.previous_extent.0.to_string()); schema: report.schema,
out.push(','); reason: match report.reason {
out.push_str(&report.previous_extent.1.to_string()); VulkanSwapchainRecreationReason::Resize => "resize",
out.push_str("],\"next_extent\":["); VulkanSwapchainRecreationReason::OutOfDate => "out_of_date",
out.push_str(&report.next_extent.0.to_string()); VulkanSwapchainRecreationReason::Suboptimal => "suboptimal",
out.push(','); },
out.push_str(&report.next_extent.1.to_string()); previous_extent: [report.previous_extent.0, report.previous_extent.1],
out.push_str("]}"); next_extent: [report.next_extent.0, report.next_extent.1],
out },
"{\"schema\":0,\"reason\":\"unknown\",\"previous_extent\":[0,0],\"next_extent\":[0,0]}",
)
} }
/// Renders a deterministic JSON frame submission plan. /// Renders a deterministic JSON frame submission plan.
#[must_use] #[must_use]
pub fn render_frame_submission_plan_json(plan: &VulkanFrameSubmissionPlan) -> String { pub fn render_frame_submission_plan_json(plan: &VulkanFrameSubmissionPlan) -> String {
let mut out = String::new(); serialize_json_or_fallback(
out.push_str("{\"schema\":"); plan,
out.push_str(&plan.schema.to_string()); "{\"schema\":0,\"frames_in_flight\":0,\"command_buffers\":0,\"semaphores_per_frame\":0,\"fences_per_frame\":0,\"draw_count\":0,\"indexed_vertex_count\":0}",
out.push_str(",\"frames_in_flight\":"); )
out.push_str(&plan.frames_in_flight.to_string()); }
out.push_str(",\"command_buffers\":");
out.push_str(&plan.command_buffers.to_string()); fn serialize_json_or_fallback<T: Serialize>(value: &T, fallback: &str) -> String {
out.push_str(",\"semaphores_per_frame\":"); match serde_json::to_string(value) {
out.push_str(&plan.semaphores_per_frame.to_string()); Ok(json) => json,
out.push_str(",\"fences_per_frame\":"); Err(_) => fallback.to_string(),
out.push_str(&plan.fences_per_frame.to_string()); }
out.push_str(",\"draw_count\":");
out.push_str(&plan.draw_count.to_string());
out.push_str(",\"indexed_vertex_count\":");
out.push_str(&plan.indexed_vertex_count.to_string());
out.push('}');
out
} }
fn format_api_version(version: u32) -> String { fn format_api_version(version: u32) -> String {
@@ -4238,25 +4211,6 @@ fn format_api_version(version: u32) -> String {
) )
} }
fn push_json_string(out: &mut String, value: &str) {
out.push('"');
for ch in value.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c.is_control() => {
use std::fmt::Write as _;
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.push(c),
}
}
out.push('"');
}
/// Diagnostics for Vulkan planning backend setup and frame progression. /// Diagnostics for Vulkan planning backend setup and frame progression.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct VulkanPlanningBackendReport { pub struct VulkanPlanningBackendReport {