hal/vulkan: generate separate hash identity for Texture/TextureViews (#7972)

This commit is contained in:
Connor Fitzgerald 2025-07-21 07:48:37 -04:00 committed by GitHub
parent c95b7feea8
commit 979487024e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 139 additions and 12 deletions

View File

@ -2162,6 +2162,9 @@ impl super::Adapter {
self.private_caps.maximum_samplers, self.private_caps.maximum_samplers,
)), )),
memory_allocations_counter: Default::default(), memory_allocations_counter: Default::default(),
texture_identity_factory: super::ResourceIdentityFactory::new(),
texture_view_identity_factory: super::ResourceIdentityFactory::new(),
}); });
let relay_semaphores = super::RelaySemaphores::new(&shared)?; let relay_semaphores = super::RelaySemaphores::new(&shared)?;

View File

@ -61,7 +61,8 @@ impl super::CommandEncoder {
Entry::Vacant(e) => { Entry::Vacant(e) => {
let super::FramebufferKey { let super::FramebufferKey {
raw_pass, raw_pass,
ref attachments, ref attachment_views,
attachment_identities: _,
extent, extent,
} = *e.key(); } = *e.key();
@ -70,7 +71,7 @@ impl super::CommandEncoder {
.width(extent.width) .width(extent.width)
.height(extent.height) .height(extent.height)
.layers(extent.depth_or_array_layers) .layers(extent.depth_or_array_layers)
.attachments(attachments); .attachments(attachment_views);
let raw = unsafe { self.device.raw.create_framebuffer(&vk_info, None).unwrap() }; let raw = unsafe { self.device.raw.create_framebuffer(&vk_info, None).unwrap() };
*e.insert(raw) *e.insert(raw)
@ -81,12 +82,13 @@ impl super::CommandEncoder {
fn make_temp_texture_view( fn make_temp_texture_view(
&mut self, &mut self,
key: super::TempTextureViewKey, key: super::TempTextureViewKey,
) -> Result<vk::ImageView, crate::DeviceError> { ) -> Result<super::IdentifiedTextureView, crate::DeviceError> {
Ok(match self.temp_texture_views.entry(key) { Ok(match self.temp_texture_views.entry(key) {
Entry::Occupied(e) => *e.get(), Entry::Occupied(e) => *e.get(),
Entry::Vacant(e) => { Entry::Vacant(e) => {
let super::TempTextureViewKey { let super::TempTextureViewKey {
texture, texture,
texture_identity: _,
format, format,
mip_level, mip_level,
depth_slice, depth_slice,
@ -105,7 +107,10 @@ impl super::CommandEncoder {
}); });
let raw = unsafe { self.device.raw.create_image_view(&vk_info, None) } let raw = unsafe { self.device.raw.create_image_view(&vk_info, None) }
.map_err(super::map_host_device_oom_and_ioca_err)?; .map_err(super::map_host_device_oom_and_ioca_err)?;
*e.insert(raw)
let identity = self.device.texture_view_identity_factory.next();
*e.insert(super::IdentifiedTextureView { raw, identity })
} }
}) })
} }
@ -779,7 +784,8 @@ impl crate::CommandEncoder for super::CommandEncoder {
}; };
let mut fb_key = super::FramebufferKey { let mut fb_key = super::FramebufferKey {
raw_pass: vk::RenderPass::null(), raw_pass: vk::RenderPass::null(),
attachments: ArrayVec::default(), attachment_views: ArrayVec::default(),
attachment_identities: ArrayVec::default(),
extent: desc.extent, extent: desc.extent,
}; };
@ -788,13 +794,14 @@ impl crate::CommandEncoder for super::CommandEncoder {
let color_view = if cat.target.view.dimension == wgt::TextureViewDimension::D3 { let color_view = if cat.target.view.dimension == wgt::TextureViewDimension::D3 {
let key = super::TempTextureViewKey { let key = super::TempTextureViewKey {
texture: cat.target.view.raw_texture, texture: cat.target.view.raw_texture,
texture_identity: cat.target.view.texture_identity,
format: cat.target.view.raw_format, format: cat.target.view.raw_format,
mip_level: cat.target.view.base_mip_level, mip_level: cat.target.view.base_mip_level,
depth_slice: cat.depth_slice.unwrap(), depth_slice: cat.depth_slice.unwrap(),
}; };
self.make_temp_texture_view(key)? self.make_temp_texture_view(key)?
} else { } else {
cat.target.view.raw cat.target.view.identified_raw_view()
}; };
vk_clear_values.push(vk::ClearValue { vk_clear_values.push(vk::ClearValue {
@ -809,10 +816,10 @@ impl crate::CommandEncoder for super::CommandEncoder {
}; };
rp_key.colors.push(Some(color)); rp_key.colors.push(Some(color));
fb_key.attachments.push(color_view); fb_key.push_view(color_view);
if let Some(ref at) = cat.resolve_target { if let Some(ref at) = cat.resolve_target {
vk_clear_values.push(unsafe { mem::zeroed() }); vk_clear_values.push(unsafe { mem::zeroed() });
fb_key.attachments.push(at.view.raw); fb_key.push_view(at.view.identified_raw_view());
} }
// Assert this attachment is valid for the detected multiview, as a sanity check // Assert this attachment is valid for the detected multiview, as a sanity check
@ -838,7 +845,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
base: ds.target.make_attachment_key(ds.depth_ops), base: ds.target.make_attachment_key(ds.depth_ops),
stencil_ops: ds.stencil_ops, stencil_ops: ds.stencil_ops,
}); });
fb_key.attachments.push(ds.target.view.raw); fb_key.push_view(ds.target.view.identified_raw_view());
// Assert this attachment is valid for the detected multiview, as a sanity check // Assert this attachment is valid for the detected multiview, as a sanity check
// The driver crash for this is really bad on AMD, so the check is worth it // The driver crash for this is really bad on AMD, so the check is worth it

View File

@ -603,6 +603,7 @@ impl super::Device {
/// `drop_callback` is [`Some`], `vk_image` must be valid until the callback is called. /// `drop_callback` is [`Some`], `vk_image` must be valid until the callback is called.
/// - If the `ImageCreateFlags` does not contain `MUTABLE_FORMAT`, the `view_formats` of `desc` must be empty. /// - If the `ImageCreateFlags` does not contain `MUTABLE_FORMAT`, the `view_formats` of `desc` must be empty.
pub unsafe fn texture_from_raw( pub unsafe fn texture_from_raw(
&self,
vk_image: vk::Image, vk_image: vk::Image,
desc: &crate::TextureDescriptor, desc: &crate::TextureDescriptor,
drop_callback: Option<crate::DropCallback>, drop_callback: Option<crate::DropCallback>,
@ -624,6 +625,8 @@ impl super::Device {
raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT; raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT;
} }
let identity = self.shared.texture_identity_factory.next();
let drop_guard = crate::DropGuard::from_option(drop_callback); let drop_guard = crate::DropGuard::from_option(drop_callback);
super::Texture { super::Texture {
@ -633,6 +636,7 @@ impl super::Device {
block: None, block: None,
format: desc.format, format: desc.format,
copy_size: desc.copy_extent(), copy_size: desc.copy_extent(),
identity,
} }
} }
@ -796,6 +800,8 @@ impl super::Device {
unsafe { self.shared.set_object_name(image.raw, label) }; unsafe { self.shared.set_object_name(image.raw, label) };
} }
let identity = self.shared.texture_identity_factory.next();
self.counters.textures.add(1); self.counters.textures.add(1);
Ok(super::Texture { Ok(super::Texture {
@ -805,6 +811,7 @@ impl super::Device {
block: None, block: None,
format: desc.format, format: desc.format,
copy_size: image.copy_size, copy_size: image.copy_size,
identity,
}) })
} }
@ -1274,6 +1281,8 @@ impl crate::Device for super::Device {
unsafe { self.shared.set_object_name(image.raw, label) }; unsafe { self.shared.set_object_name(image.raw, label) };
} }
let identity = self.shared.texture_identity_factory.next();
self.counters.textures.add(1); self.counters.textures.add(1);
Ok(super::Texture { Ok(super::Texture {
@ -1283,6 +1292,7 @@ impl crate::Device for super::Device {
block: Some(block), block: Some(block),
format: desc.format, format: desc.format,
copy_size: image.copy_size, copy_size: image.copy_size,
identity,
}) })
} }
unsafe fn destroy_texture(&self, texture: super::Texture) { unsafe fn destroy_texture(&self, texture: super::Texture) {
@ -1335,6 +1345,8 @@ impl crate::Device for super::Device {
unsafe { self.shared.set_object_name(raw, label) }; unsafe { self.shared.set_object_name(raw, label) };
} }
let identity = self.shared.texture_view_identity_factory.next();
self.counters.texture_views.add(1); self.counters.texture_views.add(1);
Ok(super::TextureView { Ok(super::TextureView {
@ -1345,6 +1357,8 @@ impl crate::Device for super::Device {
raw_format, raw_format,
base_mip_level: desc.range.base_mip_level, base_mip_level: desc.range.base_mip_level,
dimension: desc.dimension, dimension: desc.dimension,
texture_identity: texture.identity,
view_identity: identity,
}) })
} }
unsafe fn destroy_texture_view(&self, view: super::TextureView) { unsafe fn destroy_texture_view(&self, view: super::TextureView) {

View File

@ -1139,6 +1139,8 @@ impl crate::Surface for super::Surface {
return Err(crate::SurfaceError::Outdated); return Err(crate::SurfaceError::Outdated);
} }
let identity = swapchain.device.texture_identity_factory.next();
let texture = super::SurfaceTexture { let texture = super::SurfaceTexture {
index, index,
texture: super::Texture { texture: super::Texture {
@ -1152,6 +1154,7 @@ impl crate::Surface for super::Surface {
height: swapchain.config.extent.height, height: swapchain.config.extent.height,
depth: 1, depth: 1,
}, },
identity,
}, },
surface_semaphores: swapchain_semaphores_arc, surface_semaphores: swapchain_semaphores_arc,
}; };

View File

@ -651,6 +651,14 @@ struct DeviceShared {
render_passes: Mutex<FastHashMap<RenderPassKey, vk::RenderPass>>, render_passes: Mutex<FastHashMap<RenderPassKey, vk::RenderPass>>,
sampler_cache: Mutex<sampler::SamplerCache>, sampler_cache: Mutex<sampler::SamplerCache>,
memory_allocations_counter: InternalCounter, memory_allocations_counter: InternalCounter,
/// Because we have cached framebuffers which are not deleted from until
/// the device is destroyed, if the implementation of vulkan re-uses handles
/// we need some way to differentiate between the old handle and the new handle.
/// This factory allows us to have a dedicated identity value for each texture.
texture_identity_factory: ResourceIdentityFactory<vk::Image>,
/// As above, for texture views.
texture_view_identity_factory: ResourceIdentityFactory<vk::ImageView>,
} }
impl Drop for DeviceShared { impl Drop for DeviceShared {
@ -864,6 +872,7 @@ pub struct Texture {
block: Option<gpu_alloc::MemoryBlock<vk::DeviceMemory>>, block: Option<gpu_alloc::MemoryBlock<vk::DeviceMemory>>,
format: wgt::TextureFormat, format: wgt::TextureFormat,
copy_size: crate::CopyExtent, copy_size: crate::CopyExtent,
identity: ResourceIdentity<vk::Image>,
} }
impl crate::DynTexture for Texture {} impl crate::DynTexture for Texture {}
@ -886,6 +895,8 @@ pub struct TextureView {
raw_format: vk::Format, raw_format: vk::Format,
base_mip_level: u32, base_mip_level: u32,
dimension: wgt::TextureViewDimension, dimension: wgt::TextureViewDimension,
texture_identity: ResourceIdentity<vk::Image>,
view_identity: ResourceIdentity<vk::ImageView>,
} }
impl crate::DynTextureView for TextureView {} impl crate::DynTextureView for TextureView {}
@ -897,6 +908,14 @@ impl TextureView {
pub unsafe fn raw_handle(&self) -> vk::ImageView { pub unsafe fn raw_handle(&self) -> vk::ImageView {
self.raw self.raw
} }
/// Returns the raw texture view, along with its identity.
fn identified_raw_view(&self) -> IdentifiedTextureView {
IdentifiedTextureView {
raw: self.raw,
identity: self.view_identity,
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -956,16 +975,97 @@ impl Temp {
} }
} }
/// Generates unique IDs for each resource of type `T`.
///
/// Because vk handles are not permanently unique, this
/// provides a way to generate unique IDs for each resource.
struct ResourceIdentityFactory<T> {
#[cfg(not(target_has_atomic = "64"))]
next_id: Mutex<u64>,
#[cfg(target_has_atomic = "64")]
next_id: core::sync::atomic::AtomicU64,
_phantom: PhantomData<T>,
}
impl<T> ResourceIdentityFactory<T> {
fn new() -> Self {
Self {
#[cfg(not(target_has_atomic = "64"))]
next_id: Mutex::new(0),
#[cfg(target_has_atomic = "64")]
next_id: core::sync::atomic::AtomicU64::new(0),
_phantom: PhantomData,
}
}
/// Returns a new unique ID for a resource of type `T`.
fn next(&self) -> ResourceIdentity<T> {
#[cfg(not(target_has_atomic = "64"))]
{
let mut next_id = self.next_id.lock();
let id = *next_id;
*next_id += 1;
ResourceIdentity {
id,
_phantom: PhantomData,
}
}
#[cfg(target_has_atomic = "64")]
ResourceIdentity {
id: self
.next_id
.fetch_add(1, core::sync::atomic::Ordering::Relaxed),
_phantom: PhantomData,
}
}
}
/// A unique identifier for a resource of type `T`.
///
/// This is used as a hashable key for resources, which
/// is permanently unique through the lifetime of the program.
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
struct ResourceIdentity<T> {
id: u64,
_phantom: PhantomData<T>,
}
#[derive(Clone, Eq, Hash, PartialEq)] #[derive(Clone, Eq, Hash, PartialEq)]
struct FramebufferKey { struct FramebufferKey {
raw_pass: vk::RenderPass, raw_pass: vk::RenderPass,
attachments: ArrayVec<vk::ImageView, { MAX_TOTAL_ATTACHMENTS }>, /// Because this is used as a key in a hash map, we need to include the identity
/// so that this hashes differently, even if the ImageView handles are the same
/// between different views.
attachment_identities: ArrayVec<ResourceIdentity<vk::ImageView>, { MAX_TOTAL_ATTACHMENTS }>,
/// While this is redundant for calculating the hash, we need access to an array
/// of all the raw ImageViews when we are creating the actual framebuffer,
/// so we store this here.
attachment_views: ArrayVec<vk::ImageView, { MAX_TOTAL_ATTACHMENTS }>,
extent: wgt::Extent3d, extent: wgt::Extent3d,
} }
impl FramebufferKey {
fn push_view(&mut self, view: IdentifiedTextureView) {
self.attachment_identities.push(view.identity);
self.attachment_views.push(view.raw);
}
}
/// A texture view paired with its identity.
#[derive(Copy, Clone)]
struct IdentifiedTextureView {
raw: vk::ImageView,
identity: ResourceIdentity<vk::ImageView>,
}
#[derive(Clone, Eq, Hash, PartialEq)] #[derive(Clone, Eq, Hash, PartialEq)]
struct TempTextureViewKey { struct TempTextureViewKey {
texture: vk::Image, texture: vk::Image,
/// As this is used in a hashmap, we need to
/// include the identity so that this hashes differently,
/// even if the Image handles are the same between different images.
texture_identity: ResourceIdentity<vk::Image>,
format: vk::Format, format: vk::Format,
mip_level: u32, mip_level: u32,
depth_slice: u32, depth_slice: u32,
@ -1008,7 +1108,7 @@ pub struct CommandEncoder {
end_of_pass_timer_query: Option<(vk::QueryPool, u32)>, end_of_pass_timer_query: Option<(vk::QueryPool, u32)>,
framebuffers: FastHashMap<FramebufferKey, vk::Framebuffer>, framebuffers: FastHashMap<FramebufferKey, vk::Framebuffer>,
temp_texture_views: FastHashMap<TempTextureViewKey, vk::ImageView>, temp_texture_views: FastHashMap<TempTextureViewKey, IdentifiedTextureView>,
counters: Arc<wgt::HalCounters>, counters: Arc<wgt::HalCounters>,
} }
@ -1037,7 +1137,7 @@ impl Drop for CommandEncoder {
} }
for (_, view) in self.temp_texture_views.drain() { for (_, view) in self.temp_texture_views.drain() {
unsafe { self.device.raw.destroy_image_view(view, None) }; unsafe { self.device.raw.destroy_image_view(view.raw, None) };
} }
self.counters.command_encoders.sub(1); self.counters.command_encoders.sub(1);