diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1aaf78d4b..079c29bf70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -238,6 +238,8 @@ By @wumpf in [#8282](https://github.com/gfx-rs/wgpu/pull/8282), [#8285](https:// - Expose `naga::front::wgsl::UnimplementedEnableExtension`. By @ErichDonGubler in [#8237](https://github.com/gfx-rs/wgpu/pull/8237). +- Added support for transient textures on Vulkan and Metal. By @opstic in [#8247](https://github.com/gfx-rs/wgpu/pull/8247) + ### Changes #### General diff --git a/examples/features/src/msaa_line/mod.rs b/examples/features/src/msaa_line/mod.rs index 71c6b277c4b..491d68c771e 100644 --- a/examples/features/src/msaa_line/mod.rs +++ b/examples/features/src/msaa_line/mod.rs @@ -112,7 +112,7 @@ impl Example { sample_count, dimension: wgpu::TextureDimension::D2, format: config.view_formats[0], - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, label: None, view_formats: &[], }; diff --git a/examples/features/src/shadow/mod.rs b/examples/features/src/shadow/mod.rs index b89d2c902dc..09df6382456 100644 --- a/examples/features/src/shadow/mod.rs +++ b/examples/features/src/shadow/mod.rs @@ -192,7 +192,7 @@ impl Example { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, label: None, view_formats: &[], }); diff --git a/examples/features/src/skybox/mod.rs b/examples/features/src/skybox/mod.rs index 699f6615ba9..470f5f10acd 100644 --- a/examples/features/src/skybox/mod.rs +++ b/examples/features/src/skybox/mod.rs @@ -80,7 +80,7 @@ impl Example { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, label: None, view_formats: &[], }); diff --git a/tests/tests/wgpu-gpu/main.rs b/tests/tests/wgpu-gpu/main.rs index b52e5088ebb..82fe603c796 100644 --- a/tests/tests/wgpu-gpu/main.rs +++ b/tests/tests/wgpu-gpu/main.rs @@ -64,6 +64,7 @@ mod texture_view_creation; mod timestamp_normalization; mod timestamp_query; mod transfer; +mod transient; mod transition_resources; mod vertex_formats; mod vertex_indices; @@ -135,6 +136,7 @@ fn all_tests() -> Vec { timestamp_normalization::all_tests(&mut tests); timestamp_query::all_tests(&mut tests); transfer::all_tests(&mut tests); + transient::all_tests(&mut tests); transition_resources::all_tests(&mut tests); vertex_formats::all_tests(&mut tests); vertex_indices::all_tests(&mut tests); diff --git a/tests/tests/wgpu-gpu/transient.rs b/tests/tests/wgpu-gpu/transient.rs new file mode 100644 index 00000000000..072f9415559 --- /dev/null +++ b/tests/tests/wgpu-gpu/transient.rs @@ -0,0 +1,158 @@ +use wgpu_test::{gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters}; + +pub fn all_tests(vec: &mut Vec) { + vec.push(RESOLVE_WITH_TRANSIENT); +} + +#[gpu_test] +static RESOLVE_WITH_TRANSIENT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default()) + .run_async(|ctx| async move { + const SIZE: wgpu::Extent3d = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + + let shader_src = " + @vertex + fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f { + let positions: array = array( + vec2f(-1.0, -1.0), + vec2f(-1.0, 3.0), + vec2f(3.0, -1.0) + ); + return vec4f(positions[index], 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); + } + "; + + let shader = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(shader_src.into()), + }); + + let pipeline_desc = wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + buffers: &[], + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 4, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }; + let pipeline = ctx.device.create_render_pipeline(&pipeline_desc); + + let transient_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: SIZE, + mip_level_count: 1, + sample_count: 4, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, + view_formats: &[], + }); + + let target_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: SIZE, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256 * 256 * 4, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &transient_texture.create_view(&wgpu::TextureViewDescriptor::default()), + depth_slice: None, + resolve_target: Some( + &target_texture.create_view(&wgpu::TextureViewDescriptor::default()), + ), + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Discard, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&pipeline); + rpass.draw(0..3, 0..1); + } + + encoder.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { + texture: &target_texture, + mip_level: 0, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &readback_buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(256 * 4), + rows_per_image: Some(256), + }, + }, + SIZE, + ); + + ctx.queue.submit([encoder.finish()]); + + let slice = readback_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + + ctx.async_poll(wgpu::PollType::wait_indefinitely()) + .await + .unwrap(); + + let data = slice.get_mapped_range(); + let succeeded = data.iter().all(|b| *b == u8::MAX); + assert!(succeeded); + }); diff --git a/tests/tests/wgpu-validation/api/texture.rs b/tests/tests/wgpu-validation/api/texture.rs index 01c88c7bdf7..00525420375 100644 --- a/tests/tests/wgpu-validation/api/texture.rs +++ b/tests/tests/wgpu-validation/api/texture.rs @@ -508,3 +508,106 @@ fn copy_buffer_to_texture_forbidden_format_aspect() { ); } } + +/// Ensures that attempting to create a texture with [`wgpu::TextureUsages::TRANSIENT`] +/// and its unsupported usages fails validation. +#[test] +fn transient_invalid_usage() { + let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); + + let size = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + + let invalid_usages = wgpu::TextureUsages::all() + - wgpu::TextureUsages::RENDER_ATTACHMENT + - wgpu::TextureUsages::TRANSIENT; + + for usage in invalid_usages { + let invalid_texture_descriptor = wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT | usage, + view_formats: &[], + }; + fail( + &device, + || device.create_texture(&invalid_texture_descriptor), + Some(&format!("Texture usage TextureUsages(TRANSIENT) is not compatible with texture usage {usage:?}")), + ); + } + + let invalid_texture_descriptor = wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TRANSIENT, + view_formats: &[], + }; + fail( + &device, + || device.create_texture(&invalid_texture_descriptor), + Some("Invalid usage flags TextureUsages(TRANSIENT)"), + ); +} + +/// Ensures that attempting to use a texture of [`wgpu::TextureUsages::TRANSIENT`] +/// with [`wgpu::StoreOp::Store`] fails validation. +#[test] +fn transient_invalid_storeop() { + let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); + + let size = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + + let transient_texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, + view_formats: &[], + }); + + fail( + &device, + || { + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + let invalid_render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &transient_texture.create_view(&wgpu::TextureViewDescriptor::default()), + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + drop(invalid_render_pass); + + encoder.finish() + }, + Some("Color attachment's usage contains TextureUsages(TRANSIENT). This can only be used with StoreOp::Discard, but StoreOp::Store was provided") + ); +} diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 51db9634ea4..550e94190a9 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -629,6 +629,8 @@ pub enum ColorAttachmentError { mip_level: u32, depth_or_array_layer: u32, }, + #[error("Color attachment's usage contains {0:?}. This can only be used with StoreOp::{1:?}, but StoreOp::{2:?} was provided")] + InvalidUsageForStoreOp(TextureUsages, StoreOp, StoreOp), } impl WebGpuError for ColorAttachmentError { @@ -1585,6 +1587,18 @@ impl Global { let view = texture_views.get(*view_id).get()?; view.same_device(device)?; + if view.desc.usage.contains(TextureUsages::TRANSIENT) + && *store_op != StoreOp::Discard + { + return Err(RenderPassErrorInner::ColorAttachment( + ColorAttachmentError::InvalidUsageForStoreOp( + TextureUsages::TRANSIENT, + StoreOp::Discard, + *store_op, + ), + )); + } + let resolve_target = if let Some(resolve_target_id) = resolve_target { let rt_arc = texture_views.get(*resolve_target_id).get()?; rt_arc.same_device(device)?; diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index 3aca805659c..ac45677e46e 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -124,6 +124,10 @@ pub fn map_texture_usage( wgt::TextureUses::STORAGE_ATOMIC, usage.contains(wgt::TextureUsages::STORAGE_ATOMIC), ); + u.set( + wgt::TextureUses::TRANSIENT, + usage.contains(wgt::TextureUsages::TRANSIENT), + ); u } @@ -183,6 +187,10 @@ pub fn map_texture_usage_from_hal(uses: wgt::TextureUses) -> wgt::TextureUsages wgt::TextureUsages::STORAGE_ATOMIC, uses.contains(wgt::TextureUses::STORAGE_ATOMIC), ); + u.set( + wgt::TextureUsages::TRANSIENT, + uses.contains(wgt::TextureUses::TRANSIENT), + ); u } diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 804e6103c31..326fbb17ea1 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -1274,6 +1274,22 @@ impl Device { } } + if desc.usage.contains(wgt::TextureUsages::TRANSIENT) { + if !desc.usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { + return Err(CreateTextureError::InvalidUsage( + wgt::TextureUsages::TRANSIENT, + )); + } + let extra_usage = + desc.usage - wgt::TextureUsages::TRANSIENT - wgt::TextureUsages::RENDER_ATTACHMENT; + if !extra_usage.is_empty() { + return Err(CreateTextureError::IncompatibleUsage( + wgt::TextureUsages::TRANSIENT, + extra_usage, + )); + } + } + let format_features = self .describe_format_features(desc.format) .map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?; diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 4344622311d..4f5c831736c 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -699,7 +699,7 @@ impl Adapter { ), ); allowed_usages.set( - wgt::TextureUsages::RENDER_ATTACHMENT, + wgt::TextureUsages::RENDER_ATTACHMENT | wgt::TextureUsages::TRANSIENT, caps.intersects(Tfc::COLOR_ATTACHMENT | Tfc::DEPTH_STENCIL_ATTACHMENT), ); allowed_usages.set( diff --git a/wgpu-core/src/pipeline_cache.rs b/wgpu-core/src/pipeline_cache.rs index ca090e2e5fd..01de64fdf8e 100644 --- a/wgpu-core/src/pipeline_cache.rs +++ b/wgpu-core/src/pipeline_cache.rs @@ -322,6 +322,7 @@ mod tests { driver: String::new(), driver_info: String::new(), backend: wgt::Backend::Vulkan, + transient_saves_memory: true, }; // IMPORTANT: If these tests fail, then you MUST increment HEADER_VERSION diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 8d51298a711..c189d60eaed 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -1509,6 +1509,8 @@ pub enum CreateTextureError { CreateTextureView(#[from] CreateTextureViewError), #[error("Invalid usage flags {0:?}")] InvalidUsage(wgt::TextureUsages), + #[error("Texture usage {0:?} is not compatible with texture usage {1:?}")] + IncompatibleUsage(wgt::TextureUsages, wgt::TextureUsages), #[error(transparent)] InvalidDimension(#[from] TextureDimensionError), #[error("Depth texture ({1:?}) can't be created as {0:?}")] @@ -1564,6 +1566,7 @@ impl WebGpuError for CreateTextureError { Self::MissingDownlevelFlags(e) => e, Self::InvalidUsage(_) + | Self::IncompatibleUsage(_, _) | Self::InvalidDepthDimension(_, _) | Self::InvalidCompressedDimension(_, _) | Self::InvalidMipLevelCount { .. } diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 140dcf60a80..0a08e1d9884 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -142,6 +142,7 @@ impl super::Adapter { } }, driver_info: String::new(), + transient_saves_memory: false, }; let mut options = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS::default(); diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 806f5567ba2..dc73ae51207 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -189,6 +189,7 @@ impl super::Adapter { driver: "".to_owned(), driver_info: version, backend: wgt::Backend::Gl, + transient_saves_memory: false, } } diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index b45d53cc23e..e85809e6045 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -734,7 +734,8 @@ impl crate::Device for super::Device { let render_usage = wgt::TextureUses::COLOR_TARGET | wgt::TextureUses::DEPTH_STENCIL_WRITE - | wgt::TextureUses::DEPTH_STENCIL_READ; + | wgt::TextureUses::DEPTH_STENCIL_READ + | wgt::TextureUses::TRANSIENT; let format_desc = self.shared.describe_texture_format(desc.format); let inner = if render_usage.contains(desc.usage) diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 47ffbd3c6c1..4e1be2509c1 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -902,6 +902,12 @@ impl super::PrivateCapabilities { && (device.supports_family(MTLGPUFamily::Apple7) || device.supports_family(MTLGPUFamily::Mac2)), supports_shared_event: version.at_least((10, 14), (12, 0), os_is_mac), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=3 + supports_memoryless_storage: if family_check { + device.supports_family(MTLGPUFamily::Apple2) + } else { + version.at_least((11, 0), (10, 0), os_is_mac) + }, } } diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index 9f994e6302f..bde87057b1c 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -452,13 +452,21 @@ impl crate::Device for super::Device { } }; + let mtl_storage_mode = if desc.usage.contains(wgt::TextureUses::TRANSIENT) + && self.shared.private_caps.supports_memoryless_storage + { + MTLStorageMode::Memoryless + } else { + MTLStorageMode::Private + }; + descriptor.set_texture_type(mtl_type); descriptor.set_width(desc.size.width as u64); descriptor.set_height(desc.size.height as u64); descriptor.set_mipmap_level_count(desc.mip_level_count as u64); descriptor.set_pixel_format(mtl_format); descriptor.set_usage(conv::map_texture_usage(desc.format, desc.usage)); - descriptor.set_storage_mode(MTLStorageMode::Private); + descriptor.set_storage_mode(mtl_storage_mode); let raw = self.shared.device.lock().new_texture(&descriptor); if raw.as_ptr().is_null() { diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 00223b2f778..0dead249b8f 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -159,6 +159,7 @@ impl crate::Instance for Instance { driver: String::new(), driver_info: String::new(), backend: wgt::Backend::Metal, + transient_saves_memory: shared.private_caps.supports_memoryless_storage, }, features: shared.private_caps.features(), capabilities: shared.private_caps.capabilities(), @@ -300,6 +301,7 @@ struct PrivateCapabilities { int64_atomics: bool, float_atomics: bool, supports_shared_event: bool, + supports_memoryless_storage: bool, } #[derive(Clone, Debug)] diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index 4d06e04331e..e2cbdb8b0c2 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -140,6 +140,7 @@ pub fn adapter_info() -> wgt::AdapterInfo { driver: String::from("wgpu"), driver_info: String::new(), backend: wgt::Backend::Noop, + transient_saves_memory: false, } } diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 9c007bf6ef9..ba50eed76f0 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1642,6 +1642,16 @@ impl super::Instance { let (phd_capabilities, phd_features) = self.shared.inspect(phd); + let mem_properties = { + profiling::scope!("vkGetPhysicalDeviceMemoryProperties"); + unsafe { self.shared.raw.get_physical_device_memory_properties(phd) } + }; + let memory_types = &mem_properties.memory_types_as_slice(); + let supports_lazily_allocated = memory_types.iter().any(|mem| { + mem.property_flags + .contains(vk::MemoryPropertyFlags::LAZILY_ALLOCATED) + }); + let info = wgt::AdapterInfo { name: { phd_capabilities @@ -1681,6 +1691,7 @@ impl super::Instance { .to_owned() }, backend: wgt::Backend::Vulkan, + transient_saves_memory: supports_lazily_allocated, }; let (available_features, mut downlevel_flags) = phd_features.to_wgpu(&self.shared.raw, phd, &phd_capabilities); diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index 97044233e07..fcdf77224a0 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -260,6 +260,9 @@ pub fn map_texture_usage(usage: wgt::TextureUses) -> vk::ImageUsageFlags { ) { flags |= vk::ImageUsageFlags::STORAGE; } + if usage.contains(wgt::TextureUses::TRANSIENT) { + flags |= vk::ImageUsageFlags::TRANSIENT_ATTACHMENT; + } flags } @@ -349,6 +352,9 @@ pub fn map_vk_image_usage(usage: vk::ImageUsageFlags) -> wgt::TextureUses { | wgt::TextureUses::STORAGE_READ_WRITE | wgt::TextureUses::STORAGE_ATOMIC; } + if usage.contains(vk::ImageUsageFlags::TRANSIENT_ATTACHMENT) { + bits |= wgt::TextureUses::TRANSIENT; + } bits } diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 1a710cb2d99..90694371030 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -649,7 +649,6 @@ impl super::Device { } } - #[cfg(windows)] fn find_memory_type_index( &self, type_bits_req: u32, @@ -739,7 +738,17 @@ impl super::Device { // VK_ERROR_COMPRESSION_EXHAUSTED_EXT super::map_host_device_oom_and_ioca_err(err) } - let req = unsafe { self.shared.raw.get_image_memory_requirements(raw) }; + let mut req = unsafe { self.shared.raw.get_image_memory_requirements(raw) }; + + if desc.usage.contains(wgt::TextureUses::TRANSIENT) { + let mem_type_index = self.find_memory_type_index( + req.memory_type_bits, + vk::MemoryPropertyFlags::LAZILY_ALLOCATED, + ); + if let Some(mem_type_index) = mem_type_index { + req.memory_type_bits = 1 << mem_type_index; + } + } Ok(ImageWithoutMemory { raw, diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 7195e0cc31d..574e9bfbfe1 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1415,6 +1415,8 @@ pub struct AdapterInfo { pub driver_info: String, /// Backend used for device pub backend: Backend, + /// If true, adding [`TextureUsages::TRANSIENT`] will decrease memory usage. + pub transient_saves_memory: bool, } /// Hints to the device about the memory allocation strategy. @@ -3121,7 +3123,7 @@ impl TextureFormat { // Flags let basic = TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING; - let attachment = basic | TextureUsages::RENDER_ATTACHMENT; + let attachment = basic | TextureUsages::RENDER_ATTACHMENT | TextureUsages::TRANSIENT; let storage = basic | TextureUsages::STORAGE_BINDING; let binding = TextureUsages::TEXTURE_BINDING; let all_flags = attachment | storage | binding; @@ -5612,6 +5614,8 @@ bitflags::bitflags! { /// Allows a texture to be a [`BindingType::StorageTexture`] in a bind group. const STORAGE_BINDING = 1 << 3; /// Allows a texture to be an output attachment of a render pass. + /// + /// Consider adding [`TextureUsages::TRANSIENT`] if the contents are not reused. const RENDER_ATTACHMENT = 1 << 4; // @@ -5621,6 +5625,15 @@ bitflags::bitflags! { // /// Allows a texture to be used with image atomics. Requires [`Features::TEXTURE_ATOMIC`]. const STORAGE_ATOMIC = 1 << 16; + /// Specifies the contents of this texture will not be used in another pass to potentially reduce memory usage and bandwidth. + /// + /// No-op on platforms on platforms that do not benefit from transient textures. + /// Generally mobile and Apple chips care about this. + /// + /// Incompatible with ALL other usages except [`TextureUsages::RENDER_ATTACHMENT`] and requires it. + /// + /// Requires [`StoreOp::Discard`]. + const TRANSIENT = 1 << 17; } } @@ -5658,6 +5671,8 @@ bitflags::bitflags! { /// Image atomic enabled storage. /// cbindgen:ignore const STORAGE_ATOMIC = 1 << 11; + /// Transient texture that may not have any backing memory. Not a resource state stored in the trackers, only used for passing down usages to create_texture. + const TRANSIENT = 1 << 12; /// The combination of states that a texture may be in _at the same time_. /// cbindgen:ignore const INCLUSIVE = Self::COPY_SRC.bits() | Self::RESOURCE.bits() | Self::DEPTH_STENCIL_READ.bits(); @@ -5671,10 +5686,10 @@ bitflags::bitflags! { const ORDERED = Self::INCLUSIVE.bits() | Self::COLOR_TARGET.bits() | Self::DEPTH_STENCIL_WRITE.bits() | Self::STORAGE_READ_ONLY.bits(); /// Flag used by the wgpu-core texture tracker to say a texture is in different states for every sub-resource - const COMPLEX = 1 << 12; + const COMPLEX = 1 << 13; /// Flag used by the wgpu-core texture tracker to say that the tracker does not know the state of the sub-resource. /// This is different from UNINITIALIZED as that says the tracker does know, but the texture has not been initialized. - const UNKNOWN = 1 << 13; + const UNKNOWN = 1 << 14; } } diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 4c34042c55d..5d348b7ab73 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -1705,6 +1705,7 @@ impl dispatch::AdapterInterface for WebAdapter { driver: String::new(), driver_info: String::new(), backend: wgt::Backend::BrowserWebGpu, + transient_saves_memory: false, } }