diff --git a/Cargo.lock b/Cargo.lock index d53366e13..1698f1576 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1915,8 +1915,11 @@ name = "wgpu-hal" version = "0.1.0" dependencies = [ "bitflags", + "log", "metal", "naga", + "objc", + "parking_lot", "raw-window-handle", "thiserror", "wgpu-types", diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index c3fb9129e..1ac13030c 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -12,16 +12,20 @@ license = "MIT OR Apache-2.0" [lib] [features] -metal = ["mtl"] +default = ["metal"] +metal = ["mtl", "objc", "parking_lot"] [dependencies] bitflags = "1.0" +log = "0.4" +parking_lot = { version = "0.11", optional = true } raw-window-handle = "0.3" thiserror = "1" wgt = { package = "wgpu-types", path = "../wgpu-types" } # backends mtl = { package = "metal", version = "0.22", optional = true } +objc = { version = "0.2.5", optional = true } [dependencies.naga] git = "https://github.com/gfx-rs/naga" diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs new file mode 100644 index 000000000..674baa15d --- /dev/null +++ b/wgpu-hal/src/metal/adapter.rs @@ -0,0 +1,991 @@ +use mtl::{MTLFeatureSet, MTLGPUFamily, MTLLanguageVersion}; +use objc::{class, msg_send, sel, sel_impl}; + +use std::{sync::Arc, thread}; + +unsafe impl Send for super::Adapter {} +unsafe impl Sync for super::Adapter {} + +impl super::Adapter { + pub(super) fn new(shared: Arc) -> Self { + Self { shared } + } +} + +impl crate::Adapter for super::Adapter { + unsafe fn open( + &self, + features: wgt::Features, + ) -> Result, crate::DeviceError> { + let raw_device = self.shared.device.lock(); + + Ok(crate::OpenDevice { + device: super::Device { + shared: Arc::clone(&self.shared), + features, + }, + queue: super::Queue { + raw: raw_device.new_command_queue(), + }, + }) + } + + unsafe fn close(&self, _device: super::Device) {} + + unsafe fn texture_format_capabilities( + &self, + format: wgt::TextureFormat, + ) -> crate::TextureFormatCapability { + use crate::TextureFormatCapability as Tfc; + use wgt::TextureFormat as Tf; + + let pc = &self.shared.private_caps; + // Affected formats documented at: + // https://developer.apple.com/documentation/metal/mtlreadwritetexturetier/mtlreadwritetexturetier1?language=objc + // https://developer.apple.com/documentation/metal/mtlreadwritetexturetier/mtlreadwritetexturetier2?language=objc + let (read_write_tier1_if, read_write_tier2_if) = match pc.read_write_texture_tier { + mtl::MTLReadWriteTextureTier::TierNone => (Tfc::empty(), Tfc::empty()), + mtl::MTLReadWriteTextureTier::Tier1 => (Tfc::STORAGE_READ_WRITE, Tfc::empty()), + mtl::MTLReadWriteTextureTier::Tier2 => { + (Tfc::STORAGE_READ_WRITE, Tfc::STORAGE_READ_WRITE) + } + }; + + let extra = match format { + Tf::R8Unorm => { + read_write_tier2_if + | Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::R8Snorm => { + Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::R8Uint | Tf::R8Sint | Tf::R16Uint | Tf::R16Sint => { + read_write_tier2_if | Tfc::STORAGE | Tfc::COLOR_ATTACHMENT + } + Tf::R16Float => { + read_write_tier2_if + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::Rg8Unorm | Tf::Rg8Snorm => { + Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::Rg8Uint | Tf::Rg8Sint => Tfc::COLOR_ATTACHMENT, + Tf::R32Uint | Tf::R32Sint => { + if pc.format_r32_all { + read_write_tier1_if | Tfc::STORAGE | Tfc::COLOR_ATTACHMENT + } else { + Tfc::COLOR_ATTACHMENT + } + } + Tf::R32Float => { + let mut flags = Tfc::COLOR_ATTACHMENT | Tfc::COLOR_ATTACHMENT_BLEND; + if pc.format_r32float_all { + flags |= read_write_tier1_if | Tfc::STORAGE | Tfc::SAMPLED_LINEAR; + } else if pc.format_r32float_no_filter { + flags |= Tfc::SAMPLED_LINEAR; + } + flags + } + Tf::Rg16Uint | Tf::Rg16Sint => { + read_write_tier2_if | Tfc::STORAGE | Tfc::COLOR_ATTACHMENT + } + Tf::Rg16Float => { + read_write_tier2_if + | Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::Rgba8Unorm => { + read_write_tier2_if + | Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::Rgba8UnormSrgb | Tf::Bgra8UnormSrgb => { + let mut flags = + Tfc::SAMPLED_LINEAR | Tfc::COLOR_ATTACHMENT | Tfc::COLOR_ATTACHMENT_BLEND; + flags.set(Tfc::STORAGE, pc.format_rgba8_srgb_all); + flags + } + Tf::Rgba8Snorm | Tf::Bgra8Unorm => { + Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::Rgba8Uint | Tf::Rgba8Sint => { + read_write_tier2_if | Tfc::STORAGE | Tfc::COLOR_ATTACHMENT + } + Tf::Rgb10a2Unorm => { + let mut flags = + Tfc::SAMPLED_LINEAR | Tfc::COLOR_ATTACHMENT | Tfc::COLOR_ATTACHMENT_BLEND; + flags.set(Tfc::STORAGE, pc.format_rgb10a2_unorm_all); + flags + } + Tf::Rg11b10Float => { + let mut flags = + Tfc::SAMPLED_LINEAR | Tfc::COLOR_ATTACHMENT | Tfc::COLOR_ATTACHMENT_BLEND; + flags.set(Tfc::STORAGE, pc.format_rg11b10_all); + flags + } + Tf::Rg32Uint | Tf::Rg32Sint => Tfc::COLOR_ATTACHMENT | Tfc::STORAGE, + Tf::Rg32Float => { + let mut flags = Tfc::COLOR_ATTACHMENT | Tfc::COLOR_ATTACHMENT_BLEND; + if pc.format_rg32float_all { + flags |= Tfc::STORAGE | Tfc::SAMPLED_LINEAR; + } else if pc.format_rg32float_color_blend { + flags |= Tfc::SAMPLED_LINEAR; + } + flags + } + Tf::Rgba16Uint | Tf::Rgba16Sint => { + read_write_tier2_if | Tfc::STORAGE | Tfc::COLOR_ATTACHMENT + } + Tf::Rgba16Float => { + read_write_tier2_if + | Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } + Tf::Rgba32Uint | Tf::Rgba32Sint => { + if pc.format_rgba32int_color_write { + read_write_tier2_if | Tfc::COLOR_ATTACHMENT | Tfc::STORAGE + } else { + Tfc::COLOR_ATTACHMENT + } + } + Tf::Rgba32Float => { + if pc.format_rgba32float_all { + read_write_tier2_if + | Tfc::SAMPLED_LINEAR + | Tfc::STORAGE + | Tfc::COLOR_ATTACHMENT + | Tfc::COLOR_ATTACHMENT_BLEND + } else if pc.format_rgba32float_color_write { + read_write_tier2_if | Tfc::COLOR_ATTACHMENT | Tfc::STORAGE + } else { + Tfc::COLOR_ATTACHMENT + } + } + Tf::Depth32Float => { + if pc.format_depth32float_filter { + Tfc::DEPTH_STENCIL_ATTACHMENT | Tfc::SAMPLED_LINEAR + } else { + Tfc::DEPTH_STENCIL_ATTACHMENT + } + } + Tf::Depth24Plus | Tf::Depth24PlusStencil8 => { + Tfc::DEPTH_STENCIL_ATTACHMENT | Tfc::SAMPLED_LINEAR + } + Tf::Bc1RgbaUnorm + | Tf::Bc1RgbaUnormSrgb + | Tf::Bc2RgbaUnorm + | Tf::Bc2RgbaUnormSrgb + | Tf::Bc3RgbaUnorm + | Tf::Bc3RgbaUnormSrgb + | Tf::Bc4RUnorm + | Tf::Bc4RSnorm + | Tf::Bc5RgUnorm + | Tf::Bc6hRgbSfloat + | Tf::Bc7RgbaUnorm + | Tf::Bc7RgbaUnormSrgb => { + if pc.format_bc { + Tfc::SAMPLED_LINEAR + } else { + Tfc::empty() + } + } + Tf::Etc2RgbUnorm + | Tf::Etc2RgbUnormSrgb + | Tf::Etc2RgbA1Unorm + | Tf::Etc2RgbA1UnormSrgb + | Tf::EacRUnorm + | Tf::EacRSnorm + | Tf::EtcRgUnorm + | Tf::EtcRgSnorm => { + if pc.format_eac_etc { + Tfc::SAMPLED_LINEAR + } else { + Tfc::empty() + } + } + Tf::Astc4x4RgbaUnorm + | Tf::Astc4x4RgbaUnormSrgb + | Tf::Astc5x4RgbaUnorm + | Tf::Astc5x4RgbaUnormSrgb + | Tf::Astc5x5RgbaUnorm + | Tf::Astc5x5RgbaUnormSrgb + | Tf::Astc6x5RgbaUnorm + | Tf::Astc6x5RgbaUnormSrgb + | Tf::Astc6x6RgbaUnorm + | Tf::Astc6x6RgbaUnormSrgb + | Tf::Astc8x5RgbaUnorm + | Tf::Astc8x5RgbaUnormSrgb + | Tf::Astc8x6RgbaUnorm + | Tf::Astc8x6RgbaUnormSrgb + | Tf::Astc10x5RgbaUnorm + | Tf::Astc10x5RgbaUnormSrgb + | Tf::Astc10x6RgbaUnorm + | Tf::Astc10x6RgbaUnormSrgb + | Tf::Astc8x8RgbaUnorm + | Tf::Astc8x8RgbaUnormSrgb + | Tf::Astc10x8RgbaUnorm + | Tf::Astc10x8RgbaUnormSrgb + | Tf::Astc10x10RgbaUnorm + | Tf::Astc10x10RgbaUnormSrgb + | Tf::Astc12x10RgbaUnorm + | Tf::Astc12x10RgbaUnormSrgb + | Tf::Astc12x12RgbaUnorm + | Tf::Astc12x12RgbaUnormSrgb => { + if pc.format_astc { + Tfc::SAMPLED_LINEAR + } else { + Tfc::empty() + } + } + }; + + Tfc::COPY_SRC | Tfc::COPY_DST | Tfc::SAMPLED | extra + } + + unsafe fn surface_capabilities( + &self, + surface: &super::Surface, + ) -> Option { + let current_extent = if surface.main_thread_id == thread::current().id() { + Some(surface.dimensions()) + } else { + log::warn!("Unable to get the current view dimensions on a non-main thread"); + None + }; + + let pc = &self.shared.private_caps; + Some(crate::SurfaceCapabilities { + formats: vec![ + wgt::TextureFormat::Bgra8Unorm, + wgt::TextureFormat::Bgra8UnormSrgb, + wgt::TextureFormat::Rgba16Float, + ], + //Note: this is hardcoded in `CAMetalLayer` documentation + swap_chain_sizes: if pc.can_set_maximum_drawables_count { + 2..=3 + } else { + // 3 is the default in `CAMetalLayer` documentation + // iOS 10.3 was tested to use 3 on iphone5s + 3..=3 + }, + present_modes: if pc.can_set_display_sync { + vec![wgt::PresentMode::Fifo, wgt::PresentMode::Immediate] + } else { + vec![wgt::PresentMode::Fifo] + }, + composite_alpha_modes: vec![ + crate::CompositeAlphaMode::Opaque, + crate::CompositeAlphaMode::Alpha, + ], + + current_extent, + extents: wgt::Extent3d { + width: 4, + height: 4, + depth_or_array_layers: 1, + }..=wgt::Extent3d { + width: 4096, + height: 4096, + depth_or_array_layers: 1, + }, + usage: crate::TextureUse::COLOR_TARGET, //TODO: expose more + }) + } +} + +const RESOURCE_HEAP_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily1_v3, + MTLFeatureSet::iOS_GPUFamily2_v3, + MTLFeatureSet::iOS_GPUFamily3_v2, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v2, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v3, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const ARGUMENT_BUFFER_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily1_v4, + MTLFeatureSet::iOS_GPUFamily2_v4, + MTLFeatureSet::iOS_GPUFamily3_v3, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v3, + MTLFeatureSet::macOS_GPUFamily1_v3, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const MUTABLE_COMPARISON_SAMPLER_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const SAMPLER_CLAMP_TO_BORDER_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::macOS_GPUFamily1_v2, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const ASTC_PIXEL_FORMAT_FEATURES: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily2_v1, + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, +]; + +const ANY8_UNORM_SRGB_ALL: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily2_v3, + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v2, + MTLFeatureSet::tvOS_GPUFamily2_v1, +]; + +const ANY8_SNORM_RESOLVE: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily2_v1, + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const RGBA8_SRGB: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily2_v3, + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v2, + MTLFeatureSet::tvOS_GPUFamily2_v1, +]; + +const RGB10A2UNORM_ALL: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const RGB10A2UINT_COLOR_WRITE: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const RG11B10FLOAT_ALL: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const RGB9E5FLOAT_ALL: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, +]; + +const BGR10A2_ALL: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily1_v4, + MTLFeatureSet::iOS_GPUFamily2_v4, + MTLFeatureSet::iOS_GPUFamily3_v3, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v3, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v3, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const BASE_INSTANCE_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const BASE_VERTEX_INSTANCE_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const TEXTURE_CUBE_ARRAY_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v2, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const DUAL_SOURCE_BLEND_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily1_v4, + MTLFeatureSet::iOS_GPUFamily2_v4, + MTLFeatureSet::iOS_GPUFamily3_v3, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v3, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v2, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const LAYERED_RENDERING_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const FUNCTION_SPECIALIZATION_SUPPORT: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily1_v3, + MTLFeatureSet::iOS_GPUFamily2_v3, + MTLFeatureSet::iOS_GPUFamily3_v2, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v2, + MTLFeatureSet::macOS_GPUFamily1_v2, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +const DEPTH_CLIP_MODE: &[MTLFeatureSet] = &[ + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v3, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, +]; + +impl super::PrivateCapabilities { + fn version_at_least(major: u32, minor: u32, needed_major: u32, needed_minor: u32) -> bool { + major > needed_major || (major == needed_major && minor >= needed_minor) + } + + fn supports_any(raw: &mtl::DeviceRef, features_sets: &[MTLFeatureSet]) -> bool { + features_sets + .iter() + .cloned() + .any(|x| raw.supports_feature_set(x)) + } + + pub fn new(device: &mtl::Device) -> Self { + #[repr(C)] + #[derive(Clone, Copy, Debug)] + struct NSOperatingSystemVersion { + major: usize, + minor: usize, + patch: usize, + } + + let version: NSOperatingSystemVersion = unsafe { + let process_info: *mut objc::runtime::Object = + msg_send![class!(NSProcessInfo), processInfo]; + msg_send![process_info, operatingSystemVersion] + }; + + let major = version.major as u32; + let minor = version.minor as u32; + let os_is_mac = device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1); + let family_check = if os_is_mac { + Self::version_at_least(major, minor, 10, 15) + } else { + Self::version_at_least(major, minor, 13, 0) + }; + + let mut sample_count_mask: u8 = 1 | 4; // 1 and 4 samples are supported on all devices + if device.supports_texture_sample_count(2) { + sample_count_mask |= 2; + } + if device.supports_texture_sample_count(8) { + sample_count_mask |= 8; + } + + Self { + family_check, + msl_version: if os_is_mac { + if Self::version_at_least(major, minor, 10, 15) { + MTLLanguageVersion::V2_2 + } else if Self::version_at_least(major, minor, 10, 14) { + MTLLanguageVersion::V2_1 + } else if Self::version_at_least(major, minor, 10, 13) { + MTLLanguageVersion::V2_0 + } else if Self::version_at_least(major, minor, 10, 12) { + MTLLanguageVersion::V1_2 + } else if Self::version_at_least(major, minor, 10, 11) { + MTLLanguageVersion::V1_1 + } else { + MTLLanguageVersion::V1_0 + } + } else if Self::version_at_least(major, minor, 13, 0) { + MTLLanguageVersion::V2_2 + } else if Self::version_at_least(major, minor, 12, 0) { + MTLLanguageVersion::V2_1 + } else if Self::version_at_least(major, minor, 11, 0) { + MTLLanguageVersion::V2_0 + } else if Self::version_at_least(major, minor, 10, 0) { + MTLLanguageVersion::V1_2 + } else if Self::version_at_least(major, minor, 9, 0) { + MTLLanguageVersion::V1_1 + } else { + MTLLanguageVersion::V1_0 + }, + exposed_queues: 1, + read_write_texture_tier: device.read_write_texture_support(), + resource_heaps: Self::supports_any(&device, RESOURCE_HEAP_SUPPORT), + argument_buffers: Self::supports_any(&device, ARGUMENT_BUFFER_SUPPORT), + shared_textures: !os_is_mac, + mutable_comparison_samplers: Self::supports_any( + &device, + MUTABLE_COMPARISON_SAMPLER_SUPPORT, + ), + sampler_clamp_to_border: Self::supports_any(&device, SAMPLER_CLAMP_TO_BORDER_SUPPORT), + sampler_lod_average: { + let need_version = if os_is_mac { (10, 13) } else { (9, 0) }; + Self::version_at_least(major, minor, need_version.0, need_version.1) + }, + base_instance: Self::supports_any(&device, BASE_INSTANCE_SUPPORT), + base_vertex_instance_drawing: Self::supports_any(&device, BASE_VERTEX_INSTANCE_SUPPORT), + dual_source_blending: Self::supports_any(&device, DUAL_SOURCE_BLEND_SUPPORT), + low_power: !os_is_mac || device.is_low_power(), + headless: os_is_mac && device.is_headless(), + layered_rendering: Self::supports_any(&device, LAYERED_RENDERING_SUPPORT), + function_specialization: Self::supports_any(&device, FUNCTION_SPECIALIZATION_SUPPORT), + depth_clip_mode: Self::supports_any(&device, DEPTH_CLIP_MODE), + texture_cube_array: Self::supports_any(&device, TEXTURE_CUBE_ARRAY_SUPPORT), + format_depth24_stencil8: os_is_mac && device.d24_s8_supported(), + format_depth32_stencil8_filter: os_is_mac, + format_depth32_stencil8_none: !os_is_mac, + format_min_srgb_channels: if os_is_mac { 4 } else { 1 }, + format_b5: !os_is_mac, + format_bc: os_is_mac, + format_eac_etc: !os_is_mac, + format_astc: Self::supports_any(&device, ASTC_PIXEL_FORMAT_FEATURES), + format_any8_unorm_srgb_all: Self::supports_any(&device, ANY8_UNORM_SRGB_ALL), + format_any8_unorm_srgb_no_write: !Self::supports_any(&device, ANY8_UNORM_SRGB_ALL) + && !os_is_mac, + format_any8_snorm_all: Self::supports_any(&device, ANY8_SNORM_RESOLVE), + format_r16_norm_all: os_is_mac, + format_r32_all: !Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_r32_no_write: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_r32float_no_write_no_filter: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ) && !os_is_mac, + format_r32float_no_filter: !Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ) && !os_is_mac, + format_r32float_all: os_is_mac, + format_rgba8_srgb_all: Self::supports_any(&device, RGBA8_SRGB), + format_rgba8_srgb_no_write: !Self::supports_any(&device, RGBA8_SRGB), + format_rgb10a2_unorm_all: Self::supports_any(&device, RGB10A2UNORM_ALL), + format_rgb10a2_unorm_no_write: !Self::supports_any(&device, RGB10A2UNORM_ALL), + format_rgb10a2_uint_color: !Self::supports_any(&device, RGB10A2UINT_COLOR_WRITE), + format_rgb10a2_uint_color_write: Self::supports_any(&device, RGB10A2UINT_COLOR_WRITE), + format_rg11b10_all: Self::supports_any(&device, RG11B10FLOAT_ALL), + format_rg11b10_no_write: !Self::supports_any(&device, RG11B10FLOAT_ALL), + format_rgb9e5_all: Self::supports_any(&device, RGB9E5FLOAT_ALL), + format_rgb9e5_no_write: !Self::supports_any(&device, RGB9E5FLOAT_ALL) && !os_is_mac, + format_rgb9e5_filter_only: os_is_mac, + format_rg32_color: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rg32_color_write: !Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rg32float_all: os_is_mac, + format_rg32float_color_blend: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rg32float_no_filter: !os_is_mac + && !Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rgba32int_color: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rgba32int_color_write: !Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rgba32float_color: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ), + format_rgba32float_color_write: !Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v1, + MTLFeatureSet::iOS_GPUFamily2_v1, + ], + ) && !os_is_mac, + format_rgba32float_all: os_is_mac, + format_depth16unorm: device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v2), + format_depth32float_filter: device + .supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1), + format_depth32float_none: !device + .supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1), + format_bgr10a2_all: Self::supports_any(&device, BGR10A2_ALL), + format_bgr10a2_no_write: !device + .supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v3), + max_buffers_per_stage: 31, + max_textures_per_stage: if os_is_mac { 128 } else { 31 }, + max_samplers_per_stage: 16, + buffer_alignment: if os_is_mac { 256 } else { 64 }, + max_buffer_size: if device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v2) { + 1 << 30 // 1GB on macOS 1.2 and up + } else { + 1 << 28 // 256MB otherwise + }, + max_texture_size: if Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + ], + ) { + 16384 + } else if Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily1_v2, + MTLFeatureSet::iOS_GPUFamily2_v2, + MTLFeatureSet::tvOS_GPUFamily1_v1, + ], + ) { + 8192 + } else { + 4096 + }, + max_texture_3d_size: 2048, + max_texture_layers: 2048, + max_fragment_input_components: if os_is_mac { 128 } else { 60 }, + max_color_render_targets: if Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily2_v1, + MTLFeatureSet::iOS_GPUFamily3_v1, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v1, + MTLFeatureSet::macOS_GPUFamily2_v1, + ], + ) { + 8 + } else { + 4 + }, + max_total_threadgroup_memory: if Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily4_v2, + MTLFeatureSet::iOS_GPUFamily5_v1, + ], + ) { + 64 << 10 + } else if Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::macOS_GPUFamily1_v2, + MTLFeatureSet::macOS_GPUFamily2_v1, + ], + ) { + 32 << 10 + } else { + 16 << 10 + }, + sample_count_mask, + supports_debug_markers: Self::supports_any( + &device, + &[ + MTLFeatureSet::macOS_GPUFamily1_v2, + MTLFeatureSet::macOS_GPUFamily2_v1, + MTLFeatureSet::iOS_GPUFamily1_v3, + MTLFeatureSet::iOS_GPUFamily2_v3, + MTLFeatureSet::iOS_GPUFamily3_v2, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily1_v2, + MTLFeatureSet::tvOS_GPUFamily2_v1, + ], + ), + supports_binary_archives: family_check + && (device.supports_family(MTLGPUFamily::Apple3) + || device.supports_family(MTLGPUFamily::Mac1)), + can_set_maximum_drawables_count: os_is_mac + || Self::version_at_least(major, minor, 11, 2), + can_set_display_sync: os_is_mac && Self::version_at_least(major, minor, 10, 13), + can_set_next_drawable_timeout: if is_mac { + Self::version_at_least(major, minor, 10, 13) + } else { + Self::version_at_least(major, minor, 11, 0) + }, + } + } + + pub fn features(&self) -> wgt::Features { + use wgt::Features as F; + + let mut features = F::empty() + | F::DEPTH_CLAMPING + | F::TEXTURE_COMPRESSION_BC + | F::MAPPABLE_PRIMARY_BUFFERS + | F::VERTEX_WRITABLE_STORAGE; + + features.set( + F::SAMPLED_TEXTURE_BINDING_ARRAY | F::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING, + self.msl_version >= MTLLanguageVersion::V2_0, + ); + features.set( + F::ADDRESS_MODE_CLAMP_TO_BORDER, + self.sampler_clamp_to_border, + ); + + features + } + + pub fn capabilities(&self) -> crate::Capabilities { + let buffer_alignment = wgt::BufferSize::new(self.buffer_alignment).unwrap(); + let mut downlevel = wgt::DownlevelCapabilities::default(); + downlevel.flags.set( + wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES, + self.texture_cube_array, + ); + + crate::Capabilities { + limits: wgt::Limits { + max_texture_dimension_1d: self.max_texture_size as u32, + max_texture_dimension_2d: self.max_texture_size as u32, + max_texture_dimension_3d: self.max_texture_3d_size as u32, + max_texture_array_layers: self.max_texture_layers as u32, + max_bind_groups: 8, + max_dynamic_uniform_buffers_per_pipeline_layout: 8, + max_dynamic_storage_buffers_per_pipeline_layout: 4, + max_sampled_textures_per_shader_stage: 16, + max_samplers_per_shader_stage: self.max_samplers_per_stage, + max_storage_buffers_per_shader_stage: 4, + max_storage_textures_per_shader_stage: 4, + max_uniform_buffers_per_shader_stage: 12, + max_uniform_buffer_binding_size: self.max_buffer_size.min(!0u32 as u64) as u32, + max_storage_buffer_binding_size: self.max_buffer_size.min(!0u32 as u64) as u32, + max_vertex_buffers: 8, + max_vertex_attributes: 16, + max_vertex_buffer_array_stride: 2048, + max_push_constant_size: 0x1000, + }, + alignments: crate::Alignments { + buffer_copy_offset: buffer_alignment, + buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(), + storage_buffer_offset: buffer_alignment, + uniform_buffer_offset: buffer_alignment, + }, + downlevel, + } + } + + pub fn map_format(&self, format: wgt::TextureFormat) -> mtl::MTLPixelFormat { + use mtl::MTLPixelFormat::*; + use wgt::TextureFormat as Tf; + + match format { + Tf::R8Unorm => R8Unorm, + Tf::R8Snorm => R8Snorm, + Tf::R8Uint => R8Uint, + Tf::R8Sint => R8Sint, + Tf::R16Uint => R16Uint, + Tf::R16Sint => R16Sint, + Tf::R16Float => R16Float, + Tf::Rg8Unorm => RG8Unorm, + Tf::Rg8Snorm => RG8Snorm, + Tf::Rg8Uint => RG8Uint, + Tf::Rg8Sint => RG8Sint, + Tf::R32Uint => R32Uint, + Tf::R32Sint => R32Sint, + Tf::R32Float => R32Float, + Tf::Rg16Uint => RG16Uint, + Tf::Rg16Sint => RG16Sint, + Tf::Rg16Float => RG16Float, + Tf::Rgba8Unorm => RGBA8Unorm, + Tf::Rgba8UnormSrgb => RGBA8Unorm_sRGB, + Tf::Bgra8UnormSrgb => BGRA8Unorm_sRGB, + Tf::Rgba8Snorm => RGBA8Snorm, + Tf::Bgra8Unorm => BGRA8Unorm, + Tf::Rgba8Uint => RGBA8Uint, + Tf::Rgba8Sint => RGBA8Sint, + Tf::Rgb10a2Unorm => RGB10A2Unorm, + Tf::Rg11b10Float => RG11B10Float, + Tf::Rg32Uint => RG32Uint, + Tf::Rg32Sint => RG32Sint, + Tf::Rg32Float => RG32Float, + Tf::Rgba16Uint => RGBA16Uint, + Tf::Rgba16Sint => RGBA16Sint, + Tf::Rgba16Float => RGBA16Float, + Tf::Rgba32Uint => RGBA32Uint, + Tf::Rgba32Sint => RGBA32Sint, + Tf::Rgba32Float => RGBA32Float, + Tf::Depth32Float => Depth32Float, + Tf::Depth24Plus => { + if self.format_depth24_stencil8 { + Depth24Unorm_Stencil8 + } else { + Depth32Float + } + } + Tf::Depth24PlusStencil8 => { + if self.format_depth24_stencil8 { + Depth24Unorm_Stencil8 + } else { + Depth32Float_Stencil8 + } + } + Tf::Bc1RgbaUnorm => BC1_RGBA, + Tf::Bc1RgbaUnormSrgb => BC1_RGBA_sRGB, + Tf::Bc2RgbaUnorm => BC2_RGBA, + Tf::Bc2RgbaUnormSrgb => BC2_RGBA_sRGB, + Tf::Bc3RgbaUnorm => BC3_RGBA, + Tf::Bc3RgbaUnormSrgb => BC3_RGBA_sRGB, + Tf::Bc4RUnorm => BC4_RUnorm, + Tf::Bc4RSnorm => BC4_RSnorm, + Tf::Bc5RgUnorm => BC5_RGUnorm, + Tf::Bc6hRgbSfloat => BC6H_RGBFloat, + Tf::Bc7RgbaUnorm => BC7_RGBAUnorm, + Tf::Bc7RgbaUnormSrgb => BC7_RGBAUnorm_sRGB, + Tf::Etc2RgbUnorm => ETC2_RGB8, + Tf::Etc2RgbUnormSrgb => ETC2_RGB8_sRGB, + Tf::Etc2RgbA1Unorm => ETC2_RGB8A1, + Tf::Etc2RgbA1UnormSrgb => ETC2_RGB8A1_sRGB, + Tf::EacRUnorm => EAC_R11Unorm, + Tf::EacRSnorm => EAC_R11Snorm, + Tf::EtcRgUnorm => EAC_RG11Unorm, + Tf::EtcRgSnorm => EAC_RG11Snorm, + Tf::Astc4x4RgbaUnorm => ASTC_4x4_LDR, + Tf::Astc4x4RgbaUnormSrgb => ASTC_4x4_sRGB, + Tf::Astc5x4RgbaUnorm => ASTC_5x4_LDR, + Tf::Astc5x4RgbaUnormSrgb => ASTC_5x4_sRGB, + Tf::Astc5x5RgbaUnorm => ASTC_5x5_LDR, + Tf::Astc5x5RgbaUnormSrgb => ASTC_5x5_sRGB, + Tf::Astc6x5RgbaUnorm => ASTC_6x5_LDR, + Tf::Astc6x5RgbaUnormSrgb => ASTC_6x5_sRGB, + Tf::Astc6x6RgbaUnorm => ASTC_6x6_LDR, + Tf::Astc6x6RgbaUnormSrgb => ASTC_6x6_sRGB, + Tf::Astc8x5RgbaUnorm => ASTC_8x5_LDR, + Tf::Astc8x5RgbaUnormSrgb => ASTC_8x5_sRGB, + Tf::Astc8x6RgbaUnorm => ASTC_8x6_LDR, + Tf::Astc8x6RgbaUnormSrgb => ASTC_8x6_sRGB, + Tf::Astc10x5RgbaUnorm => ASTC_8x8_LDR, + Tf::Astc10x5RgbaUnormSrgb => ASTC_8x8_sRGB, + Tf::Astc10x6RgbaUnorm => ASTC_10x5_LDR, + Tf::Astc10x6RgbaUnormSrgb => ASTC_10x5_sRGB, + Tf::Astc8x8RgbaUnorm => ASTC_10x6_LDR, + Tf::Astc8x8RgbaUnormSrgb => ASTC_10x6_sRGB, + Tf::Astc10x8RgbaUnorm => ASTC_10x8_LDR, + Tf::Astc10x8RgbaUnormSrgb => ASTC_10x8_sRGB, + Tf::Astc10x10RgbaUnorm => ASTC_10x10_LDR, + Tf::Astc10x10RgbaUnormSrgb => ASTC_10x10_sRGB, + Tf::Astc12x10RgbaUnorm => ASTC_12x10_LDR, + Tf::Astc12x10RgbaUnormSrgb => ASTC_12x10_sRGB, + Tf::Astc12x12RgbaUnorm => ASTC_12x12_LDR, + Tf::Astc12x12RgbaUnormSrgb => ASTC_12x12_sRGB, + } + } +} + +impl super::PrivateDisabilities { + pub fn new(device: &mtl::Device) -> Self { + let is_intel = device.name().starts_with("Intel"); + Self { + broken_viewport_near_depth: is_intel + && !device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v4), + broken_layered_clear_image: is_intel, + } + } +} diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/wgpu-hal/src/metal/command.rs @@ -0,0 +1 @@ + diff --git a/wgpu-hal/src/metal/conv.rs b/wgpu-hal/src/metal/conv.rs new file mode 100644 index 000000000..e69de29bb diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/wgpu-hal/src/metal/device.rs @@ -0,0 +1 @@ + diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index a3973da56..91795d86c 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -1,6 +1,13 @@ #![allow(unused_variables)] -use std::ops::Range; +mod adapter; +mod command; +mod device; +mod surface; + +use std::{ops::Range, ptr::NonNull, sync::Arc, thread}; + +use parking_lot::Mutex; #[derive(Clone)] pub struct Api; @@ -10,19 +17,20 @@ pub struct Encoder; pub struct Resource; type DeviceResult = Result; +type ResourceIndex = u32; impl crate::Api for Api { - type Instance = Context; - type Surface = Context; - type Adapter = Context; - type Queue = Context; - type Device = Context; + type Instance = Instance; + type Surface = Surface; + type Adapter = Adapter; + type Queue = Queue; + type Device = Device; type CommandBuffer = Encoder; type Buffer = Resource; type Texture = Resource; - type SurfaceTexture = Resource; + type SurfaceTexture = SurfaceTexture; type TextureView = Resource; type Sampler = Resource; type QuerySet = Resource; @@ -36,59 +44,210 @@ impl crate::Api for Api { type ComputePipeline = Resource; } -impl crate::Instance for Context { +pub struct Instance {} + +impl crate::Instance for Instance { unsafe fn init() -> Result { - Ok(Context) + Ok(Instance {}) } unsafe fn create_surface( &self, - rwh: &impl raw_window_handle::HasRawWindowHandle, - ) -> Result { - Ok(Context) + has_handle: &impl raw_window_handle::HasRawWindowHandle, + ) -> Result { + match has_handle.raw_window_handle() { + #[cfg(target_os = "ios")] + raw_window_handle::RawWindowHandle::IOS(handle) => { + Ok(Surface::from_uiview(handle.ui_view)) + } + #[cfg(target_os = "macos")] + raw_window_handle::RawWindowHandle::MacOS(handle) => { + Ok(Surface::from_nsview(handle.ns_view)) + } + _ => Err(crate::InstanceError), + } } - unsafe fn destroy_surface(&self, surface: Context) {} + + unsafe fn destroy_surface(&self, surface: Surface) {} + unsafe fn enumerate_adapters(&self) -> Vec> { - Vec::new() + let devices = mtl::Device::all(); + let mut adapters: Vec> = devices + .into_iter() + .map(|dev| { + let name = dev.name().into(); + let shared = AdapterShared::new(dev); + crate::ExposedAdapter { + info: wgt::AdapterInfo { + name, + vendor: 0, + device: 0, + device_type: if shared.private_caps.low_power { + wgt::DeviceType::IntegratedGpu + } else { + wgt::DeviceType::DiscreteGpu + }, + backend: wgt::Backend::Metal, + }, + features: shared.private_caps.features(), + capabilities: shared.private_caps.capabilities(), + adapter: Adapter::new(Arc::new(shared)), + } + }) + .collect(); + adapters.sort_by_key(|ad| { + ( + ad.adapter.shared.private_caps.low_power, + ad.adapter.shared.private_caps.headless, + ) + }); + adapters } } -impl crate::Surface for Context { - unsafe fn configure( - &mut self, - device: &Context, - config: &crate::SurfaceConfiguration, - ) -> Result<(), crate::SurfaceError> { - Ok(()) - } - - unsafe fn unconfigure(&mut self, device: &Context) {} - - unsafe fn acquire_texture( - &mut self, - timeout_ms: u32, - ) -> Result>, crate::SurfaceError> { - Ok(None) - } - unsafe fn discard_texture(&mut self, texture: Resource) {} +#[derive(Clone, Debug)] +struct PrivateCapabilities { + family_check: bool, + msl_version: mtl::MTLLanguageVersion, + exposed_queues: usize, + read_write_texture_tier: mtl::MTLReadWriteTextureTier, + resource_heaps: bool, + argument_buffers: bool, + shared_textures: bool, + mutable_comparison_samplers: bool, + sampler_clamp_to_border: bool, + sampler_lod_average: bool, + base_instance: bool, + base_vertex_instance_drawing: bool, + dual_source_blending: bool, + low_power: bool, + headless: bool, + layered_rendering: bool, + function_specialization: bool, + depth_clip_mode: bool, + texture_cube_array: bool, + format_depth24_stencil8: bool, + format_depth32_stencil8_filter: bool, + format_depth32_stencil8_none: bool, + format_min_srgb_channels: u8, + format_b5: bool, + format_bc: bool, + format_eac_etc: bool, + format_astc: bool, + format_any8_unorm_srgb_all: bool, + format_any8_unorm_srgb_no_write: bool, + format_any8_snorm_all: bool, + format_r16_norm_all: bool, + format_r32_all: bool, + format_r32_no_write: bool, + format_r32float_no_write_no_filter: bool, + format_r32float_no_filter: bool, + format_r32float_all: bool, + format_rgba8_srgb_all: bool, + format_rgba8_srgb_no_write: bool, + format_rgb10a2_unorm_all: bool, + format_rgb10a2_unorm_no_write: bool, + format_rgb10a2_uint_color: bool, + format_rgb10a2_uint_color_write: bool, + format_rg11b10_all: bool, + format_rg11b10_no_write: bool, + format_rgb9e5_all: bool, + format_rgb9e5_no_write: bool, + format_rgb9e5_filter_only: bool, + format_rg32_color: bool, + format_rg32_color_write: bool, + format_rg32float_all: bool, + format_rg32float_color_blend: bool, + format_rg32float_no_filter: bool, + format_rgba32int_color: bool, + format_rgba32int_color_write: bool, + format_rgba32float_color: bool, + format_rgba32float_color_write: bool, + format_rgba32float_all: bool, + format_depth16unorm: bool, + format_depth32float_filter: bool, + format_depth32float_none: bool, + format_bgr10a2_all: bool, + format_bgr10a2_no_write: bool, + max_buffers_per_stage: ResourceIndex, + max_textures_per_stage: ResourceIndex, + max_samplers_per_stage: ResourceIndex, + buffer_alignment: u64, + max_buffer_size: u64, + max_texture_size: u64, + max_texture_3d_size: u64, + max_texture_layers: u64, + max_fragment_input_components: u64, + max_color_render_targets: u8, + max_total_threadgroup_memory: u32, + sample_count_mask: u8, + supports_debug_markers: bool, + supports_binary_archives: bool, + can_set_maximum_drawables_count: bool, + can_set_display_sync: bool, + can_set_next_drawable_timeout: bool, } -impl crate::Adapter for Context { - unsafe fn open(&self, features: wgt::Features) -> DeviceResult> { - Err(crate::DeviceError::Lost) - } - unsafe fn close(&self, device: Context) {} - unsafe fn texture_format_capabilities( - &self, - format: wgt::TextureFormat, - ) -> crate::TextureFormatCapability { - crate::TextureFormatCapability::empty() - } - unsafe fn surface_capabilities(&self, surface: &Context) -> Option { - None +#[derive(Clone, Debug)] +struct PrivateDisabilities { + /// Near depth is not respected properly on some Intel GPUs. + broken_viewport_near_depth: bool, + /// Multi-target clears don't appear to work properly on Intel GPUs. + broken_layered_clear_image: bool, +} + +struct AdapterShared { + device: Mutex, + disabilities: PrivateDisabilities, + private_caps: PrivateCapabilities, +} + +impl AdapterShared { + fn new(device: mtl::Device) -> Self { + let private_caps = PrivateCapabilities::new(&device); + log::debug!("{:#?}", private_caps); + + Self { + disabilities: PrivateDisabilities::new(&device), + private_caps: PrivateCapabilities::new(&device), + device: Mutex::new(device), + } } } -impl crate::Queue for Context { +struct Adapter { + shared: Arc, +} + +struct Queue { + raw: mtl::CommandQueue, +} + +struct Device { + shared: Arc, + features: wgt::Features, +} + +struct Surface { + view: Option>, + render_layer: Mutex, + raw_swapchain_format: mtl::MTLPixelFormat, + main_thread_id: thread::ThreadId, + // Useful for UI-intensive applications that are sensitive to + // window resizing. + pub present_with_transaction: bool, +} + +#[derive(Debug)] +struct SurfaceTexture { + texture: Resource, + drawable: mtl::MetalDrawable, + present_with_transaction: bool, +} + +unsafe impl Send for SurfaceTexture {} +unsafe impl Sync for SurfaceTexture {} + +impl crate::Queue for Queue { unsafe fn submit( &mut self, command_buffers: I, @@ -98,8 +257,8 @@ impl crate::Queue for Context { } unsafe fn present( &mut self, - surface: &mut Context, - texture: Resource, + surface: &mut Surface, + texture: SurfaceTexture, ) -> Result<(), crate::SurfaceError> { Ok(()) } @@ -114,7 +273,7 @@ impl crate::Device for Context { &self, buffer: &Resource, range: crate::MemoryRange, - ) -> DeviceResult> { + ) -> DeviceResult> { Err(crate::DeviceError::Lost) } unsafe fn unmap_buffer(&self, buffer: &Resource) -> DeviceResult<()> { diff --git a/wgpu-hal/src/metal/surface.rs b/wgpu-hal/src/metal/surface.rs new file mode 100644 index 000000000..e9c0edec9 --- /dev/null +++ b/wgpu-hal/src/metal/surface.rs @@ -0,0 +1,265 @@ +use std::{mem, os::raw::c_void, ptr::NonNull, thread}; + +use objc::{ + class, msg_send, + rc::autoreleasepool, + runtime::{Object, BOOL, YES}, + sel, sel_impl, +}; +use parking_lot::Mutex; + +#[link(name = "QuartzCore", kind = "framework")] +extern "C" { + #[allow(non_upper_case_globals)] + static kCAGravityTopLeft: *mut Object; +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct CGPoint { + pub x: mtl::CGFloat, + pub y: mtl::CGFloat, +} + +impl CGPoint { + #[inline] + pub fn new(x: mtl::CGFloat, y: mtl::CGFloat) -> CGPoint { + CGPoint { x, y } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct CGRect { + pub origin: CGPoint, + pub size: mtl::CGSize, +} + +impl CGRect { + #[inline] + pub fn new(origin: CGPoint, size: mtl::CGSize) -> CGRect { + CGRect { origin, size } + } +} + +impl super::Surface { + fn new(view: Option>, layer: mtl::MetalLayer) -> Self { + Self { + view, + render_layer: Mutex::new(layer), + raw_swapchain_format: mtl::MTLPixelFormat::Invalid, + main_thread_id: thread::current().id(), + present_with_transaction: false, + } + } + + #[cfg(target_os = "ios")] + pub unsafe fn from_uiview(uiview: *mut c_void) -> Self { + let view: cocoa_foundation::base::id = mem::transmute(uiview); + if view.is_null() { + panic!("window does not have a valid contentView"); + } + + let main_layer: *mut Object = msg_send![view, layer]; + let class = class!(CAMetalLayer); + let is_valid_layer: BOOL = msg_send![main_layer, isKindOfClass: class]; + let render_layer = if is_valid_layer == YES { + mem::transmute::<_, &MetalLayerRef>(main_layer).to_owned() + } else { + // If the main layer is not a CAMetalLayer, we create a CAMetalLayer sublayer and use it instead. + // Unlike on macOS, we cannot replace the main view as UIView does not allow it (when NSView does). + let new_layer: mtl::MetalLayer = msg_send![class, new]; + let bounds: CGRect = msg_send![main_layer, bounds]; + let () = msg_send![new_layer.as_ref(), setFrame: bounds]; + let () = msg_send![main_layer, addSublayer: new_layer.as_ref()]; + new_layer + }; + + let window: *mut Object = msg_send![view, window]; + if !window.is_null() { + let screen: *mut Object = msg_send![window, screen]; + assert!(!screen.is_null(), "window is not attached to a screen"); + + let scale_factor: CGFloat = msg_send![screen, nativeScale]; + let () = msg_send![view, setContentScaleFactor: scale_factor]; + } + + let _: *mut c_void = msg_send![view, retain]; + Self::new(NonNull::new(view), render_layer) + } + + #[cfg(target_os = "macos")] + pub unsafe fn from_nsview(nsview: *mut c_void) -> Self { + let view = nsview as *mut Object; + if view.is_null() { + panic!("window does not have a valid contentView"); + } + + let class = class!(CAMetalLayer); + // Deprecated! Clients should use `create_surface_from_layer` instead. + let is_actually_layer: BOOL = msg_send![view, isKindOfClass: class]; + if is_actually_layer == YES { + return Self::from_layer(mem::transmute(view)); + } + + let existing: *mut Object = msg_send![view, layer]; + let use_current = if existing.is_null() { + false + } else { + let result: BOOL = msg_send![existing, isKindOfClass: class]; + result == YES + }; + + let render_layer: mtl::MetalLayer = if use_current { + mem::transmute::<_, &mtl::MetalLayerRef>(existing).to_owned() + } else { + let layer: mtl::MetalLayer = msg_send![class, new]; + let () = msg_send![view, setLayer: layer.as_ref()]; + let () = msg_send![view, setWantsLayer: YES]; + let bounds: CGRect = msg_send![view, bounds]; + let () = msg_send![layer.as_ref(), setBounds: bounds]; + + let window: *mut Object = msg_send![view, window]; + if !window.is_null() { + let scale_factor: mtl::CGFloat = msg_send![window, backingScaleFactor]; + let () = msg_send![layer, setContentsScale: scale_factor]; + } + //let () = msg_send![layer, setDelegate: self.gfx_managed_metal_layer_delegate.0]; + layer + }; + + let () = msg_send![render_layer, setContentsGravity: kCAGravityTopLeft]; + + let _: *mut c_void = msg_send![view, retain]; + Self::new(NonNull::new(view), render_layer) + } + + pub unsafe fn from_layer(layer: &mtl::MetalLayerRef) -> Self { + let class = class!(CAMetalLayer); + let proper_kind: BOOL = msg_send![layer, isKindOfClass: class]; + assert_eq!(proper_kind, YES); + Self::new(None, layer.to_owned()) + } + + fn dimensions(&self) -> wgt::Extent3d { + let (size, scale): (mtl::CGSize, mtl::CGFloat) = match self.view { + Some(view) if !cfg!(target_os = "macos") => unsafe { + let bounds: CGRect = msg_send![view.as_ptr(), bounds]; + let window: Option> = msg_send![view.as_ptr(), window]; + let screen = window.and_then(|window| -> Option> { + msg_send![window.as_ptr(), screen] + }); + match screen { + Some(screen) => { + let screen_space: *mut Object = msg_send![screen.as_ptr(), coordinateSpace]; + let rect: CGRect = msg_send![view.as_ptr(), convertRect:bounds toCoordinateSpace:screen_space]; + let scale_factor: mtl::CGFloat = msg_send![screen.as_ptr(), nativeScale]; + (rect.size, scale_factor) + } + None => (bounds.size, 1.0), + } + }, + _ => unsafe { + let render_layer_borrow = self.render_layer.lock(); + let render_layer = render_layer_borrow.as_ref(); + let bounds: CGRect = msg_send![render_layer, bounds]; + let contents_scale: mtl::CGFloat = msg_send![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 { + unsafe fn configure( + &mut self, + device: &super::Device, + config: &crate::SurfaceConfiguration, + ) -> Result<(), crate::SurfaceError> { + log::info!("build swapchain {:?}", config); + + let caps = &device.shared.private_caps; + let mtl_format = caps.map_format(config.format); + + let render_layer = self.render_layer.lock(); + let framebuffer_only = config.usage == crate::TextureUse::COLOR_TARGET; + let display_sync = config.present_mode != wgt::PresentMode::Immediate; + let drawable_size = + mtl::CGSize::new(config.extent.width as f64, config.extent.height as f64); + + match config.composite_alpha_mode { + crate::CompositeAlphaMode::Opaque => render_layer.set_opaque(true), + crate::CompositeAlphaMode::Alpha => render_layer.set_opaque(false), + crate::CompositeAlphaMode::PremultipliedAlpha => (), + } + + let device_raw = device.shared.device.lock(); + unsafe { + // On iOS, unless the user supplies a view with a CAMetalLayer, we + // create one as a sublayer. However, when the view changes size, + // its sublayers are not automatically resized, and we must resize + // it here. The drawable size and the layer size don't correlate + #[cfg(target_os = "ios")] + { + if let Some(view) = self.view { + let main_layer: *mut Object = msg_send![view.as_ptr(), layer]; + let bounds: CGRect = msg_send![main_layer, bounds]; + let () = msg_send![*render_layer, setFrame: bounds]; + } + } + render_layer.set_device(&*device_raw); + render_layer.set_pixel_format(mtl_format); + render_layer.set_framebuffer_only(framebuffer_only); + render_layer.set_presents_with_transaction(self.present_with_transaction); + + // this gets ignored on iOS for certain OS/device combinations (iphone5s iOS 10.3) + let () = + msg_send![*render_layer, setMaximumDrawableCount: config.swap_chain_size as u64]; + + render_layer.set_drawable_size(drawable_size); + if caps.can_set_next_drawable_timeout { + let () = msg_send![*render_layer, setAllowsNextDrawableTimeout:false]; + } + if caps.can_set_display_sync { + let () = msg_send![*render_layer, setDisplaySyncEnabled: display_sync]; + } + }; + + Ok(()) + } + + unsafe fn unconfigure(&mut self, _device: &super::Device) { + self.raw_swapchain_format = mtl::MTLPixelFormat::Invalid; + } + + unsafe fn acquire_texture( + &mut self, + _timeout_ms: u32, //TODO + ) -> Result>, crate::SurfaceError> { + let render_layer = self.render_layer.lock(); + let (drawable, _texture) = autoreleasepool(|| { + let drawable = render_layer.next_drawable().unwrap(); + (drawable.to_owned(), drawable.texture().to_owned()) + }); + let size = render_layer.drawable_size(); + + let suf_texture = super::SurfaceTexture { + texture: super::Resource, + drawable, + present_with_transaction: self.present_with_transaction, + }; + + Ok(Some(crate::AcquiredSurfaceTexture { + texture: suf_texture, + suboptimal: false, + })) + } + + unsafe fn discard_texture(&mut self, _texture: super::SurfaceTexture) {} +} diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 3642b24c2..4bb97084f 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1434,12 +1434,12 @@ pub enum TextureFormat { /// [0, 255] converted to/from float [0, 1] in shader. /// /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2RgbA8Unorm = 56, + //Etc2RgbA8Unorm = 56, /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 8 bit integer RGB + 8 bit alpha. /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. /// /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2RgbA8UnormSrgb = 57, + //Etc2RgbA8UnormSrgb = 57, /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer R. /// [0, 255] converted to/from float [0, 1] in shader. /// @@ -1704,8 +1704,8 @@ impl TextureFormat { Self::Etc2RgbUnormSrgb => (etc2, float, srgb, (4, 4), 8, basic), Self::Etc2RgbA1Unorm => (etc2, float, linear, (4, 4), 8, basic), Self::Etc2RgbA1UnormSrgb => (etc2, float, srgb, (4, 4), 8, basic), - Self::Etc2RgbA8Unorm => (etc2, float, linear, (4, 4), 16, basic), - Self::Etc2RgbA8UnormSrgb => (etc2, float, srgb, (4, 4), 16, basic), + //Self::Etc2RgbA8Unorm => (etc2, float, linear, (4, 4), 16, basic), + //Self::Etc2RgbA8UnormSrgb => (etc2, float, srgb, (4, 4), 16, basic), Self::EacRUnorm => (etc2, float, linear, (4, 4), 8, basic), Self::EacRSnorm => (etc2, float, linear, (4, 4), 8, basic), Self::EtcRgUnorm => (etc2, float, linear, (4, 4), 16, basic),