Add support for transient textures on Vulkan and Metal (#8247)

Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
This commit is contained in:
Opstic 2025-10-16 15:50:01 -04:00 committed by GitHub
parent 7fdc5f1086
commit f0209e3db8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 380 additions and 11 deletions

View File

@ -275,6 +275,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). - 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 ### Changes
#### General #### General

View File

@ -112,7 +112,7 @@ impl Example {
sample_count, sample_count,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: config.view_formats[0], format: config.view_formats[0],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT,
label: None, label: None,
view_formats: &[], view_formats: &[],
}; };

View File

@ -192,7 +192,7 @@ impl Example {
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: Self::DEPTH_FORMAT, format: Self::DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT,
label: None, label: None,
view_formats: &[], view_formats: &[],
}); });

View File

@ -80,7 +80,7 @@ impl Example {
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: Self::DEPTH_FORMAT, format: Self::DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT,
label: None, label: None,
view_formats: &[], view_formats: &[],
}); });

View File

@ -64,6 +64,7 @@ mod texture_view_creation;
mod timestamp_normalization; mod timestamp_normalization;
mod timestamp_query; mod timestamp_query;
mod transfer; mod transfer;
mod transient;
mod transition_resources; mod transition_resources;
mod vertex_formats; mod vertex_formats;
mod vertex_indices; mod vertex_indices;
@ -135,6 +136,7 @@ fn all_tests() -> Vec<wgpu_test::GpuTestInitializer> {
timestamp_normalization::all_tests(&mut tests); timestamp_normalization::all_tests(&mut tests);
timestamp_query::all_tests(&mut tests); timestamp_query::all_tests(&mut tests);
transfer::all_tests(&mut tests); transfer::all_tests(&mut tests);
transient::all_tests(&mut tests);
transition_resources::all_tests(&mut tests); transition_resources::all_tests(&mut tests);
vertex_formats::all_tests(&mut tests); vertex_formats::all_tests(&mut tests);
vertex_indices::all_tests(&mut tests); vertex_indices::all_tests(&mut tests);

View File

@ -0,0 +1,158 @@
use wgpu_test::{gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters};
pub fn all_tests(vec: &mut Vec<GpuTestInitializer>) {
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<vec2f, 3> = array<vec2f, 3>(
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);
});

View File

@ -496,3 +496,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")
);
}

View File

@ -629,6 +629,8 @@ pub enum ColorAttachmentError {
mip_level: u32, mip_level: u32,
depth_or_array_layer: 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 { impl WebGpuError for ColorAttachmentError {
@ -1585,6 +1587,18 @@ impl Global {
let view = texture_views.get(*view_id).get()?; let view = texture_views.get(*view_id).get()?;
view.same_device(device)?; 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 resolve_target = if let Some(resolve_target_id) = resolve_target {
let rt_arc = texture_views.get(*resolve_target_id).get()?; let rt_arc = texture_views.get(*resolve_target_id).get()?;
rt_arc.same_device(device)?; rt_arc.same_device(device)?;

View File

@ -124,6 +124,10 @@ pub fn map_texture_usage(
wgt::TextureUses::STORAGE_ATOMIC, wgt::TextureUses::STORAGE_ATOMIC,
usage.contains(wgt::TextureUsages::STORAGE_ATOMIC), usage.contains(wgt::TextureUsages::STORAGE_ATOMIC),
); );
u.set(
wgt::TextureUses::TRANSIENT,
usage.contains(wgt::TextureUsages::TRANSIENT),
);
u u
} }
@ -183,6 +187,10 @@ pub fn map_texture_usage_from_hal(uses: wgt::TextureUses) -> wgt::TextureUsages
wgt::TextureUsages::STORAGE_ATOMIC, wgt::TextureUsages::STORAGE_ATOMIC,
uses.contains(wgt::TextureUses::STORAGE_ATOMIC), uses.contains(wgt::TextureUses::STORAGE_ATOMIC),
); );
u.set(
wgt::TextureUsages::TRANSIENT,
uses.contains(wgt::TextureUses::TRANSIENT),
);
u u
} }

View File

@ -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 let format_features = self
.describe_format_features(desc.format) .describe_format_features(desc.format)
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?; .map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;

View File

@ -699,7 +699,7 @@ impl Adapter {
), ),
); );
allowed_usages.set( allowed_usages.set(
wgt::TextureUsages::RENDER_ATTACHMENT, wgt::TextureUsages::RENDER_ATTACHMENT | wgt::TextureUsages::TRANSIENT,
caps.intersects(Tfc::COLOR_ATTACHMENT | Tfc::DEPTH_STENCIL_ATTACHMENT), caps.intersects(Tfc::COLOR_ATTACHMENT | Tfc::DEPTH_STENCIL_ATTACHMENT),
); );
allowed_usages.set( allowed_usages.set(

View File

@ -322,6 +322,7 @@ mod tests {
driver: String::new(), driver: String::new(),
driver_info: String::new(), driver_info: String::new(),
backend: wgt::Backend::Vulkan, backend: wgt::Backend::Vulkan,
transient_saves_memory: true,
}; };
// IMPORTANT: If these tests fail, then you MUST increment HEADER_VERSION // IMPORTANT: If these tests fail, then you MUST increment HEADER_VERSION

View File

@ -1509,6 +1509,8 @@ pub enum CreateTextureError {
CreateTextureView(#[from] CreateTextureViewError), CreateTextureView(#[from] CreateTextureViewError),
#[error("Invalid usage flags {0:?}")] #[error("Invalid usage flags {0:?}")]
InvalidUsage(wgt::TextureUsages), InvalidUsage(wgt::TextureUsages),
#[error("Texture usage {0:?} is not compatible with texture usage {1:?}")]
IncompatibleUsage(wgt::TextureUsages, wgt::TextureUsages),
#[error(transparent)] #[error(transparent)]
InvalidDimension(#[from] TextureDimensionError), InvalidDimension(#[from] TextureDimensionError),
#[error("Depth texture ({1:?}) can't be created as {0:?}")] #[error("Depth texture ({1:?}) can't be created as {0:?}")]
@ -1564,6 +1566,7 @@ impl WebGpuError for CreateTextureError {
Self::MissingDownlevelFlags(e) => e, Self::MissingDownlevelFlags(e) => e,
Self::InvalidUsage(_) Self::InvalidUsage(_)
| Self::IncompatibleUsage(_, _)
| Self::InvalidDepthDimension(_, _) | Self::InvalidDepthDimension(_, _)
| Self::InvalidCompressedDimension(_, _) | Self::InvalidCompressedDimension(_, _)
| Self::InvalidMipLevelCount { .. } | Self::InvalidMipLevelCount { .. }

View File

@ -142,6 +142,7 @@ impl super::Adapter {
} }
}, },
driver_info: String::new(), driver_info: String::new(),
transient_saves_memory: false,
}; };
let mut options = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS::default(); let mut options = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS::default();

View File

@ -189,6 +189,7 @@ impl super::Adapter {
driver: "".to_owned(), driver: "".to_owned(),
driver_info: version, driver_info: version,
backend: wgt::Backend::Gl, backend: wgt::Backend::Gl,
transient_saves_memory: false,
} }
} }

View File

@ -734,7 +734,8 @@ impl crate::Device for super::Device {
let render_usage = wgt::TextureUses::COLOR_TARGET let render_usage = wgt::TextureUses::COLOR_TARGET
| wgt::TextureUses::DEPTH_STENCIL_WRITE | 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 format_desc = self.shared.describe_texture_format(desc.format);
let inner = if render_usage.contains(desc.usage) let inner = if render_usage.contains(desc.usage)

View File

@ -902,6 +902,12 @@ impl super::PrivateCapabilities {
&& (device.supports_family(MTLGPUFamily::Apple7) && (device.supports_family(MTLGPUFamily::Apple7)
|| device.supports_family(MTLGPUFamily::Mac2)), || device.supports_family(MTLGPUFamily::Mac2)),
supports_shared_event: version.at_least((10, 14), (12, 0), os_is_mac), 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)
},
} }
} }

View File

@ -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_texture_type(mtl_type);
descriptor.set_width(desc.size.width as u64); descriptor.set_width(desc.size.width as u64);
descriptor.set_height(desc.size.height as u64); descriptor.set_height(desc.size.height as u64);
descriptor.set_mipmap_level_count(desc.mip_level_count as u64); descriptor.set_mipmap_level_count(desc.mip_level_count as u64);
descriptor.set_pixel_format(mtl_format); descriptor.set_pixel_format(mtl_format);
descriptor.set_usage(conv::map_texture_usage(desc.format, desc.usage)); 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); let raw = self.shared.device.lock().new_texture(&descriptor);
if raw.as_ptr().is_null() { if raw.as_ptr().is_null() {

View File

@ -159,6 +159,7 @@ impl crate::Instance for Instance {
driver: String::new(), driver: String::new(),
driver_info: String::new(), driver_info: String::new(),
backend: wgt::Backend::Metal, backend: wgt::Backend::Metal,
transient_saves_memory: shared.private_caps.supports_memoryless_storage,
}, },
features: shared.private_caps.features(), features: shared.private_caps.features(),
capabilities: shared.private_caps.capabilities(), capabilities: shared.private_caps.capabilities(),
@ -300,6 +301,7 @@ struct PrivateCapabilities {
int64_atomics: bool, int64_atomics: bool,
float_atomics: bool, float_atomics: bool,
supports_shared_event: bool, supports_shared_event: bool,
supports_memoryless_storage: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -140,6 +140,7 @@ pub fn adapter_info() -> wgt::AdapterInfo {
driver: String::from("wgpu"), driver: String::from("wgpu"),
driver_info: String::new(), driver_info: String::new(),
backend: wgt::Backend::Noop, backend: wgt::Backend::Noop,
transient_saves_memory: false,
} }
} }

View File

@ -1642,6 +1642,16 @@ impl super::Instance {
let (phd_capabilities, phd_features) = self.shared.inspect(phd); 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 { let info = wgt::AdapterInfo {
name: { name: {
phd_capabilities phd_capabilities
@ -1681,6 +1691,7 @@ impl super::Instance {
.to_owned() .to_owned()
}, },
backend: wgt::Backend::Vulkan, backend: wgt::Backend::Vulkan,
transient_saves_memory: supports_lazily_allocated,
}; };
let (available_features, mut downlevel_flags) = let (available_features, mut downlevel_flags) =
phd_features.to_wgpu(&self.shared.raw, phd, &phd_capabilities); phd_features.to_wgpu(&self.shared.raw, phd, &phd_capabilities);

View File

@ -260,6 +260,9 @@ pub fn map_texture_usage(usage: wgt::TextureUses) -> vk::ImageUsageFlags {
) { ) {
flags |= vk::ImageUsageFlags::STORAGE; flags |= vk::ImageUsageFlags::STORAGE;
} }
if usage.contains(wgt::TextureUses::TRANSIENT) {
flags |= vk::ImageUsageFlags::TRANSIENT_ATTACHMENT;
}
flags flags
} }
@ -349,6 +352,9 @@ pub fn map_vk_image_usage(usage: vk::ImageUsageFlags) -> wgt::TextureUses {
| wgt::TextureUses::STORAGE_READ_WRITE | wgt::TextureUses::STORAGE_READ_WRITE
| wgt::TextureUses::STORAGE_ATOMIC; | wgt::TextureUses::STORAGE_ATOMIC;
} }
if usage.contains(vk::ImageUsageFlags::TRANSIENT_ATTACHMENT) {
bits |= wgt::TextureUses::TRANSIENT;
}
bits bits
} }

View File

@ -649,7 +649,6 @@ impl super::Device {
} }
} }
#[cfg(windows)]
fn find_memory_type_index( fn find_memory_type_index(
&self, &self,
type_bits_req: u32, type_bits_req: u32,
@ -739,7 +738,17 @@ impl super::Device {
// VK_ERROR_COMPRESSION_EXHAUSTED_EXT // VK_ERROR_COMPRESSION_EXHAUSTED_EXT
super::map_host_device_oom_and_ioca_err(err) 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 { Ok(ImageWithoutMemory {
raw, raw,

View File

@ -1415,6 +1415,8 @@ pub struct AdapterInfo {
pub driver_info: String, pub driver_info: String,
/// Backend used for device /// Backend used for device
pub backend: Backend, pub backend: Backend,
/// If true, adding [`TextureUsages::TRANSIENT`] to a texture will decrease memory usage.
pub transient_saves_memory: bool,
} }
/// Hints to the device about the memory allocation strategy. /// Hints to the device about the memory allocation strategy.
@ -3121,7 +3123,7 @@ impl TextureFormat {
// Flags // Flags
let basic = let basic =
TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING; 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 storage = basic | TextureUsages::STORAGE_BINDING;
let binding = TextureUsages::TEXTURE_BINDING; let binding = TextureUsages::TEXTURE_BINDING;
let all_flags = attachment | storage | 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. /// Allows a texture to be a [`BindingType::StorageTexture`] in a bind group.
const STORAGE_BINDING = 1 << 3; const STORAGE_BINDING = 1 << 3;
/// Allows a texture to be an output attachment of a render pass. /// 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; const RENDER_ATTACHMENT = 1 << 4;
// //
@ -5621,6 +5625,15 @@ bitflags::bitflags! {
// //
/// Allows a texture to be used with image atomics. Requires [`Features::TEXTURE_ATOMIC`]. /// Allows a texture to be used with image atomics. Requires [`Features::TEXTURE_ATOMIC`].
const STORAGE_ATOMIC = 1 << 16; 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. /// Image atomic enabled storage.
/// cbindgen:ignore /// cbindgen:ignore
const STORAGE_ATOMIC = 1 << 11; 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_. /// The combination of states that a texture may be in _at the same time_.
/// cbindgen:ignore /// cbindgen:ignore
const INCLUSIVE = Self::COPY_SRC.bits() | Self::RESOURCE.bits() | Self::DEPTH_STENCIL_READ.bits(); 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(); 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 /// 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. /// 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. /// 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;
} }
} }

View File

@ -1710,6 +1710,7 @@ impl dispatch::AdapterInterface for WebAdapter {
driver: String::new(), driver: String::new(),
driver_info: String::new(), driver_info: String::new(),
backend: wgt::Backend::BrowserWebGpu, backend: wgt::Backend::BrowserWebGpu,
transient_saves_memory: false,
} }
} }