Merge branch 'fix/vulkan-open-leak' into land/sweep-all
This commit is contained in:
@@ -183,7 +183,8 @@ pub(crate) unsafe fn make_plain_image(
|
|||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
let req = device.get_image_memory_requirements(img);
|
let req = device.get_image_memory_requirements(img);
|
||||||
let mem = device.allocate_memory(
|
// Unwind on failure: callers (the encoders' open paths) only ever see the completed triple.
|
||||||
|
let mem = match device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
.allocation_size(req.size)
|
.allocation_size(req.size)
|
||||||
.memory_type_index(find_mem(
|
.memory_type_index(find_mem(
|
||||||
@@ -192,8 +193,24 @@ pub(crate) unsafe fn make_plain_image(
|
|||||||
vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
)?;
|
) {
|
||||||
device.bind_image_memory(img, mem, 0)?;
|
Ok(m) => m,
|
||||||
let view = make_view(device, img, fmt, 0)?;
|
Err(e) => {
|
||||||
Ok((img, mem, view))
|
device.destroy_image(img, None);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = device.bind_image_memory(img, mem, 0) {
|
||||||
|
device.destroy_image(img, None);
|
||||||
|
device.free_memory(mem, None);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
match make_view(device, img, fmt, 0) {
|
||||||
|
Ok(view) => Ok((img, mem, view)),
|
||||||
|
Err(e) => {
|
||||||
|
device.destroy_image(img, None);
|
||||||
|
device.free_memory(mem, None);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ fn build_h265_rps_s0(
|
|||||||
/// `submit()` records into a free slot and returns without blocking; `poll()` reads back the
|
/// `submit()` records into a free slot and returns without blocking; `poll()` reads back the
|
||||||
/// oldest slot once its `fence` signals. Everything here is written by one frame and read by the
|
/// oldest slot once its `fence` signals. Everything here is written by one frame and read by the
|
||||||
/// next-but-K, so it cannot be shared while a submission is outstanding.
|
/// next-but-K, so it cannot be shared while a submission is outstanding.
|
||||||
|
///
|
||||||
|
/// [`Frame::default`] is the all-null placeholder `open_inner` pre-pushes into its unwind guard so
|
||||||
|
/// `make_frame` can build in place; destroying one is a no-op (`vkDestroy*` ignores null handles).
|
||||||
|
#[derive(Default)]
|
||||||
struct Frame {
|
struct Frame {
|
||||||
compute_cmd: vk::CommandBuffer, // CSC (compute+transfer)
|
compute_cmd: vk::CommandBuffer, // CSC (compute+transfer)
|
||||||
cmd: vk::CommandBuffer, // encode queue
|
cmd: vk::CommandBuffer, // encode queue
|
||||||
@@ -290,6 +294,11 @@ impl VulkanVideoEncoder {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.context("create instance")?;
|
.context("create instance")?;
|
||||||
|
// From here on, every created object is mirrored into `guard` the moment it exists, so any
|
||||||
|
// early `?`/`bail!` unwinds exactly what was built (see [`VkTeardown`]). The locals keep
|
||||||
|
// aliasing the handles for the rest of the build; only the `Ok(Self)` hand-off at the
|
||||||
|
// bottom disarms the guard.
|
||||||
|
let mut guard = VkTeardown::new(instance.clone());
|
||||||
|
|
||||||
let vq_inst = ash::khr::video_queue::Instance::new(&entry, &instance);
|
let vq_inst = ash::khr::video_queue::Instance::new(&entry, &instance);
|
||||||
|
|
||||||
@@ -426,6 +435,8 @@ impl VulkanVideoEncoder {
|
|||||||
let ext_fd = ash::khr::external_memory_fd::Device::new(&instance, &device);
|
let ext_fd = ash::khr::external_memory_fd::Device::new(&instance, &device);
|
||||||
let vq_dev = ash::khr::video_queue::Device::new(&instance, &device);
|
let vq_dev = ash::khr::video_queue::Device::new(&instance, &device);
|
||||||
let venc_dev = ash::khr::video_encode_queue::Device::new(&instance, &device);
|
let venc_dev = ash::khr::video_encode_queue::Device::new(&instance, &device);
|
||||||
|
guard.device = Some(device.clone());
|
||||||
|
guard.vq_dev = Some(vq_dev.clone());
|
||||||
|
|
||||||
// ---- video session ---- (AV1 pins the max level from caps via a chained create-info)
|
// ---- video session ---- (AV1 pins the max level from caps via a chained create-info)
|
||||||
let av1_sci = av1b::VideoEncodeAV1SessionCreateInfoKHR {
|
let av1_sci = av1b::VideoEncodeAV1SessionCreateInfoKHR {
|
||||||
@@ -459,13 +470,13 @@ impl VulkanVideoEncoder {
|
|||||||
if r != vk::Result::SUCCESS {
|
if r != vk::Result::SUCCESS {
|
||||||
bail!("create_video_session: {r:?}");
|
bail!("create_video_session: {r:?}");
|
||||||
}
|
}
|
||||||
|
guard.session = session;
|
||||||
// bind session memory
|
// bind session memory
|
||||||
let get_mem = vq_dev.fp().get_video_session_memory_requirements_khr;
|
let get_mem = vq_dev.fp().get_video_session_memory_requirements_khr;
|
||||||
let mut n = 0u32;
|
let mut n = 0u32;
|
||||||
let _ = get_mem(device.handle(), session, &mut n, std::ptr::null_mut());
|
let _ = get_mem(device.handle(), session, &mut n, std::ptr::null_mut());
|
||||||
let mut reqs = vec![vk::VideoSessionMemoryRequirementsKHR::default(); n as usize];
|
let mut reqs = vec![vk::VideoSessionMemoryRequirementsKHR::default(); n as usize];
|
||||||
let _ = get_mem(device.handle(), session, &mut n, reqs.as_mut_ptr());
|
let _ = get_mem(device.handle(), session, &mut n, reqs.as_mut_ptr());
|
||||||
let mut session_mem = Vec::new();
|
|
||||||
let mut binds = Vec::new();
|
let mut binds = Vec::new();
|
||||||
for rq in &reqs {
|
for rq in &reqs {
|
||||||
let mr = rq.memory_requirements;
|
let mr = rq.memory_requirements;
|
||||||
@@ -480,7 +491,7 @@ impl VulkanVideoEncoder {
|
|||||||
.memory_type_index(ti),
|
.memory_type_index(ti),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
session_mem.push(m);
|
guard.session_mem.push(m);
|
||||||
binds.push(
|
binds.push(
|
||||||
vk::BindVideoSessionMemoryInfoKHR::default()
|
vk::BindVideoSessionMemoryInfoKHR::default()
|
||||||
.memory_bind_index(rq.memory_bind_index)
|
.memory_bind_index(rq.memory_bind_index)
|
||||||
@@ -518,6 +529,7 @@ impl VulkanVideoEncoder {
|
|||||||
build_parameters_h265(&device, &vq_dev, &venc_dev, session, w, h, rw, rh)?;
|
build_parameters_h265(&device, &vq_dev, &venc_dev, session, w, h, rw, rh)?;
|
||||||
(p, hdr, Vec::new())
|
(p, hdr, Vec::new())
|
||||||
};
|
};
|
||||||
|
guard.params = params;
|
||||||
|
|
||||||
// ---- DPB image (NV12 OPTIMAL, ring of slots) — encode queue only ----
|
// ---- DPB image (NV12 OPTIMAL, ring of slots) — encode queue only ----
|
||||||
let mut profile_list =
|
let mut profile_list =
|
||||||
@@ -533,9 +545,13 @@ impl VulkanVideoEncoder {
|
|||||||
&mut profile_list,
|
&mut profile_list,
|
||||||
&[],
|
&[],
|
||||||
)?;
|
)?;
|
||||||
let dpb_views: Vec<vk::ImageView> = (0..DPB_SLOTS)
|
guard.dpb_image = dpb_image;
|
||||||
.map(|slot| make_view(&device, dpb_image, NV12, slot))
|
guard.dpb_mem = dpb_mem;
|
||||||
.collect::<Result<_>>()?;
|
for slot in 0..DPB_SLOTS {
|
||||||
|
guard
|
||||||
|
.dpb_views
|
||||||
|
.push(make_view(&device, dpb_image, NV12, slot)?);
|
||||||
|
}
|
||||||
|
|
||||||
// NV12 encode-src, CSC scratch (Y/UV), bitstream, query and command buffers are all per
|
// NV12 encode-src, CSC scratch (Y/UV), bitstream, query and command buffers are all per
|
||||||
// in-flight frame (built in `make_frame` below); only the queue-family list is shared here.
|
// in-flight frame (built in `make_frame` below); only the queue-family list is shared here.
|
||||||
@@ -554,9 +570,11 @@ impl VulkanVideoEncoder {
|
|||||||
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE),
|
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
guard.sampler = sampler;
|
||||||
let spv = ash::util::read_spv(&mut std::io::Cursor::new(CSC_SPV))?;
|
let spv = ash::util::read_spv(&mut std::io::Cursor::new(CSC_SPV))?;
|
||||||
let shader =
|
let shader =
|
||||||
device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&spv), None)?;
|
device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&spv), None)?;
|
||||||
|
guard.shader = shader;
|
||||||
let sb = |b: u32, t: vk::DescriptorType| {
|
let sb = |b: u32, t: vk::DescriptorType| {
|
||||||
vk::DescriptorSetLayoutBinding::default()
|
vk::DescriptorSetLayoutBinding::default()
|
||||||
.binding(b)
|
.binding(b)
|
||||||
@@ -574,6 +592,7 @@ impl VulkanVideoEncoder {
|
|||||||
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
|
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
guard.csc_dsl = csc_dsl;
|
||||||
let dsls = [csc_dsl];
|
let dsls = [csc_dsl];
|
||||||
// Push constant: cursor {ivec2 origin, ivec2 size} = 16 bytes (size.x<=0 disables the blend).
|
// Push constant: cursor {ivec2 origin, ivec2 size} = 16 bytes (size.x<=0 disables the blend).
|
||||||
let pc_ranges = [vk::PushConstantRange::default()
|
let pc_ranges = [vk::PushConstantRange::default()
|
||||||
@@ -586,6 +605,7 @@ impl VulkanVideoEncoder {
|
|||||||
.push_constant_ranges(&pc_ranges),
|
.push_constant_ranges(&pc_ranges),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
guard.csc_layout = csc_layout;
|
||||||
let stage = vk::PipelineShaderStageCreateInfo::default()
|
let stage = vk::PipelineShaderStageCreateInfo::default()
|
||||||
.stage(vk::ShaderStageFlags::COMPUTE)
|
.stage(vk::ShaderStageFlags::COMPUTE)
|
||||||
.module(shader)
|
.module(shader)
|
||||||
@@ -599,7 +619,10 @@ impl VulkanVideoEncoder {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.map_err(|(_, e)| e)?[0];
|
.map_err(|(_, e)| e)?[0];
|
||||||
|
guard.csc_pipe = csc_pipe;
|
||||||
device.destroy_shader_module(shader, None);
|
device.destroy_shader_module(shader, None);
|
||||||
|
// The shader is gone — null the guard's copy so a later failure doesn't unwind it again.
|
||||||
|
guard.shader = vk::ShaderModule::null();
|
||||||
// One CSC descriptor set + its own Y/UV/NV12/bitstream per in-flight frame.
|
// One CSC descriptor set + its own Y/UV/NV12/bitstream per in-flight frame.
|
||||||
let nframes = ring_depth();
|
let nframes = ring_depth();
|
||||||
let pool_sizes = [
|
let pool_sizes = [
|
||||||
@@ -617,6 +640,7 @@ impl VulkanVideoEncoder {
|
|||||||
.pool_sizes(&pool_sizes),
|
.pool_sizes(&pool_sizes),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
guard.csc_pool = csc_pool;
|
||||||
|
|
||||||
// ---- bitstream size (shared) + shared command pools ----
|
// ---- bitstream size (shared) + shared command pools ----
|
||||||
let bs_size = align_up(
|
let bs_size = align_up(
|
||||||
@@ -629,17 +653,21 @@ impl VulkanVideoEncoder {
|
|||||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER),
|
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
guard.cmd_pool = cmd_pool;
|
||||||
let compute_pool = device.create_command_pool(
|
let compute_pool = device.create_command_pool(
|
||||||
&vk::CommandPoolCreateInfo::default()
|
&vk::CommandPoolCreateInfo::default()
|
||||||
.queue_family_index(compute_family)
|
.queue_family_index(compute_family)
|
||||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER),
|
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
guard.compute_pool = compute_pool;
|
||||||
|
|
||||||
// ---- build the in-flight frame ring ----
|
// ---- build the in-flight frame ring ----
|
||||||
let mut frames = Vec::with_capacity(nframes);
|
|
||||||
for _ in 0..nframes {
|
for _ in 0..nframes {
|
||||||
frames.push(make_frame(
|
// Pre-push a null Frame and build it in place, so a mid-`make_frame` failure leaves
|
||||||
|
// the partial handles in the guard rather than losing them with the Err.
|
||||||
|
guard.frames.push(Frame::default());
|
||||||
|
make_frame(
|
||||||
&device,
|
&device,
|
||||||
&mem_props,
|
&mem_props,
|
||||||
w,
|
w,
|
||||||
@@ -653,9 +681,17 @@ impl VulkanVideoEncoder {
|
|||||||
compute_pool,
|
compute_pool,
|
||||||
bs_size,
|
bs_size,
|
||||||
sampler,
|
sampler,
|
||||||
)?);
|
guard.frames.last_mut().expect("frame just pushed"),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fully constructed: move the built collections out and disarm the guard — from here every
|
||||||
|
// handle is owned by `Self`, whose own `Drop` is the (only) teardown path.
|
||||||
|
let session_mem = std::mem::take(&mut guard.session_mem);
|
||||||
|
let dpb_views = std::mem::take(&mut guard.dpb_views);
|
||||||
|
let frames = std::mem::take(&mut guard.frames);
|
||||||
|
std::mem::forget(guard);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_entry: entry,
|
_entry: entry,
|
||||||
instance,
|
instance,
|
||||||
@@ -2091,82 +2127,183 @@ impl Encoder for VulkanVideoEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for VulkanVideoEncoder {
|
/// Every destructible Vulkan object the encoder owns, with the one `Drop` that destroys them in
|
||||||
|
/// dependency order. Both teardown paths run through it so they cannot drift:
|
||||||
|
///
|
||||||
|
/// - `open_inner` mirrors each object into one as it is created, so any early `?`/`bail!` (or
|
||||||
|
/// panic) unwinds exactly what was built — previously every open failure leaked all prior
|
||||||
|
/// objects (a `VkDevice` + GPU memory per retried open). The `Ok(Self)` hand-off disarms the
|
||||||
|
/// guard with `mem::forget` after moving the collections out.
|
||||||
|
/// - [`VulkanVideoEncoder`]'s `Drop` rebuilds one from its fields and drops it.
|
||||||
|
///
|
||||||
|
/// Handles a failed build never reached stay null, and `vkDestroy*`/`vkFree*` are defined no-ops
|
||||||
|
/// on `VK_NULL_HANDLE`, so the full sequence is safe to run against any prefix of the build.
|
||||||
|
struct VkTeardown {
|
||||||
|
instance: Option<ash::Instance>,
|
||||||
|
// `device` and `vq_dev` are set together (the wrapper constructors after `create_device` are
|
||||||
|
// infallible), so device-level objects can only exist once both are `Some`.
|
||||||
|
device: Option<ash::Device>,
|
||||||
|
vq_dev: Option<ash::khr::video_queue::Device>,
|
||||||
|
import_cache: Vec<(u64, u64, vk::Image, vk::DeviceMemory, vk::ImageView)>,
|
||||||
|
frames: Vec<Frame>,
|
||||||
|
compute_pool: vk::CommandPool,
|
||||||
|
cmd_pool: vk::CommandPool,
|
||||||
|
// Transient: alive only between its creation and the post-pipeline destroy in `open_inner`
|
||||||
|
// (which nulls this); always null when rebuilt from the encoder's `Drop`.
|
||||||
|
shader: vk::ShaderModule,
|
||||||
|
csc_pipe: vk::Pipeline,
|
||||||
|
csc_layout: vk::PipelineLayout,
|
||||||
|
csc_pool: vk::DescriptorPool,
|
||||||
|
csc_dsl: vk::DescriptorSetLayout,
|
||||||
|
sampler: vk::Sampler,
|
||||||
|
dpb_views: Vec<vk::ImageView>,
|
||||||
|
dpb_image: vk::Image,
|
||||||
|
dpb_mem: vk::DeviceMemory,
|
||||||
|
params: vk::VideoSessionParametersKHR,
|
||||||
|
session: vk::VideoSessionKHR,
|
||||||
|
session_mem: Vec<vk::DeviceMemory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkTeardown {
|
||||||
|
/// A fresh guard owning only the instance — every other handle starts null/empty. Written out
|
||||||
|
/// field by field because struct-update syntax is not allowed on a `Drop` type (E0509).
|
||||||
|
fn new(instance: ash::Instance) -> Self {
|
||||||
|
Self {
|
||||||
|
instance: Some(instance),
|
||||||
|
device: None,
|
||||||
|
vq_dev: None,
|
||||||
|
import_cache: Vec::new(),
|
||||||
|
frames: Vec::new(),
|
||||||
|
compute_pool: vk::CommandPool::null(),
|
||||||
|
cmd_pool: vk::CommandPool::null(),
|
||||||
|
shader: vk::ShaderModule::null(),
|
||||||
|
csc_pipe: vk::Pipeline::null(),
|
||||||
|
csc_layout: vk::PipelineLayout::null(),
|
||||||
|
csc_pool: vk::DescriptorPool::null(),
|
||||||
|
csc_dsl: vk::DescriptorSetLayout::null(),
|
||||||
|
sampler: vk::Sampler::null(),
|
||||||
|
dpb_views: Vec::new(),
|
||||||
|
dpb_image: vk::Image::null(),
|
||||||
|
dpb_mem: vk::DeviceMemory::null(),
|
||||||
|
params: vk::VideoSessionParametersKHR::null(),
|
||||||
|
session: vk::VideoSessionKHR::null(),
|
||||||
|
session_mem: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkTeardown {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// SAFETY: `device_wait_idle` first guarantees no GPU work still references any object, so
|
// SAFETY: `device_wait_idle` first guarantees no GPU work still references any object, so
|
||||||
// every handle destroyed below is idle and owned solely by `self`; each is freed exactly once
|
// every handle destroyed below is idle and owned solely by `self`; each is freed exactly
|
||||||
// (the drains prevent a double free) and in dependency order (views before images before
|
// once (the takes prevent a double free) and in dependency order (views before images
|
||||||
// memory, per-frame objects before their shared pools, session params before session).
|
// before memory, per-frame objects before their shared pools, session params before
|
||||||
|
// session, session memory after the session, the device before the instance). Null handles
|
||||||
|
// (a build prefix from a failed `open_inner`) are no-ops per the Vulkan spec.
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = self.device.device_wait_idle();
|
if let Some(device) = self.device.take() {
|
||||||
for (_, _, img, mem, view) in std::mem::take(&mut self.import_cache) {
|
let _ = device.device_wait_idle();
|
||||||
self.device.destroy_image_view(view, None);
|
for (_, _, img, mem, view) in std::mem::take(&mut self.import_cache) {
|
||||||
self.device.destroy_image(img, None);
|
device.destroy_image_view(view, None);
|
||||||
self.device.free_memory(mem, None);
|
device.destroy_image(img, None);
|
||||||
}
|
device.free_memory(mem, None);
|
||||||
// Per-frame ring resources (command buffers, descriptor sets freed with their pools).
|
|
||||||
for f in std::mem::take(&mut self.frames) {
|
|
||||||
self.device.destroy_semaphore(f.csc_sem, None);
|
|
||||||
self.device.destroy_fence(f.fence, None);
|
|
||||||
self.device.destroy_query_pool(f.query_pool, None);
|
|
||||||
self.device.destroy_buffer(f.bs_buf, None);
|
|
||||||
self.device.free_memory(f.bs_mem, None);
|
|
||||||
for (img, mem, view) in [
|
|
||||||
(f.y_img, f.y_mem, f.y_view),
|
|
||||||
(f.uv_img, f.uv_mem, f.uv_view),
|
|
||||||
(f.nv12_src, f.nv12_mem, f.nv12_view),
|
|
||||||
] {
|
|
||||||
self.device.destroy_image_view(view, None);
|
|
||||||
self.device.destroy_image(img, None);
|
|
||||||
self.device.free_memory(mem, None);
|
|
||||||
}
|
}
|
||||||
if let Some((i, m, v, _)) = f.cpu_img {
|
// Per-frame ring resources (command buffers, descriptor sets freed with their pools).
|
||||||
self.device.destroy_image_view(v, None);
|
for f in std::mem::take(&mut self.frames) {
|
||||||
self.device.destroy_image(i, None);
|
device.destroy_semaphore(f.csc_sem, None);
|
||||||
self.device.free_memory(m, None);
|
device.destroy_fence(f.fence, None);
|
||||||
|
device.destroy_query_pool(f.query_pool, None);
|
||||||
|
device.destroy_buffer(f.bs_buf, None);
|
||||||
|
device.free_memory(f.bs_mem, None);
|
||||||
|
for (img, mem, view) in [
|
||||||
|
(f.y_img, f.y_mem, f.y_view),
|
||||||
|
(f.uv_img, f.uv_mem, f.uv_view),
|
||||||
|
(f.nv12_src, f.nv12_mem, f.nv12_view),
|
||||||
|
] {
|
||||||
|
device.destroy_image_view(view, None);
|
||||||
|
device.destroy_image(img, None);
|
||||||
|
device.free_memory(mem, None);
|
||||||
|
}
|
||||||
|
if let Some((i, m, v, _)) = f.cpu_img {
|
||||||
|
device.destroy_image_view(v, None);
|
||||||
|
device.destroy_image(i, None);
|
||||||
|
device.free_memory(m, None);
|
||||||
|
}
|
||||||
|
if let Some((b, m, _)) = f.cpu_stage {
|
||||||
|
device.destroy_buffer(b, None);
|
||||||
|
device.free_memory(m, None);
|
||||||
|
}
|
||||||
|
device.destroy_image_view(f.cursor_view, None);
|
||||||
|
device.destroy_image(f.cursor_img, None);
|
||||||
|
device.free_memory(f.cursor_mem, None);
|
||||||
|
device.destroy_buffer(f.cursor_stage, None);
|
||||||
|
device.free_memory(f.cursor_stage_mem, None);
|
||||||
}
|
}
|
||||||
if let Some((b, m, _)) = f.cpu_stage {
|
device.destroy_command_pool(self.compute_pool, None);
|
||||||
self.device.destroy_buffer(b, None);
|
device.destroy_command_pool(self.cmd_pool, None);
|
||||||
self.device.free_memory(m, None);
|
device.destroy_shader_module(self.shader, None);
|
||||||
|
device.destroy_pipeline(self.csc_pipe, None);
|
||||||
|
device.destroy_pipeline_layout(self.csc_layout, None);
|
||||||
|
device.destroy_descriptor_pool(self.csc_pool, None);
|
||||||
|
device.destroy_descriptor_set_layout(self.csc_dsl, None);
|
||||||
|
device.destroy_sampler(self.sampler, None);
|
||||||
|
for &v in &self.dpb_views {
|
||||||
|
device.destroy_image_view(v, None);
|
||||||
}
|
}
|
||||||
self.device.destroy_image_view(f.cursor_view, None);
|
device.destroy_image(self.dpb_image, None);
|
||||||
self.device.destroy_image(f.cursor_img, None);
|
device.free_memory(self.dpb_mem, None);
|
||||||
self.device.free_memory(f.cursor_mem, None);
|
if let Some(vq_dev) = self.vq_dev.take() {
|
||||||
self.device.destroy_buffer(f.cursor_stage, None);
|
(vq_dev.fp().destroy_video_session_parameters_khr)(
|
||||||
self.device.free_memory(f.cursor_stage_mem, None);
|
device.handle(),
|
||||||
|
self.params,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
|
(vq_dev.fp().destroy_video_session_khr)(
|
||||||
|
device.handle(),
|
||||||
|
self.session,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for &m in &self.session_mem {
|
||||||
|
device.free_memory(m, None);
|
||||||
|
}
|
||||||
|
device.destroy_device(None);
|
||||||
}
|
}
|
||||||
self.device.destroy_command_pool(self.compute_pool, None);
|
if let Some(instance) = self.instance.take() {
|
||||||
self.device.destroy_command_pool(self.cmd_pool, None);
|
instance.destroy_instance(None);
|
||||||
self.device.destroy_pipeline(self.csc_pipe, None);
|
|
||||||
self.device.destroy_pipeline_layout(self.csc_layout, None);
|
|
||||||
self.device.destroy_descriptor_pool(self.csc_pool, None);
|
|
||||||
self.device
|
|
||||||
.destroy_descriptor_set_layout(self.csc_dsl, None);
|
|
||||||
self.device.destroy_sampler(self.sampler, None);
|
|
||||||
for &v in &self.dpb_views {
|
|
||||||
self.device.destroy_image_view(v, None);
|
|
||||||
}
|
}
|
||||||
self.device.destroy_image(self.dpb_image, None);
|
|
||||||
self.device.free_memory(self.dpb_mem, None);
|
|
||||||
(self.vq_dev.fp().destroy_video_session_parameters_khr)(
|
|
||||||
self.device.handle(),
|
|
||||||
self.params,
|
|
||||||
std::ptr::null(),
|
|
||||||
);
|
|
||||||
(self.vq_dev.fp().destroy_video_session_khr)(
|
|
||||||
self.device.handle(),
|
|
||||||
self.session,
|
|
||||||
std::ptr::null(),
|
|
||||||
);
|
|
||||||
for &m in &self.session_mem {
|
|
||||||
self.device.free_memory(m, None);
|
|
||||||
}
|
|
||||||
self.device.destroy_device(None);
|
|
||||||
self.instance.destroy_instance(None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanVideoEncoder {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// The whole teardown sequence lives in `VkTeardown` (shared with `open_inner`'s failure
|
||||||
|
// unwind): rebuild one from our fields and let its Drop run it.
|
||||||
|
drop(VkTeardown {
|
||||||
|
instance: Some(self.instance.clone()),
|
||||||
|
device: Some(self.device.clone()),
|
||||||
|
vq_dev: Some(self.vq_dev.clone()),
|
||||||
|
import_cache: std::mem::take(&mut self.import_cache),
|
||||||
|
frames: std::mem::take(&mut self.frames),
|
||||||
|
compute_pool: self.compute_pool,
|
||||||
|
cmd_pool: self.cmd_pool,
|
||||||
|
shader: vk::ShaderModule::null(),
|
||||||
|
csc_pipe: self.csc_pipe,
|
||||||
|
csc_layout: self.csc_layout,
|
||||||
|
csc_pool: self.csc_pool,
|
||||||
|
csc_dsl: self.csc_dsl,
|
||||||
|
sampler: self.sampler,
|
||||||
|
dpb_views: std::mem::take(&mut self.dpb_views),
|
||||||
|
dpb_image: self.dpb_image,
|
||||||
|
dpb_mem: self.dpb_mem,
|
||||||
|
params: self.params,
|
||||||
|
session: self.session,
|
||||||
|
session_mem: std::mem::take(&mut self.session_mem),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- free helpers ----------
|
// ---------- free helpers ----------
|
||||||
|
|
||||||
fn align_up(v: u64, a: u64) -> u64 {
|
fn align_up(v: u64, a: u64) -> u64 {
|
||||||
@@ -2208,7 +2345,8 @@ unsafe fn make_video_image(
|
|||||||
}
|
}
|
||||||
let img = device.create_image(&ci, None)?;
|
let img = device.create_image(&ci, None)?;
|
||||||
let req = device.get_image_memory_requirements(img);
|
let req = device.get_image_memory_requirements(img);
|
||||||
let mem = device.allocate_memory(
|
// Unwind on failure: callers (the open path) only ever see the completed pair.
|
||||||
|
let mem = match device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
.allocation_size(req.size)
|
.allocation_size(req.size)
|
||||||
.memory_type_index(find_mem(
|
.memory_type_index(find_mem(
|
||||||
@@ -2217,14 +2355,28 @@ unsafe fn make_video_image(
|
|||||||
vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
)?;
|
) {
|
||||||
device.bind_image_memory(img, mem, 0)?;
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
device.destroy_image(img, None);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = device.bind_image_memory(img, mem, 0) {
|
||||||
|
device.destroy_image(img, None);
|
||||||
|
device.free_memory(mem, None);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
Ok((img, mem))
|
Ok((img, mem))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build one in-flight frame's private resources: NV12 encode-src, Y/UV CSC scratch, its CSC
|
/// Build one in-flight frame's private resources: NV12 encode-src, Y/UV CSC scratch, its CSC
|
||||||
/// descriptor set (Y/UV bound now, RGB per use), the bitstream buffer + feedback query, and the
|
/// descriptor set (Y/UV bound now, RGB per use), the bitstream buffer + feedback query, and the
|
||||||
/// per-frame command buffers + sync. `profile_list`/`profile` are borrowed only during creation.
|
/// per-frame command buffers + sync. `profile_list`/`profile` are borrowed only during creation.
|
||||||
|
///
|
||||||
|
/// Builds in place into `f` — a [`Frame::default`] the caller has already parked in its
|
||||||
|
/// [`VkTeardown`] guard — so every handle is owned by the unwind the moment it exists and a
|
||||||
|
/// mid-build failure leaks nothing.
|
||||||
unsafe fn make_frame(
|
unsafe fn make_frame(
|
||||||
device: &ash::Device,
|
device: &ash::Device,
|
||||||
mem_props: &vk::PhysicalDeviceMemoryProperties,
|
mem_props: &vk::PhysicalDeviceMemoryProperties,
|
||||||
@@ -2239,9 +2391,12 @@ unsafe fn make_frame(
|
|||||||
compute_pool: vk::CommandPool,
|
compute_pool: vk::CommandPool,
|
||||||
bs_size: u64,
|
bs_size: u64,
|
||||||
sampler: vk::Sampler,
|
sampler: vk::Sampler,
|
||||||
) -> Result<Frame> {
|
f: &mut Frame,
|
||||||
|
) -> Result<()> {
|
||||||
|
// "no cursor uploaded yet" sentinel — a real serial may be 0 (see `prep_cursor`).
|
||||||
|
f.cursor_serial = u64::MAX;
|
||||||
// NV12 encode-src (filled by the CSC copy) — concurrent compute+encode.
|
// NV12 encode-src (filled by the CSC copy) — concurrent compute+encode.
|
||||||
let (nv12_src, nv12_mem) = make_video_image(
|
(f.nv12_src, f.nv12_mem) = make_video_image(
|
||||||
device,
|
device,
|
||||||
mem_props,
|
mem_props,
|
||||||
NV12,
|
NV12,
|
||||||
@@ -2252,9 +2407,9 @@ unsafe fn make_frame(
|
|||||||
profile_list,
|
profile_list,
|
||||||
fams,
|
fams,
|
||||||
)?;
|
)?;
|
||||||
let nv12_view = make_view(device, nv12_src, NV12, 0)?;
|
f.nv12_view = make_view(device, f.nv12_src, NV12, 0)?;
|
||||||
// CSC scratch (Y R8 full-res, UV RG8 half-res).
|
// CSC scratch (Y R8 full-res, UV RG8 half-res).
|
||||||
let (y_img, y_mem, y_view) = make_plain_image(
|
(f.y_img, f.y_mem, f.y_view) = make_plain_image(
|
||||||
device,
|
device,
|
||||||
mem_props,
|
mem_props,
|
||||||
vk::Format::R8_UNORM,
|
vk::Format::R8_UNORM,
|
||||||
@@ -2262,7 +2417,7 @@ unsafe fn make_frame(
|
|||||||
h,
|
h,
|
||||||
vk::ImageUsageFlags::STORAGE | vk::ImageUsageFlags::TRANSFER_SRC,
|
vk::ImageUsageFlags::STORAGE | vk::ImageUsageFlags::TRANSFER_SRC,
|
||||||
)?;
|
)?;
|
||||||
let (uv_img, uv_mem, uv_view) = make_plain_image(
|
(f.uv_img, f.uv_mem, f.uv_view) = make_plain_image(
|
||||||
device,
|
device,
|
||||||
mem_props,
|
mem_props,
|
||||||
vk::Format::R8G8_UNORM,
|
vk::Format::R8G8_UNORM,
|
||||||
@@ -2273,7 +2428,7 @@ unsafe fn make_frame(
|
|||||||
// Cursor overlay: fixed CURSOR_MAX² RGBA8 sampled image + host staging (cursor-as-metadata). The
|
// Cursor overlay: fixed CURSOR_MAX² RGBA8 sampled image + host staging (cursor-as-metadata). The
|
||||||
// view/descriptor is static (bound at binding 3 below); only the image *content* changes, and
|
// view/descriptor is static (bound at binding 3 below); only the image *content* changes, and
|
||||||
// only when the pointer bitmap does — see `prep_cursor`.
|
// only when the pointer bitmap does — see `prep_cursor`.
|
||||||
let (cursor_img, cursor_mem, cursor_view) = make_plain_image(
|
(f.cursor_img, f.cursor_mem, f.cursor_view) = make_plain_image(
|
||||||
device,
|
device,
|
||||||
mem_props,
|
mem_props,
|
||||||
vk::Format::R8G8B8A8_UNORM,
|
vk::Format::R8G8B8A8_UNORM,
|
||||||
@@ -2281,14 +2436,14 @@ unsafe fn make_frame(
|
|||||||
CURSOR_MAX,
|
CURSOR_MAX,
|
||||||
vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
|
vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
|
||||||
)?;
|
)?;
|
||||||
let cursor_stage = device.create_buffer(
|
f.cursor_stage = device.create_buffer(
|
||||||
&vk::BufferCreateInfo::default()
|
&vk::BufferCreateInfo::default()
|
||||||
.size((CURSOR_MAX * CURSOR_MAX * 4) as u64)
|
.size((CURSOR_MAX * CURSOR_MAX * 4) as u64)
|
||||||
.usage(vk::BufferUsageFlags::TRANSFER_SRC),
|
.usage(vk::BufferUsageFlags::TRANSFER_SRC),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
let cs_req = device.get_buffer_memory_requirements(cursor_stage);
|
let cs_req = device.get_buffer_memory_requirements(f.cursor_stage);
|
||||||
let cursor_stage_mem = device.allocate_memory(
|
f.cursor_stage_mem = device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
.allocation_size(cs_req.size)
|
.allocation_size(cs_req.size)
|
||||||
.memory_type_index(find_mem(
|
.memory_type_index(find_mem(
|
||||||
@@ -2298,39 +2453,39 @@ unsafe fn make_frame(
|
|||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
device.bind_buffer_memory(cursor_stage, cursor_stage_mem, 0)?;
|
device.bind_buffer_memory(f.cursor_stage, f.cursor_stage_mem, 0)?;
|
||||||
// Descriptor set — Y/UV storage bindings fixed; binding 0 (RGB) rewritten per use; binding 3
|
// Descriptor set — Y/UV storage bindings fixed; binding 0 (RGB) rewritten per use; binding 3
|
||||||
// (cursor) points at the static cursor image (its layout is SHADER_READ_ONLY once prepped).
|
// (cursor) points at the static cursor image (its layout is SHADER_READ_ONLY once prepped).
|
||||||
let dsls = [csc_dsl];
|
let dsls = [csc_dsl];
|
||||||
let csc_set = device.allocate_descriptor_sets(
|
f.csc_set = device.allocate_descriptor_sets(
|
||||||
&vk::DescriptorSetAllocateInfo::default()
|
&vk::DescriptorSetAllocateInfo::default()
|
||||||
.descriptor_pool(csc_pool)
|
.descriptor_pool(csc_pool)
|
||||||
.set_layouts(&dsls),
|
.set_layouts(&dsls),
|
||||||
)?[0];
|
)?[0];
|
||||||
let y_info = [vk::DescriptorImageInfo::default()
|
let y_info = [vk::DescriptorImageInfo::default()
|
||||||
.image_view(y_view)
|
.image_view(f.y_view)
|
||||||
.image_layout(vk::ImageLayout::GENERAL)];
|
.image_layout(vk::ImageLayout::GENERAL)];
|
||||||
let uv_info = [vk::DescriptorImageInfo::default()
|
let uv_info = [vk::DescriptorImageInfo::default()
|
||||||
.image_view(uv_view)
|
.image_view(f.uv_view)
|
||||||
.image_layout(vk::ImageLayout::GENERAL)];
|
.image_layout(vk::ImageLayout::GENERAL)];
|
||||||
let cur_info = [vk::DescriptorImageInfo::default()
|
let cur_info = [vk::DescriptorImageInfo::default()
|
||||||
.sampler(sampler)
|
.sampler(sampler)
|
||||||
.image_view(cursor_view)
|
.image_view(f.cursor_view)
|
||||||
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)];
|
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)];
|
||||||
device.update_descriptor_sets(
|
device.update_descriptor_sets(
|
||||||
&[
|
&[
|
||||||
vk::WriteDescriptorSet::default()
|
vk::WriteDescriptorSet::default()
|
||||||
.dst_set(csc_set)
|
.dst_set(f.csc_set)
|
||||||
.dst_binding(1)
|
.dst_binding(1)
|
||||||
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
|
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
|
||||||
.image_info(&y_info),
|
.image_info(&y_info),
|
||||||
vk::WriteDescriptorSet::default()
|
vk::WriteDescriptorSet::default()
|
||||||
.dst_set(csc_set)
|
.dst_set(f.csc_set)
|
||||||
.dst_binding(2)
|
.dst_binding(2)
|
||||||
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
|
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
|
||||||
.image_info(&uv_info),
|
.image_info(&uv_info),
|
||||||
vk::WriteDescriptorSet::default()
|
vk::WriteDescriptorSet::default()
|
||||||
.dst_set(csc_set)
|
.dst_set(f.csc_set)
|
||||||
.dst_binding(3)
|
.dst_binding(3)
|
||||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.image_info(&cur_info),
|
.image_info(&cur_info),
|
||||||
@@ -2338,15 +2493,15 @@ unsafe fn make_frame(
|
|||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
// Bitstream buffer + feedback query.
|
// Bitstream buffer + feedback query.
|
||||||
let bs_buf = device.create_buffer(
|
f.bs_buf = device.create_buffer(
|
||||||
&vk::BufferCreateInfo::default()
|
&vk::BufferCreateInfo::default()
|
||||||
.size(bs_size)
|
.size(bs_size)
|
||||||
.usage(vk::BufferUsageFlags::VIDEO_ENCODE_DST_KHR)
|
.usage(vk::BufferUsageFlags::VIDEO_ENCODE_DST_KHR)
|
||||||
.push_next(profile_list),
|
.push_next(profile_list),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
let bs_req = device.get_buffer_memory_requirements(bs_buf);
|
let bs_req = device.get_buffer_memory_requirements(f.bs_buf);
|
||||||
let bs_mem = device.allocate_memory(
|
f.bs_mem = device.allocate_memory(
|
||||||
&vk::MemoryAllocateInfo::default()
|
&vk::MemoryAllocateInfo::default()
|
||||||
.allocation_size(bs_req.size)
|
.allocation_size(bs_req.size)
|
||||||
.memory_type_index(find_mem(
|
.memory_type_index(find_mem(
|
||||||
@@ -2356,7 +2511,7 @@ unsafe fn make_frame(
|
|||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
device.bind_buffer_memory(bs_buf, bs_mem, 0)?;
|
device.bind_buffer_memory(f.bs_buf, f.bs_mem, 0)?;
|
||||||
let mut fb_ci = vk::QueryPoolVideoEncodeFeedbackCreateInfoKHR::default().encode_feedback_flags(
|
let mut fb_ci = vk::QueryPoolVideoEncodeFeedbackCreateInfoKHR::default().encode_feedback_flags(
|
||||||
vk::VideoEncodeFeedbackFlagsKHR::BITSTREAM_BUFFER_OFFSET
|
vk::VideoEncodeFeedbackFlagsKHR::BITSTREAM_BUFFER_OFFSET
|
||||||
| vk::VideoEncodeFeedbackFlagsKHR::BITSTREAM_BYTES_WRITTEN,
|
| vk::VideoEncodeFeedbackFlagsKHR::BITSTREAM_BYTES_WRITTEN,
|
||||||
@@ -2366,51 +2521,21 @@ unsafe fn make_frame(
|
|||||||
.query_type(vk::QueryType::VIDEO_ENCODE_FEEDBACK_KHR)
|
.query_type(vk::QueryType::VIDEO_ENCODE_FEEDBACK_KHR)
|
||||||
.query_count(1);
|
.query_count(1);
|
||||||
query_ci.p_next = &fb_ci as *const _ as *const c_void;
|
query_ci.p_next = &fb_ci as *const _ as *const c_void;
|
||||||
let query_pool = device.create_query_pool(&query_ci, None)?;
|
f.query_pool = device.create_query_pool(&query_ci, None)?;
|
||||||
// Command buffers + per-frame sync.
|
// Command buffers + per-frame sync.
|
||||||
let cmd = device.allocate_command_buffers(
|
f.cmd = device.allocate_command_buffers(
|
||||||
&vk::CommandBufferAllocateInfo::default()
|
&vk::CommandBufferAllocateInfo::default()
|
||||||
.command_pool(cmd_pool)
|
.command_pool(cmd_pool)
|
||||||
.command_buffer_count(1),
|
.command_buffer_count(1),
|
||||||
)?[0];
|
)?[0];
|
||||||
let compute_cmd = device.allocate_command_buffers(
|
f.compute_cmd = device.allocate_command_buffers(
|
||||||
&vk::CommandBufferAllocateInfo::default()
|
&vk::CommandBufferAllocateInfo::default()
|
||||||
.command_pool(compute_pool)
|
.command_pool(compute_pool)
|
||||||
.command_buffer_count(1),
|
.command_buffer_count(1),
|
||||||
)?[0];
|
)?[0];
|
||||||
let csc_sem = device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?;
|
f.csc_sem = device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)?;
|
||||||
let fence = device.create_fence(&vk::FenceCreateInfo::default(), None)?;
|
f.fence = device.create_fence(&vk::FenceCreateInfo::default(), None)?;
|
||||||
Ok(Frame {
|
Ok(())
|
||||||
compute_cmd,
|
|
||||||
cmd,
|
|
||||||
csc_sem,
|
|
||||||
fence,
|
|
||||||
query_pool,
|
|
||||||
bs_buf,
|
|
||||||
bs_mem,
|
|
||||||
csc_set,
|
|
||||||
y_img,
|
|
||||||
y_mem,
|
|
||||||
y_view,
|
|
||||||
uv_img,
|
|
||||||
uv_mem,
|
|
||||||
uv_view,
|
|
||||||
nv12_src,
|
|
||||||
nv12_mem,
|
|
||||||
nv12_view,
|
|
||||||
cpu_img: None,
|
|
||||||
cpu_stage: None,
|
|
||||||
cursor_img,
|
|
||||||
cursor_mem,
|
|
||||||
cursor_view,
|
|
||||||
cursor_stage,
|
|
||||||
cursor_stage_mem,
|
|
||||||
cursor_serial: u64::MAX,
|
|
||||||
cursor_ready: false,
|
|
||||||
pts_ns: 0,
|
|
||||||
keyframe: false,
|
|
||||||
recovery_anchor: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Author VPS/SPS/PPS (Main, level 4.0, low-latency, conformance-window crop) and return the
|
/// Author VPS/SPS/PPS (Main, level 4.0, low-latency, conformance-window crop) and return the
|
||||||
@@ -2513,6 +2638,12 @@ unsafe fn build_parameters_h265(
|
|||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
);
|
);
|
||||||
if r != vk::Result::SUCCESS {
|
if r != vk::Result::SUCCESS {
|
||||||
|
// `params` is live but not yet the caller's guard's to unwind — destroy before bailing.
|
||||||
|
(vq_dev.fp().destroy_video_session_parameters_khr)(
|
||||||
|
device.handle(),
|
||||||
|
params,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
bail!("get header size: {r:?}");
|
bail!("get header size: {r:?}");
|
||||||
}
|
}
|
||||||
let mut buf = vec![0u8; size];
|
let mut buf = vec![0u8; size];
|
||||||
@@ -2524,6 +2655,11 @@ unsafe fn build_parameters_h265(
|
|||||||
buf.as_mut_ptr() as *mut c_void,
|
buf.as_mut_ptr() as *mut c_void,
|
||||||
);
|
);
|
||||||
if r != vk::Result::SUCCESS {
|
if r != vk::Result::SUCCESS {
|
||||||
|
(vq_dev.fp().destroy_video_session_parameters_khr)(
|
||||||
|
device.handle(),
|
||||||
|
params,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
bail!("get header bytes: {r:?}");
|
bail!("get header bytes: {r:?}");
|
||||||
}
|
}
|
||||||
buf.truncate(size);
|
buf.truncate(size);
|
||||||
|
|||||||
Reference in New Issue
Block a user