Vendor wgpu-hal 29.0.4 and i-slint-renderer-skia 1.17.1, unmodified

The Android develop view reads its frame back through memory (TD-1)
because wgpu's Vulkan swapchain never pre-rotates, and a portrait window
on this tablet's landscape panel then tears. The fix is a small patch to
each of these two crates, and this commit is only the ground it lands on:
both are byte-for-byte the crates.io sources the lockfile already
resolved, so the commits that follow are the patch and nothing else.

third_party/ is excluded from the workspace, or every path dependency
under the root would become a member and `--workspace` would test and
lint upstream code as ours. The README says how to carry the patches
across a Slint or wgpu bump, which matters because a stale version here
does not fail the build — cargo just warns and uses the unpatched crate.
This commit is contained in:
2026-09-25 04:18:39 -04:00
parent b5ae5c2be1
commit dc1add9dbb
112 changed files with 60304 additions and 4 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+487
View File
@@ -0,0 +1,487 @@
use objc2::rc::Retained;
use objc2_foundation::{NSArray, NSRange};
use objc2_metal::{
MTLAccelerationStructureBoundingBoxGeometryDescriptor, MTLAccelerationStructureDescriptor,
MTLAccelerationStructureGeometryDescriptor, MTLAccelerationStructureInstanceDescriptorType,
MTLAccelerationStructureTriangleGeometryDescriptor, MTLAccelerationStructureUsage,
MTLAttributeFormat, MTLBlendFactor, MTLBlendOperation, MTLBlitOption, MTLClearColor,
MTLColorWriteMask, MTLCompareFunction, MTLCullMode, MTLIndexType,
MTLInstanceAccelerationStructureDescriptor, MTLOrigin,
MTLPrimitiveAccelerationStructureDescriptor, MTLPrimitiveTopologyClass, MTLPrimitiveType,
MTLRenderStages, MTLResourceUsage, MTLSamplerAddressMode, MTLSamplerBorderColor,
MTLSamplerMinMagFilter, MTLSize, MTLStencilOperation, MTLStoreAction, MTLTextureType,
MTLTextureUsage, MTLVertexFormat, MTLVertexStepFunction, MTLWinding,
};
pub fn map_texture_usage(format: wgt::TextureFormat, usage: wgt::TextureUses) -> MTLTextureUsage {
use wgt::TextureUses as Tu;
let mut mtl_usage = MTLTextureUsage::Unknown;
mtl_usage.set(
MTLTextureUsage::RenderTarget,
usage.intersects(Tu::COLOR_TARGET | Tu::DEPTH_STENCIL_READ | Tu::DEPTH_STENCIL_WRITE),
);
mtl_usage.set(
MTLTextureUsage::ShaderRead,
usage.intersects(
Tu::RESOURCE | Tu::DEPTH_STENCIL_READ | Tu::STORAGE_READ_ONLY | Tu::STORAGE_READ_WRITE,
),
);
mtl_usage.set(
MTLTextureUsage::ShaderWrite,
usage.intersects(Tu::STORAGE_WRITE_ONLY | Tu::STORAGE_READ_WRITE),
);
// needed for combined depth/stencil formats since we might
// create a stencil-only view from them
mtl_usage.set(
MTLTextureUsage::PixelFormatView,
format.is_combined_depth_stencil_format(),
);
mtl_usage.set(
MTLTextureUsage::ShaderAtomic,
usage.intersects(Tu::STORAGE_ATOMIC),
);
mtl_usage
}
pub fn map_texture_view_dimension(dim: wgt::TextureViewDimension) -> MTLTextureType {
use wgt::TextureViewDimension as Tvd;
use MTLTextureType as MTL;
match dim {
Tvd::D1 => MTL::Type1D,
Tvd::D2 => MTL::Type2D,
Tvd::D2Array => MTL::Type2DArray,
Tvd::D3 => MTL::Type3D,
Tvd::Cube => MTL::TypeCube,
Tvd::CubeArray => MTL::TypeCubeArray,
}
}
pub fn map_compare_function(fun: wgt::CompareFunction) -> MTLCompareFunction {
use wgt::CompareFunction as Cf;
use MTLCompareFunction as MTL;
match fun {
Cf::Never => MTL::Never,
Cf::Less => MTL::Less,
Cf::LessEqual => MTL::LessEqual,
Cf::Equal => MTL::Equal,
Cf::GreaterEqual => MTL::GreaterEqual,
Cf::Greater => MTL::Greater,
Cf::NotEqual => MTL::NotEqual,
Cf::Always => MTL::Always,
}
}
pub fn map_filter_mode(filter: wgt::FilterMode) -> MTLSamplerMinMagFilter {
use MTLSamplerMinMagFilter as MTL;
match filter {
wgt::FilterMode::Nearest => MTL::Nearest,
wgt::FilterMode::Linear => MTL::Linear,
}
}
pub fn map_address_mode(address: wgt::AddressMode) -> MTLSamplerAddressMode {
use wgt::AddressMode as Fm;
use MTLSamplerAddressMode as MTL;
match address {
Fm::Repeat => MTL::Repeat,
Fm::MirrorRepeat => MTL::MirrorRepeat,
Fm::ClampToEdge => MTL::ClampToEdge,
Fm::ClampToBorder => MTL::ClampToBorderColor,
//Fm::MirrorClamp => MTL::MirrorClampToEdge,
}
}
pub fn map_border_color(border_color: wgt::SamplerBorderColor) -> MTLSamplerBorderColor {
use MTLSamplerBorderColor as MTL;
match border_color {
wgt::SamplerBorderColor::TransparentBlack => MTL::TransparentBlack,
wgt::SamplerBorderColor::OpaqueBlack => MTL::OpaqueBlack,
wgt::SamplerBorderColor::OpaqueWhite => MTL::OpaqueWhite,
wgt::SamplerBorderColor::Zero => unreachable!(),
}
}
pub fn map_primitive_topology(
topology: wgt::PrimitiveTopology,
) -> (MTLPrimitiveTopologyClass, MTLPrimitiveType) {
use wgt::PrimitiveTopology as Pt;
match topology {
Pt::PointList => (MTLPrimitiveTopologyClass::Point, MTLPrimitiveType::Point),
Pt::LineList => (MTLPrimitiveTopologyClass::Line, MTLPrimitiveType::Line),
Pt::LineStrip => (MTLPrimitiveTopologyClass::Line, MTLPrimitiveType::LineStrip),
Pt::TriangleList => (
MTLPrimitiveTopologyClass::Triangle,
MTLPrimitiveType::Triangle,
),
Pt::TriangleStrip => (
MTLPrimitiveTopologyClass::Triangle,
MTLPrimitiveType::TriangleStrip,
),
}
}
pub fn map_color_write(mask: wgt::ColorWrites) -> MTLColorWriteMask {
let mut raw_mask = MTLColorWriteMask::empty();
if mask.contains(wgt::ColorWrites::RED) {
raw_mask |= MTLColorWriteMask::Red;
}
if mask.contains(wgt::ColorWrites::GREEN) {
raw_mask |= MTLColorWriteMask::Green;
}
if mask.contains(wgt::ColorWrites::BLUE) {
raw_mask |= MTLColorWriteMask::Blue;
}
if mask.contains(wgt::ColorWrites::ALPHA) {
raw_mask |= MTLColorWriteMask::Alpha;
}
raw_mask
}
pub fn map_blend_factor(factor: wgt::BlendFactor) -> MTLBlendFactor {
use wgt::BlendFactor as Bf;
use MTLBlendFactor as MTL;
match factor {
Bf::Zero => MTL::Zero,
Bf::One => MTL::One,
Bf::Src => MTL::SourceColor,
Bf::OneMinusSrc => MTL::OneMinusSourceColor,
Bf::Dst => MTL::DestinationColor,
Bf::OneMinusDst => MTL::OneMinusDestinationColor,
Bf::SrcAlpha => MTL::SourceAlpha,
Bf::OneMinusSrcAlpha => MTL::OneMinusSourceAlpha,
Bf::DstAlpha => MTL::DestinationAlpha,
Bf::OneMinusDstAlpha => MTL::OneMinusDestinationAlpha,
Bf::Constant => MTL::BlendColor,
Bf::OneMinusConstant => MTL::OneMinusBlendColor,
Bf::SrcAlphaSaturated => MTL::SourceAlphaSaturated,
Bf::Src1 => MTL::Source1Color,
Bf::OneMinusSrc1 => MTL::OneMinusSource1Color,
Bf::Src1Alpha => MTL::Source1Alpha,
Bf::OneMinusSrc1Alpha => MTL::OneMinusSource1Alpha,
}
}
pub fn map_blend_op(operation: wgt::BlendOperation) -> MTLBlendOperation {
use wgt::BlendOperation as Bo;
use MTLBlendOperation as MTL;
match operation {
Bo::Add => MTL::Add,
Bo::Subtract => MTL::Subtract,
Bo::ReverseSubtract => MTL::ReverseSubtract,
Bo::Min => MTL::Min,
Bo::Max => MTL::Max,
}
}
pub fn map_blend_component(
component: &wgt::BlendComponent,
) -> (MTLBlendOperation, MTLBlendFactor, MTLBlendFactor) {
(
map_blend_op(component.operation),
map_blend_factor(component.src_factor),
map_blend_factor(component.dst_factor),
)
}
pub fn map_vertex_format(format: wgt::VertexFormat) -> MTLVertexFormat {
use wgt::VertexFormat as Vf;
use MTLVertexFormat as MTL;
match format {
Vf::Unorm8 => MTL::UCharNormalized,
Vf::Snorm8 => MTL::CharNormalized,
Vf::Uint8 => MTL::UChar,
Vf::Sint8 => MTL::Char,
Vf::Unorm8x2 => MTL::UChar2Normalized,
Vf::Snorm8x2 => MTL::Char2Normalized,
Vf::Uint8x2 => MTL::UChar2,
Vf::Sint8x2 => MTL::Char2,
Vf::Unorm8x4 => MTL::UChar4Normalized,
Vf::Snorm8x4 => MTL::Char4Normalized,
Vf::Uint8x4 => MTL::UChar4,
Vf::Sint8x4 => MTL::Char4,
Vf::Unorm16 => MTL::UShortNormalized,
Vf::Snorm16 => MTL::ShortNormalized,
Vf::Uint16 => MTL::UShort,
Vf::Sint16 => MTL::Short,
Vf::Float16 => MTL::Half,
Vf::Unorm16x2 => MTL::UShort2Normalized,
Vf::Snorm16x2 => MTL::Short2Normalized,
Vf::Uint16x2 => MTL::UShort2,
Vf::Sint16x2 => MTL::Short2,
Vf::Float16x2 => MTL::Half2,
Vf::Unorm16x4 => MTL::UShort4Normalized,
Vf::Snorm16x4 => MTL::Short4Normalized,
Vf::Uint16x4 => MTL::UShort4,
Vf::Sint16x4 => MTL::Short4,
Vf::Float16x4 => MTL::Half4,
Vf::Uint32 => MTL::UInt,
Vf::Sint32 => MTL::Int,
Vf::Float32 => MTL::Float,
Vf::Uint32x2 => MTL::UInt2,
Vf::Sint32x2 => MTL::Int2,
Vf::Float32x2 => MTL::Float2,
Vf::Uint32x3 => MTL::UInt3,
Vf::Sint32x3 => MTL::Int3,
Vf::Float32x3 => MTL::Float3,
Vf::Uint32x4 => MTL::UInt4,
Vf::Sint32x4 => MTL::Int4,
Vf::Float32x4 => MTL::Float4,
Vf::Unorm10_10_10_2 => MTL::UInt1010102Normalized,
Vf::Unorm8x4Bgra => MTL::UChar4Normalized_BGRA,
Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(),
}
}
pub fn map_index_format(format: wgt::IndexFormat) -> (u64, MTLIndexType) {
match format {
wgt::IndexFormat::Uint16 => (2, MTLIndexType::UInt16),
wgt::IndexFormat::Uint32 => (4, MTLIndexType::UInt32),
}
}
pub fn map_step_mode(mode: wgt::VertexStepMode) -> MTLVertexStepFunction {
match mode {
wgt::VertexStepMode::Vertex => MTLVertexStepFunction::PerVertex,
wgt::VertexStepMode::Instance => MTLVertexStepFunction::PerInstance,
}
}
pub fn map_stencil_op(op: wgt::StencilOperation) -> MTLStencilOperation {
use wgt::StencilOperation as So;
use MTLStencilOperation as MTL;
match op {
So::Keep => MTL::Keep,
So::Zero => MTL::Zero,
So::Replace => MTL::Replace,
So::IncrementClamp => MTL::IncrementClamp,
So::IncrementWrap => MTL::IncrementWrap,
So::DecrementClamp => MTL::DecrementClamp,
So::DecrementWrap => MTL::DecrementWrap,
So::Invert => MTL::Invert,
}
}
pub fn map_winding(winding: wgt::FrontFace) -> MTLWinding {
match winding {
wgt::FrontFace::Cw => MTLWinding::Clockwise,
wgt::FrontFace::Ccw => MTLWinding::CounterClockwise,
}
}
pub fn map_cull_mode(face: Option<wgt::Face>) -> MTLCullMode {
match face {
None => MTLCullMode::None,
Some(wgt::Face::Front) => MTLCullMode::Front,
Some(wgt::Face::Back) => MTLCullMode::Back,
}
}
pub fn map_range(range: &crate::MemoryRange) -> NSRange {
NSRange {
location: range.start as usize,
length: (range.end - range.start) as usize,
}
}
pub fn map_copy_extent(extent: &crate::CopyExtent) -> MTLSize {
MTLSize {
width: extent.width as usize,
height: extent.height as usize,
depth: extent.depth as usize,
}
}
pub fn map_origin(origin: &wgt::Origin3d) -> MTLOrigin {
MTLOrigin {
x: origin.x as usize,
y: origin.y as usize,
z: origin.z as usize,
}
}
pub fn map_store_action(store: bool, resolve: bool) -> MTLStoreAction {
use MTLStoreAction as MTL;
match (store, resolve) {
(true, true) => MTL::StoreAndMultisampleResolve,
(false, true) => MTL::MultisampleResolve,
(true, false) => MTL::Store,
(false, false) => MTL::DontCare,
}
}
pub fn map_clear_color(color: &wgt::Color) -> MTLClearColor {
MTLClearColor {
red: color.r,
green: color.g,
blue: color.b,
alpha: color.a,
}
}
pub fn get_blit_option(format: wgt::TextureFormat, aspect: crate::FormatAspects) -> MTLBlitOption {
if format.is_combined_depth_stencil_format() {
match aspect {
crate::FormatAspects::DEPTH => MTLBlitOption::DepthFromDepthStencil,
crate::FormatAspects::STENCIL => MTLBlitOption::StencilFromDepthStencil,
_ => unreachable!(),
}
} else {
MTLBlitOption::None
}
}
pub fn map_render_stages(stage: wgt::ShaderStages) -> MTLRenderStages {
let mut raw_stages = MTLRenderStages::empty();
if stage.contains(wgt::ShaderStages::VERTEX) {
raw_stages |= MTLRenderStages::Vertex;
}
if stage.contains(wgt::ShaderStages::FRAGMENT) {
raw_stages |= MTLRenderStages::Fragment;
}
raw_stages
}
pub fn map_resource_usage(ty: &wgt::BindingType) -> MTLResourceUsage {
match ty {
#[allow(deprecated)]
wgt::BindingType::Texture { .. } => MTLResourceUsage::Sample,
wgt::BindingType::StorageTexture { access, .. } => match access {
wgt::StorageTextureAccess::WriteOnly => MTLResourceUsage::Write,
wgt::StorageTextureAccess::ReadOnly => MTLResourceUsage::Read,
wgt::StorageTextureAccess::Atomic | wgt::StorageTextureAccess::ReadWrite => {
MTLResourceUsage::Read | MTLResourceUsage::Write
}
},
wgt::BindingType::Sampler(..) => MTLResourceUsage::empty(),
_ => unreachable!(),
}
}
pub fn map_acceleration_structure_descriptor<'a>(
entries: &'a crate::AccelerationStructureEntries<'a, super::Buffer>,
flags: crate::AccelerationStructureBuildFlags,
) -> Retained<MTLAccelerationStructureDescriptor> {
let descriptor = match entries {
crate::AccelerationStructureEntries::Instances(instances) => {
let descriptor = MTLInstanceAccelerationStructureDescriptor::new();
descriptor.setInstanceDescriptorType(
MTLAccelerationStructureInstanceDescriptorType::Indirect,
);
descriptor.setInstanceCount(instances.count as usize);
descriptor.setInstanceDescriptorBuffer(Some(&instances.buffer.unwrap().raw));
unsafe {
descriptor.setInstanceDescriptorBufferOffset(instances.offset as usize);
}
descriptor.into_super()
}
crate::AccelerationStructureEntries::Triangles(entries) => {
let geometry_descriptors = entries
.iter()
.map(|triangles| {
let descriptor = MTLAccelerationStructureTriangleGeometryDescriptor::new();
if let Some(indices) = triangles.indices.as_ref() {
descriptor.setIndexBuffer(Some(&indices.buffer.unwrap().raw));
unsafe {
descriptor.setIndexBufferOffset(indices.offset as usize);
}
descriptor.setIndexType(map_index_format(indices.format).1);
descriptor.setTriangleCount(indices.count as usize / 3);
} else {
descriptor.setTriangleCount(triangles.vertex_count as usize / 3);
}
descriptor.setVertexBuffer(Some(&triangles.vertex_buffer.unwrap().raw));
unsafe {
descriptor.setVertexBufferOffset(
triangles.first_vertex as usize * triangles.vertex_stride as usize,
);
}
descriptor.setVertexStride(triangles.vertex_stride as usize);
// Safety: MTLVertexFormat and MTLAttributeFormat are identical.
// https://docs.rs/objc2-metal/latest/objc2_metal/struct.MTLAttributeFormat.html
// https://docs.rs/objc2-metal/latest/objc2_metal/struct.MTLVertexFormat.html
descriptor.setVertexFormat(unsafe {
core::mem::transmute::<MTLVertexFormat, MTLAttributeFormat>(
map_vertex_format(triangles.vertex_format),
)
});
if let Some(transform) = triangles.transform.as_ref() {
unsafe {
descriptor.setTransformationMatrixBuffer(Some(&transform.buffer.raw));
descriptor
.setTransformationMatrixBufferOffset(transform.offset as usize);
}
}
descriptor.setOpaque(
triangles
.flags
.contains(wgt::AccelerationStructureGeometryFlags::OPAQUE),
);
if !triangles.flags.contains(
wgt::AccelerationStructureGeometryFlags::NO_DUPLICATE_ANY_HIT_INVOCATION,
) {
descriptor.allowDuplicateIntersectionFunctionInvocation();
}
// descriptor.setIntersectionFunctionTableOffset(offset);
descriptor.into_super()
})
.collect::<alloc::vec::Vec<Retained<MTLAccelerationStructureGeometryDescriptor>>>();
let descriptor = MTLPrimitiveAccelerationStructureDescriptor::new();
descriptor.setGeometryDescriptors(Some(&NSArray::from_retained_slice(
geometry_descriptors.as_slice(),
)));
descriptor.into_super()
}
crate::AccelerationStructureEntries::AABBs(entries) => {
let geometry_descriptors = entries
.iter()
.map(|aabbs| {
let descriptor = MTLAccelerationStructureBoundingBoxGeometryDescriptor::new();
descriptor.setBoundingBoxBuffer(Some(&aabbs.buffer.unwrap().raw));
descriptor.setBoundingBoxCount(aabbs.count as usize);
unsafe {
descriptor.setBoundingBoxStride(aabbs.stride as usize);
descriptor.setBoundingBoxBufferOffset(aabbs.offset as usize);
}
descriptor.setOpaque(
aabbs
.flags
.contains(wgt::AccelerationStructureGeometryFlags::OPAQUE),
);
if !aabbs.flags.contains(
wgt::AccelerationStructureGeometryFlags::NO_DUPLICATE_ANY_HIT_INVOCATION,
) {
descriptor.allowDuplicateIntersectionFunctionInvocation();
}
// descriptor.setIntersectionFunctionTableOffset(offset);
descriptor.into_super()
})
.collect::<alloc::vec::Vec<Retained<MTLAccelerationStructureGeometryDescriptor>>>();
let descriptor = MTLPrimitiveAccelerationStructureDescriptor::new();
descriptor.setGeometryDescriptors(Some(&NSArray::from_retained_slice(
geometry_descriptors.as_slice(),
)));
descriptor.into_super()
}
};
let mut usage = MTLAccelerationStructureUsage::None;
if flags.contains(wgt::AccelerationStructureFlags::ALLOW_UPDATE) {
usage |= MTLAccelerationStructureUsage::Refit;
}
if flags.contains(wgt::AccelerationStructureFlags::PREFER_FAST_BUILD) {
usage |= MTLAccelerationStructureUsage::PreferFastBuild;
}
descriptor.setUsage(usage);
descriptor
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
use core::{ffi::c_void, ptr::NonNull};
use objc2::{
msg_send,
rc::Retained,
runtime::{AnyObject, ProtocolObject},
};
use objc2_foundation::NSError;
use objc2_metal::{MTLDevice, MTLLibrary};
#[link(name = "System")] // libdispatch lives inside libSystem
extern "C" {
fn dispatch_data_create(
buffer: NonNull<c_void>,
size: usize,
queue: Option<NonNull<c_void>>, // dispatch_queue_t _Nullable
destructor: *mut c_void, // dispatch_block_t _Nullable
) -> *mut AnyObject; // dispatch_data_t
fn dispatch_release(object: *mut AnyObject); // dispatch_data_t
}
const DISPATCH_DATA_DESTRUCTOR_DEFAULT: *mut c_void = core::ptr::null_mut();
/// See <https://github.com/madsmtm/objc2/issues/819>.
///
/// newLibraryWithData requires DispatchData, part of the Dispatch library.
/// Not only is this a big additional dependency for a glorified `Vec<u8>`
/// in this case, but the code in the dispatch2 rust crate is a WIP and
/// Mozilla has decided not to vendor it yet.
///
/// This function allows us to deal with DispatchData without dealing
/// with these problems.
pub(crate) fn new_library_from_metallib_bytes(
device: &ProtocolObject<dyn MTLDevice>,
data: &[u8],
) -> Result<Retained<ProtocolObject<dyn MTLLibrary>>, Retained<NSError>> {
let buffer = NonNull::new(data.as_ptr().cast_mut()).unwrap().cast();
let data =
unsafe { dispatch_data_create(buffer, data.len(), None, DISPATCH_DATA_DESTRUCTOR_DEFAULT) };
assert!(!data.is_null());
let res: Result<Retained<ProtocolObject<dyn MTLLibrary>>, Retained<NSError>> =
unsafe { msg_send![device, newLibraryWithData: data, error: _] };
unsafe { dispatch_release(data) };
res
}
File diff suppressed because it is too large Load Diff
+195
View File
@@ -0,0 +1,195 @@
use alloc::borrow::ToOwned as _;
use objc2::{
available,
rc::{autoreleasepool, Retained},
runtime::ProtocolObject,
ClassType, Message,
};
use objc2_core_foundation::CGSize;
use objc2_foundation::NSObjectProtocol;
use objc2_metal::MTLTextureType;
use objc2_quartz_core::{CAMetalDrawable, CAMetalLayer};
use parking_lot::{Mutex, RwLock};
use super::OsFeatures;
impl super::Surface {
pub fn new(layer: Retained<CAMetalLayer>) -> Self {
Self {
render_layer: Mutex::new(layer),
swapchain_format: RwLock::new(None),
extent: RwLock::new(wgt::Extent3d::default()),
}
}
pub fn from_layer(layer: &CAMetalLayer) -> Self {
assert!(layer.isKindOfClass(CAMetalLayer::class()));
Self::new(layer.retain())
}
pub fn render_layer(&self) -> &Mutex<Retained<CAMetalLayer>> {
&self.render_layer
}
/// Gets the current dimensions of the `Surface`.
///
/// This function is safe to call off of the main thread. However, note that
/// `bounds` and `contentsScale` may be modified by the main thread while
/// this function is running, possibly resulting in the two values being out
/// of sync. This is sound, as these properties are accessed atomically.
/// See: <https://github.com/gfx-rs/wgpu/pull/7692>
pub(super) fn dimensions(&self) -> wgt::Extent3d {
let (size, scale) = {
let render_layer = self.render_layer.lock();
let bounds = render_layer.bounds();
let contents_scale = render_layer.contentsScale();
(bounds.size, contents_scale)
};
wgt::Extent3d {
width: (size.width * scale) as u32,
height: (size.height * scale) as u32,
depth_or_array_layers: 1,
}
}
}
impl crate::Surface for super::Surface {
type A = super::Api;
unsafe fn configure(
&self,
device: &super::Device,
config: &crate::SurfaceConfiguration,
) -> Result<(), crate::SurfaceError> {
log::debug!("build swapchain {config:?}");
let caps = &device.shared.private_texture_format_caps;
*self.swapchain_format.write() = Some(config.format);
*self.extent.write() = config.extent;
let render_layer = self.render_layer.lock();
let framebuffer_only = config.usage == wgt::TextureUses::COLOR_TARGET;
let display_sync = match config.present_mode {
wgt::PresentMode::Fifo => true,
wgt::PresentMode::Immediate => false,
m => unreachable!("Unsupported present mode: {m:?}"),
};
let drawable_size = CGSize::new(config.extent.width as f64, config.extent.height as f64);
match config.composite_alpha_mode {
wgt::CompositeAlphaMode::Opaque => render_layer.setOpaque(true),
wgt::CompositeAlphaMode::PostMultiplied => render_layer.setOpaque(false),
_ => (),
}
let device_raw = &device.shared.device;
render_layer.setDevice(Some(device_raw));
render_layer.setPixelFormat(caps.map_format(config.format));
render_layer.setFramebufferOnly(framebuffer_only);
// opt-in to Metal EDR
// EDR potentially more power used in display and more bandwidth, memory footprint.
let wants_edr = config.format == wgt::TextureFormat::Rgba16Float;
if wants_edr != render_layer.wantsExtendedDynamicRangeContent() {
render_layer.setWantsExtendedDynamicRangeContent(wants_edr);
}
// this gets ignored on iOS for certain OS/device combinations (iphone5s iOS 10.3)
render_layer.setMaximumDrawableCount(config.maximum_frame_latency as usize + 1);
render_layer.setDrawableSize(drawable_size);
// https://developer.apple.com/documentation/quartzcore/cametallayer/allowsnextdrawabletimeout
if available!(macos = 10.13, ios = 11.0, tvos = 11.0, visionos = 1.0) {
render_layer.setAllowsNextDrawableTimeout(false);
}
if OsFeatures::display_sync() {
render_layer.setDisplaySyncEnabled(display_sync);
}
Ok(())
}
unsafe fn unconfigure(&self, _device: &super::Device) {
*self.swapchain_format.write() = None;
}
unsafe fn acquire_texture(
&self,
_timeout: Option<core::time::Duration>, // TODO
_fence: &super::Fence,
) -> Result<crate::AcquiredSurfaceTexture<super::Api>, crate::SurfaceError> {
let render_layer = self.render_layer.lock();
#[cfg(target_os = "macos")]
{
// Workaround for https://github.com/gfx-rs/wgpu/issues/8309
// When the window is occluded on macOS, presented drawables get stuck waiting
// for vsync. Check the window's occlusion state and skip acquisition if
// the window is not visible - this avoids a 1-second hang in nextDrawable().
use objc2::rc::Retained;
use objc2::runtime::NSObject;
use objc2_quartz_core::CALayer;
// The CAMetalLayer is typically a sublayer, so we need to traverse up
// to find the root layer whose delegate is the NSView.
let mut current_layer: Option<Retained<CALayer>> =
Some(Retained::into_super(render_layer.clone()));
while let Some(layer) = current_layer {
if let Some(delegate) = layer.delegate() {
// Found a layer with a delegate - this should be the NSView
let window: Option<Retained<NSObject>> =
unsafe { objc2::msg_send![&*delegate, window] };
if let Some(window) = window {
const NS_WINDOW_OCCLUSION_STATE_VISIBLE: usize = 1 << 1;
let occlusion_state: usize =
unsafe { objc2::msg_send![&*window, occlusionState] };
let is_visible = (occlusion_state & NS_WINDOW_OCCLUSION_STATE_VISIBLE) != 0;
if !is_visible {
return Err(crate::SurfaceError::Occluded);
}
}
break;
}
current_layer = layer.superlayer();
}
}
let (drawable, texture) = match autoreleasepool(|_| {
render_layer
.nextDrawable()
.map(|drawable| (drawable.to_owned(), drawable.texture().to_owned()))
}) {
Some(pair) => pair,
None => return Err(crate::SurfaceError::Timeout),
};
let swapchain_format = self.swapchain_format.read().unwrap();
let extent = self.extent.read();
let suf_texture = super::SurfaceTexture {
texture: super::Texture {
raw: texture,
format: swapchain_format,
raw_type: MTLTextureType::Type2D,
array_layers: 1,
mip_levels: 1,
copy_size: crate::CopyExtent {
width: extent.width,
height: extent.height,
depth: 1,
},
},
drawable: ProtocolObject::from_retained(drawable),
present_with_transaction: render_layer.presentsWithTransaction(),
};
Ok(crate::AcquiredSurfaceTexture {
texture: suf_texture,
suboptimal: false,
})
}
unsafe fn discard_texture(&self, _texture: super::SurfaceTexture) {}
}
+38
View File
@@ -0,0 +1,38 @@
//! Handling of global timestamps.
#[repr(C)]
#[derive(Debug)]
struct MachTimebaseInfo {
numerator: u32,
denominator: u32,
}
extern "C" {
fn mach_timebase_info(out: *mut MachTimebaseInfo) -> u32;
fn mach_absolute_time() -> u64;
}
/// A timer which uses mach_absolute_time to get its time. This is what the metal callbacks use.
#[derive(Debug)]
pub struct PresentationTimer {
scale: MachTimebaseInfo,
}
impl PresentationTimer {
/// Generates a new timer.
pub fn new() -> Self {
// Default to 1 / 1 in case the call to timebase_info fails.
let mut scale = MachTimebaseInfo {
numerator: 1,
denominator: 1,
};
unsafe { mach_timebase_info(&mut scale) };
Self { scale }
}
/// Gets the current time in nanoseconds.
pub fn get_timestamp_ns(&self) -> u128 {
let time = unsafe { mach_absolute_time() };
(time as u128 * self.scale.numerator as u128) / self.scale.denominator as u128
}
}