feat(vulkan): select readback-capable swapchains
This commit is contained in:
@@ -241,6 +241,7 @@ impl VulkanSmokeRenderer {
|
|||||||
enabled_extension_count: 0,
|
enabled_extension_count: 0,
|
||||||
swapchain_extent: (0, 0),
|
swapchain_extent: (0, 0),
|
||||||
swapchain_image_count: 0,
|
swapchain_image_count: 0,
|
||||||
|
swapchain_image_usage: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
renderer.rebuild_swapchain_resources(false)?;
|
renderer.rebuild_swapchain_resources(false)?;
|
||||||
@@ -268,6 +269,7 @@ impl VulkanSmokeRenderer {
|
|||||||
.unwrap_or(u32::MAX),
|
.unwrap_or(u32::MAX),
|
||||||
swapchain_extent: swapchain_ref.report.plan.extent,
|
swapchain_extent: swapchain_ref.report.plan.extent,
|
||||||
swapchain_image_count: swapchain_ref.report.image_count,
|
swapchain_image_count: swapchain_ref.report.image_count,
|
||||||
|
swapchain_image_usage: swapchain_ref.report.plan.image_usage,
|
||||||
};
|
};
|
||||||
Ok(renderer)
|
Ok(renderer)
|
||||||
}
|
}
|
||||||
@@ -571,11 +573,13 @@ impl VulkanSmokeRenderer {
|
|||||||
let frame_sync = create_frame_sync(device)?;
|
let frame_sync = create_frame_sync(device)?;
|
||||||
let swapchain_extent = self.swapchain_ref()?.report.plan.extent;
|
let swapchain_extent = self.swapchain_ref()?.report.plan.extent;
|
||||||
let swapchain_image_count = self.swapchain_ref()?.report.image_count;
|
let swapchain_image_count = self.swapchain_ref()?.report.image_count;
|
||||||
|
let swapchain_image_usage = self.swapchain_ref()?.report.plan.image_usage;
|
||||||
let resources = resources.commit();
|
let resources = resources.commit();
|
||||||
self.images_in_flight = vec![vk::Fence::null(); resources.image_views.len()];
|
self.images_in_flight = vec![vk::Fence::null(); resources.image_views.len()];
|
||||||
self.frame_sync = frame_sync;
|
self.frame_sync = frame_sync;
|
||||||
self.report.swapchain_extent = swapchain_extent;
|
self.report.swapchain_extent = swapchain_extent;
|
||||||
self.report.swapchain_image_count = swapchain_image_count;
|
self.report.swapchain_image_count = swapchain_image_count;
|
||||||
|
self.report.swapchain_image_usage = swapchain_image_usage;
|
||||||
self.swapchain_resources = Some(resources);
|
self.swapchain_resources = Some(resources);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -457,6 +457,8 @@ pub struct VulkanSmokeRendererReport {
|
|||||||
pub swapchain_extent: (u32, u32),
|
pub swapchain_extent: (u32, u32),
|
||||||
/// Current swapchain image count.
|
/// Current swapchain image count.
|
||||||
pub swapchain_image_count: u32,
|
pub swapchain_image_count: u32,
|
||||||
|
/// Current swapchain image-usage flags as raw Vulkan bits.
|
||||||
|
pub swapchain_image_usage: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Measured validation counters from the live smoke loop.
|
/// Measured validation counters from the live smoke loop.
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ pub fn create_vulkan_swapchain_probe_for_extent(
|
|||||||
height: plan.extent.1,
|
height: plan.extent.1,
|
||||||
})
|
})
|
||||||
.image_array_layers(1)
|
.image_array_layers(1)
|
||||||
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
.image_usage(vk::ImageUsageFlags::from_raw(plan.image_usage))
|
||||||
.image_sharing_mode(sharing_mode)
|
.image_sharing_mode(sharing_mode)
|
||||||
.queue_family_indices(&queue_family_indices)
|
.queue_family_indices(&queue_family_indices)
|
||||||
.pre_transform(raw_capabilities.current_transform)
|
.pre_transform(raw_capabilities.current_transform)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ fn frame_submission_plan_json_is_stable() -> Result<(), RenderError> {
|
|||||||
},
|
},
|
||||||
present_mode: vk::PresentModeKHR::FIFO.as_raw(),
|
present_mode: vk::PresentModeKHR::FIFO.as_raw(),
|
||||||
image_count: 3,
|
image_count: 3,
|
||||||
|
image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let plan = plan_vulkan_frame_submission(&swapchain, &commands)?;
|
let plan = plan_vulkan_frame_submission(&swapchain, &commands)?;
|
||||||
@@ -543,6 +544,23 @@ fn swapchain_plan_prefers_srgb_mailbox_and_clamps_extent() {
|
|||||||
assert_eq!(plan.present_mode, vk::PresentModeKHR::MAILBOX.as_raw());
|
assert_eq!(plan.present_mode, vk::PresentModeKHR::MAILBOX.as_raw());
|
||||||
assert_eq!(plan.extent, (1024, 720));
|
assert_eq!(plan.extent, (1024, 720));
|
||||||
assert_eq!(plan.image_count, 3);
|
assert_eq!(plan.image_count, 3);
|
||||||
|
assert_eq!(
|
||||||
|
plan.image_usage,
|
||||||
|
vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swapchain_plan_enables_transfer_source_when_surface_supports_it() {
|
||||||
|
let mut request = swapchain_request();
|
||||||
|
request.capabilities.supported_usage_flags |= vk::ImageUsageFlags::TRANSFER_SRC.as_raw();
|
||||||
|
|
||||||
|
let plan = plan_vulkan_swapchain(&request).expect("swapchain plan");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
plan.image_usage,
|
||||||
|
(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC).as_raw()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -606,7 +624,7 @@ fn swapchain_plan_json_and_recreation_reports_are_stable() {
|
|||||||
let plan = plan_vulkan_swapchain(&swapchain_request()).expect("swapchain plan");
|
let plan = plan_vulkan_swapchain(&swapchain_request()).expect("swapchain plan");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_swapchain_plan_json(&plan),
|
render_swapchain_plan_json(&plan),
|
||||||
"{\"schema\":1,\"extent\":[1024,720],\"format\":50,\"color_space\":0,\"present_mode\":1,\"image_count\":3}"
|
"{\"schema\":1,\"extent\":[1024,720],\"format\":50,\"color_space\":0,\"present_mode\":1,\"image_count\":3,\"image_usage\":16}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let report = swapchain_recreation_report(
|
let report = swapchain_recreation_report(
|
||||||
|
|||||||
@@ -163,5 +163,6 @@ fn default_stage0_swapchain_plan() -> VulkanSwapchainPlan {
|
|||||||
},
|
},
|
||||||
present_mode: vk::PresentModeKHR::FIFO.as_raw(),
|
present_mode: vk::PresentModeKHR::FIFO.as_raw(),
|
||||||
image_count: 2,
|
image_count: 2,
|
||||||
|
image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ pub struct VulkanSwapchainPlan {
|
|||||||
pub present_mode: i32,
|
pub present_mode: i32,
|
||||||
/// Selected image count.
|
/// Selected image count.
|
||||||
pub image_count: u32,
|
pub image_count: u32,
|
||||||
|
/// Selected image-usage flags as raw Vulkan bits.
|
||||||
|
pub image_usage: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Swapchain planning error.
|
/// Swapchain planning error.
|
||||||
@@ -425,6 +427,7 @@ pub fn plan_vulkan_swapchain(
|
|||||||
format,
|
format,
|
||||||
present_mode,
|
present_mode,
|
||||||
image_count: select_image_count(request.capabilities),
|
image_count: select_image_count(request.capabilities),
|
||||||
|
image_usage: select_swapchain_image_usage(request.capabilities),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,6 +524,7 @@ pub fn render_swapchain_plan_json(plan: &VulkanSwapchainPlan) -> String {
|
|||||||
color_space: i32,
|
color_space: i32,
|
||||||
present_mode: i32,
|
present_mode: i32,
|
||||||
image_count: u32,
|
image_count: u32,
|
||||||
|
image_usage: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize_json_or_fallback(
|
serialize_json_or_fallback(
|
||||||
@@ -531,8 +535,9 @@ pub fn render_swapchain_plan_json(plan: &VulkanSwapchainPlan) -> String {
|
|||||||
color_space: plan.format.color_space,
|
color_space: plan.format.color_space,
|
||||||
present_mode: plan.present_mode,
|
present_mode: plan.present_mode,
|
||||||
image_count: plan.image_count,
|
image_count: plan.image_count,
|
||||||
|
image_usage: plan.image_usage,
|
||||||
},
|
},
|
||||||
"{\"schema\":0,\"extent\":[0,0],\"format\":0,\"color_space\":0,\"present_mode\":0,\"image_count\":0}",
|
"{\"schema\":0,\"extent\":[0,0],\"format\":0,\"color_space\":0,\"present_mode\":0,\"image_count\":0,\"image_usage\":0}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -809,6 +814,15 @@ fn supports_depth_stencil_request(
|
|||||||
select_depth_stencil_attachment_format(&device.supported_depth_stencil_formats, depth).is_some()
|
select_depth_stencil_attachment_format(&device.supported_depth_stencil_formats, depth).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_swapchain_image_usage(capabilities: VulkanSwapchainSurfaceCapabilities) -> u32 {
|
||||||
|
let mut usage = vk::ImageUsageFlags::COLOR_ATTACHMENT;
|
||||||
|
let supported = vk::ImageUsageFlags::from_raw(capabilities.supported_usage_flags);
|
||||||
|
if supported.contains(vk::ImageUsageFlags::TRANSFER_SRC) {
|
||||||
|
usage |= vk::ImageUsageFlags::TRANSFER_SRC;
|
||||||
|
}
|
||||||
|
usage.as_raw()
|
||||||
|
}
|
||||||
|
|
||||||
/// Selects the first canonical depth/stencil attachment format supported by a device.
|
/// Selects the first canonical depth/stencil attachment format supported by a device.
|
||||||
///
|
///
|
||||||
/// The order is part of the Windows Vulkan compatibility contract and is shared
|
/// The order is part of the Windows Vulkan compatibility contract and is shared
|
||||||
|
|||||||
@@ -778,6 +778,9 @@ impl SmokeApp {
|
|||||||
vulkan_swapchain_image_count: renderer
|
vulkan_swapchain_image_count: renderer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |snapshot| snapshot.report.swapchain_image_count),
|
.map_or(0, |snapshot| snapshot.report.swapchain_image_count),
|
||||||
|
vulkan_swapchain_image_usage: renderer
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0, |snapshot| snapshot.report.swapchain_image_usage),
|
||||||
vulkan_portability_enumeration: renderer
|
vulkan_portability_enumeration: renderer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|snapshot| snapshot.report.portability_enumeration),
|
.is_some_and(|snapshot| snapshot.report.portability_enumeration),
|
||||||
@@ -980,6 +983,7 @@ fn render_timeout_failure_report(
|
|||||||
vulkan_swapchain_width: 0,
|
vulkan_swapchain_width: 0,
|
||||||
vulkan_swapchain_height: 0,
|
vulkan_swapchain_height: 0,
|
||||||
vulkan_swapchain_image_count: 0,
|
vulkan_swapchain_image_count: 0,
|
||||||
|
vulkan_swapchain_image_usage: 0,
|
||||||
vulkan_portability_enumeration: false,
|
vulkan_portability_enumeration: false,
|
||||||
vulkan_portability_subset_enabled: false,
|
vulkan_portability_subset_enabled: false,
|
||||||
};
|
};
|
||||||
@@ -1200,6 +1204,7 @@ struct SmokeReport<'a> {
|
|||||||
vulkan_swapchain_width: u32,
|
vulkan_swapchain_width: u32,
|
||||||
vulkan_swapchain_height: u32,
|
vulkan_swapchain_height: u32,
|
||||||
vulkan_swapchain_image_count: u32,
|
vulkan_swapchain_image_count: u32,
|
||||||
|
vulkan_swapchain_image_usage: u32,
|
||||||
vulkan_portability_enumeration: bool,
|
vulkan_portability_enumeration: bool,
|
||||||
vulkan_portability_subset_enabled: bool,
|
vulkan_portability_subset_enabled: bool,
|
||||||
}
|
}
|
||||||
@@ -1703,6 +1708,7 @@ mod tests {
|
|||||||
vulkan_swapchain_width: 960,
|
vulkan_swapchain_width: 960,
|
||||||
vulkan_swapchain_height: 540,
|
vulkan_swapchain_height: 540,
|
||||||
vulkan_swapchain_image_count: 3,
|
vulkan_swapchain_image_count: 3,
|
||||||
|
vulkan_swapchain_image_usage: 17,
|
||||||
vulkan_portability_enumeration: false,
|
vulkan_portability_enumeration: false,
|
||||||
vulkan_portability_subset_enabled: false,
|
vulkan_portability_subset_enabled: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -859,6 +859,22 @@ dynamic material data и не входит в `PipelineKey`. Также не у
|
|||||||
дополнительного дизассемблирования. Это частично реализованная compatibility
|
дополнительного дизассемблирования. Это частично реализованная compatibility
|
||||||
boundary, а не заявление о готовой parity fixed-function state.
|
boundary, а не заявление о готовой parity fixed-function state.
|
||||||
|
|
||||||
|
### Capability-gated swapchain source for future pixel capture
|
||||||
|
|
||||||
|
Pixel parity требует чтения результата GPU, но swapchain image разрешает это
|
||||||
|
только если surface сообщает `VK_IMAGE_USAGE_TRANSFER_SRC_BIT`. Поэтому
|
||||||
|
swapchain policy всегда запрашивает обязательный `COLOR_ATTACHMENT`, а
|
||||||
|
`TRANSFER_SRC` добавляет лишь при фактической поддержке surface; выбранные raw
|
||||||
|
usage bits попадают в deterministic plan и native smoke report. Это не создаёт
|
||||||
|
readback buffer, не копирует pixels и не закрывает pixel-capture acceptance.
|
||||||
|
|
||||||
|
На Windows GOG native smoke 2026-07-18 (`MTCHECK.MSH` из `system.rlb` и
|
||||||
|
`DEFAULT.0` из `Textures.lib`) AMD Radeon Pro WX 3200 Series подтвердил
|
||||||
|
`image_usage=17` (`COLOR_ATTACHMENT | TRANSFER_SRC`): 300 frames, 3 resize
|
||||||
|
events, 2 swapchain recreations, Vulkan validation warnings/errors `0/0`.
|
||||||
|
Следующий шаг должен выполнить явный image-to-buffer copy и сравнить полученные
|
||||||
|
bytes с зафиксированным reference capture.
|
||||||
|
|
||||||
`Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28`
|
`Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28`
|
||||||
сохраняет source triangle order, а его positions и packed UV0 попадают в тот же
|
сохраняет source triangle order, а его positions и packed UV0 попадают в тот же
|
||||||
static vertex/index upload path. Для текущего диагностического viewer XZ bounds
|
static vertex/index upload path. Для текущего диагностического viewer XZ bounds
|
||||||
|
|||||||
Reference in New Issue
Block a user