Skip to main content

apple_metal/
argument.rs

1#![allow(clippy::missing_errors_doc)]
2
3use crate::{ffi, storage_mode, MetalBuffer, MetalDevice, MetalTexture, SamplerState};
4use core::ffi::c_void;
5use std::sync::MutexGuard;
6
7const DATA_TYPE_TEXTURE: usize = 58;
8const DATA_TYPE_SAMPLER: usize = 59;
9const DATA_TYPE_POINTER: usize = 60;
10const DESCRIPTOR_WORD_COUNT: usize = 6;
11const FIRST_CONSTANT_DATA_TYPE: usize = 3;
12const LAST_CONSTANT_DATA_TYPE: usize = 56;
13const FIRST_LONG_DATA_TYPE: usize = 81;
14const LAST_LONG_DATA_TYPE: usize = 88;
15const FIRST_BFLOAT_DATA_TYPE: usize = 121;
16const LAST_BFLOAT_DATA_TYPE: usize = 124;
17
18/// `MTLArgumentBuffersTier` enum values.
19pub mod argument_buffers_tier {
20    /// Mirrors the `Metal` framework constant `TIER1`.
21    pub const TIER1: usize = 0;
22    /// Mirrors the `Metal` framework constant `TIER2`.
23    pub const TIER2: usize = 1;
24}
25
26/// `MTLBindingAccess` enum values.
27pub mod binding_access {
28    /// Mirrors the `Metal` framework constant `READ_ONLY`.
29    pub const READ_ONLY: usize = 0;
30    /// Mirrors the `Metal` framework constant `READ_WRITE`.
31    pub const READ_WRITE: usize = 1;
32    /// Mirrors the `Metal` framework constant `WRITE_ONLY`.
33    pub const WRITE_ONLY: usize = 2;
34}
35
36/// `MTLTextureType` enum values.
37pub mod texture_type {
38    /// Mirrors the `Metal` framework constant `TYPE_1D`.
39    pub const TYPE_1D: usize = 0;
40    /// Mirrors the `Metal` framework constant `TYPE_1D_ARRAY`.
41    pub const TYPE_1D_ARRAY: usize = 1;
42    /// Mirrors the `Metal` framework constant `TYPE_2D`.
43    pub const TYPE_2D: usize = 2;
44    /// Mirrors the `Metal` framework constant `TYPE_2D_ARRAY`.
45    pub const TYPE_2D_ARRAY: usize = 3;
46    /// Mirrors the `Metal` framework constant `TYPE_2D_MULTISAMPLE`.
47    pub const TYPE_2D_MULTISAMPLE: usize = 4;
48    /// Mirrors the `Metal` framework constant `CUBE`.
49    pub const CUBE: usize = 5;
50    /// Mirrors the `Metal` framework constant `CUBE_ARRAY`.
51    pub const CUBE_ARRAY: usize = 6;
52    /// Mirrors the `Metal` framework constant `TYPE_3D`.
53    pub const TYPE_3D: usize = 7;
54    /// Mirrors the `Metal` framework constant `TYPE_2D_MULTISAMPLE_ARRAY`.
55    pub const TYPE_2D_MULTISAMPLE_ARRAY: usize = 8;
56    /// Mirrors the `Metal` framework constant `TEXTURE_BUFFER`.
57    pub const TEXTURE_BUFFER: usize = 9;
58}
59
60/// Resource kind expected at an argument-buffer index.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum ArgumentBindingType {
63    /// Buffer pointer binding.
64    Buffer,
65    /// Texture binding.
66    Texture,
67    /// Sampler binding.
68    Sampler,
69    /// Constant-data binding.
70    Constant,
71}
72
73/// Errors returned while configuring an [`ArgumentEncoder`].
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum ArgumentEncoderError {
76    /// The descriptor set is too large for the native ABI.
77    DescriptorCountOutOfRange,
78    /// At least one argument descriptor is required.
79    EmptyDescriptorSet,
80    /// The raw data type is not valid for descriptor-based argument encoding.
81    UnsupportedDataType { data_type: usize },
82    /// The raw binding-access value is invalid for this descriptor.
83    InvalidAccess { access: usize },
84    /// The raw texture-type value is invalid.
85    InvalidTextureType { texture_type: usize },
86    /// Buffer-pointer descriptors cannot declare an array length.
87    BufferArrayUnsupported { array_length: usize },
88    /// Constant-block alignment is invalid for this descriptor.
89    InvalidConstantBlockAlignment { alignment: usize },
90    /// A descriptor's binding range exceeds the supported index space.
91    BindingRangeOutOfBounds { index: usize, array_length: usize },
92    /// Two descriptors claim the same binding index.
93    DuplicateBinding { index: usize },
94    /// Metal could not create an argument encoder.
95    NativeCreationFailed,
96    /// Another CPU mapping panicked while holding the argument buffer lock.
97    MappingLockPoisoned,
98    /// The function-derived encoder does not expose type metadata.
99    LayoutUnavailable,
100    /// The binding index is not present in the encoder layout.
101    InvalidBindingIndex { index: usize },
102    /// The setter does not match the descriptor's resource type.
103    BindingTypeMismatch {
104        index: usize,
105        expected: ArgumentBindingType,
106        actual: ArgumentBindingType,
107    },
108    /// The destination argument-buffer offset is not suitably aligned.
109    MisalignedArgumentBufferOffset { offset: usize, alignment: usize },
110    /// The encoded argument range exceeds the destination buffer.
111    ArgumentBufferRangeOutOfBounds {
112        offset: usize,
113        encoded_length: usize,
114        buffer_length: usize,
115    },
116    /// The destination argument buffer is not CPU-addressable.
117    CpuInaccessibleArgumentBuffer { storage_mode: usize },
118    /// A referenced buffer offset exceeds that buffer's length.
119    BufferOffsetOutOfBounds { offset: usize, buffer_length: usize },
120    /// A value cannot be represented by the native API.
121    IntegerOutOfRange { field: &'static str, value: usize },
122    /// The native bridge rejected a validated setter.
123    NativeRejected { operation: &'static str },
124}
125
126impl core::fmt::Display for ArgumentEncoderError {
127    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
128        match self {
129            Self::DescriptorCountOutOfRange => {
130                formatter.write_str("argument descriptor count exceeds native Int")
131            }
132            Self::EmptyDescriptorSet => {
133                formatter.write_str("at least one argument descriptor is required")
134            }
135            Self::UnsupportedDataType { data_type } => {
136                write!(
137                    formatter,
138                    "data type {data_type} is not valid for an argument descriptor"
139                )
140            }
141            Self::InvalidAccess { access } => {
142                write!(formatter, "binding access value {access} is invalid")
143            }
144            Self::InvalidTextureType { texture_type } => {
145                write!(formatter, "texture type value {texture_type} is invalid")
146            }
147            Self::BufferArrayUnsupported { array_length } => write!(
148                formatter,
149                "buffer-pointer descriptor array length {array_length} is unsupported"
150            ),
151            Self::InvalidConstantBlockAlignment { alignment } => {
152                write!(formatter, "constant-block alignment {alignment} is invalid")
153            }
154            Self::BindingRangeOutOfBounds {
155                index,
156                array_length,
157            } => write!(
158                formatter,
159                "argument binding range {index}..{} is out of bounds",
160                index.saturating_add(*array_length)
161            ),
162            Self::DuplicateBinding { index } => {
163                write!(formatter, "argument binding index {index} is duplicated")
164            }
165            Self::NativeCreationFailed => {
166                formatter.write_str("Metal could not create argument encoder")
167            }
168            Self::MappingLockPoisoned => {
169                formatter.write_str("argument-buffer mapping lock is poisoned")
170            }
171            Self::LayoutUnavailable => formatter.write_str(
172                "function-derived argument layout is unavailable; use an explicit unsafe setter",
173            ),
174            Self::InvalidBindingIndex { index } => {
175                write!(formatter, "argument binding index {index} is not present")
176            }
177            Self::BindingTypeMismatch {
178                index,
179                expected,
180                actual,
181            } => write!(
182                formatter,
183                "argument binding {index} expects {expected:?}, not {actual:?}"
184            ),
185            Self::MisalignedArgumentBufferOffset { offset, alignment } => write!(
186                formatter,
187                "argument-buffer offset {offset} is not aligned to {alignment}"
188            ),
189            Self::ArgumentBufferRangeOutOfBounds {
190                offset,
191                encoded_length,
192                buffer_length,
193            } => write!(
194                formatter,
195                "encoded argument range {offset}..{} exceeds buffer length {buffer_length}",
196                offset.saturating_add(*encoded_length)
197            ),
198            Self::CpuInaccessibleArgumentBuffer { storage_mode } => write!(
199                formatter,
200                "storage mode {storage_mode} cannot be used as an argument-encoder destination"
201            ),
202            Self::BufferOffsetOutOfBounds {
203                offset,
204                buffer_length,
205            } => write!(
206                formatter,
207                "buffer offset {offset} exceeds buffer length {buffer_length}"
208            ),
209            Self::IntegerOutOfRange { field, value } => {
210                write!(formatter, "{field} value {value} exceeds native Int")
211            }
212            Self::NativeRejected { operation } => write!(formatter, "Metal rejected {operation}"),
213        }
214    }
215}
216
217impl std::error::Error for ArgumentEncoderError {}
218
219/// Safe Rust description of `MTLArgumentDescriptor`.
220#[derive(Debug, Clone, Copy)]
221pub struct ArgumentDescriptor {
222    data_type: usize,
223    index: usize,
224    array_length: usize,
225    access: usize,
226    texture_type: usize,
227    constant_block_alignment: usize,
228}
229
230impl ArgumentDescriptor {
231    /// Describe a buffer pointer argument at `index`.
232    #[must_use]
233    pub const fn buffer(index: usize, access: usize) -> Self {
234        Self {
235            data_type: DATA_TYPE_POINTER,
236            index,
237            array_length: 0,
238            access,
239            texture_type: texture_type::TYPE_2D,
240            constant_block_alignment: 0,
241        }
242    }
243
244    /// Describe a texture argument at `index`.
245    #[must_use]
246    pub const fn texture(index: usize, texture_type: usize, access: usize) -> Self {
247        Self {
248            data_type: DATA_TYPE_TEXTURE,
249            index,
250            array_length: 0,
251            access,
252            texture_type,
253            constant_block_alignment: 0,
254        }
255    }
256
257    /// Describe a sampler argument at `index`.
258    #[must_use]
259    pub const fn sampler(index: usize) -> Self {
260        Self {
261            data_type: DATA_TYPE_SAMPLER,
262            index,
263            array_length: 0,
264            access: binding_access::READ_ONLY,
265            texture_type: texture_type::TYPE_2D,
266            constant_block_alignment: 0,
267        }
268    }
269
270    /// Describe a constant block argument using a raw `MTLDataType` value.
271    #[must_use]
272    pub const fn constant(data_type: usize, index: usize, array_length: usize) -> Self {
273        Self {
274            data_type,
275            index,
276            array_length,
277            access: binding_access::READ_ONLY,
278            texture_type: texture_type::TYPE_2D,
279            constant_block_alignment: 0,
280        }
281    }
282
283    /// Override the descriptor's array length.
284    #[must_use]
285    pub fn with_array_length(mut self, array_length: usize) -> Self {
286        self.array_length = array_length;
287        self
288    }
289
290    /// Override the descriptor's constant-block alignment.
291    #[must_use]
292    pub fn with_constant_block_alignment(mut self, alignment: usize) -> Self {
293        self.constant_block_alignment = alignment;
294        self
295    }
296
297    const fn as_words(self) -> [usize; DESCRIPTOR_WORD_COUNT] {
298        [
299            self.data_type,
300            self.index,
301            self.array_length,
302            self.access,
303            self.texture_type,
304            self.constant_block_alignment,
305        ]
306    }
307
308    const fn binding_type(self) -> ArgumentBindingType {
309        match self.data_type {
310            DATA_TYPE_POINTER => ArgumentBindingType::Buffer,
311            DATA_TYPE_TEXTURE => ArgumentBindingType::Texture,
312            DATA_TYPE_SAMPLER => ArgumentBindingType::Sampler,
313            _ => ArgumentBindingType::Constant,
314        }
315    }
316}
317
318/// Apple's `id<MTLArgumentEncoder>` with an explicit active binding.
319pub struct ArgumentEncoder {
320    ptr: *mut c_void,
321    layout: Option<Vec<ArgumentBindingRange>>,
322}
323
324#[derive(Clone, Copy)]
325struct ArgumentBindingRange {
326    start: usize,
327    last: usize,
328    binding_type: ArgumentBindingType,
329}
330
331/// Scoped active destination for argument-encoder setter operations.
332pub struct ArgumentBufferBinding<'a> {
333    encoder: &'a mut ArgumentEncoder,
334    buffer: &'a MetalBuffer,
335    offset: usize,
336    encoded_length: usize,
337    storage_mode: usize,
338    _mapping_lock: MutexGuard<'a, ()>,
339}
340
341// SAFETY: the encoder may move between threads, but its mutating methods require
342// exclusive access and it is intentionally not `Sync`.
343unsafe impl Send for ArgumentEncoder {}
344
345impl Drop for ArgumentEncoder {
346    fn drop(&mut self) {
347        if !self.ptr.is_null() {
348            unsafe { ffi::am_object_release(self.ptr) };
349            self.ptr = core::ptr::null_mut();
350        }
351    }
352}
353
354impl MetalDevice {
355    /// Create an argument encoder from explicit descriptors.
356    ///
357    /// # Errors
358    ///
359    /// Returns descriptor validation failures or native creation failure.
360    pub fn new_argument_encoder_with_descriptors(
361        &self,
362        descriptors: &[ArgumentDescriptor],
363    ) -> Result<ArgumentEncoder, ArgumentEncoderError> {
364        let layout = build_layout(descriptors)?;
365        let word_count = descriptors
366            .len()
367            .checked_mul(DESCRIPTOR_WORD_COUNT)
368            .filter(|count| isize::try_from(*count).is_ok())
369            .ok_or(ArgumentEncoderError::DescriptorCountOutOfRange)?;
370        let mut words = Vec::with_capacity(word_count);
371        for descriptor in descriptors {
372            words.extend_from_slice(&descriptor.as_words());
373        }
374        let ptr = unsafe {
375            ffi::am_device_new_argument_encoder_with_descriptors(
376                self.as_ptr(),
377                words.as_ptr(),
378                descriptors.len(),
379            )
380        };
381        if ptr.is_null() {
382            Err(ArgumentEncoderError::NativeCreationFailed)
383        } else {
384            Ok(unsafe { ArgumentEncoder::from_descriptor_ptr(ptr, layout) })
385        }
386    }
387}
388
389impl ArgumentEncoder {
390    /// Number of bytes required to encode the argument layout.
391    #[must_use]
392    pub fn encoded_length(&self) -> usize {
393        unsafe { ffi::am_argument_encoder_encoded_length(self.as_ptr()) }
394    }
395
396    /// Required alignment for the encoded argument data.
397    #[must_use]
398    pub fn alignment(&self) -> usize {
399        unsafe { ffi::am_argument_encoder_alignment(self.as_ptr()) }
400    }
401
402    /// Bind a destination argument buffer for a scoped sequence of setters.
403    ///
404    /// Managed storage is marked modified when the returned guard is dropped.
405    ///
406    /// # Safety
407    ///
408    /// The caller must exclude all GPU access to the encoded byte range until
409    /// the returned binding guard is dropped, and must not submit GPU work that
410    /// uses the argument buffer until then. Once a dispatch or draw references
411    /// this argument buffer, its bindings must not change until that command
412    /// buffer completes because resource declarations are captured while the
413    /// command is encoded.
414    pub unsafe fn bind_argument_buffer<'a>(
415        &'a mut self,
416        buffer: &'a MetalBuffer,
417        offset: usize,
418    ) -> Result<ArgumentBufferBinding<'a>, ArgumentEncoderError> {
419        ensure_native_int(offset, "argument-buffer offset")?;
420        let alignment = self.alignment();
421        if alignment == 0 || offset % alignment != 0 {
422            return Err(ArgumentEncoderError::MisalignedArgumentBufferOffset { offset, alignment });
423        }
424        let encoded_length = self.encoded_length();
425        let end = offset.checked_add(encoded_length).ok_or_else(|| {
426            ArgumentEncoderError::ArgumentBufferRangeOutOfBounds {
427                offset,
428                encoded_length,
429                buffer_length: buffer.length(),
430            }
431        })?;
432        if end > buffer.length() {
433            return Err(ArgumentEncoderError::ArgumentBufferRangeOutOfBounds {
434                offset,
435                encoded_length,
436                buffer_length: buffer.length(),
437            });
438        }
439        let storage_mode = buffer.storage_mode();
440        if !matches!(storage_mode, storage_mode::SHARED | storage_mode::MANAGED) {
441            return Err(ArgumentEncoderError::CpuInaccessibleArgumentBuffer { storage_mode });
442        }
443        let mapping_lock = buffer
444            .lock_mapping()
445            .map_err(|_| ArgumentEncoderError::MappingLockPoisoned)?;
446        let accepted = unsafe {
447            ffi::am_argument_encoder_set_argument_buffer(self.as_ptr(), buffer.as_ptr(), offset)
448        };
449        if !accepted {
450            return Err(ArgumentEncoderError::NativeRejected {
451                operation: "argument-buffer binding",
452            });
453        }
454        Ok(ArgumentBufferBinding {
455            encoder: self,
456            buffer,
457            offset,
458            encoded_length,
459            storage_mode,
460            _mapping_lock: mapping_lock,
461        })
462    }
463
464    /// Borrowed raw `id<MTLArgumentEncoder>` pointer.
465    ///
466    /// The pointer is valid only while this wrapper is alive. Native setter
467    /// calls through it bypass active-binding and layout validation.
468    #[must_use]
469    pub const fn as_ptr(&self) -> *mut c_void {
470        self.ptr
471    }
472
473    pub(crate) unsafe fn from_function_ptr(ptr: *mut c_void) -> Self {
474        Self { ptr, layout: None }
475    }
476
477    unsafe fn from_descriptor_ptr(ptr: *mut c_void, layout: Vec<ArgumentBindingRange>) -> Self {
478        Self {
479            ptr,
480            layout: Some(layout),
481        }
482    }
483
484    fn validate_binding(
485        &self,
486        index: usize,
487        actual: ArgumentBindingType,
488    ) -> Result<(), ArgumentEncoderError> {
489        validate_index(index)?;
490        let layout = self
491            .layout
492            .as_ref()
493            .ok_or(ArgumentEncoderError::LayoutUnavailable)?;
494        let expected = layout
495            .iter()
496            .find(|range| index >= range.start && index <= range.last)
497            .map(|range| range.binding_type)
498            .ok_or(ArgumentEncoderError::InvalidBindingIndex { index })?;
499        if expected == actual {
500            Ok(())
501        } else {
502            Err(ArgumentEncoderError::BindingTypeMismatch {
503                index,
504                expected,
505                actual,
506            })
507        }
508    }
509}
510
511impl ArgumentBufferBinding<'_> {
512    /// Encode a buffer binding at a descriptor-validated index.
513    pub fn set_buffer(
514        &mut self,
515        buffer: &MetalBuffer,
516        offset: usize,
517        index: usize,
518    ) -> Result<(), ArgumentEncoderError> {
519        self.encoder
520            .validate_binding(index, ArgumentBindingType::Buffer)?;
521        unsafe { self.set_buffer_unchecked(buffer, offset, index) }
522    }
523
524    /// Encode a texture binding at a descriptor-validated index.
525    pub fn set_texture(
526        &mut self,
527        texture: &MetalTexture,
528        index: usize,
529    ) -> Result<(), ArgumentEncoderError> {
530        self.encoder
531            .validate_binding(index, ArgumentBindingType::Texture)?;
532        unsafe { self.set_texture_unchecked(texture, index) }
533    }
534
535    /// Encode a sampler binding at a descriptor-validated index.
536    pub fn set_sampler_state(
537        &mut self,
538        sampler: &SamplerState,
539        index: usize,
540    ) -> Result<(), ArgumentEncoderError> {
541        self.encoder
542            .validate_binding(index, ArgumentBindingType::Sampler)?;
543        unsafe { self.set_sampler_state_unchecked(sampler, index) }
544    }
545
546    /// Encode a buffer when the function-derived layout is known externally.
547    ///
548    /// # Safety
549    ///
550    /// `index` must identify a buffer binding in the actual function argument
551    /// layout.
552    pub unsafe fn set_buffer_unchecked(
553        &mut self,
554        buffer: &MetalBuffer,
555        offset: usize,
556        index: usize,
557    ) -> Result<(), ArgumentEncoderError> {
558        validate_index(index)?;
559        ensure_native_int(offset, "buffer offset")?;
560        if offset > buffer.length() {
561            return Err(ArgumentEncoderError::BufferOffsetOutOfBounds {
562                offset,
563                buffer_length: buffer.length(),
564            });
565        }
566        if ffi::am_argument_encoder_set_buffer(
567            self.encoder.as_ptr(),
568            buffer.as_ptr(),
569            offset,
570            index,
571        ) {
572            Ok(())
573        } else {
574            Err(ArgumentEncoderError::NativeRejected {
575                operation: "argument buffer resource binding",
576            })
577        }
578    }
579
580    /// Encode a texture when the function-derived layout is known externally.
581    ///
582    /// # Safety
583    ///
584    /// `index` must identify a texture binding in the actual function argument
585    /// layout.
586    pub unsafe fn set_texture_unchecked(
587        &mut self,
588        texture: &MetalTexture,
589        index: usize,
590    ) -> Result<(), ArgumentEncoderError> {
591        validate_index(index)?;
592        if ffi::am_argument_encoder_set_texture(self.encoder.as_ptr(), texture.as_ptr(), index) {
593            Ok(())
594        } else {
595            Err(ArgumentEncoderError::NativeRejected {
596                operation: "argument texture binding",
597            })
598        }
599    }
600
601    /// Encode a sampler when the function-derived layout is known externally.
602    ///
603    /// # Safety
604    ///
605    /// `index` must identify a sampler binding in the actual function argument
606    /// layout.
607    pub unsafe fn set_sampler_state_unchecked(
608        &mut self,
609        sampler: &SamplerState,
610        index: usize,
611    ) -> Result<(), ArgumentEncoderError> {
612        validate_index(index)?;
613        if ffi::am_argument_encoder_set_sampler_state(
614            self.encoder.as_ptr(),
615            sampler.as_ptr(),
616            index,
617        ) {
618            Ok(())
619        } else {
620            Err(ArgumentEncoderError::NativeRejected {
621                operation: "argument sampler binding",
622            })
623        }
624    }
625}
626
627impl Drop for ArgumentBufferBinding<'_> {
628    fn drop(&mut self) {
629        if self.storage_mode == storage_mode::MANAGED {
630            unsafe {
631                ffi::am_buffer_did_modify_range(
632                    self.buffer.as_ptr(),
633                    self.offset,
634                    self.encoded_length,
635                );
636            }
637        }
638    }
639}
640
641fn build_layout(
642    descriptors: &[ArgumentDescriptor],
643) -> Result<Vec<ArgumentBindingRange>, ArgumentEncoderError> {
644    if descriptors.is_empty() {
645        return Err(ArgumentEncoderError::EmptyDescriptorSet);
646    }
647    if descriptors.len() > isize::MAX as usize {
648        return Err(ArgumentEncoderError::DescriptorCountOutOfRange);
649    }
650    let mut layout = Vec::with_capacity(descriptors.len());
651    for descriptor in descriptors {
652        validate_descriptor(*descriptor)?;
653        let occupied_binding_count = descriptor.array_length.max(1);
654        let last = descriptor
655            .index
656            .checked_add(occupied_binding_count - 1)
657            .filter(|last| isize::try_from(*last).is_ok())
658            .ok_or(ArgumentEncoderError::BindingRangeOutOfBounds {
659                index: descriptor.index,
660                array_length: descriptor.array_length,
661            })?;
662        layout.push(ArgumentBindingRange {
663            start: descriptor.index,
664            last,
665            binding_type: descriptor.binding_type(),
666        });
667    }
668    layout.sort_unstable_by_key(|range| range.start);
669    for ranges in layout.windows(2) {
670        if ranges[1].start <= ranges[0].last {
671            return Err(ArgumentEncoderError::DuplicateBinding {
672                index: ranges[1].start,
673            });
674        }
675    }
676    Ok(layout)
677}
678
679fn validate_index(index: usize) -> Result<(), ArgumentEncoderError> {
680    if isize::try_from(index).is_ok() {
681        Ok(())
682    } else {
683        Err(ArgumentEncoderError::InvalidBindingIndex { index })
684    }
685}
686
687fn validate_descriptor(descriptor: ArgumentDescriptor) -> Result<(), ArgumentEncoderError> {
688    validate_index(descriptor.index)?;
689    if descriptor.access > binding_access::WRITE_ONLY {
690        return Err(ArgumentEncoderError::InvalidAccess {
691            access: descriptor.access,
692        });
693    }
694    match descriptor.binding_type() {
695        ArgumentBindingType::Buffer => {
696            if descriptor.array_length != 0 {
697                return Err(ArgumentEncoderError::BufferArrayUnsupported {
698                    array_length: descriptor.array_length,
699                });
700            }
701            if descriptor.constant_block_alignment != 0 {
702                return Err(ArgumentEncoderError::InvalidConstantBlockAlignment {
703                    alignment: descriptor.constant_block_alignment,
704                });
705            }
706        }
707        ArgumentBindingType::Texture => {
708            if descriptor.texture_type > texture_type::TEXTURE_BUFFER {
709                return Err(ArgumentEncoderError::InvalidTextureType {
710                    texture_type: descriptor.texture_type,
711                });
712            }
713            if descriptor.constant_block_alignment != 0 {
714                return Err(ArgumentEncoderError::InvalidConstantBlockAlignment {
715                    alignment: descriptor.constant_block_alignment,
716                });
717            }
718        }
719        ArgumentBindingType::Sampler => {
720            if descriptor.access != binding_access::READ_ONLY {
721                return Err(ArgumentEncoderError::InvalidAccess {
722                    access: descriptor.access,
723                });
724            }
725            if descriptor.constant_block_alignment != 0 {
726                return Err(ArgumentEncoderError::InvalidConstantBlockAlignment {
727                    alignment: descriptor.constant_block_alignment,
728                });
729            }
730        }
731        ArgumentBindingType::Constant => {
732            if !is_supported_constant_data_type(descriptor.data_type) {
733                return Err(ArgumentEncoderError::UnsupportedDataType {
734                    data_type: descriptor.data_type,
735                });
736            }
737            if descriptor.access != binding_access::READ_ONLY {
738                return Err(ArgumentEncoderError::InvalidAccess {
739                    access: descriptor.access,
740                });
741            }
742            let alignment = descriptor.constant_block_alignment;
743            if alignment != 0
744                && (!alignment.is_power_of_two() || isize::try_from(alignment).is_err())
745            {
746                return Err(ArgumentEncoderError::InvalidConstantBlockAlignment { alignment });
747            }
748        }
749    }
750    Ok(())
751}
752
753fn is_supported_constant_data_type(data_type: usize) -> bool {
754    (FIRST_CONSTANT_DATA_TYPE..=LAST_CONSTANT_DATA_TYPE).contains(&data_type)
755        || (FIRST_LONG_DATA_TYPE..=LAST_LONG_DATA_TYPE).contains(&data_type)
756        || (FIRST_BFLOAT_DATA_TYPE..=LAST_BFLOAT_DATA_TYPE).contains(&data_type)
757}
758
759fn ensure_native_int(value: usize, field: &'static str) -> Result<(), ArgumentEncoderError> {
760    if isize::try_from(value).is_ok() {
761        Ok(())
762    } else {
763        Err(ArgumentEncoderError::IntegerOutOfRange { field, value })
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    use super::*;
770
771    #[test]
772    fn modern_scalar_vector_types_are_supported() {
773        for data_type in [81, 88, 121, 124] {
774            assert!(is_supported_constant_data_type(data_type));
775        }
776    }
777}