Metal writes to the depth/stencil attachment before fragment
shader execution if the execution does not modify the depth
value. However, Vulkan expects the write to happen after
fragment shader execution. To circumvent the issue we add
a simple depth passthrough if the user opts in. Only
required when the depth/stencil attachment is used as
input attachment at the same time. It seems Metal does not
correctly detect the dependency.
Metal will incorrectly discard fragments with side effects under
certain circumstances prematurely. The conditions are the following:
- Fragment will always be discarded after side effect operation
- Pre fragment depth fails
- Modifies depth value for a constant value in the fragment shader.
This constant value will also fail the depth test.
However, Metal will also discard the fragment even if it has
operations with side effects inside the fragment shader before the
discard operation.
Vulkan states the graphics pipeline to execute in the following
order:
- Pre fragment depth test (cannot discard here due to modifying
depth value in fragment shader)
- Fragment shader (where the depth is modified and fragment
discarded)
- Post fragment depth test
Therefore, we need to enforce fragment shader execution and not
let Metal discard the fragment before that for such cases. This
change adds an option to provide such utility.
Comment claims we can't, but I tested a number of older Metal compilers (Xcode 8 targeting macOS 10.11, macOS 10.13 online compiler, Xcode 14 targeting iOS 8) and none of them had any issues with it
An entry-point array of buffers, that is not part of a Metal argument
buffer, requires a known length, so it can be emitted as discrete buffers.
For runtime arrays of resources, this can be retrieved from the resource
binding information added via add_msl_resource_binding().
- Redefine get_resource_array_size() to consolidate array sizing using both var
type, and runtime array sizing from resource bindings, if not found in type.
- Use get_resource_array_size() to fix issue for runtime arrays of buffers.
- Update runtime arrays of images and samplers to use get_resource_array_size().
- Add .DS_Store to .gitignore (unrelated).
I cannot find any evidence that this does not actually work.
The original case here was from Epic's PR series in 2019, but I cannot see why it doesn't work.
It might have been a bug in a very old compiler at some point.
The check_discard code was too annoying to deal with,
and there is no requirement to return anything meaningful.
Vulkan specs state that values returned by atomic instructions are undefined.
To date, all released Apple Silicon GPUs incorrectly interpret the
gradient vectors when sampling a cube texture. Specifically, they ignore
one of the three partial derivatives in each gradient depending on the
selected major axis, and they expect the remaining derivatives to be
partially transformed.
h/t @lexaknyazev for the code used in the `spvGradientCube()` function.
Fixes 8 tests under `dEQP-VK.glsl.texture_functions.texturegrad.*`.
Argument buffers can contain multiple runtime arrays if they have fixed
lengths as specified by the binding API. Regression error had assumed each
runtime array is in separate argument buffer with undefined array length.
- Add CompilerMSL::is_var_runtime_size_array() to include test for
setting of array length via CompilerMSL::add_msl_resource_binding().
- Fixed unrelated test case MSL compile syntax failure when acceleration
structure is the first entry point function argument (unrelated).
Metal 3.1 introduced a Metal regression bug which causes an infinite recursion
crash during Metal's analysis of an entry point input structure that itself
contains internal recursion. This patch works around this by replacing the
recursive input declaration with a alternate variable of type void*, and
then casting to the correct type at the top of the entry point function.
- Add CompilerMSL::Options::replace_recursive_inputs to enable
replacing recursive input.
- Add Compiler::type_contains_recursion() to determine if a struct
contains internal recursion, and add custom Decorations to mark
such structs, to short-cut future similar checks.
- Replace recursive input struct declarations with void*,
and emit a recast to correct type at top of entry function.
- Add unit test.
- Compiler::type_is_top_level_block() remove hardcode reference to spirv_cross
namespace, as it interferes with configurable namespaces (unrelated).
Some Metal devices have a bug with depth array textures using comparison
with explicit LoD, where the LoD given will be biased by some amount.
For these devices, we can use a gradient instead, which does not exhibit
this problem. As with the fragment demote workaround, this is only
expected to be needed until the bug is fixed in Metal.
- Add CompilerMSL::Options::argument_buffers_tier as an enumeration to
allow calling app to specify platform argument buffer tier capabilities.
- Support iOS writable images in Tier2 argument buffers when specified.
Tier capabilities based on recommendations from Apple engineering.
Undef values may be of struct type and may be used in constants.
Therefore, they must be interleaved with constants and types.
Fixes the rest of the Vulkan CTS test
`dEQP-VK.spirv_assembly.instruction.compute.opundef.undefined_spec_constant_composite`.
(Please excuse the churn in the reference output; it's an inevitable
result of this change.)
Some Metal devices have a bug where storage resources can still be
written to even if the fragment is discarded. This is obviously a bug in
Metal, but bothering Apple to fix it will only fix it for newer
versions; therefore, a workaround is needed for older versions. I have
made this an option so that, in case the bug is ever fixed, the
workaround can be disabled.
This workaround is simple: if a fragment shader may discard its fragment
and writes to a storage resource, a variable representing the
`HelperInvocation` built-in is created and passed to all functions. The
flag is checked on all resource writes; writes do not occur when
`HelperInvocation` is `true`. This relies on the earlier workaround to
update `HelperInvocation` when the fragment is discarded.
Fixes at least 3 failures in the CTS.
Some Metal devices have a bug where `simd_is_helper_thread()` won't
return true after a fragment has been discarded. We can work around this
by manually setting `gl_HelperInvocation` upon discarding a fragment.
This is fairly unintrusive, so it is enabled by default. I've made it an
option so that, when the bug is fixed, we can disable it.
restrict was supported, but it broke in MSL 3.0. __restrict works on all
versions, so opt for that instead.
Also check for RestrictPointer decoration and refactor to_restrict() to
not take optional parameter to make it more obvious when implied space
character is added.
Flattening doesn't play well with dynamic indices. In this case, it's
better to leave it as an array of structs.
(I wanted to do this for named blocks generally. Trouble is, the builtin
`gl_out` block is *also* a named block...)
Fixes six more CTS tests, under
`dEQP-VK.tessellation.user_defined_io.per_patch_block_array.*`.
Using vertex-style stage input is complex, and it doesn't support
nesting of structures or arrays. By using raw buffer input instead, we
get this support "for free," and everything becomes much simpler.
Arguably, this is the way I should've done this in the first place.
Eventually, I'd like to make this the default, and then remove the
option altogether. (And I still need to do that with
`multi_patch_workgroup`...)
Should help fix 66 tests in the Vulkan CTS, under the following trees:
- `dEQP-VK.pipeline.*.interface_matching.*`
- `dEQP-VK.tessellation.user_defined_io.*`
- `dEQP-VK.clipping.user_defined.*`
It is allowed to redeclare descriptors with different types in Vulkan.
MSL in general does not allow this, but for raw buffers, we can cast the
reference type at the very least.
For typed resources we are kinda hosed. Without descriptor indexing's
PARTIALLY_BOUND_BIT, descriptors must be valid if they are statically
accessed, so it would not be valid to access differently typed aliases
unless that flag is used. There might be a way to reinterpret cast
descriptors, but that seems very sketchy.
Implements support for:
- Single discrete descriptor
- Single argument buffer descriptor
- Array of argument buffer descriptors
Other cases are unimplemented for now since they are extremely painful
to unroll.
- Add CompilerMSL::emit_binary_ptr_op() and to_ptr_expression()
to emit binary pointer op. Compare matrix addresses without automatic
transpose() conversion, to avoid error taking address of temporary copy.
- Add Compiler::add_active_interface_variable() to also track active
interface vars in the entry point for SPIR-V 1.4 and above.
- For OpPtrAccessChain that ends in array element, use Element
as offset to existing index, otherwise it will access into
array dimension that doesn't exist.
- Dereference pointer function call arguments. Ultimately, this
dereferencing is actually backwards, and in future, we should aim
to properly support passing pointer variables between functions,
but such a refactoring was beyond the scope here.
- Use [] to declare array of pointers, as array<T*> is not supported in MSL.
- Add unit test shaders.
This is analogous to the existing support for fixing up shader inputs.
It is intended to be used with tessellation to add implicit builtins
that are read from a later stage, despite not being written in an
earlier stage. (Believe it or not, this is in fact legal in Vulkan.)
Helps fix 8 CTS tests under `dEQP-VK.pipeline.*.no_position`. (Eight
other tests work solely by accident without this change.)
The standard `matrix` type in MSL lacked a constructor in the
`threadgroup` AS. This means that it was impossible to declare a
`threadgroup` variable that contains a matrix. This appears to have been
an oversight that was corrected in macOS 13/Xcode 14 beta 4. This
workaround continues to be required, however, for older systems.
To avoid changing interfaces unnecessarily (which shouldn't be a problem
regardless because the old and new types take up the same amount of
storage), only do this for structs if the struct is positively
identified as being used for workgroup storage.
I'm entirely aware this is inconsistent with the way packed matrices are
handled. One of them should be changed to match the other. Not sure
which one.
Fixes 23 CTS tests under `dEQP-VK.memory_model.shared`.
Since `bool` is a logical type, it cannot be used in uniform or storage
buffers. Therefore, replacing it in structures should not change the
shader interface.
We leave it alone for builtins. (FIXME: Should we also leave it for
I/O varyings?)
Fixes 24 CTS tests under `dEQP-VK.memory_model.shared`.