hal/mtl: pipelines and fences

This commit is contained in:
Dzmitry Malyshau
2021-06-09 23:38:48 -04:00
parent 9f904700ee
commit bdaf57dbed
12 changed files with 761 additions and 98 deletions

1
Cargo.lock generated
View File

@@ -1917,7 +1917,6 @@ dependencies = [
"arrayvec",
"bitflags",
"foreign-types",
"fxhash",
"log",
"metal",
"naga",

View File

@@ -2175,6 +2175,7 @@ impl<A: HalApi> Device<A> {
use resource::CreateQuerySetError as Error;
match desc.ty {
wgt::QueryType::Occlusion => {}
wgt::QueryType::Timestamp => {
self.require_features(wgt::Features::TIMESTAMP_QUERY)?;
}

View File

@@ -18,7 +18,6 @@ metal = ["foreign-types", "mtl", "objc", "parking_lot", "naga/msl-out"]
[dependencies]
arrayvec = "0.5"
bitflags = "1.0"
fxhash = "0.2"
log = "0.4"
parking_lot = { version = "0.11", optional = true }
raw-window-handle = "0.3"

View File

@@ -174,7 +174,7 @@ impl crate::Device<Api> for Context {
&self,
desc: &crate::ShaderModuleDescriptor,
shader: crate::NagaShader,
) -> Result<Resource, (crate::ShaderError, crate::NagaShader)> {
) -> Result<Resource, crate::ShaderError> {
Ok(Resource)
}
unsafe fn destroy_shader_module(&self, module: Resource) {}

View File

@@ -88,6 +88,8 @@ pub enum ShaderError {
pub enum PipelineError {
#[error("linkage failed for stage {0:?}: {1}")]
Linkage(wgt::ShaderStage, String),
#[error("entry point for stage {0:?} is invalid")]
EntryPoint(naga::ShaderStage),
#[error(transparent)]
Device(#[from] DeviceError),
}
@@ -239,7 +241,7 @@ pub trait Device<A: Api> {
&self,
desc: &ShaderModuleDescriptor,
shader: NagaShader,
) -> Result<A::ShaderModule, (ShaderError, NagaShader)>;
) -> Result<A::ShaderModule, ShaderError>;
unsafe fn destroy_shader_module(&self, module: A::ShaderModule);
unsafe fn create_render_pipeline(
&self,
@@ -738,6 +740,15 @@ pub struct BindGroupEntry {
pub resource_index: u32,
}
/// BindGroup descriptor.
///
/// Valid usage:
///. - `entries` has to be sorted by ascending `BindGroupEntry::binding`
///. - `entries` has to have the same set of `BindGroupEntry::binding` as `layout`
///. - each entry has to be compatible with the `layout`
///. - each entry's `BindGroupEntry::resource_index` is within range
/// of the corresponding resource array, selected by the relevant
/// `BindGroupLayoutEntry`.
#[derive(Clone, Debug)]
pub struct BindGroupDescriptor<'a, A: Api> {
pub label: Label<'a>,
@@ -754,6 +765,7 @@ pub struct CommandBufferDescriptor<'a> {
}
/// Naga shader module.
#[derive(Debug)]
pub struct NagaShader {
/// Shader module IR.
pub module: naga::Module,

View File

@@ -25,7 +25,6 @@ impl crate::Adapter<super::Api> for super::Adapter {
features,
},
queue: super::Queue {
raw: raw_device.new_command_queue(),
},
})
}
@@ -802,10 +801,15 @@ impl super::PrivateCapabilities {
supports_binary_archives: family_check
&& (device.supports_family(MTLGPUFamily::Apple3)
|| device.supports_family(MTLGPUFamily::Mac1)),
supports_capture_manager: if os_is_mac {
Self::version_at_least(major, minor, 10, 13)
} else {
Self::version_at_least(major, minor, 11, 0)
},
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 {
can_set_next_drawable_timeout: if os_is_mac {
Self::version_at_least(major, minor, 10, 13)
} else {
Self::version_at_least(major, minor, 11, 0)

View File

@@ -1,19 +1,17 @@
use super::{Api, Resource};
use std::ops::Range;
impl crate::CommandBuffer<Api> for super::Encoder {
impl crate::CommandBuffer<super::Api> for super::CommandBuffer {
unsafe fn finish(&mut self) {}
unsafe fn transition_buffers<'a, T>(&mut self, barriers: T)
where
T: Iterator<Item = crate::BufferBarrier<'a, Api>>,
T: Iterator<Item = crate::BufferBarrier<'a, super::Api>>,
{
}
unsafe fn transition_textures<'a, T>(&mut self, barriers: T)
where
T: Iterator<Item = crate::TextureBarrier<'a, Api>>,
T: Iterator<Item = crate::TextureBarrier<'a, super::Api>>,
{
}
@@ -62,7 +60,7 @@ impl crate::CommandBuffer<Api> for super::Encoder {
// render
unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor<Api>) {}
unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor<super::Api>) {}
unsafe fn end_render_pass(&mut self) {}
unsafe fn set_bind_group(
@@ -90,11 +88,11 @@ impl crate::CommandBuffer<Api> for super::Encoder {
unsafe fn set_index_buffer<'a>(
&mut self,
binding: crate::BufferBinding<'a, Api>,
binding: crate::BufferBinding<'a, super::Api>,
format: wgt::IndexFormat,
) {
}
unsafe fn set_vertex_buffer<'a>(&mut self, index: u32, binding: crate::BufferBinding<'a, Api>) {
unsafe fn set_vertex_buffer<'a>(&mut self, index: u32, binding: crate::BufferBinding<'a, super::Api>) {
}
unsafe fn set_viewport(&mut self, rect: &crate::Rect<f32>, depth_range: Range<f32>) {}
unsafe fn set_scissor_rect(&mut self, rect: &crate::Rect<u32>) {}

View File

@@ -75,3 +75,180 @@ pub fn map_border_color(border_color: wgt::SamplerBorderColor) -> mtl::MTLSample
wgt::SamplerBorderColor::OpaqueWhite => OpaqueWhite,
}
}
pub fn map_primitive_topology(
topology: wgt::PrimitiveTopology,
) -> (mtl::MTLPrimitiveTopologyClass, mtl::MTLPrimitiveType) {
use wgt::PrimitiveTopology as Pt;
match topology {
Pt::PointList => (
mtl::MTLPrimitiveTopologyClass::Point,
mtl::MTLPrimitiveType::Point,
),
Pt::LineList => (
mtl::MTLPrimitiveTopologyClass::Line,
mtl::MTLPrimitiveType::Line,
),
Pt::LineStrip => (
mtl::MTLPrimitiveTopologyClass::Line,
mtl::MTLPrimitiveType::LineStrip,
),
Pt::TriangleList => (
mtl::MTLPrimitiveTopologyClass::Triangle,
mtl::MTLPrimitiveType::Triangle,
),
Pt::TriangleStrip => (
mtl::MTLPrimitiveTopologyClass::Triangle,
mtl::MTLPrimitiveType::TriangleStrip,
),
}
}
pub fn map_color_write(mask: wgt::ColorWrite) -> mtl::MTLColorWriteMask {
let mut raw_mask = mtl::MTLColorWriteMask::empty();
if mask.contains(wgt::ColorWrite::RED) {
raw_mask |= mtl::MTLColorWriteMask::Red;
}
if mask.contains(wgt::ColorWrite::GREEN) {
raw_mask |= mtl::MTLColorWriteMask::Green;
}
if mask.contains(wgt::ColorWrite::BLUE) {
raw_mask |= mtl::MTLColorWriteMask::Blue;
}
if mask.contains(wgt::ColorWrite::ALPHA) {
raw_mask |= mtl::MTLColorWriteMask::Alpha;
}
raw_mask
}
pub fn map_blend_factor(factor: wgt::BlendFactor) -> mtl::MTLBlendFactor {
use mtl::MTLBlendFactor::*;
use wgt::BlendFactor as Bf;
match factor {
Bf::Zero => Zero,
Bf::One => One,
Bf::Src => SourceColor,
Bf::OneMinusSrc => OneMinusSourceColor,
Bf::Dst => DestinationColor,
Bf::OneMinusDst => OneMinusDestinationColor,
Bf::SrcAlpha => SourceAlpha,
Bf::OneMinusSrcAlpha => OneMinusSourceAlpha,
Bf::DstAlpha => DestinationAlpha,
Bf::OneMinusDstAlpha => OneMinusDestinationAlpha,
Bf::Constant => BlendColor,
Bf::OneMinusConstant => OneMinusBlendColor,
//Bf::ConstantAlpha => BlendAlpha,
//Bf::OneMinusConstantAlpha => OneMinusBlendAlpha,
Bf::SrcAlphaSaturated => SourceAlphaSaturated,
//Bf::Src1 => Source1Color,
//Bf::OneMinusSrc1 => OneMinusSource1Color,
//Bf::Src1Alpha => Source1Alpha,
//Bf::OneMinusSrc1Alpha => OneMinusSource1Alpha,
}
}
pub fn map_blend_op(operation: wgt::BlendOperation) -> mtl::MTLBlendOperation {
use mtl::MTLBlendOperation::*;
use wgt::BlendOperation as Bo;
match operation {
Bo::Add => Add,
Bo::Subtract => Subtract,
Bo::ReverseSubtract => ReverseSubtract,
Bo::Min => Min,
Bo::Max => Max,
}
}
pub fn map_blend_component(
component: &wgt::BlendComponent,
) -> (
mtl::MTLBlendOperation,
mtl::MTLBlendFactor,
mtl::MTLBlendFactor,
) {
(
map_blend_op(component.operation),
map_blend_factor(component.src_factor),
map_blend_factor(component.dst_factor),
)
}
pub fn map_vertex_format(format: wgt::VertexFormat) -> mtl::MTLVertexFormat {
use mtl::MTLVertexFormat::*;
use wgt::VertexFormat as Vf;
match format {
Vf::Unorm8x2 => UChar2Normalized,
Vf::Snorm8x2 => Char2Normalized,
Vf::Uint8x2 => UChar2,
Vf::Sint8x2 => Char2,
Vf::Unorm8x4 => UChar4Normalized,
Vf::Snorm8x4 => Char4Normalized,
Vf::Uint8x4 => UChar4,
Vf::Sint8x4 => Char4,
Vf::Unorm16x2 => UShort2Normalized,
Vf::Snorm16x2 => Short2Normalized,
Vf::Uint16x2 => UShort2,
Vf::Sint16x2 => Short2,
Vf::Float16x2 => Half2,
Vf::Unorm16x4 => UShort4Normalized,
Vf::Snorm16x4 => Short4Normalized,
Vf::Uint16x4 => UShort4,
Vf::Sint16x4 => Short4,
Vf::Float16x4 => Half4,
Vf::Uint32 => UInt,
Vf::Sint32 => Int,
Vf::Float32 => Float,
Vf::Uint32x2 => UInt2,
Vf::Sint32x2 => Int2,
Vf::Float32x2 => Float2,
Vf::Uint32x3 => UInt3,
Vf::Sint32x3 => Int3,
Vf::Float32x3 => Float3,
Vf::Uint32x4 => UInt4,
Vf::Sint32x4 => Int4,
Vf::Float32x4 => Float4,
}
}
pub fn map_step_mode(mode: wgt::InputStepMode) -> mtl::MTLVertexStepFunction {
match mode {
wgt::InputStepMode::Vertex => mtl::MTLVertexStepFunction::PerVertex,
wgt::InputStepMode::Instance => mtl::MTLVertexStepFunction::PerInstance,
}
}
pub fn map_stencil_op(op: wgt::StencilOperation) -> mtl::MTLStencilOperation {
use mtl::MTLStencilOperation::*;
use wgt::StencilOperation as So;
match op {
So::Keep => Keep,
So::Zero => Zero,
So::Replace => Replace,
So::IncrementClamp => IncrementClamp,
So::IncrementWrap => IncrementWrap,
So::DecrementClamp => DecrementClamp,
So::DecrementWrap => DecrementWrap,
So::Invert => Invert,
}
}
pub fn map_winding(winding: wgt::FrontFace) -> mtl::MTLWinding {
match winding {
wgt::FrontFace::Cw => mtl::MTLWinding::Clockwise,
wgt::FrontFace::Ccw => mtl::MTLWinding::CounterClockwise,
}
}
pub fn map_cull_mode(face: Option<wgt::Face>) -> mtl::MTLCullMode {
match face {
None => mtl::MTLCullMode::None,
Some(wgt::Face::Front) => mtl::MTLCullMode::Front,
Some(wgt::Face::Back) => mtl::MTLCullMode::Back,
}
}

View File

@@ -1,9 +1,136 @@
use std::{ptr, sync::Arc};
use std::{
ptr,
sync::{atomic, Arc},
thread, time,
};
use super::conv;
use crate::aux::map_naga_stage;
type DeviceResult<T> = Result<T, crate::DeviceError>;
struct CompiledShader {
library: mtl::Library,
function: mtl::Function,
wg_size: mtl::MTLSize,
sized_bindings: Vec<naga::ResourceBinding>,
}
fn create_stencil_desc(
face: &wgt::StencilFaceState,
read_mask: u32,
write_mask: u32,
) -> mtl::StencilDescriptor {
let desc = mtl::StencilDescriptor::new();
desc.set_stencil_compare_function(conv::map_compare_function(face.compare));
desc.set_read_mask(read_mask);
desc.set_write_mask(write_mask);
desc.set_stencil_failure_operation(conv::map_stencil_op(face.fail_op));
desc.set_depth_failure_operation(conv::map_stencil_op(face.depth_fail_op));
desc.set_depth_stencil_pass_operation(conv::map_stencil_op(face.pass_op));
desc
}
fn create_depth_stencil_desc(state: &wgt::DepthStencilState) -> mtl::DepthStencilDescriptor {
let desc = mtl::DepthStencilDescriptor::new();
desc.set_depth_compare_function(conv::map_compare_function(state.depth_compare));
desc.set_depth_write_enabled(state.depth_write_enabled);
let s = &state.stencil;
if s.is_enabled() {
let front_desc = create_stencil_desc(&s.front, s.read_mask, s.write_mask);
desc.set_front_face_stencil(Some(&front_desc));
let back_desc = create_stencil_desc(&s.back, s.read_mask, s.write_mask);
desc.set_front_face_stencil(Some(&back_desc));
}
desc
}
impl super::Device {
fn load_shader(
&self,
stage: &crate::ProgrammableStage<super::Api>,
layout: &super::PipelineLayout,
primitive_class: mtl::MTLPrimitiveTopologyClass,
naga_stage: naga::ShaderStage,
) -> Result<CompiledShader, crate::PipelineError> {
let stage_bit = map_naga_stage(naga_stage);
let pipeline_options = naga::back::msl::PipelineOptions {
allow_point_size: match primitive_class {
mtl::MTLPrimitiveTopologyClass::Point => true,
_ => false,
},
};
let module = &stage.module.raw.module;
let (source, info) = naga::back::msl::write_string(
module,
&stage.module.raw.info,
&layout.naga_options,
&pipeline_options,
)
.map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("MSL: {:?}", e)))?;
let options = mtl::CompileOptions::new();
options.set_language_version(self.shared.private_caps.msl_version);
let library = self
.shared
.device
.lock()
.new_library_with_source(source.as_ref(), &options)
.map_err(|err| {
log::warn!("Naga generated shader:\n{}", source);
crate::PipelineError::Linkage(stage_bit, format!("Metal: {}", err))
})?;
// collect sizes indices
let mut sized_bindings = Vec::new();
for (_handle, var) in module.global_variables.iter() {
if let naga::TypeInner::Struct { ref members, .. } = module.types[var.ty].inner {
if let Some(member) = members.last() {
if let naga::TypeInner::Array {
size: naga::ArraySize::Dynamic,
..
} = module.types[member.ty].inner
{
// Note: unwraps are fine, since the MSL is already generated
let br = var.binding.clone().unwrap();
sized_bindings.push(br);
}
}
}
}
let (ep, internal_name) = module
.entry_points
.iter()
.zip(info.entry_point_names)
.find(|&(ep, _)| ep.stage == naga_stage && ep.name == stage.entry_point)
.ok_or(crate::PipelineError::EntryPoint(naga_stage))?;
let name = internal_name
.as_ref()
.map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("{}", e)))?;
let wg_size = mtl::MTLSize {
width: ep.workgroup_size[0] as _,
height: ep.workgroup_size[1] as _,
depth: ep.workgroup_size[2] as _,
};
let function = library.get_function(name, None).map_err(|e| {
log::error!("get_function: {:?}", e);
crate::PipelineError::EntryPoint(naga_stage)
})?;
Ok(CompiledShader {
library,
function,
wg_size,
sized_bindings,
})
}
}
impl crate::Device<super::Api> for super::Device {
unsafe fn create_buffer(&self, desc: &crate::BufferDescriptor) -> DeviceResult<super::Buffer> {
let map_read = desc.usage.contains(crate::BufferUse::MAP_READ);
@@ -15,7 +142,10 @@ impl crate::Device<super::Api> for super::Device {
} else {
mtl::MTLResourceOptions::StorageModePrivate
};
options.set(mtl::MTLResourceOptions::CPUCacheModeDefaultCache, map_read);
options.set(
mtl::MTLResourceOptions::CPUCacheModeWriteCombined,
map_write,
);
//TODO: HazardTrackingModeUntracked
@@ -111,16 +241,16 @@ impl crate::Device<super::Api> for super::Device {
texture: &super::Texture,
desc: &crate::TextureViewDescriptor,
) -> DeviceResult<super::TextureView> {
let mtl_format = self.shared.private_caps.map_format(desc.format);
let raw_format = self.shared.private_caps.map_format(desc.format);
let mtl_type = if texture.raw_type == mtl::MTLTextureType::D2Multisample {
let raw_type = if texture.raw_type == mtl::MTLTextureType::D2Multisample {
texture.raw_type
} else {
conv::map_texture_view_dimension(desc.dimension)
};
let raw = if mtl_format == texture.raw_format
&& mtl_type == texture.raw_type
let raw = if raw_format == texture.raw_format
&& raw_type == texture.raw_type
&& desc.range == wgt::ImageSubresourceRange::default()
{
// Some images are marked as framebuffer-only, and we can't create aliases of them.
@@ -137,8 +267,8 @@ impl crate::Device<super::Api> for super::Device {
};
texture.raw.new_texture_view_from_slice(
mtl_format,
mtl_type,
raw_format,
raw_type,
mtl::NSRange {
location: desc.range.base_mip_level as _,
length: mip_level_count as _,
@@ -205,21 +335,19 @@ impl crate::Device<super::Api> for super::Device {
unsafe fn create_command_buffer(
&self,
desc: &crate::CommandBufferDescriptor,
) -> DeviceResult<super::Encoder> {
Ok(super::Encoder)
) -> DeviceResult<super::CommandBuffer> {
let raw = self.shared.create_command_buffer().to_owned();
Ok(super::CommandBuffer { raw })
}
unsafe fn destroy_command_buffer(&self, cmd_buf: super::Encoder) {}
unsafe fn destroy_command_buffer(&self, _cmd_buf: super::CommandBuffer) {}
unsafe fn create_bind_group_layout(
&self,
desc: &crate::BindGroupLayoutDescriptor,
) -> DeviceResult<super::BindGroupLayout> {
let map = desc
.entries
.iter()
.cloned()
.map(|entry| (entry.binding, entry))
.collect();
let mut map = desc.entries.to_vec();
map.sort_by_key(|e| e.binding);
Ok(super::BindGroupLayout {
entries: Arc::new(map),
})
@@ -239,11 +367,6 @@ impl crate::Device<super::Api> for super::Device {
sizes_buffer: Option<super::ResourceIndex>,
sizes_count: u8,
}
impl StageInfo {
fn stage_bit(&self) -> wgt::ShaderStage {
crate::aux::map_naga_stage(self.stage)
}
}
let mut stage_data = super::NAGA_STAGES.map(|&stage| StageInfo {
stage,
@@ -259,7 +382,7 @@ impl crate::Device<super::Api> for super::Device {
// First, place the push constants
for info in stage_data.iter_mut() {
for pcr in desc.push_constant_ranges {
if pcr.stages.contains(info.stage_bit()) {
if pcr.stages.contains(map_naga_stage(info.stage)) {
debug_assert_eq!(pcr.range.end % 4, 0);
info.pc_limit = (pcr.range.end / 4).max(info.pc_limit);
}
@@ -287,7 +410,7 @@ impl crate::Device<super::Api> for super::Device {
let mut sized_buffer_bindings = Vec::new();
let base_resource_indices = stage_data.map(|info| info.counters.clone());
for entry in bgl.entries.values() {
for entry in bgl.entries.iter() {
match entry.ty {
wgt::BindingType::Buffer {
ty,
@@ -296,7 +419,7 @@ impl crate::Device<super::Api> for super::Device {
} => {
if has_dynamic_offset {
dynamic_buffers.push(stage_data.map(|info| {
if entry.visibility.contains(info.stage_bit()) {
if entry.visibility.contains(map_naga_stage(info.stage)) {
info.counters.buffers
} else {
!0
@@ -307,7 +430,7 @@ impl crate::Device<super::Api> for super::Device {
wgt::BufferBindingType::Storage { .. } => {
sized_buffer_bindings.push((entry.binding, entry.visibility));
for info in stage_data.iter_mut() {
if entry.visibility.contains(info.stage_bit()) {
if entry.visibility.contains(map_naga_stage(info.stage)) {
info.sizes_count += 1;
}
}
@@ -318,7 +441,7 @@ impl crate::Device<super::Api> for super::Device {
}
for info in stage_data.iter_mut() {
if !entry.visibility.contains(info.stage_bit()) {
if !entry.visibility.contains(map_naga_stage(info.stage)) {
continue;
}
@@ -418,12 +541,12 @@ impl crate::Device<super::Api> for super::Device {
naga_options,
bind_group_infos,
push_constants_infos: stage_data.map(|info| {
info.pc_buffer
.map(|buffer_index| super::PushConstantsStage {
count: info.pc_limit,
buffer_index,
})
info.pc_buffer.map(|buffer_index| super::PushConstantsInfo {
count: info.pc_limit,
buffer_index,
})
}),
total_counters: stage_data.map(|info| info.counters.clone()),
})
}
unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) {}
@@ -432,15 +555,10 @@ impl crate::Device<super::Api> for super::Device {
&self,
desc: &crate::BindGroupDescriptor<super::Api>,
) -> DeviceResult<super::BindGroup> {
//TODO: avoid heap allocation
let mut entries = desc.entries.to_vec();
entries.sort_by_key(|e| e.binding);
let mut bg = super::BindGroup::default();
for (&stage, counter) in super::NAGA_STAGES.iter().zip(bg.counters.iter_mut()) {
let stage_bit = crate::aux::map_naga_stage(stage);
for entry in entries.iter() {
let layout = &desc.layout.entries[&entry.binding];
let stage_bit = map_naga_stage(stage);
for (entry, layout) in desc.entries.iter().zip(desc.layout.entries.iter()) {
if !layout.visibility.contains(stage_bit) {
continue;
}
@@ -476,47 +594,310 @@ impl crate::Device<super::Api> for super::Device {
&self,
desc: &crate::ShaderModuleDescriptor,
shader: crate::NagaShader,
) -> Result<Resource, (crate::ShaderError, crate::NagaShader)> {
Ok(Resource)
) -> Result<super::ShaderModule, crate::ShaderError> {
Ok(super::ShaderModule { raw: shader })
}
unsafe fn destroy_shader_module(&self, module: Resource) {}
unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) {}
unsafe fn create_render_pipeline(
&self,
desc: &crate::RenderPipelineDescriptor<Api>,
) -> Result<Resource, crate::PipelineError> {
Ok(Resource)
desc: &crate::RenderPipelineDescriptor<super::Api>,
) -> Result<super::RenderPipeline, crate::PipelineError> {
let descriptor = mtl::RenderPipelineDescriptor::new();
let (primitive_class, raw_primitive_type) =
conv::map_primitive_topology(desc.primitive.topology);
let vs = self.load_shader(
&desc.vertex_stage,
desc.layout,
primitive_class,
naga::ShaderStage::Vertex,
)?;
descriptor.set_vertex_function(Some(&vs.function));
// Fragment shader
let (fs_lib, fs_sized_bindings) = match desc.fragment_stage {
Some(ref stage) => {
let compiled = self.load_shader(
stage,
desc.layout,
primitive_class,
naga::ShaderStage::Fragment,
)?;
descriptor.set_fragment_function(Some(&compiled.function));
(Some(compiled.library), compiled.sized_bindings)
}
None => {
// TODO: This is a workaround for what appears to be a Metal validation bug
// A pixel format is required even though no attachments are provided
if desc.color_targets.is_empty() && desc.depth_stencil.is_none() {
descriptor.set_depth_attachment_pixel_format(mtl::MTLPixelFormat::Depth32Float);
}
(None, Vec::new())
}
};
for (i, ct) in desc.color_targets.iter().enumerate() {
let at_descriptor = descriptor.color_attachments().object_at(i as u64).unwrap();
let raw_format = self.shared.private_caps.map_format(ct.format);
at_descriptor.set_pixel_format(raw_format);
at_descriptor.set_write_mask(conv::map_color_write(ct.write_mask));
if let Some(ref blend) = ct.blend {
at_descriptor.set_blending_enabled(true);
let (color_op, color_src, color_dst) = conv::map_blend_component(&blend.color);
let (alpha_op, alpha_src, alpha_dst) = conv::map_blend_component(&blend.alpha);
at_descriptor.set_rgb_blend_operation(color_op);
at_descriptor.set_source_rgb_blend_factor(color_src);
at_descriptor.set_destination_rgb_blend_factor(color_dst);
at_descriptor.set_alpha_blend_operation(alpha_op);
at_descriptor.set_source_alpha_blend_factor(alpha_src);
at_descriptor.set_destination_alpha_blend_factor(alpha_dst);
}
}
let (raw_depth_stencil, depth_bias) = match desc.depth_stencil {
Some(ref ds) => {
let raw_format = self.shared.private_caps.map_format(ds.format);
let aspects = crate::FormatAspect::from(ds.format);
if aspects.contains(crate::FormatAspect::DEPTH) {
descriptor.set_depth_attachment_pixel_format(raw_format);
}
if aspects.contains(crate::FormatAspect::STENCIL) {
descriptor.set_stencil_attachment_pixel_format(raw_format);
}
let ds_descriptor = create_depth_stencil_desc(ds);
let raw = self
.shared
.device
.lock()
.new_depth_stencil_state(&ds_descriptor);
(Some(raw), ds.bias)
}
None => (None, wgt::DepthBiasState::default()),
};
if desc.layout.total_counters.vs.buffers + (desc.vertex_buffers.len() as u32)
> self.shared.private_caps.max_buffers_per_stage
{
let msg = format!(
"pipeline needs too many buffers in the vertex stage: {} vertex and {} layout",
desc.vertex_buffers.len(),
desc.layout.total_counters.vs.buffers
);
return Err(crate::PipelineError::Linkage(wgt::ShaderStage::VERTEX, msg));
}
if !desc.vertex_buffers.is_empty() {
let vertex_descriptor = mtl::VertexDescriptor::new();
for (i, vb) in desc.vertex_buffers.iter().enumerate() {
let buffer_index =
self.shared.private_caps.max_buffers_per_stage as u64 - 1 - i as u64;
let buffer_desc = vertex_descriptor.layouts().object_at(buffer_index).unwrap();
buffer_desc.set_stride(vb.array_stride);
buffer_desc.set_step_function(conv::map_step_mode(vb.step_mode));
for (j, at) in vb.attributes.iter().enumerate() {
let attribute_desc =
vertex_descriptor.attributes().object_at(i as u64).unwrap();
attribute_desc.set_format(conv::map_vertex_format(at.format));
attribute_desc.set_buffer_index(buffer_index);
attribute_desc.set_offset(at.offset);
}
}
descriptor.set_vertex_descriptor(Some(&vertex_descriptor));
}
if desc.multisample.count != 1 {
//TODO: handle sample mask
descriptor.set_sample_count(desc.multisample.count as u64);
descriptor.set_alpha_to_coverage_enabled(desc.multisample.alpha_to_coverage_enabled);
//descriptor.set_alpha_to_one_enabled(desc.multisample.alpha_to_one_enabled);
}
if let Some(name) = desc.label {
descriptor.set_label(name);
}
let raw = self
.shared
.device
.lock()
.new_render_pipeline_state(&descriptor)
.map_err(|e| {
crate::PipelineError::Linkage(
wgt::ShaderStage::VERTEX | wgt::ShaderStage::FRAGMENT,
format!("new_render_pipeline_state: {:?}", e),
)
})?;
Ok(super::RenderPipeline {
raw,
vs_lib: vs.library,
fs_lib,
vs_info: super::PipelineStageInfo {
push_constants: desc.layout.push_constants_infos.vs,
sizes_slot: desc.layout.naga_options.per_stage_map.vs.sizes_buffer,
sized_bindings: vs.sized_bindings,
},
fs_info: super::PipelineStageInfo {
push_constants: desc.layout.push_constants_infos.fs,
sizes_slot: desc.layout.naga_options.per_stage_map.fs.sizes_buffer,
sized_bindings: fs_sized_bindings,
},
raw_primitive_type,
raw_front_winding: conv::map_winding(desc.primitive.front_face),
raw_cull_mode: conv::map_cull_mode(desc.primitive.cull_mode),
raw_depth_clip_mode: if self.features.contains(wgt::Features::DEPTH_CLAMPING) {
Some(if desc.primitive.clamp_depth {
mtl::MTLDepthClipMode::Clamp
} else {
mtl::MTLDepthClipMode::Clip
})
} else {
None
},
raw_depth_stencil,
depth_bias,
})
}
unsafe fn destroy_render_pipeline(&self, pipeline: Resource) {}
unsafe fn destroy_render_pipeline(&self, _pipeline: super::RenderPipeline) {}
unsafe fn create_compute_pipeline(
&self,
desc: &crate::ComputePipelineDescriptor<Api>,
) -> Result<Resource, crate::PipelineError> {
Ok(Resource)
}
unsafe fn destroy_compute_pipeline(&self, pipeline: Resource) {}
desc: &crate::ComputePipelineDescriptor<super::Api>,
) -> Result<super::ComputePipeline, crate::PipelineError> {
let descriptor = mtl::ComputePipelineDescriptor::new();
unsafe fn create_query_set(&self, desc: &wgt::QuerySetDescriptor) -> DeviceResult<Resource> {
Ok(Resource)
let cs = self.load_shader(
&desc.stage,
desc.layout,
mtl::MTLPrimitiveTopologyClass::Unspecified,
naga::ShaderStage::Compute,
)?;
descriptor.set_compute_function(Some(&cs.function));
if let Some(name) = desc.label {
descriptor.set_label(name);
}
let raw = self
.shared
.device
.lock()
.new_compute_pipeline_state(&descriptor)
.map_err(|e| {
crate::PipelineError::Linkage(
wgt::ShaderStage::COMPUTE,
format!("new_compute_pipeline_state: {:?}", e),
)
})?;
Ok(super::ComputePipeline {
raw,
cs_info: super::PipelineStageInfo {
push_constants: desc.layout.push_constants_infos.cs,
sizes_slot: desc.layout.naga_options.per_stage_map.cs.sizes_buffer,
sized_bindings: cs.sized_bindings,
},
cs_lib: cs.library,
work_group_size: cs.wg_size,
})
}
unsafe fn destroy_query_set(&self, set: Resource) {}
unsafe fn create_fence(&self) -> DeviceResult<Resource> {
Ok(Resource)
unsafe fn destroy_compute_pipeline(&self, _pipeline: super::ComputePipeline) {}
unsafe fn create_query_set(
&self,
desc: &wgt::QuerySetDescriptor,
) -> DeviceResult<super::QuerySet> {
match desc.ty {
wgt::QueryType::Occlusion => {
let size = desc.count as u64 * 8;
let options = mtl::MTLResourceOptions::empty();
//TODO: HazardTrackingModeUntracked
let raw_buffer = self.shared.device.lock().new_buffer(size, options);
raw_buffer.set_label("_QuerySet");
Ok(super::QuerySet { raw_buffer })
}
wgt::QueryType::Timestamp | wgt::QueryType::PipelineStatistics(_) => {
Err(crate::DeviceError::OutOfMemory)
}
}
}
unsafe fn destroy_fence(&self, fence: Resource) {}
unsafe fn get_fence_value(&self, fence: &Resource) -> DeviceResult<crate::FenceValue> {
Ok(0)
unsafe fn destroy_query_set(&self, _set: super::QuerySet) {}
unsafe fn create_fence(&self) -> DeviceResult<super::Fence> {
Ok(super::Fence {
completed_value: atomic::AtomicU64::new(0),
pending_command_buffers: Vec::new(),
})
}
unsafe fn destroy_fence(&self, _fence: super::Fence) {}
unsafe fn get_fence_value(&self, fence: &super::Fence) -> DeviceResult<crate::FenceValue> {
let mut max_value = fence.completed_value.load(atomic::Ordering::Acquire);
for &(value, ref cmd_buf) in fence.pending_command_buffers.iter() {
if cmd_buf.status() == mtl::MTLCommandBufferStatus::Completed {
max_value = value;
}
}
Ok(max_value)
}
unsafe fn wait(
&self,
fence: &Resource,
value: crate::FenceValue,
fence: &super::Fence,
wait_value: crate::FenceValue,
timeout_ms: u32,
) -> DeviceResult<bool> {
Ok(true)
if wait_value <= fence.completed_value.load(atomic::Ordering::Acquire) {
return Ok(true);
}
let cmd_buf = match fence
.pending_command_buffers
.iter()
.find(|&&(value, _)| value == wait_value)
{
Some(&(_, ref cmd_buf)) => cmd_buf,
None => {
log::error!("No active command buffers for fence value {}", wait_value);
return Err(crate::DeviceError::Lost);
}
};
let start = time::Instant::now();
loop {
if let mtl::MTLCommandBufferStatus::Completed = cmd_buf.status() {
return Ok(true);
}
if start.elapsed().as_millis() >= timeout_ms as u128 {
return Ok(false);
}
thread::sleep(time::Duration::from_millis(1));
}
}
unsafe fn start_capture(&self) -> bool {
false
if !self.shared.private_caps.supports_capture_manager {
return false;
}
let device = self.shared.device.lock();
let shared_capture_manager = mtl::CaptureManager::shared();
let default_capture_scope = shared_capture_manager.new_capture_scope_with_device(&device);
shared_capture_manager.set_default_capture_scope(&default_capture_scope);
shared_capture_manager.start_capture_with_scope(&default_capture_scope);
default_capture_scope.begin_scope();
true
}
unsafe fn stop_capture(&self) {
let shared_capture_manager = mtl::CaptureManager::shared();
if let Some(default_capture_scope) = shared_capture_manager.default_capture_scope() {
default_capture_scope.end_scope();
}
shared_capture_manager.stop_capture();
}
unsafe fn stop_capture(&self) {}
}

View File

@@ -4,7 +4,12 @@ mod conv;
mod device;
mod surface;
use std::{iter, ops, ptr::NonNull, sync::Arc, thread};
use std::{
iter, ops,
ptr::NonNull,
sync::{atomic, Arc},
thread,
};
use arrayvec::ArrayVec;
use foreign_types::ForeignTypeRef as _;
@@ -12,9 +17,6 @@ use parking_lot::Mutex;
#[derive(Clone)]
pub struct Api;
pub struct Encoder;
#[derive(Debug)]
pub struct Resource;
type ResourceIndex = u32;
@@ -25,22 +27,22 @@ impl crate::Api for Api {
type Queue = Queue;
type Device = Device;
type CommandBuffer = Encoder;
type CommandBuffer = CommandBuffer;
type Buffer = Buffer;
type Texture = Texture;
type SurfaceTexture = SurfaceTexture;
type TextureView = TextureView;
type Sampler = Sampler;
type QuerySet = Resource;
type Fence = Resource;
type QuerySet = QuerySet;
type Fence = Fence;
type BindGroupLayout = BindGroupLayout;
type BindGroup = BindGroup;
type PipelineLayout = PipelineLayout;
type ShaderModule = Resource;
type RenderPipeline = Resource;
type ComputePipeline = Resource;
type ShaderModule = ShaderModule;
type RenderPipeline = RenderPipeline;
type ComputePipeline = ComputePipeline;
}
pub struct Instance {}
@@ -181,6 +183,7 @@ struct PrivateCapabilities {
sample_count_mask: u8,
supports_debug_markers: bool,
supports_binary_archives: bool,
supports_capture_manager: bool,
can_set_maximum_drawables_count: bool,
can_set_display_sync: bool,
can_set_next_drawable_timeout: bool,
@@ -194,10 +197,17 @@ struct PrivateDisabilities {
broken_layered_clear_image: bool,
}
#[derive(Debug, Default)]
struct Settings {
retain_command_buffer_references: bool,
}
struct AdapterShared {
device: Mutex<mtl::Device>,
queue: Mutex<mtl::CommandQueue>,
disabilities: PrivateDisabilities,
private_caps: PrivateCapabilities,
settings: Settings,
}
impl AdapterShared {
@@ -208,18 +218,29 @@ impl AdapterShared {
Self {
disabilities: PrivateDisabilities::new(&device),
private_caps: PrivateCapabilities::new(&device),
queue: Mutex::new(device.new_command_queue()),
device: Mutex::new(device),
settings: Settings::default(),
}
}
fn create_command_buffer(&self) -> &mtl::CommandBufferRef {
let queue = self.queue.lock();
objc::rc::autoreleasepool(|| {
if self.settings.retain_command_buffer_references {
queue.new_command_buffer()
} else {
queue.new_command_buffer_with_unretained_references()
}
})
}
}
struct Adapter {
shared: Arc<AdapterShared>,
}
struct Queue {
raw: mtl::CommandQueue,
}
struct Queue {}
struct Device {
shared: Arc<AdapterShared>,
@@ -256,7 +277,7 @@ impl crate::Queue<Api> for Queue {
unsafe fn submit<I>(
&mut self,
command_buffers: I,
signal_fence: Option<(&Resource, crate::FenceValue)>,
signal_fence: Option<(&Fence, crate::FenceValue)>,
) -> Result<(), crate::DeviceError> {
Ok(())
}
@@ -325,11 +346,10 @@ impl Sampler {
}
}
type BindingMap = fxhash::FxHashMap<u32, wgt::BindGroupLayoutEntry>;
#[derive(Debug)]
pub struct BindGroupLayout {
entries: Arc<BindingMap>,
/// Sorted list of BGL entries.
entries: Arc<Vec<wgt::BindGroupLayoutEntry>>,
}
#[derive(Clone, Debug, Default)]
@@ -393,7 +413,7 @@ struct BindGroupLayoutInfo {
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct PushConstantsStage {
struct PushConstantsInfo {
count: u32,
buffer_index: ResourceIndex,
}
@@ -402,7 +422,8 @@ struct PushConstantsStage {
pub struct PipelineLayout {
naga_options: naga::back::msl::Options,
bind_group_infos: ArrayVec<[BindGroupLayoutInfo; crate::MAX_BIND_GROUPS]>,
push_constants_infos: MultiStageData<Option<PushConstantsStage>>,
push_constants_infos: MultiStageData<Option<PushConstantsInfo>>,
total_counters: MultiStageResourceCounters,
}
trait AsNative {
@@ -468,3 +489,72 @@ pub struct BindGroup {
unsafe impl Send for BindGroup {}
unsafe impl Sync for BindGroup {}
#[derive(Debug)]
pub struct ShaderModule {
raw: crate::NagaShader,
}
#[derive(Debug, Default)]
struct PipelineStageInfo {
push_constants: Option<PushConstantsInfo>,
sizes_slot: Option<naga::back::msl::Slot>,
sized_bindings: Vec<naga::ResourceBinding>,
}
impl PipelineStageInfo {
fn clear(&mut self) {
self.push_constants = None;
self.sizes_slot = None;
self.sized_bindings.clear();
}
fn assign_from(&mut self, other: &Self) {
self.push_constants = other.push_constants;
self.sizes_slot = other.sizes_slot;
self.sized_bindings.clear();
self.sized_bindings.extend_from_slice(&other.sized_bindings);
}
}
pub struct RenderPipeline {
raw: mtl::RenderPipelineState,
vs_lib: mtl::Library,
fs_lib: Option<mtl::Library>,
vs_info: PipelineStageInfo,
fs_info: PipelineStageInfo,
raw_primitive_type: mtl::MTLPrimitiveType,
raw_front_winding: mtl::MTLWinding,
raw_cull_mode: mtl::MTLCullMode,
raw_depth_clip_mode: Option<mtl::MTLDepthClipMode>,
raw_depth_stencil: Option<mtl::DepthStencilState>,
depth_bias: wgt::DepthBiasState,
}
pub struct ComputePipeline {
raw: mtl::ComputePipelineState,
cs_lib: mtl::Library,
cs_info: PipelineStageInfo,
work_group_size: mtl::MTLSize,
}
#[derive(Debug)]
pub struct QuerySet {
raw_buffer: mtl::Buffer,
}
unsafe impl Send for QuerySet {}
unsafe impl Sync for QuerySet {}
#[derive(Debug)]
pub struct Fence {
completed_value: atomic::AtomicU64,
pending_command_buffers: Vec<(crate::FenceValue, mtl::CommandBuffer)>,
}
unsafe impl Send for Fence {}
unsafe impl Sync for Fence {}
pub struct CommandBuffer {
raw: mtl::CommandBuffer,
}

View File

@@ -141,7 +141,7 @@ impl super::Surface {
Self::new(None, layer.to_owned())
}
fn dimensions(&self) -> wgt::Extent3d {
pub(super) 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];

View File

@@ -3065,6 +3065,8 @@ pub struct QuerySetDescriptor {
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub enum QueryType {
/// Query returns a single 64-bit number, serving as an occlusion boolean.
Occlusion,
/// Query returns up to 5 64-bit numbers based on the given flags.
///
/// See [`PipelineStatisticsTypes`]'s documentation for more information