mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-11-22 14:55:05 +00:00
hal/metal: instance,adapter, and surface
This commit is contained in:
parent
6d229847be
commit
3475d839f9
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1915,8 +1915,11 @@ name = "wgpu-hal"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"log",
|
||||||
"metal",
|
"metal",
|
||||||
"naga",
|
"naga",
|
||||||
|
"objc",
|
||||||
|
"parking_lot",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
|
@ -12,16 +12,20 @@ license = "MIT OR Apache-2.0"
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
metal = ["mtl"]
|
default = ["metal"]
|
||||||
|
metal = ["mtl", "objc", "parking_lot"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
|
log = "0.4"
|
||||||
|
parking_lot = { version = "0.11", optional = true }
|
||||||
raw-window-handle = "0.3"
|
raw-window-handle = "0.3"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
wgt = { package = "wgpu-types", path = "../wgpu-types" }
|
wgt = { package = "wgpu-types", path = "../wgpu-types" }
|
||||||
|
|
||||||
# backends
|
# backends
|
||||||
mtl = { package = "metal", version = "0.22", optional = true }
|
mtl = { package = "metal", version = "0.22", optional = true }
|
||||||
|
objc = { version = "0.2.5", optional = true }
|
||||||
|
|
||||||
[dependencies.naga]
|
[dependencies.naga]
|
||||||
git = "https://github.com/gfx-rs/naga"
|
git = "https://github.com/gfx-rs/naga"
|
||||||
|
991
wgpu-hal/src/metal/adapter.rs
Normal file
991
wgpu-hal/src/metal/adapter.rs
Normal file
@ -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<super::AdapterShared>) -> Self {
|
||||||
|
Self { shared }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Adapter<super::Api> for super::Adapter {
|
||||||
|
unsafe fn open(
|
||||||
|
&self,
|
||||||
|
features: wgt::Features,
|
||||||
|
) -> Result<crate::OpenDevice<super::Api>, 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<crate::SurfaceCapabilities> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
wgpu-hal/src/metal/command.rs
Normal file
1
wgpu-hal/src/metal/command.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
0
wgpu-hal/src/metal/conv.rs
Normal file
0
wgpu-hal/src/metal/conv.rs
Normal file
1
wgpu-hal/src/metal/device.rs
Normal file
1
wgpu-hal/src/metal/device.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,6 +1,13 @@
|
|||||||
#![allow(unused_variables)]
|
#![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)]
|
#[derive(Clone)]
|
||||||
pub struct Api;
|
pub struct Api;
|
||||||
@ -10,19 +17,20 @@ pub struct Encoder;
|
|||||||
pub struct Resource;
|
pub struct Resource;
|
||||||
|
|
||||||
type DeviceResult<T> = Result<T, crate::DeviceError>;
|
type DeviceResult<T> = Result<T, crate::DeviceError>;
|
||||||
|
type ResourceIndex = u32;
|
||||||
|
|
||||||
impl crate::Api for Api {
|
impl crate::Api for Api {
|
||||||
type Instance = Context;
|
type Instance = Instance;
|
||||||
type Surface = Context;
|
type Surface = Surface;
|
||||||
type Adapter = Context;
|
type Adapter = Adapter;
|
||||||
type Queue = Context;
|
type Queue = Queue;
|
||||||
type Device = Context;
|
type Device = Device;
|
||||||
|
|
||||||
type CommandBuffer = Encoder;
|
type CommandBuffer = Encoder;
|
||||||
|
|
||||||
type Buffer = Resource;
|
type Buffer = Resource;
|
||||||
type Texture = Resource;
|
type Texture = Resource;
|
||||||
type SurfaceTexture = Resource;
|
type SurfaceTexture = SurfaceTexture;
|
||||||
type TextureView = Resource;
|
type TextureView = Resource;
|
||||||
type Sampler = Resource;
|
type Sampler = Resource;
|
||||||
type QuerySet = Resource;
|
type QuerySet = Resource;
|
||||||
@ -36,59 +44,210 @@ impl crate::Api for Api {
|
|||||||
type ComputePipeline = Resource;
|
type ComputePipeline = Resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Instance<Api> for Context {
|
pub struct Instance {}
|
||||||
|
|
||||||
|
impl crate::Instance<Api> for Instance {
|
||||||
unsafe fn init() -> Result<Self, crate::InstanceError> {
|
unsafe fn init() -> Result<Self, crate::InstanceError> {
|
||||||
Ok(Context)
|
Ok(Instance {})
|
||||||
}
|
}
|
||||||
unsafe fn create_surface(
|
unsafe fn create_surface(
|
||||||
&self,
|
&self,
|
||||||
rwh: &impl raw_window_handle::HasRawWindowHandle,
|
has_handle: &impl raw_window_handle::HasRawWindowHandle,
|
||||||
) -> Result<Context, crate::InstanceError> {
|
) -> Result<Surface, crate::InstanceError> {
|
||||||
Ok(Context)
|
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<crate::ExposedAdapter<Api>> {
|
unsafe fn enumerate_adapters(&self) -> Vec<crate::ExposedAdapter<Api>> {
|
||||||
Vec::new()
|
let devices = mtl::Device::all();
|
||||||
|
let mut adapters: Vec<crate::ExposedAdapter<Api>> = 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<Api> for Context {
|
#[derive(Clone, Debug)]
|
||||||
unsafe fn configure(
|
struct PrivateCapabilities {
|
||||||
&mut self,
|
family_check: bool,
|
||||||
device: &Context,
|
msl_version: mtl::MTLLanguageVersion,
|
||||||
config: &crate::SurfaceConfiguration,
|
exposed_queues: usize,
|
||||||
) -> Result<(), crate::SurfaceError> {
|
read_write_texture_tier: mtl::MTLReadWriteTextureTier,
|
||||||
Ok(())
|
resource_heaps: bool,
|
||||||
}
|
argument_buffers: bool,
|
||||||
|
shared_textures: bool,
|
||||||
unsafe fn unconfigure(&mut self, device: &Context) {}
|
mutable_comparison_samplers: bool,
|
||||||
|
sampler_clamp_to_border: bool,
|
||||||
unsafe fn acquire_texture(
|
sampler_lod_average: bool,
|
||||||
&mut self,
|
base_instance: bool,
|
||||||
timeout_ms: u32,
|
base_vertex_instance_drawing: bool,
|
||||||
) -> Result<Option<crate::AcquiredSurfaceTexture<Api>>, crate::SurfaceError> {
|
dual_source_blending: bool,
|
||||||
Ok(None)
|
low_power: bool,
|
||||||
}
|
headless: bool,
|
||||||
unsafe fn discard_texture(&mut self, texture: Resource) {}
|
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<Api> for Context {
|
#[derive(Clone, Debug)]
|
||||||
unsafe fn open(&self, features: wgt::Features) -> DeviceResult<crate::OpenDevice<Api>> {
|
struct PrivateDisabilities {
|
||||||
Err(crate::DeviceError::Lost)
|
/// Near depth is not respected properly on some Intel GPUs.
|
||||||
}
|
broken_viewport_near_depth: bool,
|
||||||
unsafe fn close(&self, device: Context) {}
|
/// Multi-target clears don't appear to work properly on Intel GPUs.
|
||||||
unsafe fn texture_format_capabilities(
|
broken_layered_clear_image: bool,
|
||||||
&self,
|
}
|
||||||
format: wgt::TextureFormat,
|
|
||||||
) -> crate::TextureFormatCapability {
|
struct AdapterShared {
|
||||||
crate::TextureFormatCapability::empty()
|
device: Mutex<mtl::Device>,
|
||||||
}
|
disabilities: PrivateDisabilities,
|
||||||
unsafe fn surface_capabilities(&self, surface: &Context) -> Option<crate::SurfaceCapabilities> {
|
private_caps: PrivateCapabilities,
|
||||||
None
|
}
|
||||||
|
|
||||||
|
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<Api> for Context {
|
struct Adapter {
|
||||||
|
shared: Arc<AdapterShared>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Queue {
|
||||||
|
raw: mtl::CommandQueue,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
shared: Arc<AdapterShared>,
|
||||||
|
features: wgt::Features,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Surface {
|
||||||
|
view: Option<NonNull<objc::runtime::Object>>,
|
||||||
|
render_layer: Mutex<mtl::MetalLayer>,
|
||||||
|
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<Api> for Queue {
|
||||||
unsafe fn submit<I>(
|
unsafe fn submit<I>(
|
||||||
&mut self,
|
&mut self,
|
||||||
command_buffers: I,
|
command_buffers: I,
|
||||||
@ -98,8 +257,8 @@ impl crate::Queue<Api> for Context {
|
|||||||
}
|
}
|
||||||
unsafe fn present(
|
unsafe fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
surface: &mut Context,
|
surface: &mut Surface,
|
||||||
texture: Resource,
|
texture: SurfaceTexture,
|
||||||
) -> Result<(), crate::SurfaceError> {
|
) -> Result<(), crate::SurfaceError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -114,7 +273,7 @@ impl crate::Device<Api> for Context {
|
|||||||
&self,
|
&self,
|
||||||
buffer: &Resource,
|
buffer: &Resource,
|
||||||
range: crate::MemoryRange,
|
range: crate::MemoryRange,
|
||||||
) -> DeviceResult<std::ptr::NonNull<u8>> {
|
) -> DeviceResult<NonNull<u8>> {
|
||||||
Err(crate::DeviceError::Lost)
|
Err(crate::DeviceError::Lost)
|
||||||
}
|
}
|
||||||
unsafe fn unmap_buffer(&self, buffer: &Resource) -> DeviceResult<()> {
|
unsafe fn unmap_buffer(&self, buffer: &Resource) -> DeviceResult<()> {
|
||||||
|
265
wgpu-hal/src/metal/surface.rs
Normal file
265
wgpu-hal/src/metal/surface.rs
Normal file
@ -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<NonNull<Object>>, 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<NonNull<Object>> = msg_send![view.as_ptr(), window];
|
||||||
|
let screen = window.and_then(|window| -> Option<NonNull<Object>> {
|
||||||
|
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<super::Api> 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<Option<crate::AcquiredSurfaceTexture<super::Api>>, 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) {}
|
||||||
|
}
|
@ -1434,12 +1434,12 @@ pub enum TextureFormat {
|
|||||||
/// [0, 255] converted to/from float [0, 1] in shader.
|
/// [0, 255] converted to/from float [0, 1] in shader.
|
||||||
///
|
///
|
||||||
/// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format.
|
/// [`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.
|
/// 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.
|
/// 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.
|
/// [`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.
|
/// 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.
|
/// [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::Etc2RgbUnormSrgb => (etc2, float, srgb, (4, 4), 8, basic),
|
||||||
Self::Etc2RgbA1Unorm => (etc2, float, linear, (4, 4), 8, basic),
|
Self::Etc2RgbA1Unorm => (etc2, float, linear, (4, 4), 8, basic),
|
||||||
Self::Etc2RgbA1UnormSrgb => (etc2, float, srgb, (4, 4), 8, basic),
|
Self::Etc2RgbA1UnormSrgb => (etc2, float, srgb, (4, 4), 8, basic),
|
||||||
Self::Etc2RgbA8Unorm => (etc2, float, linear, (4, 4), 16, basic),
|
//Self::Etc2RgbA8Unorm => (etc2, float, linear, (4, 4), 16, basic),
|
||||||
Self::Etc2RgbA8UnormSrgb => (etc2, float, srgb, (4, 4), 16, basic),
|
//Self::Etc2RgbA8UnormSrgb => (etc2, float, srgb, (4, 4), 16, basic),
|
||||||
Self::EacRUnorm => (etc2, float, linear, (4, 4), 8, basic),
|
Self::EacRUnorm => (etc2, float, linear, (4, 4), 8, basic),
|
||||||
Self::EacRSnorm => (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),
|
Self::EtcRgUnorm => (etc2, float, linear, (4, 4), 16, basic),
|
||||||
|
Loading…
Reference in New Issue
Block a user