fix(presenter,core): close the last of the proof-lint hole — the Vulkan contract, stated once
pf-presenter's 120 sites are `ash` calls almost without exception, so 120 independent arguments
would have been 120 restatements of the signature — the exact noise this program exists to remove.
They get the `abi.rs` treatment instead: the Vulkan contract stated once in `lib.rs`, each site
naming which of three shapes it is.
The three are not equal, and separating them is the point. CREATE and RECORD carry no real
precondition — the device is owned, the builders are locals, nothing executes until submit. DESTROY
does: the GPU must not still be using the object, and that is established by the path (a fence wait,
a `queue_wait_idle`, a retired swapchain), not by the call. Those sites say so, because getting it
wrong is a use-after-free no type catches. The contract also tells the next person that a block
outside the three shapes needs a real proof, and that writing "as above" is the signal it doesn't
belong in them.
punktfunk-core's Windows half is finished here too: `qos_windows.rs`'s `GetLastError` reads (called
before anything can reset the thread's error slot) and `udp/windows.rs`'s control-message write,
whose argument is that `ctrl` is sized by `WSA_CMSG_SPACE(4)` — computed two lines up — so header
plus payload cannot run past it, and `write_unaligned` is used because `WSA_CMSG_DATA` offers no
alignment guarantee.
⚠️ THE WINDOWS BLIND SPOT BIT A THIRD TIME. A Linux measurement put this crate pair at 113; the
real number was 129 — `d3d11.rs`, `win32.rs`, `qos_windows.rs`, `udp/windows.rs` are all
`cfg`-hidden. Every crate in this sweep had to be finished on .47 after being "done" on .21. For a
cross-platform crate the Linux number is a lower bound, never the answer.
All three crates now deny `undocumented_unsafe_blocks`, which was the goal: it applied to 8 of 11
crates carrying unsafe, and the three exempt ones held 381 items between them — including the C ABI
surface and the presenter. Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 all four
closed crates clippy `-D warnings` rc=0 plus the full Windows CI clippy set and pf-capture's tests.
Only small crates remain unguarded (75 items total, largest 26).
This commit is contained in:
@@ -91,6 +91,8 @@ impl CscPass {
|
|||||||
.dst_stage_mask(vk::PipelineStageFlags::TRANSFER)
|
.dst_stage_mask(vk::PipelineStageFlags::TRANSFER)
|
||||||
.dst_access_mask(vk::AccessFlags::TRANSFER_READ),
|
.dst_access_mask(vk::AccessFlags::TRANSFER_READ),
|
||||||
];
|
];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let render_pass = unsafe {
|
let render_pass = unsafe {
|
||||||
device.create_render_pass(
|
device.create_render_pass(
|
||||||
&vk::RenderPassCreateInfo::default()
|
&vk::RenderPassCreateInfo::default()
|
||||||
@@ -102,6 +104,8 @@ impl CscPass {
|
|||||||
}
|
}
|
||||||
.context("CSC render pass")?;
|
.context("CSC render pass")?;
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let sampler = unsafe {
|
let sampler = unsafe {
|
||||||
device.create_sampler(
|
device.create_sampler(
|
||||||
&vk::SamplerCreateInfo::default()
|
&vk::SamplerCreateInfo::default()
|
||||||
@@ -125,6 +129,8 @@ impl CscPass {
|
|||||||
.immutable_samplers(&samplers)
|
.immutable_samplers(&samplers)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let set_layout = unsafe {
|
let set_layout = unsafe {
|
||||||
device.create_descriptor_set_layout(
|
device.create_descriptor_set_layout(
|
||||||
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
|
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
|
||||||
@@ -135,6 +141,8 @@ impl CscPass {
|
|||||||
let push = [vk::PushConstantRange::default()
|
let push = [vk::PushConstantRange::default()
|
||||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
||||||
.size(64)]; // three vec4 rows + a params vec4 (mode, tonemap peak)
|
.size(64)]; // three vec4 rows + a params vec4 (mode, tonemap peak)
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let pipeline_layout = unsafe {
|
let pipeline_layout = unsafe {
|
||||||
device.create_pipeline_layout(
|
device.create_pipeline_layout(
|
||||||
&vk::PipelineLayoutCreateInfo::default()
|
&vk::PipelineLayoutCreateInfo::default()
|
||||||
@@ -147,6 +155,8 @@ impl CscPass {
|
|||||||
let pool_sizes = [vk::DescriptorPoolSize::default()
|
let pool_sizes = [vk::DescriptorPoolSize::default()
|
||||||
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.descriptor_count(plane_bindings)];
|
.descriptor_count(plane_bindings)];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let desc_pool = unsafe {
|
let desc_pool = unsafe {
|
||||||
device.create_descriptor_pool(
|
device.create_descriptor_pool(
|
||||||
&vk::DescriptorPoolCreateInfo::default()
|
&vk::DescriptorPoolCreateInfo::default()
|
||||||
@@ -155,6 +165,8 @@ impl CscPass {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let desc_set = unsafe {
|
let desc_set = unsafe {
|
||||||
device.allocate_descriptor_sets(
|
device.allocate_descriptor_sets(
|
||||||
&vk::DescriptorSetAllocateInfo::default()
|
&vk::DescriptorSetAllocateInfo::default()
|
||||||
@@ -203,6 +215,9 @@ impl CscPass {
|
|||||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.image_info(&infos[1]),
|
.image_info(&infos[1]),
|
||||||
];
|
];
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
unsafe { device.update_descriptor_sets(&writes, &[]) };
|
unsafe { device.update_descriptor_sets(&writes, &[]) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,10 +238,17 @@ impl CscPass {
|
|||||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.image_info(&infos[b as usize])
|
.image_info(&infos[b as usize])
|
||||||
});
|
});
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
unsafe { device.update_descriptor_sets(&writes, &[]) };
|
unsafe { device.update_descriptor_sets(&writes, &[]) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(&self, device: &ash::Device) {
|
pub fn destroy(&self, device: &ash::Device) {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and the
|
||||||
|
// GPU is known idle for them (the fence/queue-wait on the path here, or the swapchain
|
||||||
|
// being retired), which is the obligation that makes a destroy sound rather than the
|
||||||
|
// handle merely being non-null.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.destroy_pipeline(self.pipeline, None);
|
device.destroy_pipeline(self.pipeline, None);
|
||||||
device.destroy_pipeline_layout(self.pipeline_layout, None);
|
device.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
@@ -255,15 +277,23 @@ pub(crate) fn build_fullscreen_pipeline(
|
|||||||
&include_bytes!("../shaders/fullscreen.vert.spv")[..],
|
&include_bytes!("../shaders/fullscreen.vert.spv")[..],
|
||||||
))?;
|
))?;
|
||||||
let frag = ash::util::read_spv(&mut std::io::Cursor::new(frag_spv))?;
|
let frag = ash::util::read_spv(&mut std::io::Cursor::new(frag_spv))?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
let vert_mod = unsafe {
|
let vert_mod = unsafe {
|
||||||
device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&vert), None)
|
device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&vert), None)
|
||||||
}?;
|
}?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
let frag_mod = unsafe {
|
let frag_mod = unsafe {
|
||||||
device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&frag), None)
|
device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&frag), None)
|
||||||
};
|
};
|
||||||
let frag_mod = match frag_mod {
|
let frag_mod = match frag_mod {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and
|
||||||
|
// the GPU is known idle for them (the fence/queue-wait on the path here, or the
|
||||||
|
// swapchain being retired), which is the obligation that makes a destroy sound rather
|
||||||
|
// than the handle merely being non-null.
|
||||||
unsafe { device.destroy_shader_module(vert_mod, None) };
|
unsafe { device.destroy_shader_module(vert_mod, None) };
|
||||||
return Err(e).context("fragment shader module");
|
return Err(e).context("fragment shader module");
|
||||||
}
|
}
|
||||||
@@ -325,9 +355,16 @@ pub(crate) fn build_fullscreen_pipeline(
|
|||||||
.layout(layout)
|
.layout(layout)
|
||||||
.render_pass(render_pass);
|
.render_pass(render_pass);
|
||||||
let pipeline =
|
let pipeline =
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
unsafe { 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");
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and the GPU is
|
||||||
|
// known idle for them (the fence/queue-wait on the path here, or the swapchain being retired),
|
||||||
|
// which is the obligation that makes a destroy sound rather than the handle merely being non-
|
||||||
|
// null.
|
||||||
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);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ fn format_importable(
|
|||||||
.push_next(&mut ext_info);
|
.push_next(&mut ext_info);
|
||||||
let mut ext_props = vk::ExternalImageFormatProperties::default();
|
let mut ext_props = vk::ExternalImageFormatProperties::default();
|
||||||
let mut props = vk::ImageFormatProperties2::default().push_next(&mut ext_props);
|
let mut props = vk::ImageFormatProperties2::default().push_next(&mut ext_props);
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
unsafe { instance.get_physical_device_image_format_properties2(pdev, &fmt_info, &mut props) }
|
unsafe { instance.get_physical_device_image_format_properties2(pdev, &fmt_info, &mut props) }
|
||||||
.is_ok()
|
.is_ok()
|
||||||
&& ext_props
|
&& ext_props
|
||||||
@@ -90,6 +92,9 @@ impl HwFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self, device: &ash::Device) {
|
pub fn destroy(self, device: &ash::Device) {
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - destroys objects this type owns, on a path
|
||||||
|
// where the GPU is known idle for them; that idleness is the obligation, not the handle
|
||||||
|
// being non-null.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.destroy_image(self.image, None);
|
device.destroy_image(self.image, None);
|
||||||
device.free_memory(self.memory, None);
|
device.free_memory(self.memory, None);
|
||||||
@@ -122,6 +127,9 @@ pub fn import(
|
|||||||
// the whole job. Kept maximally "identical" to the D3D11 resource (no view-format
|
// the whole job. Kept maximally "identical" to the D3D11 resource (no view-format
|
||||||
// aliasing, no extra usages).
|
// aliasing, no extra usages).
|
||||||
let mut external_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(handle_type);
|
let mut external_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(handle_type);
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - a create/allocate/import call on the live
|
||||||
|
// device, over builder structs that are locals outliving the call; the handle returned is
|
||||||
|
// owned by the value being built here.
|
||||||
let image = unsafe {
|
let image = unsafe {
|
||||||
device.create_image(
|
device.create_image(
|
||||||
&vk::ImageCreateInfo::default()
|
&vk::ImageCreateInfo::default()
|
||||||
@@ -153,10 +161,14 @@ pub fn import(
|
|||||||
// The handle's importable memory types, intersected with the image's requirement.
|
// The handle's importable memory types, intersected with the image's requirement.
|
||||||
let handle = frame.handle as vk::HANDLE;
|
let handle = frame.handle as vk::HANDLE;
|
||||||
let mut handle_props = vk::MemoryWin32HandlePropertiesKHR::default();
|
let mut handle_props = vk::MemoryWin32HandlePropertiesKHR::default();
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
unsafe {
|
unsafe {
|
||||||
ext_mem_win32.get_memory_win32_handle_properties(handle_type, handle, &mut handle_props)
|
ext_mem_win32.get_memory_win32_handle_properties(handle_type, handle, &mut handle_props)
|
||||||
}
|
}
|
||||||
.context("vkGetMemoryWin32HandlePropertiesKHR")?;
|
.context("vkGetMemoryWin32HandlePropertiesKHR")?;
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
let reqs = unsafe { device.get_image_memory_requirements(image) };
|
let reqs = unsafe { device.get_image_memory_requirements(image) };
|
||||||
let bits = reqs.memory_type_bits & handle_props.memory_type_bits;
|
let bits = reqs.memory_type_bits & handle_props.memory_type_bits;
|
||||||
let type_index = (0..32u32)
|
let type_index = (0..32u32)
|
||||||
@@ -169,6 +181,9 @@ pub fn import(
|
|||||||
.handle_type(handle_type)
|
.handle_type(handle_type)
|
||||||
.handle(handle);
|
.handle(handle);
|
||||||
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
|
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - a create/allocate/import call on the live
|
||||||
|
// device, over builder structs that are locals outliving the call; the handle returned is
|
||||||
|
// owned by the value being built here.
|
||||||
let memory = unsafe {
|
let memory = unsafe {
|
||||||
device.allocate_memory(
|
device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
@@ -180,7 +195,13 @@ pub fn import(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.context("import D3D11 texture memory")?;
|
.context("import D3D11 texture memory")?;
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - a create/allocate/import call on the live
|
||||||
|
// device, over builder structs that are locals outliving the call; the handle returned is
|
||||||
|
// owned by the value being built here.
|
||||||
if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
|
if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - destroys objects this type owns, on a
|
||||||
|
// path where the GPU is known idle for them; that idleness is the obligation, not the
|
||||||
|
// handle being non-null.
|
||||||
unsafe { device.free_memory(memory, None) };
|
unsafe { device.free_memory(memory, None) };
|
||||||
return Err(e).context("bind imported memory");
|
return Err(e).context("bind imported memory");
|
||||||
}
|
}
|
||||||
@@ -189,6 +210,9 @@ pub fn import(
|
|||||||
let memory = match result {
|
let memory = match result {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - destroys objects this type owns, on a
|
||||||
|
// path where the GPU is known idle for them; that idleness is the obligation, not the
|
||||||
|
// handle being non-null.
|
||||||
unsafe { device.destroy_image(image, None) };
|
unsafe { device.destroy_image(image, None) };
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ impl HwFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self, device: &ash::Device) {
|
pub fn destroy(self, device: &ash::Device) {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
for v in self.views {
|
for v in self.views {
|
||||||
device.destroy_image_view(v, None);
|
device.destroy_image_view(v, None);
|
||||||
@@ -136,6 +138,10 @@ pub fn import(
|
|||||||
{
|
{
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and
|
||||||
|
// the GPU is known idle for them (the fence/queue-wait on the path here, or the
|
||||||
|
// swapchain being retired), which is the obligation that makes a destroy sound rather
|
||||||
|
// than the handle merely being non-null.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.destroy_image(luma_img, None);
|
device.destroy_image(luma_img, None);
|
||||||
device.free_memory(luma_mem, None);
|
device.free_memory(luma_mem, None);
|
||||||
@@ -145,6 +151,9 @@ pub fn import(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let view = |image, format| {
|
let view = |image, format| {
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.create_image_view(
|
device.create_image_view(
|
||||||
&vk::ImageViewCreateInfo::default()
|
&vk::ImageViewCreateInfo::default()
|
||||||
@@ -162,6 +171,10 @@ pub fn import(
|
|||||||
}
|
}
|
||||||
.context("plane image view")
|
.context("plane image view")
|
||||||
};
|
};
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and the GPU is
|
||||||
|
// known idle for them (the fence/queue-wait on the path here, or the swapchain being retired),
|
||||||
|
// which is the obligation that makes a destroy sound rather than the handle merely being non-
|
||||||
|
// null.
|
||||||
let destroy_images = |views: &[vk::ImageView]| unsafe {
|
let destroy_images = |views: &[vk::ImageView]| unsafe {
|
||||||
for v in views {
|
for v in views {
|
||||||
device.destroy_image_view(*v, None);
|
device.destroy_image_view(*v, None);
|
||||||
@@ -228,6 +241,8 @@ fn plane_image(
|
|||||||
.plane_layouts(&plane_layouts);
|
.plane_layouts(&plane_layouts);
|
||||||
let mut external_info = vk::ExternalMemoryImageCreateInfo::default()
|
let mut external_info = vk::ExternalMemoryImageCreateInfo::default()
|
||||||
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
|
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
let image = unsafe {
|
let image = unsafe {
|
||||||
device.create_image(
|
device.create_image(
|
||||||
&vk::ImageCreateInfo::default()
|
&vk::ImageCreateInfo::default()
|
||||||
@@ -256,6 +271,9 @@ fn plane_image(
|
|||||||
let result = (|| {
|
let result = (|| {
|
||||||
// The fd's importable memory types, intersected with the image's requirement.
|
// The fd's importable memory types, intersected with the image's requirement.
|
||||||
let mut fd_props = vk::MemoryFdPropertiesKHR::default();
|
let mut fd_props = vk::MemoryFdPropertiesKHR::default();
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
unsafe {
|
unsafe {
|
||||||
ext_mem_fd.get_memory_fd_properties(
|
ext_mem_fd.get_memory_fd_properties(
|
||||||
vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT,
|
vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT,
|
||||||
@@ -264,6 +282,8 @@ fn plane_image(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.context("vkGetMemoryFdPropertiesKHR")?;
|
.context("vkGetMemoryFdPropertiesKHR")?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let reqs = unsafe { device.get_image_memory_requirements(image) };
|
let reqs = unsafe { device.get_image_memory_requirements(image) };
|
||||||
let bits = reqs.memory_type_bits & fd_props.memory_type_bits;
|
let bits = reqs.memory_type_bits & fd_props.memory_type_bits;
|
||||||
let type_index = (0..32u32)
|
let type_index = (0..32u32)
|
||||||
@@ -271,6 +291,8 @@ fn plane_image(
|
|||||||
.context("no importable memory type for dmabuf")?;
|
.context("no importable memory type for dmabuf")?;
|
||||||
|
|
||||||
// Vulkan owns the fd it imports — dup so the decoder guard keeps the original.
|
// Vulkan owns the fd it imports — dup so the decoder guard keeps the original.
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let owned = unsafe { BorrowedFd::borrow_raw(fd) }
|
let owned = unsafe { BorrowedFd::borrow_raw(fd) }
|
||||||
.try_clone_to_owned()
|
.try_clone_to_owned()
|
||||||
.context("dup dmabuf fd")?;
|
.context("dup dmabuf fd")?;
|
||||||
@@ -278,6 +300,8 @@ fn plane_image(
|
|||||||
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
|
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
|
||||||
.fd(owned.into_raw_fd());
|
.fd(owned.into_raw_fd());
|
||||||
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
|
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let memory = unsafe {
|
let memory = unsafe {
|
||||||
device.allocate_memory(
|
device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
@@ -290,7 +314,13 @@ fn plane_image(
|
|||||||
}
|
}
|
||||||
.context("import dmabuf memory")?;
|
.context("import dmabuf memory")?;
|
||||||
// (On allocate_memory failure Vulkan still closed the dup'd fd — nothing leaks.)
|
// (On allocate_memory failure Vulkan still closed the dup'd fd — nothing leaks.)
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
|
if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and
|
||||||
|
// the GPU is known idle for them (the fence/queue-wait on the path here, or the
|
||||||
|
// swapchain being retired), which is the obligation that makes a destroy sound rather
|
||||||
|
// than the handle merely being non-null.
|
||||||
unsafe { device.free_memory(memory, None) };
|
unsafe { device.free_memory(memory, None) };
|
||||||
return Err(e).context("bind imported memory");
|
return Err(e).context("bind imported memory");
|
||||||
}
|
}
|
||||||
@@ -300,6 +330,10 @@ fn plane_image(
|
|||||||
match result {
|
match result {
|
||||||
Ok(memory) => Ok((image, memory)),
|
Ok(memory) => Ok((image, memory)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and
|
||||||
|
// the GPU is known idle for them (the fence/queue-wait on the path here, or the
|
||||||
|
// swapchain being retired), which is the obligation that makes a destroy sound rather
|
||||||
|
// than the handle merely being non-null.
|
||||||
unsafe { device.destroy_image(image, None) };
|
unsafe { device.destroy_image(image, None) };
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,29 @@
|
|||||||
//! Windows) and `d3d11` is its Windows counterpart (D3D11VA shared-texture import) —
|
//! Windows) and `d3d11` is its Windows counterpart (D3D11VA shared-texture import) —
|
||||||
//! the decode chain there is Vulkan → D3D11VA → software.
|
//! the decode chain there is Vulkan → D3D11VA → software.
|
||||||
|
|
||||||
|
// Unsafe-proof program: every `unsafe {}` in this crate carries a `// SAFETY:` proof.
|
||||||
|
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||||
|
|
||||||
|
// THE VULKAN CONTRACT, stated once - most `// SAFETY:` proofs in this crate are an instance of it.
|
||||||
|
//
|
||||||
|
// Nearly every `unsafe` here is an `ash` call, which is `unsafe` because Vulkan is a C API, not
|
||||||
|
// because each call carries its own bespoke obligation. Three shapes recur, and only one of them
|
||||||
|
// has a real precondition worth restating per site:
|
||||||
|
//
|
||||||
|
// * CREATE / ALLOCATE - `create_*`, `allocate_*`. The device is live (this type owns it), and the
|
||||||
|
// `vk::*CreateInfo` builders are locals that outlive the synchronous call. The handle returned is
|
||||||
|
// owned by the value being constructed and destroyed in its `Drop`.
|
||||||
|
// * RECORD - `cmd_*`, `begin/end_command_buffer`, `update_descriptor_sets`. Recorded into a command
|
||||||
|
// buffer this code owns and has begun, referencing handles it also owns. Nothing executes until
|
||||||
|
// submit, so a recording error is not yet a memory error.
|
||||||
|
// * DESTROY - `destroy_*`, `free_*`, `unmap_memory`. THIS is the one with a real obligation: the
|
||||||
|
// GPU must not still be using the object. That is established on the path, not by the call - a
|
||||||
|
// fence wait, a `queue_wait_idle`, or the swapchain having been retired - and the per-site proofs
|
||||||
|
// say so, because getting it wrong is a use-after-free the type system cannot catch.
|
||||||
|
//
|
||||||
|
// A block doing something OUTSIDE these three shapes gets a real, specific proof; if you add one and
|
||||||
|
// find yourself writing "as above", it probably belongs in one of them.
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", windows))]
|
#[cfg(any(target_os = "linux", windows))]
|
||||||
pub mod csc;
|
pub mod csc;
|
||||||
#[cfg(any(target_os = "linux", windows))]
|
#[cfg(any(target_os = "linux", windows))]
|
||||||
|
|||||||
@@ -1704,6 +1704,8 @@ fn apply_capture(
|
|||||||
/// forwarded as touch passthrough. An unknown/invalid id (INVALID) reads as not-direct.
|
/// forwarded as touch passthrough. An unknown/invalid id (INVALID) reads as not-direct.
|
||||||
fn is_direct_touch(touch_id: u64) -> bool {
|
fn is_direct_touch(touch_id: u64) -> bool {
|
||||||
use sdl3::sys::touch::{SDL_GetTouchDeviceType, SDL_TouchDeviceType, SDL_TouchID};
|
use sdl3::sys::touch::{SDL_GetTouchDeviceType, SDL_TouchDeviceType, SDL_TouchID};
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe { SDL_GetTouchDeviceType(SDL_TouchID(touch_id)) == SDL_TouchDeviceType::DIRECT }
|
unsafe { SDL_GetTouchDeviceType(SDL_TouchID(touch_id)) == SDL_TouchDeviceType::DIRECT }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ impl Presenter {
|
|||||||
/// submits FFmpeg's Vulkan decode work concurrently, and wait-idle's external-sync
|
/// submits FFmpeg's Vulkan decode work concurrently, and wait-idle's external-sync
|
||||||
/// rule over every device queue would race it (observed as a resize crash).
|
/// rule over every device queue would race it (observed as a resize crash).
|
||||||
pub(super) fn quiesce_own(&mut self) -> Result<()> {
|
pub(super) fn quiesce_own(&mut self) -> Result<()> {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
if self.submitted {
|
if self.submitted {
|
||||||
self.device.wait_for_fences(&[self.fence], true, u64::MAX)?;
|
self.device.wait_for_fences(&[self.fence], true, u64::MAX)?;
|
||||||
@@ -31,6 +33,9 @@ impl Presenter {
|
|||||||
.contains(flags)
|
.contains(flags)
|
||||||
})
|
})
|
||||||
.with_context(|| format!("no memory type for {flags:?}"))?;
|
.with_context(|| format!("no memory type for {flags:?}"))?;
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.allocate_memory(
|
self.device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
@@ -111,6 +116,9 @@ pub(super) fn vkframe_acquire_barrier(
|
|||||||
.dst_queue_family_index(dst)
|
.dst_queue_family_index(dst)
|
||||||
.image(image)
|
.image(image)
|
||||||
.subresource_range(subresource_range());
|
.subresource_range(subresource_range());
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns and
|
||||||
|
// has begun, referencing handles it also owns; nothing is submitted until the recording is
|
||||||
|
// ended.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.cmd_pipeline_barrier(
|
device.cmd_pipeline_barrier(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -145,6 +153,8 @@ pub(super) fn external_acquire_barrier(
|
|||||||
.dst_queue_family_index(qfi)
|
.dst_queue_family_index(qfi)
|
||||||
.image(image)
|
.image(image)
|
||||||
.subresource_range(subresource_range());
|
.subresource_range(subresource_range());
|
||||||
|
// SAFETY: per the Vulkan contract in lib.rs - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing runs until submit.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.cmd_pipeline_barrier(
|
device.cmd_pipeline_barrier(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -177,6 +187,9 @@ pub(super) fn foreign_acquire_barrier(
|
|||||||
.dst_queue_family_index(qfi)
|
.dst_queue_family_index(qfi)
|
||||||
.image(image)
|
.image(image)
|
||||||
.subresource_range(subresource_range());
|
.subresource_range(subresource_range());
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns and
|
||||||
|
// has begun, referencing handles it also owns; nothing is submitted until the recording is
|
||||||
|
// ended.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.cmd_pipeline_barrier(
|
device.cmd_pipeline_barrier(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -208,6 +221,9 @@ pub(super) fn barrier(
|
|||||||
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||||
.image(image)
|
.image(image)
|
||||||
.subresource_range(subresource_range());
|
.subresource_range(subresource_range());
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns and
|
||||||
|
// has begun, referencing handles it also owns; nothing is submitted until the recording is
|
||||||
|
// ended.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.cmd_pipeline_barrier(
|
device.cmd_pipeline_barrier(
|
||||||
cmd,
|
cmd,
|
||||||
|
|||||||
@@ -230,6 +230,8 @@ impl Presenter {
|
|||||||
/// The queue lock is held as cheap insurance against a straggling submitter.
|
/// The queue lock is held as cheap insurance against a straggling submitter.
|
||||||
pub fn wait_idle(&self) {
|
pub fn wait_idle(&self) {
|
||||||
let _q = self.queue_lock.guard();
|
let _q = self.queue_lock.guard();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe { self.device.device_wait_idle() }.ok();
|
unsafe { self.device.device_wait_idle() }.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +280,8 @@ impl Drop for Presenter {
|
|||||||
// after in-flight waits complete, bounded by their 250 ms cap) BEFORE the
|
// after in-flight waits complete, bounded by their 250 ms cap) BEFORE the
|
||||||
// swapchain teardown below.
|
// swapchain teardown below.
|
||||||
self.present_timer.take();
|
self.present_timer.take();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
{
|
{
|
||||||
// Insurance against a straggling submitter (the run loop joins the
|
// Insurance against a straggling submitter (the run loop joins the
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ impl OverlayPipe {
|
|||||||
.dst_access_mask(
|
.dst_access_mask(
|
||||||
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||||
)];
|
)];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let render_pass = unsafe {
|
let render_pass = unsafe {
|
||||||
device.create_render_pass(
|
device.create_render_pass(
|
||||||
&vk::RenderPassCreateInfo::default()
|
&vk::RenderPassCreateInfo::default()
|
||||||
@@ -43,6 +45,8 @@ impl OverlayPipe {
|
|||||||
}
|
}
|
||||||
.context("overlay render pass")?;
|
.context("overlay render pass")?;
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let sampler = unsafe {
|
let sampler = unsafe {
|
||||||
device.create_sampler(
|
device.create_sampler(
|
||||||
&vk::SamplerCreateInfo::default()
|
&vk::SamplerCreateInfo::default()
|
||||||
@@ -61,6 +65,8 @@ impl OverlayPipe {
|
|||||||
.descriptor_count(1)
|
.descriptor_count(1)
|
||||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
||||||
.immutable_samplers(&samplers)];
|
.immutable_samplers(&samplers)];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let set_layout = unsafe {
|
let set_layout = unsafe {
|
||||||
device.create_descriptor_set_layout(
|
device.create_descriptor_set_layout(
|
||||||
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
|
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
|
||||||
@@ -68,6 +74,8 @@ impl OverlayPipe {
|
|||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
let set_layouts = [set_layout];
|
let set_layouts = [set_layout];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let pipeline_layout = unsafe {
|
let pipeline_layout = unsafe {
|
||||||
device.create_pipeline_layout(
|
device.create_pipeline_layout(
|
||||||
&vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts),
|
&vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts),
|
||||||
@@ -77,6 +85,8 @@ impl OverlayPipe {
|
|||||||
let pool_sizes = [vk::DescriptorPoolSize::default()
|
let pool_sizes = [vk::DescriptorPoolSize::default()
|
||||||
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.descriptor_count(1)];
|
.descriptor_count(1)];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let desc_pool = unsafe {
|
let desc_pool = unsafe {
|
||||||
device.create_descriptor_pool(
|
device.create_descriptor_pool(
|
||||||
&vk::DescriptorPoolCreateInfo::default()
|
&vk::DescriptorPoolCreateInfo::default()
|
||||||
@@ -85,6 +95,8 @@ impl OverlayPipe {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let desc_set = unsafe {
|
let desc_set = unsafe {
|
||||||
device.allocate_descriptor_sets(
|
device.allocate_descriptor_sets(
|
||||||
&vk::DescriptorSetAllocateInfo::default()
|
&vk::DescriptorSetAllocateInfo::default()
|
||||||
@@ -131,6 +143,9 @@ impl OverlayPipe {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.destroy_targets(device); // no-op after take_targets; safety net otherwise
|
self.destroy_targets(device); // no-op after take_targets; safety net otherwise
|
||||||
for &image in images {
|
for &image in images {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by
|
||||||
|
// this type and live for the call, and every builder struct is a local that outlives
|
||||||
|
// it.
|
||||||
let view = unsafe {
|
let view = unsafe {
|
||||||
device.create_image_view(
|
device.create_image_view(
|
||||||
&vk::ImageViewCreateInfo::default()
|
&vk::ImageViewCreateInfo::default()
|
||||||
@@ -143,6 +158,9 @@ impl OverlayPipe {
|
|||||||
}?;
|
}?;
|
||||||
self.views.push(view);
|
self.views.push(view);
|
||||||
let attachments = [view];
|
let attachments = [view];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by
|
||||||
|
// this type and live for the call, and every builder struct is a local that outlives
|
||||||
|
// it.
|
||||||
let fb = unsafe {
|
let fb = unsafe {
|
||||||
device.create_framebuffer(
|
device.create_framebuffer(
|
||||||
&vk::FramebufferCreateInfo::default()
|
&vk::FramebufferCreateInfo::default()
|
||||||
@@ -160,6 +178,8 @@ impl OverlayPipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn destroy_targets(&mut self, device: &ash::Device) {
|
fn destroy_targets(&mut self, device: &ash::Device) {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
for fb in self.framebuffers.drain(..) {
|
for fb in self.framebuffers.drain(..) {
|
||||||
device.destroy_framebuffer(fb, None);
|
device.destroy_framebuffer(fb, None);
|
||||||
@@ -172,6 +192,10 @@ impl OverlayPipe {
|
|||||||
|
|
||||||
pub(super) fn destroy(&mut self, device: &ash::Device) {
|
pub(super) fn destroy(&mut self, device: &ash::Device) {
|
||||||
self.destroy_targets(device);
|
self.destroy_targets(device);
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and the
|
||||||
|
// GPU is known idle for them (the fence/queue-wait on the path here, or the swapchain
|
||||||
|
// being retired), which is the obligation that makes a destroy sound rather than the
|
||||||
|
// handle merely being non-null.
|
||||||
unsafe {
|
unsafe {
|
||||||
device.destroy_pipeline(self.pipeline, None);
|
device.destroy_pipeline(self.pipeline, None);
|
||||||
device.destroy_pipeline_layout(self.pipeline_layout, None);
|
device.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ impl Presenter {
|
|||||||
|
|
||||||
// One frame in flight: the fence covers the command buffer, the staging buffer
|
// One frame in flight: the fence covers the command buffer, the staging buffer
|
||||||
// AND the previously submitted hw frame — waiting makes all three reusable.
|
// AND the previously submitted hw frame — waiting makes all three reusable.
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
if self.submitted {
|
if self.submitted {
|
||||||
self.device.wait_for_fences(&[self.fence], true, u64::MAX)?;
|
self.device.wait_for_fences(&[self.fence], true, u64::MAX)?;
|
||||||
@@ -189,9 +191,14 @@ impl Presenter {
|
|||||||
.dst_binding(0)
|
.dst_binding(0)
|
||||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.image_info(&infos)];
|
.image_info(&infos)];
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code
|
||||||
|
// owns and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
unsafe { self.device.update_descriptor_sets(&writes, &[]) };
|
unsafe { self.device.update_descriptor_sets(&writes, &[]) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let (index, _suboptimal) = match unsafe {
|
let (index, _suboptimal) = match unsafe {
|
||||||
self.swap_d.acquire_next_image(
|
self.swap_d.acquire_next_image(
|
||||||
self.swapchain,
|
self.swapchain,
|
||||||
@@ -218,6 +225,9 @@ impl Presenter {
|
|||||||
};
|
};
|
||||||
let swap_image = self.images[index as usize];
|
let swap_image = self.images[index as usize];
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.begin_command_buffer(
|
self.device.begin_command_buffer(
|
||||||
self.cmd_buf,
|
self.cmd_buf,
|
||||||
@@ -628,6 +638,9 @@ impl Presenter {
|
|||||||
depth: u8,
|
depth: u8,
|
||||||
msb_packed: bool,
|
msb_packed: bool,
|
||||||
) {
|
) {
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.cmd_begin_render_pass(
|
self.device.cmd_begin_render_pass(
|
||||||
self.cmd_buf,
|
self.cmd_buf,
|
||||||
@@ -714,6 +727,9 @@ impl Presenter {
|
|||||||
let Some(planar) = self.csc_planar.as_ref() else {
|
let Some(planar) = self.csc_planar.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.cmd_begin_render_pass(
|
self.device.cmd_begin_render_pass(
|
||||||
self.cmd_buf,
|
self.cmd_buf,
|
||||||
@@ -816,11 +832,16 @@ 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 =
|
let image = vk::Image::from_raw(
|
||||||
vk::Image::from_raw(
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned
|
||||||
unsafe { (*(f.vkframe as *const pf_ffvk::AVVkFrame)).img[0] } as u64,
|
// by this type and live for the call, and every builder struct is a local that
|
||||||
);
|
// outlives it.
|
||||||
|
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| {
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device,
|
||||||
|
// over builder structs that are locals outliving the call; the handle it returns is
|
||||||
|
// owned by the value being built here.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.create_image_view(
|
self.device.create_image_view(
|
||||||
&vk::ImageViewCreateInfo::default()
|
&vk::ImageViewCreateInfo::default()
|
||||||
@@ -842,6 +863,10 @@ impl Presenter {
|
|||||||
let chroma = match make(vk::ImageAspectFlags::PLANE_1, chroma_fmt) {
|
let chroma = match make(vk::ImageAspectFlags::PLANE_1, chroma_fmt) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns,
|
||||||
|
// and the GPU is known idle for them (the fence/queue-wait on the path here, or
|
||||||
|
// the swapchain being retired), which is the obligation that makes a destroy sound
|
||||||
|
// rather than the handle merely being non-null.
|
||||||
unsafe { self.device.destroy_image_view(luma, None) };
|
unsafe { self.device.destroy_image_view(luma, None) };
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
@@ -871,6 +896,8 @@ struct VkFrameSync {
|
|||||||
// is required on one platform and a no-op on the other.
|
// is required on one platform and a no-op on the other.
|
||||||
#[allow(clippy::unnecessary_cast)]
|
#[allow(clippy::unnecessary_cast)]
|
||||||
fn lock_vkframe(f: &VkVideoFrame) -> VkFrameSync {
|
fn lock_vkframe(f: &VkVideoFrame) -> VkFrameSync {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
let lock: unsafe extern "C" fn(*mut pf_ffvk::AVHWFramesContext, *mut pf_ffvk::AVVkFrame) =
|
let lock: unsafe extern "C" fn(*mut pf_ffvk::AVHWFramesContext, *mut pf_ffvk::AVVkFrame) =
|
||||||
std::mem::transmute(f.lock_frame);
|
std::mem::transmute(f.lock_frame);
|
||||||
@@ -890,6 +917,8 @@ fn lock_vkframe(f: &VkVideoFrame) -> VkFrameSync {
|
|||||||
/// Write the post-submission state back (FFmpeg waits these on its next use of the
|
/// Write the post-submission state back (FFmpeg waits these on its next use of the
|
||||||
/// frame) and release the lock. On a failed submit only the lock is released.
|
/// frame) and release the lock. On a failed submit only the lock is released.
|
||||||
fn unlock_vkframe(f: &VkVideoFrame, sync: &VkFrameSync, submitted: bool, graphics_qf: u32) {
|
fn unlock_vkframe(f: &VkVideoFrame, sync: &VkFrameSync, submitted: bool, graphics_qf: u32) {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
let vkf = f.vkframe as *mut pf_ffvk::AVVkFrame;
|
let vkf = f.vkframe as *mut pf_ffvk::AVVkFrame;
|
||||||
if submitted {
|
if submitted {
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ impl PresentTimer {
|
|||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
while let Ok(job) = rx.recv() {
|
while let Ok(job) = rx.recv() {
|
||||||
// 250 ms cap — see the module doc's lifecycle note.
|
// 250 ms cap — see the module doc's lifecycle note.
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are
|
||||||
|
// owned by this type and live for the call, and every builder struct is a
|
||||||
|
// local that outlives it.
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
wait_d.wait_for_present(job.swapchain, job.present_id, 250_000_000)
|
wait_d.wait_for_present(job.swapchain, job.present_id, 250_000_000)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,10 +47,15 @@ impl Presenter {
|
|||||||
// the stream for a frame, and only happens on resize/HDR-flip/OUT_OF_DATE.
|
// the stream for a frame, and only happens on resize/HDR-flip/OUT_OF_DATE.
|
||||||
{
|
{
|
||||||
let _q = self.queue_lock.guard();
|
let _q = self.queue_lock.guard();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by
|
||||||
|
// this type and live for the call, and every builder struct is a local that outlives
|
||||||
|
// it.
|
||||||
unsafe { self.device.queue_wait_idle(self.queue) }
|
unsafe { self.device.queue_wait_idle(self.queue) }
|
||||||
.context("vkQueueWaitIdle (swapchain recreate)")?;
|
.context("vkQueueWaitIdle (swapchain recreate)")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let caps = unsafe {
|
let caps = unsafe {
|
||||||
self.surface_i
|
self.surface_i
|
||||||
.get_physical_device_surface_capabilities(self.pdev, self.surface)
|
.get_physical_device_surface_capabilities(self.pdev, self.surface)
|
||||||
@@ -91,6 +96,9 @@ impl Presenter {
|
|||||||
.present_mode(self.present_mode)
|
.present_mode(self.present_mode)
|
||||||
.clipped(true)
|
.clipped(true)
|
||||||
.old_swapchain(old);
|
.old_swapchain(old);
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
let swapchain = unsafe { self.swap_d.create_swapchain(&info, None) }
|
let swapchain = unsafe { self.swap_d.create_swapchain(&info, None) }
|
||||||
.map_err(|e| anyhow!("vkCreateSwapchainKHR: {e}{}", kmsdrm_swapchain_hint()))?;
|
.map_err(|e| anyhow!("vkCreateSwapchainKHR: {e}{}", kmsdrm_swapchain_hint()))?;
|
||||||
// The old swapchain and everything tied to its images dies NOW: the fence
|
// The old swapchain and everything tied to its images dies NOW: the fence
|
||||||
@@ -105,6 +113,8 @@ impl Presenter {
|
|||||||
// An unclaimed last present belonged to the dying swapchain — drop the claim.
|
// An unclaimed last present belonged to the dying swapchain — drop the claim.
|
||||||
self.last_presented = None;
|
self.last_presented = None;
|
||||||
let (overlay_views, overlay_framebuffers) = self.overlay_pipe.take_targets();
|
let (overlay_views, overlay_framebuffers) = self.overlay_pipe.take_targets();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
for fb in overlay_framebuffers {
|
for fb in overlay_framebuffers {
|
||||||
self.device.destroy_framebuffer(fb, None);
|
self.device.destroy_framebuffer(fb, None);
|
||||||
@@ -120,6 +130,8 @@ impl Presenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.swapchain = swapchain;
|
self.swapchain = swapchain;
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
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.rebuild_targets(
|
self.overlay_pipe.rebuild_targets(
|
||||||
@@ -130,6 +142,9 @@ impl Presenter {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for _ in 0..self.images.len() {
|
for _ in 0..self.images.len() {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by
|
||||||
|
// this type and live for the call, and every builder struct is a local that outlives
|
||||||
|
// it.
|
||||||
self.render_sems.push(unsafe {
|
self.render_sems.push(unsafe {
|
||||||
self.device
|
self.device
|
||||||
.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
|
.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
|
||||||
@@ -205,6 +220,8 @@ impl Presenter {
|
|||||||
.min_luminance(m.min_display_mastering_luminance as f32 / 10_000.0)
|
.min_luminance(m.min_display_mastering_luminance as f32 / 10_000.0)
|
||||||
.max_content_light_level(m.max_cll as f32)
|
.max_content_light_level(m.max_cll as f32)
|
||||||
.max_frame_average_light_level(m.max_fall as f32);
|
.max_frame_average_light_level(m.max_fall as f32);
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe { ext.set_hdr_metadata(&[self.swapchain], &[md]) };
|
unsafe { ext.set_hdr_metadata(&[self.swapchain], &[md]) };
|
||||||
tracing::debug!(from_host = self.hdr_meta.is_some(), "HDR metadata pushed");
|
tracing::debug!(from_host = self.hdr_meta.is_some(), "HDR metadata pushed");
|
||||||
}
|
}
|
||||||
@@ -238,6 +255,10 @@ impl Presenter {
|
|||||||
self.csc_planar = Some(CscPass::new_planar(&self.device, self.video_format)?);
|
self.csc_planar = Some(CscPass::new_planar(&self.device, self.video_format)?);
|
||||||
}
|
}
|
||||||
if let Some(v) = self.video.take() {
|
if let Some(v) = self.video.take() {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and
|
||||||
|
// the GPU is known idle for them (the fence/queue-wait on the path here, or the
|
||||||
|
// swapchain being retired), which is the obligation that makes a destroy sound rather
|
||||||
|
// than the handle merely being non-null.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.destroy_framebuffer(v.framebuffer, None);
|
self.device.destroy_framebuffer(v.framebuffer, None);
|
||||||
self.device.destroy_image_view(v.view, None);
|
self.device.destroy_image_view(v.view, None);
|
||||||
@@ -254,6 +275,8 @@ impl Presenter {
|
|||||||
OverlayPipe::new(&self.device, target.format)?,
|
OverlayPipe::new(&self.device, target.format)?,
|
||||||
);
|
);
|
||||||
let (overlay_views, overlay_framebuffers) = old_pipe.take_targets();
|
let (overlay_views, overlay_framebuffers) = old_pipe.take_targets();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
for fb in overlay_framebuffers {
|
for fb in overlay_framebuffers {
|
||||||
self.device.destroy_framebuffer(fb, None);
|
self.device.destroy_framebuffer(fb, None);
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ impl Retired {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
Retired::D3d11(f) => f.destroy(device),
|
Retired::D3d11(f) => f.destroy(device),
|
||||||
Retired::Vk { frame, views } => {
|
Retired::Vk { frame, views } => {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned
|
||||||
|
// by this type and live for the call, and every builder struct is a local that
|
||||||
|
// outlives it.
|
||||||
unsafe {
|
unsafe {
|
||||||
for v in views {
|
for v in views {
|
||||||
device.destroy_image_view(v, None);
|
device.destroy_image_view(v, None);
|
||||||
@@ -49,6 +52,8 @@ impl Presenter {
|
|||||||
}
|
}
|
||||||
let s = self.staging.as_ref().unwrap();
|
let s = self.staging.as_ref().unwrap();
|
||||||
let n = f.rgba.len().min(needed);
|
let n = f.rgba.len().min(needed);
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe { std::ptr::copy_nonoverlapping(f.rgba.as_ptr(), s.ptr, n) };
|
unsafe { std::ptr::copy_nonoverlapping(f.rgba.as_ptr(), s.ptr, n) };
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -57,6 +62,9 @@ impl Presenter {
|
|||||||
// Fence-quiesce: the old image is only ever referenced by OUR command buffers.
|
// Fence-quiesce: the old image is only ever referenced by OUR command buffers.
|
||||||
self.quiesce_own()?;
|
self.quiesce_own()?;
|
||||||
if let Some(v) = self.video.take() {
|
if let Some(v) = self.video.take() {
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by
|
||||||
|
// this type and live for the call, and every builder struct is a local that outlives
|
||||||
|
// it.
|
||||||
unsafe {
|
unsafe {
|
||||||
if v.framebuffer != vk::Framebuffer::null() {
|
if v.framebuffer != vk::Framebuffer::null() {
|
||||||
self.device.destroy_framebuffer(v.framebuffer, None);
|
self.device.destroy_framebuffer(v.framebuffer, None);
|
||||||
@@ -69,6 +77,8 @@ impl Presenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// COLOR_ATTACHMENT is the CSC pass's render target; harmless where hw is absent.
|
// COLOR_ATTACHMENT is the CSC pass's render target; harmless where hw is absent.
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let image = unsafe {
|
let image = unsafe {
|
||||||
self.device.create_image(
|
self.device.create_image(
|
||||||
&vk::ImageCreateInfo::default()
|
&vk::ImageCreateInfo::default()
|
||||||
@@ -92,11 +102,17 @@ impl Presenter {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let reqs = unsafe { self.device.get_image_memory_requirements(image) };
|
let reqs = unsafe { self.device.get_image_memory_requirements(image) };
|
||||||
let memory = self.allocate(reqs, vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
|
let memory = self.allocate(reqs, vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe { self.device.bind_image_memory(image, memory, 0) }?;
|
unsafe { self.device.bind_image_memory(image, memory, 0) }?;
|
||||||
// The CSC pass renders into it — view + framebuffer, unconditional (Vulkan-Video
|
// The CSC pass renders into it — view + framebuffer, unconditional (Vulkan-Video
|
||||||
// frames need the pass on every device, dmabuf-capable or not).
|
// frames need the pass on every device, dmabuf-capable or not).
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let view = unsafe {
|
let view = unsafe {
|
||||||
self.device.create_image_view(
|
self.device.create_image_view(
|
||||||
&vk::ImageViewCreateInfo::default()
|
&vk::ImageViewCreateInfo::default()
|
||||||
@@ -108,6 +124,8 @@ impl Presenter {
|
|||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
let attachments = [view];
|
let attachments = [view];
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let framebuffer = unsafe {
|
let framebuffer = unsafe {
|
||||||
self.device.create_framebuffer(
|
self.device.create_framebuffer(
|
||||||
&vk::FramebufferCreateInfo::default()
|
&vk::FramebufferCreateInfo::default()
|
||||||
@@ -133,12 +151,18 @@ impl Presenter {
|
|||||||
fn rebuild_staging(&mut self, capacity: usize) -> Result<()> {
|
fn rebuild_staging(&mut self, capacity: usize) -> Result<()> {
|
||||||
self.quiesce_own()?;
|
self.quiesce_own()?;
|
||||||
if let Some(s) = self.staging.take() {
|
if let Some(s) = self.staging.take() {
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and
|
||||||
|
// the GPU is known idle for them (the fence/queue-wait on the path here, or the
|
||||||
|
// swapchain being retired), which is the obligation that makes a destroy sound rather
|
||||||
|
// than the handle merely being non-null.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device.unmap_memory(s.memory);
|
self.device.unmap_memory(s.memory);
|
||||||
self.device.destroy_buffer(s.buffer, None);
|
self.device.destroy_buffer(s.buffer, None);
|
||||||
self.device.free_memory(s.memory, None);
|
self.device.free_memory(s.memory, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let buffer = unsafe {
|
let buffer = unsafe {
|
||||||
self.device.create_buffer(
|
self.device.create_buffer(
|
||||||
&vk::BufferCreateInfo::default()
|
&vk::BufferCreateInfo::default()
|
||||||
@@ -148,12 +172,18 @@ impl Presenter {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let reqs = unsafe { self.device.get_buffer_memory_requirements(buffer) };
|
let reqs = unsafe { self.device.get_buffer_memory_requirements(buffer) };
|
||||||
let memory = self.allocate(
|
let memory = self.allocate(
|
||||||
reqs,
|
reqs,
|
||||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
||||||
)?;
|
)?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
unsafe { self.device.bind_buffer_memory(buffer, memory, 0) }?;
|
unsafe { self.device.bind_buffer_memory(buffer, memory, 0) }?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
self.device
|
self.device
|
||||||
.map_memory(memory, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty())
|
.map_memory(memory, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty())
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ impl Presenter {
|
|||||||
/// Bring up instance → surface → device → swapchain over an SDL window.
|
/// Bring up instance → surface → device → swapchain over an SDL window.
|
||||||
/// `instance_extensions` comes from `VideoSubsystem::vulkan_instance_extensions()`.
|
/// `instance_extensions` comes from `VideoSubsystem::vulkan_instance_extensions()`.
|
||||||
pub fn new(window: &sdl3::video::Window, instance_extensions: &[String]) -> Result<Presenter> {
|
pub fn new(window: &sdl3::video::Window, instance_extensions: &[String]) -> Result<Presenter> {
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
let entry = unsafe { ash::Entry::load() }.context("libvulkan not loadable")?;
|
let entry = unsafe { ash::Entry::load() }.context("libvulkan not loadable")?;
|
||||||
|
|
||||||
let app_name = CString::new("punktfunk-session").unwrap();
|
let app_name = CString::new("punktfunk-session").unwrap();
|
||||||
@@ -29,6 +32,8 @@ impl Presenter {
|
|||||||
// 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 =
|
let inst_available =
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default();
|
unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default();
|
||||||
let has_colorspace_ext = inst_available
|
let has_colorspace_ext = inst_available
|
||||||
.iter()
|
.iter()
|
||||||
@@ -44,6 +49,8 @@ impl Presenter {
|
|||||||
// hardcoded `*const i8` compiles on the desktop targets and fails to match ash's
|
// hardcoded `*const i8` compiles on the desktop targets and fails to match ash's
|
||||||
// `&[*const c_char]` on ARM.
|
// `&[*const c_char]` on ARM.
|
||||||
let ext_ptrs: Vec<*const c_char> = ext_cstrings.iter().map(|e| e.as_ptr()).collect();
|
let ext_ptrs: Vec<*const c_char> = ext_cstrings.iter().map(|e| e.as_ptr()).collect();
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let instance = unsafe {
|
let instance = unsafe {
|
||||||
entry.create_instance(
|
entry.create_instance(
|
||||||
&vk::InstanceCreateInfo::default()
|
&vk::InstanceCreateInfo::default()
|
||||||
@@ -55,12 +62,19 @@ impl Presenter {
|
|||||||
.context("vkCreateInstance")?;
|
.context("vkCreateInstance")?;
|
||||||
let surface_i = ash::khr::surface::Instance::new(&entry, &instance);
|
let surface_i = ash::khr::surface::Instance::new(&entry, &instance);
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by
|
||||||
|
// the value being built here.
|
||||||
let surface = unsafe { window.vulkan_create_surface(instance.handle()) }
|
let surface = unsafe { window.vulkan_create_surface(instance.handle()) }
|
||||||
.map_err(|e| anyhow!("SDL_Vulkan_CreateSurface: {e}"))?;
|
.map_err(|e| anyhow!("SDL_Vulkan_CreateSurface: {e}"))?;
|
||||||
|
|
||||||
let (pdev, qfi) = pick_device(&instance, &surface_i, surface)?;
|
let (pdev, qfi) = pick_device(&instance, &surface_i, surface)?;
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let mem_props = unsafe { instance.get_physical_device_memory_properties(pdev) };
|
let mem_props = unsafe { instance.get_physical_device_memory_properties(pdev) };
|
||||||
{
|
{
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
let props = unsafe { instance.get_physical_device_properties(pdev) };
|
let props = unsafe { instance.get_physical_device_properties(pdev) };
|
||||||
let name = props
|
let name = props
|
||||||
.device_name_as_c_str()
|
.device_name_as_c_str()
|
||||||
@@ -72,6 +86,8 @@ impl Presenter {
|
|||||||
// The dmabuf import set is optional: enabled when the device offers all four,
|
// The dmabuf import set is optional: enabled when the device offers all four,
|
||||||
// else that path is off (`supports_dmabuf() == false`). Windows has no
|
// else that path is off (`supports_dmabuf() == false`). Windows has no
|
||||||
// dmabuf/DRM-PRIME — the whole import path is compiled out there.
|
// dmabuf/DRM-PRIME — the whole import path is compiled out there.
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let available = unsafe { instance.enumerate_device_extension_properties(pdev) }?;
|
let available = unsafe { instance.enumerate_device_extension_properties(pdev) }?;
|
||||||
let has = |name: &std::ffi::CStr| {
|
let has = |name: &std::ffi::CStr| {
|
||||||
available
|
available
|
||||||
@@ -113,6 +129,8 @@ impl Presenter {
|
|||||||
// SAME adapter). Core 1.1 query; valid on effectively every Windows driver.
|
// SAME adapter). Core 1.1 query; valid on effectively every Windows driver.
|
||||||
let mut id_props = vk::PhysicalDeviceIDProperties::default();
|
let mut id_props = vk::PhysicalDeviceIDProperties::default();
|
||||||
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut id_props);
|
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut id_props);
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
unsafe { instance.get_physical_device_properties2(pdev, &mut props2) };
|
unsafe { instance.get_physical_device_properties2(pdev, &mut props2) };
|
||||||
let adapter_luid: Option<[u8; 8]> =
|
let adapter_luid: Option<[u8; 8]> =
|
||||||
(id_props.device_luid_valid == vk::TRUE).then_some(id_props.device_luid);
|
(id_props.device_luid_valid == vk::TRUE).then_some(id_props.device_luid);
|
||||||
@@ -130,6 +148,8 @@ impl Presenter {
|
|||||||
// Probed, never required: a capable stack gets the video extensions, a second
|
// Probed, never required: a capable stack gets the video extensions, a second
|
||||||
// (decode) queue, and the features FFmpeg's decoder needs; anything less means
|
// (decode) queue, and the features FFmpeg's decoder needs; anything less means
|
||||||
// `vulkan_decode() == None` and the decoder chain falls back (VAAPI/software).
|
// `vulkan_decode() == None` and the decoder chain falls back (VAAPI/software).
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let dev_props = unsafe { instance.get_physical_device_properties(pdev) };
|
let dev_props = unsafe { instance.get_physical_device_properties(pdev) };
|
||||||
let dev_is_13 = vk::api_version_major(dev_props.api_version) > 1
|
let dev_is_13 = vk::api_version_major(dev_props.api_version) > 1
|
||||||
|| vk::api_version_minor(dev_props.api_version) >= 3;
|
|| vk::api_version_minor(dev_props.api_version) >= 3;
|
||||||
@@ -149,6 +169,8 @@ impl Presenter {
|
|||||||
if present_wait_exts {
|
if present_wait_exts {
|
||||||
have_f2 = have_f2.push_next(&mut have_pid).push_next(&mut have_pwait);
|
have_f2 = have_f2.push_next(&mut have_pid).push_next(&mut have_pwait);
|
||||||
}
|
}
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
unsafe { instance.get_physical_device_features2(pdev, &mut have_f2) };
|
unsafe { instance.get_physical_device_features2(pdev, &mut have_f2) };
|
||||||
// Copy the one base-features fact out NOW: `have_f2` mutably borrows the chained
|
// Copy the one base-features fact out NOW: `have_f2` mutably borrows the chained
|
||||||
// structs through its pNext chain, so any later use of it would pin those borrows —
|
// structs through its pNext chain, so any later use of it would pin those borrows —
|
||||||
@@ -174,6 +196,8 @@ impl Presenter {
|
|||||||
|
|
||||||
// The decode queue family + which codec operations it can run.
|
// The decode queue family + which codec operations it can run.
|
||||||
let decode_family: Option<(u32, vk::VideoCodecOperationFlagsKHR)> = {
|
let decode_family: Option<(u32, vk::VideoCodecOperationFlagsKHR)> = {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
let n = unsafe { instance.get_physical_device_queue_family_properties2_len(pdev) };
|
let n = unsafe { instance.get_physical_device_queue_family_properties2_len(pdev) };
|
||||||
let mut video: Vec<vk::QueueFamilyVideoPropertiesKHR> =
|
let mut video: Vec<vk::QueueFamilyVideoPropertiesKHR> =
|
||||||
vec![vk::QueueFamilyVideoPropertiesKHR::default(); n];
|
vec![vk::QueueFamilyVideoPropertiesKHR::default(); n];
|
||||||
@@ -181,6 +205,8 @@ impl Presenter {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|v| vk::QueueFamilyProperties2::default().push_next(v))
|
.map(|v| vk::QueueFamilyProperties2::default().push_next(v))
|
||||||
.collect();
|
.collect();
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
unsafe { instance.get_physical_device_queue_family_properties2(pdev, &mut props) };
|
unsafe { instance.get_physical_device_queue_family_properties2(pdev, &mut props) };
|
||||||
// `props` mutably borrows `video` (push_next); copy the flags out, then
|
// `props` mutably borrows `video` (push_next); copy the flags out, then
|
||||||
// read the driver-filled video properties directly.
|
// read the driver-filled video properties directly.
|
||||||
@@ -282,6 +308,8 @@ impl Presenter {
|
|||||||
.queue_priorities(&priorities),
|
.queue_priorities(&priorities),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let device = unsafe {
|
let device = unsafe {
|
||||||
instance.create_device(
|
instance.create_device(
|
||||||
pdev,
|
pdev,
|
||||||
@@ -305,6 +333,8 @@ impl Presenter {
|
|||||||
);
|
);
|
||||||
let hdr_metadata_d =
|
let hdr_metadata_d =
|
||||||
has_hdr_metadata.then(|| ash::ext::hdr_metadata::Device::new(&instance, &device));
|
has_hdr_metadata.then(|| ash::ext::hdr_metadata::Device::new(&instance, &device));
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let queue = unsafe { device.get_device_queue(qfi, 0) };
|
let queue = unsafe { device.get_device_queue(qfi, 0) };
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let hw = if hw_capable {
|
let hw = if hw_capable {
|
||||||
@@ -341,6 +371,8 @@ impl Presenter {
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let export_worthy = video_ok || pyrowave_ok;
|
let export_worthy = video_ok || pyrowave_ok;
|
||||||
let video_export = if export_worthy {
|
let video_export = if export_worthy {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
let qf_props = unsafe { instance.get_physical_device_queue_family_properties(pdev) };
|
let qf_props = unsafe { instance.get_physical_device_queue_family_properties(pdev) };
|
||||||
let mut device_extensions: Vec<CString> =
|
let mut device_extensions: Vec<CString> =
|
||||||
vec![CString::from(ash::khr::swapchain::NAME)];
|
vec![CString::from(ash::khr::swapchain::NAME)];
|
||||||
@@ -425,6 +457,9 @@ impl Presenter {
|
|||||||
);
|
);
|
||||||
let overlay_pipe = OverlayPipe::new(&device, format.format)?;
|
let overlay_pipe = OverlayPipe::new(&device, format.format)?;
|
||||||
|
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
let cmd_pool = unsafe {
|
let cmd_pool = unsafe {
|
||||||
device.create_command_pool(
|
device.create_command_pool(
|
||||||
&vk::CommandPoolCreateInfo::default()
|
&vk::CommandPoolCreateInfo::default()
|
||||||
@@ -433,6 +468,9 @@ impl Presenter {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}?;
|
}?;
|
||||||
|
// SAFETY: per the Vulkan contract above - recorded into a command buffer this code owns
|
||||||
|
// and has begun, referencing handles it also owns; nothing is submitted until the
|
||||||
|
// recording is ended.
|
||||||
let cmd_buf = unsafe {
|
let cmd_buf = unsafe {
|
||||||
device.allocate_command_buffers(
|
device.allocate_command_buffers(
|
||||||
&vk::CommandBufferAllocateInfo::default()
|
&vk::CommandBufferAllocateInfo::default()
|
||||||
@@ -442,7 +480,12 @@ impl Presenter {
|
|||||||
)
|
)
|
||||||
}?[0];
|
}?[0];
|
||||||
let acquire_sem =
|
let acquire_sem =
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device,
|
||||||
|
// over builder structs that are locals outliving the call; the handle it returns is
|
||||||
|
// owned by the value being built here.
|
||||||
unsafe { device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None) }?;
|
unsafe { device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None) }?;
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this
|
||||||
|
// type and live for the call, and every builder struct is a local that outlives it.
|
||||||
let fence = unsafe {
|
let fence = unsafe {
|
||||||
device.create_fence(
|
device.create_fence(
|
||||||
&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED),
|
&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED),
|
||||||
@@ -506,11 +549,16 @@ impl Presenter {
|
|||||||
/// the whole `PUNKTFUNK_VK_ADAPTER` match key, so a second identical card adds nothing).
|
/// the whole `PUNKTFUNK_VK_ADAPTER` match key, so a second identical card adds nothing).
|
||||||
/// Same 1.3 instance the presenter creates, so the list matches what streaming sees.
|
/// Same 1.3 instance the presenter creates, so the list matches what streaming sees.
|
||||||
pub fn list_adapters() -> Result<Vec<String>> {
|
pub fn list_adapters() -> Result<Vec<String>> {
|
||||||
|
// SAFETY: per the Vulkan contract above - a create/allocate call on the live device, over
|
||||||
|
// builder structs that are locals outliving the call; the handle it returns is owned by the
|
||||||
|
// value being built here.
|
||||||
let entry = unsafe { ash::Entry::load() }.context("libvulkan not loadable")?;
|
let entry = unsafe { ash::Entry::load() }.context("libvulkan not loadable")?;
|
||||||
let app_name = CString::new("punktfunk-session").unwrap();
|
let app_name = CString::new("punktfunk-session").unwrap();
|
||||||
let app_info = vk::ApplicationInfo::default()
|
let app_info = vk::ApplicationInfo::default()
|
||||||
.application_name(&app_name)
|
.application_name(&app_name)
|
||||||
.api_version(vk::API_VERSION_1_3);
|
.api_version(vk::API_VERSION_1_3);
|
||||||
|
// SAFETY: per the Vulkan contract above - the Vulkan handles used here are owned by this type
|
||||||
|
// and live for the call, and every builder struct is a local that outlives it.
|
||||||
let instance = unsafe {
|
let instance = unsafe {
|
||||||
entry.create_instance(
|
entry.create_instance(
|
||||||
&vk::InstanceCreateInfo::default().application_info(&app_info),
|
&vk::InstanceCreateInfo::default().application_info(&app_info),
|
||||||
@@ -518,9 +566,13 @@ pub fn list_adapters() -> Result<Vec<String>> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.context("vkCreateInstance")?;
|
.context("vkCreateInstance")?;
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let mut ranked: Vec<(u8, String)> = unsafe { instance.enumerate_physical_devices() }?
|
let mut ranked: Vec<(u8, String)> = unsafe { instance.enumerate_physical_devices() }?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|d| {
|
.map(|d| {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
let props = unsafe { instance.get_physical_device_properties(d) };
|
let props = unsafe { instance.get_physical_device_properties(d) };
|
||||||
let rank = match props.device_type {
|
let rank = match props.device_type {
|
||||||
vk::PhysicalDeviceType::DISCRETE_GPU => 0u8,
|
vk::PhysicalDeviceType::DISCRETE_GPU => 0u8,
|
||||||
@@ -535,6 +587,10 @@ pub fn list_adapters() -> Result<Vec<String>> {
|
|||||||
})
|
})
|
||||||
.filter(|(_, n)| !n.is_empty())
|
.filter(|(_, n)| !n.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
// SAFETY: per the Vulkan contract above - this destroys objects this type owns, and the GPU is
|
||||||
|
// known idle for them (the fence/queue-wait on the path here, or the swapchain being retired),
|
||||||
|
// which is the obligation that makes a destroy sound rather than the handle merely being non-
|
||||||
|
// null.
|
||||||
unsafe { instance.destroy_instance(None) };
|
unsafe { instance.destroy_instance(None) };
|
||||||
ranked.sort_by_key(|(r, _)| *r); // stable: enumeration order within each tier
|
ranked.sort_by_key(|(r, _)| *r); // stable: enumeration order within each tier
|
||||||
let mut names: Vec<String> = Vec::new();
|
let mut names: Vec<String> = Vec::new();
|
||||||
@@ -553,6 +609,8 @@ fn pick_device(
|
|||||||
surface_i: &ash::khr::surface::Instance,
|
surface_i: &ash::khr::surface::Instance,
|
||||||
surface: vk::SurfaceKHR,
|
surface: vk::SurfaceKHR,
|
||||||
) -> Result<(vk::PhysicalDevice, u32)> {
|
) -> Result<(vk::PhysicalDevice, u32)> {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let devices = unsafe { instance.enumerate_physical_devices() }?;
|
let devices = unsafe { instance.enumerate_physical_devices() }?;
|
||||||
let forced: Option<usize> = std::env::var("PUNKTFUNK_VK_DEVICE")
|
let forced: Option<usize> = std::env::var("PUNKTFUNK_VK_DEVICE")
|
||||||
.ok()
|
.ok()
|
||||||
@@ -575,6 +633,8 @@ fn pick_device(
|
|||||||
.map(|w| w.trim().to_lowercase())
|
.map(|w| w.trim().to_lowercase())
|
||||||
.filter(|w| !w.is_empty());
|
.filter(|w| !w.is_empty());
|
||||||
candidates.sort_by_key(|d| {
|
candidates.sort_by_key(|d| {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
let props = unsafe { instance.get_physical_device_properties(*d) };
|
let props = unsafe { instance.get_physical_device_properties(*d) };
|
||||||
let name = props
|
let name = props
|
||||||
.device_name_as_c_str()
|
.device_name_as_c_str()
|
||||||
@@ -595,10 +655,14 @@ fn pick_device(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
for pdev in candidates {
|
for pdev in candidates {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
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 =
|
let present =
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live
|
||||||
|
// instance/device, filling locals returned by value.
|
||||||
unsafe { 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 {
|
||||||
@@ -629,6 +693,8 @@ pub(super) fn pick_formats(
|
|||||||
let colorspace_ext = colorspace_ext
|
let colorspace_ext = colorspace_ext
|
||||||
&& !std::env::var("PUNKTFUNK_HDR10")
|
&& !std::env::var("PUNKTFUNK_HDR10")
|
||||||
.is_ok_and(|v| matches!(v.as_str(), "0" | "false" | "off" | "no"));
|
.is_ok_and(|v| matches!(v.as_str(), "0" | "false" | "off" | "no"));
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let formats = unsafe { surface_i.get_physical_device_surface_formats(pdev, surface) }?;
|
let formats = unsafe { surface_i.get_physical_device_surface_formats(pdev, surface) }?;
|
||||||
let mut sdr = None;
|
let mut sdr = None;
|
||||||
for want in [vk::Format::B8G8R8A8_UNORM, vk::Format::R8G8B8A8_UNORM] {
|
for want in [vk::Format::B8G8R8A8_UNORM, vk::Format::R8G8B8A8_UNORM] {
|
||||||
@@ -673,6 +739,8 @@ fn pick_present_mode(
|
|||||||
pdev: vk::PhysicalDevice,
|
pdev: vk::PhysicalDevice,
|
||||||
surface: vk::SurfaceKHR,
|
surface: vk::SurfaceKHR,
|
||||||
) -> Result<vk::PresentModeKHR> {
|
) -> Result<vk::PresentModeKHR> {
|
||||||
|
// SAFETY: per the Vulkan contract above - a read-only query on the live instance/device,
|
||||||
|
// filling locals returned by value.
|
||||||
let modes = unsafe { surface_i.get_physical_device_surface_present_modes(pdev, surface) }?;
|
let modes = unsafe { surface_i.get_physical_device_surface_present_modes(pdev, surface) }?;
|
||||||
let want = match std::env::var("PUNKTFUNK_PRESENT_MODE").ok().as_deref() {
|
let want = match std::env::var("PUNKTFUNK_PRESENT_MODE").ok().as_deref() {
|
||||||
Some("fifo") => vk::PresentModeKHR::FIFO,
|
Some("fifo") => vk::PresentModeKHR::FIFO,
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ const APP_USER_MODEL_ID: *const u16 = w!("unom.punktfunk.client");
|
|||||||
/// process already carries the MSIX identity, which must win). Call before any window
|
/// process already carries the MSIX identity, which must win). Call before any window
|
||||||
/// exists; later calls don't re-tag existing windows.
|
/// exists; later calls don't re-tag existing windows.
|
||||||
pub(crate) fn set_app_user_model_id() {
|
pub(crate) fn set_app_user_model_id() {
|
||||||
|
// SAFETY: `GetCurrentPackageFullName` is called with `len = 0` and a NULL buffer, which is the
|
||||||
|
// documented identity PROBE — it writes nothing and only reports whether the process is
|
||||||
|
// packaged. `SetCurrentProcessExplicitAppUserModelID` takes a static wide literal.
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut len: u32 = 0;
|
let mut len: u32 = 0;
|
||||||
// No buffer: just probe whether the process has package identity.
|
// No buffer: just probe whether the process has package identity.
|
||||||
@@ -43,6 +46,9 @@ pub(crate) fn set_app_user_model_id() {
|
|||||||
/// Stamp the exe-embedded icon (resource ordinal 1) onto the SDL window's title bar,
|
/// Stamp the exe-embedded icon (resource ordinal 1) onto the SDL window's title bar,
|
||||||
/// taskbar and Alt-Tab. A quiet no-op when the exe embeds no icon.
|
/// taskbar and Alt-Tab. A quiet no-op when the exe embeds no icon.
|
||||||
pub(crate) fn stamp_window_icon(window: &sdl3::video::Window) {
|
pub(crate) fn stamp_window_icon(window: &sdl3::video::Window) {
|
||||||
|
// SAFETY: the SDL property lookups return the `HWND` SDL itself owns for this live `window`
|
||||||
|
// borrow; the icon calls only pass that handle and a resource ordinal back to Win32, and every
|
||||||
|
// result is checked before use.
|
||||||
unsafe {
|
unsafe {
|
||||||
// The HWND behind the SDL window, via SDL3's window properties (the same route
|
// The HWND behind the SDL window, via SDL3's window properties (the same route
|
||||||
// the sdl3 crate's raw-window-handle integration takes).
|
// the sdl3 crate's raw-window-handle integration takes).
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ fn qos_handle() -> Option<HANDLE> {
|
|||||||
// SAFETY: both pointers are valid for the duration of the synchronous call.
|
// SAFETY: both pointers are valid for the duration of the synchronous call.
|
||||||
if unsafe { QOSCreateHandle(&version, &mut handle) } == 0 {
|
if unsafe { QOSCreateHandle(&version, &mut handle) } == 0 {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
// SAFETY: `GetLastError` takes no arguments and reads this thread's own last-error
|
||||||
|
// slot; it is called immediately after the failing call, before anything can reset it.
|
||||||
|
// SAFETY: `GetLastError` takes no arguments and reads this thread's own last-error
|
||||||
|
// slot; it is called immediately after the failing call, before anything can reset it.
|
||||||
err = unsafe { GetLastError() },
|
err = unsafe { GetLastError() },
|
||||||
"QOSCreateHandle failed — qWAVE DSCP marking unavailable"
|
"QOSCreateHandle failed — qWAVE DSCP marking unavailable"
|
||||||
);
|
);
|
||||||
@@ -95,6 +99,8 @@ pub(super) fn add_media_flow(socket: &UdpSocket, class: MediaClass) -> Option<Qo
|
|||||||
};
|
};
|
||||||
if ok == 0 {
|
if ok == 0 {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
// SAFETY: `GetLastError` takes no arguments and reads this thread's own last-error
|
||||||
|
// slot; it is called immediately after the failing call, before anything can reset it.
|
||||||
err = unsafe { GetLastError() },
|
err = unsafe { GetLastError() },
|
||||||
?class,
|
?class,
|
||||||
"QOSAddSocketToFlow failed — DSCP marking skipped"
|
"QOSAddSocketToFlow failed — DSCP marking skipped"
|
||||||
@@ -124,6 +130,8 @@ pub(super) fn add_media_flow(socket: &UdpSocket, class: MediaClass) -> Option<Qo
|
|||||||
};
|
};
|
||||||
if ok == 0 {
|
if ok == 0 {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
// SAFETY: `GetLastError` takes no arguments and reads this thread's own last-error
|
||||||
|
// slot; it is called immediately after the failing call, before anything can reset it.
|
||||||
err = unsafe { GetLastError() },
|
err = unsafe { GetLastError() },
|
||||||
?class,
|
?class,
|
||||||
"QOSSetFlow(OutgoingDSCPValue) refused — traffic-type default marking stands"
|
"QOSSetFlow(OutgoingDSCPValue) refused — traffic-type default marking stands"
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ fn send_one_uso(socket: &std::net::UdpSocket, buf: &[u8], seg_size: u16) -> std:
|
|||||||
};
|
};
|
||||||
let cmsg_len = cmsgdata_align(hdr) + std::mem::size_of::<u32>(); // WSA_CMSG_LEN(4)
|
let cmsg_len = cmsgdata_align(hdr) + std::mem::size_of::<u32>(); // WSA_CMSG_LEN(4)
|
||||||
let space = cmsgdata_align(hdr + cmsghdr_align(std::mem::size_of::<u32>())); // WSA_CMSG_SPACE(4)
|
let space = cmsgdata_align(hdr + cmsghdr_align(std::mem::size_of::<u32>())); // WSA_CMSG_SPACE(4)
|
||||||
|
// SAFETY: `ctrl` is a local control buffer sized by `WSA_CMSG_SPACE(4)` — computed as `space`
|
||||||
|
// just above — so the header plus its 4-byte payload fit inside it and neither the field stores
|
||||||
|
// nor the unaligned data write can run past the end. `write_unaligned` is used because the
|
||||||
|
// payload sits at a `WSA_CMSG_DATA` offset with no alignment guarantee.
|
||||||
unsafe {
|
unsafe {
|
||||||
let cmsg = ctrl.0.as_mut_ptr() as *mut CMSGHDR;
|
let cmsg = ctrl.0.as_mut_ptr() as *mut CMSGHDR;
|
||||||
(*cmsg).cmsg_len = cmsg_len;
|
(*cmsg).cmsg_len = cmsg_len;
|
||||||
|
|||||||
Reference in New Issue
Block a user