diff options
Diffstat (limited to 'source/blender/gpu/metal/mtl_shader_interface.mm')
-rw-r--r-- | source/blender/gpu/metal/mtl_shader_interface.mm | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/source/blender/gpu/metal/mtl_shader_interface.mm b/source/blender/gpu/metal/mtl_shader_interface.mm new file mode 100644 index 00000000000..97a82345761 --- /dev/null +++ b/source/blender/gpu/metal/mtl_shader_interface.mm @@ -0,0 +1,604 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + * + * GPU shader interface (C --> GLSL) + */ + +#include "BLI_bitmap.h" + +#include "GPU_capabilities.h" + +#include "mtl_common.hh" +#include "mtl_debug.hh" +#include "mtl_shader_interface.hh" +#include "mtl_shader_interface_type.hh" + +#include "BLI_blenlib.h" +#include "BLI_math_base.h" +#include "BLI_utildefines.h" +#include "MEM_guardedalloc.h" + +namespace blender::gpu { + +MTLShaderInterface::MTLShaderInterface(const char *name) +{ + /* Shared ShaderInputs array is populated later on in `prepare_common_shader_inputs` + * after Metal Shader Interface preparation. */ + inputs_ = nullptr; + + if (name != nullptr) { + strcpy(this->name, name); + } + + /* Ensure #ShaderInterface parameters are cleared. */ + this->init(); +} + +MTLShaderInterface::~MTLShaderInterface() +{ + for (const int i : IndexRange(ARGUMENT_ENCODERS_CACHE_SIZE)) { + if (arg_encoders_[i].encoder != nil) { + id<MTLArgumentEncoder> enc = arg_encoders_[i].encoder; + [enc release]; + } + } +} + +const char *MTLShaderInterface::get_name_at_offset(uint32_t offset) const +{ + return name_buffer_ + offset; +} + +void MTLShaderInterface::init() +{ + total_attributes_ = 0; + total_uniform_blocks_ = 0; + total_uniforms_ = 0; + total_textures_ = 0; + max_texture_index_ = -1; + enabled_attribute_mask_ = 0; + total_vert_stride_ = 0; + sampler_use_argument_buffer_ = false; + sampler_argument_buffer_bind_index_vert_ = -1; + sampler_argument_buffer_bind_index_frag_ = -1; + + /* NULL initialize uniform location markers for builtins. */ + for (const int u : IndexRange(GPU_NUM_UNIFORMS)) { + builtins_[u] = -1; + } + for (const int ubo : IndexRange(GPU_NUM_UNIFORM_BLOCKS)) { + builtin_blocks_[ubo] = -1; + } + for (const int tex : IndexRange(MTL_MAX_TEXTURE_SLOTS)) { + textures_[tex].used = false; + textures_[tex].slot_index = -1; + } + + /* Null initialization for argument encoders. */ + for (const int i : IndexRange(ARGUMENT_ENCODERS_CACHE_SIZE)) { + arg_encoders_[i].encoder = nil; + arg_encoders_[i].buffer_index = -1; + } +} + +void MTLShaderInterface::add_input_attribute(uint32_t name_offset, + uint32_t attribute_location, + MTLVertexFormat format, + uint32_t buffer_index, + uint32_t size, + uint32_t offset, + int matrix_element_count) +{ + MTLShaderInputAttribute &input_attr = attributes_[total_attributes_]; + input_attr.name_offset = name_offset; + input_attr.format = format; + input_attr.location = attribute_location; + input_attr.size = size; + input_attr.buffer_index = buffer_index; + input_attr.offset = offset; + input_attr.matrix_element_count = matrix_element_count; + input_attr.index = total_attributes_; + total_attributes_++; + total_vert_stride_ = max_ii(total_vert_stride_, offset + size); + enabled_attribute_mask_ |= (1 << attribute_location); +} + +uint32_t MTLShaderInterface::add_uniform_block(uint32_t name_offset, + uint32_t buffer_index, + uint32_t size, + ShaderStage stage_mask) +{ + /* Ensure Size is 16 byte aligned to guarantees alignment rules are satisfied. */ + if ((size % 16) != 0) { + size += 16 - (size % 16); + } + + MTLShaderUniformBlock &uni_block = ubos_[total_uniform_blocks_]; + uni_block.name_offset = name_offset; + uni_block.buffer_index = buffer_index; + uni_block.size = size; + uni_block.current_offset = 0; + uni_block.stage_mask = ShaderStage::BOTH; + return (total_uniform_blocks_++); +} + +void MTLShaderInterface::add_push_constant_block(uint32_t name_offset) +{ + push_constant_block_.name_offset = name_offset; + /* Push constant data block is always uniform buffer index 0. */ + push_constant_block_.buffer_index = 0; + /* Size starts at zero and grows as uniforms are added. */ + push_constant_block_.size = 0; + + push_constant_block_.current_offset = 0; + push_constant_block_.stage_mask = ShaderStage::BOTH; +} + +void MTLShaderInterface::add_uniform(uint32_t name_offset, eMTLDataType type, int array_len) +{ + BLI_assert(array_len > 0); + BLI_assert(total_uniforms_ < MTL_MAX_UNIFORMS_PER_BLOCK); + if (total_uniforms_ >= MTL_MAX_UNIFORMS_PER_BLOCK) { + MTL_LOG_WARNING( + "[Warning] Cannot add uniform '%s' to shader interface '%s' as the uniform limit of %d " + "has been reached.\n", + name, + name, + MTL_MAX_UNIFORMS_PER_BLOCK); + return; + } + MTLShaderUniform &uniform = uniforms_[total_uniforms_]; + uniform.name_offset = name_offset; + + /* Determine size and offset alignment -- C++ struct alignment rules: Base address of value must + * match alignment of type. GLSL follows minimum type alignment of 4. */ + int data_type_size = mtl_get_data_type_size(type) * array_len; + int data_type_alignment = max_ii(mtl_get_data_type_alignment(type), 4); + int current_offset = push_constant_block_.current_offset; + if ((current_offset % data_type_alignment) != 0) { + current_offset += data_type_alignment - (current_offset % data_type_alignment); + } + + uniform.size_in_bytes = data_type_size; + uniform.byte_offset = current_offset; + uniform.type = type; + uniform.array_len = array_len; + total_uniforms_++; + + /* Update Push constant block-- update offset, re-size and re-align total memory requirement to + * be 16-byte aligned. Following GLSL std140. */ + push_constant_block_.current_offset = current_offset + data_type_size; + if (push_constant_block_.current_offset > push_constant_block_.size) { + push_constant_block_.size = push_constant_block_.current_offset; + if ((push_constant_block_.size % 16) != 0) { + push_constant_block_.size += 16 - (push_constant_block_.size % 16); + } + } + + /* Validate properties. */ + BLI_assert(uniform.size_in_bytes > 0); + BLI_assert_msg( + current_offset + data_type_size <= push_constant_block_.size, + "Uniform size and offset sits outside the specified size range for the uniform block"); +} + +void MTLShaderInterface::add_texture(uint32_t name_offset, + uint32_t texture_slot, + eGPUTextureType tex_binding_type, + ShaderStage stage_mask) +{ + BLI_assert(texture_slot >= 0 && texture_slot < GPU_max_textures()); + if (texture_slot >= 0 && texture_slot < GPU_max_textures()) { + + MTLShaderTexture &tex = textures_[texture_slot]; + BLI_assert_msg(tex.used == false, "Texture slot already in-use by another binding"); + tex.name_offset = name_offset; + tex.slot_index = texture_slot; + tex.type = tex_binding_type; + tex.stage_mask = stage_mask; + tex.used = true; + total_textures_++; + max_texture_index_ = max_ii(max_texture_index_, texture_slot); + } + else { + BLI_assert_msg(false, "Exceeding maximum supported texture count."); + MTL_LOG_WARNING( + "Could not add additional texture with index %d to shader interface. Maximum " + "supported texture count is %d\n", + texture_slot, + GPU_max_textures()); + } +} + +void MTLShaderInterface::map_builtins() +{ + /* Clear builtin arrays to NULL locations. */ + for (const int u : IndexRange(GPU_NUM_UNIFORMS)) { + builtins_[u] = -1; + } + for (const int ubo : IndexRange(GPU_NUM_UNIFORM_BLOCKS)) { + builtin_blocks_[ubo] = -1; + } + + /* Resolve and cache uniform locations for builtin uniforms. */ + for (const int u : IndexRange(GPU_NUM_UNIFORMS)) { + const ShaderInput *uni = this->uniform_get(builtin_uniform_name((GPUUniformBuiltin)u)); + if (uni != nullptr) { + BLI_assert(uni->location >= 0); + if (uni->location >= 0) { + builtins_[u] = uni->location; + MTL_LOG_INFO("Mapped builtin uniform '%s' NB: '%s' to location: %d\n", + builtin_uniform_name((GPUUniformBuiltin)u), + get_name_at_offset(uni->name_offset), + uni->location); + } + } + } + + /* Resolve and cache uniform locations for builtin uniform blocks. */ + for (const int u : IndexRange(GPU_NUM_UNIFORM_BLOCKS)) { + const ShaderInput *uni = this->ubo_get(builtin_uniform_block_name((GPUUniformBlockBuiltin)u)); + + if (uni != nullptr) { + BLI_assert(uni->location >= 0); + if (uni->location >= 0) { + builtin_blocks_[u] = uni->binding; + MTL_LOG_INFO("Mapped builtin uniform block '%s' to location %d\n", + builtin_uniform_block_name((GPUUniformBlockBuiltin)u), + uni->location); + } + } + } +} + +/* Populate #ShaderInput struct based on interface. */ +void MTLShaderInterface::prepare_common_shader_inputs() +{ + /* `ShaderInput inputs_` maps a uniform name to an external + * uniform location, which is used as an array index to look-up + * information in the local #MTLShaderInterface input structs. + * + * #ShaderInput population follows the ordering rules in #gpu_shader_interface. */ + + /* Populate #ShaderInterface counts. */ + attr_len_ = this->get_total_attributes(); + ubo_len_ = this->get_total_uniform_blocks(); + uniform_len_ = this->get_total_uniforms() + this->get_total_textures(); + + /* TODO(Metal): Support storage buffer bindings. Pending compute shader support. */ + ssbo_len_ = 0; + + /* Calculate total inputs and allocate #ShaderInput array. */ + /* NOTE: We use the existing `name_buffer_` allocated for internal input structs. */ + int input_tot_len = attr_len_ + ubo_len_ + uniform_len_ + ssbo_len_; + inputs_ = (ShaderInput *)MEM_callocN(sizeof(ShaderInput) * input_tot_len, __func__); + ShaderInput *current_input = inputs_; + + /* Attributes. */ + for (const int attr_index : IndexRange(total_attributes_)) { + MTLShaderInputAttribute &shd_attr = attributes_[attr_index]; + current_input->name_offset = shd_attr.name_offset; + current_input->name_hash = BLI_hash_string(this->get_name_at_offset(shd_attr.name_offset)); + current_input->location = attr_index; + current_input->binding = attr_index; + current_input++; + } + + /* UBOs. */ + BLI_assert(&inputs_[attr_len_] >= current_input); + current_input = &inputs_[attr_len_]; + for (const int ubo_index : IndexRange(total_uniform_blocks_)) { + MTLShaderUniformBlock &shd_ubo = ubos_[ubo_index]; + current_input->name_offset = shd_ubo.name_offset; + current_input->name_hash = BLI_hash_string(this->get_name_at_offset(shd_ubo.name_offset)); + /* Location refers to the index in the ubos_ array. */ + current_input->location = ubo_index; + /* Binding location refers to the UBO bind slot in + * #MTLContextGlobalShaderPipelineState::ubo_bindings. The buffer bind index [[buffer(N)]] + * within the shader will apply an offset for bound vertex buffers and the default uniform + * PushConstantBlock. */ + current_input->binding = shd_ubo.buffer_index; + current_input++; + } + + /* Uniforms. */ + BLI_assert(&inputs_[attr_len_ + ubo_len_] >= current_input); + current_input = &inputs_[attr_len_ + ubo_len_]; + for (const int uniform_index : IndexRange(total_uniforms_)) { + MTLShaderUniform &shd_uni = uniforms_[uniform_index]; + current_input->name_offset = shd_uni.name_offset; + current_input->name_hash = BLI_hash_string(this->get_name_at_offset(shd_uni.name_offset)); + current_input->location = uniform_index; + current_input->binding = uniform_index; + current_input++; + } + + /* Textures. + * NOTE(Metal): Textures are externally treated as uniforms in #gpu_shader_interface. + * Location for textures resolved as `binding` value. This + * is the index into the local `MTLShaderTexture textures[]` array. + * + * In MSL, we cannot trivially remap which texture slot a given texture + * handle points to, unlike in GLSL, where a uniform sampler/image can be updated + * and queried as both a texture and a uniform. */ + for (int texture_index = 0; texture_index <= max_texture_index_; texture_index++) { + const MTLShaderTexture &shd_tex = textures_[texture_index]; + + /* Not all texture entries are used when explicit texture locations are specified. */ + if (shd_tex.used) { + BLI_assert_msg(shd_tex.slot_index == texture_index, + "Texture binding slot should match array index for texture."); + current_input->name_offset = shd_tex.name_offset; + current_input->name_hash = BLI_hash_string(this->get_name_at_offset(shd_tex.name_offset)); + + /* Location represents look-up address. + * For Metal, this location is a unique value offset by + * total_uniforms such that it does not overlap. + * + * This range offset allows a check in the uniform look-up + * to ensure texture handles are not treated as standard uniforms in Metal. */ + current_input->location = texture_index + total_uniforms_; + + /* Binding represents texture slot `[[texture(n)]]`. */ + current_input->binding = shd_tex.slot_index; + current_input++; + } + } + + /* SSBO bindings. + * TODO(Metal): Support SSBOs. Pending compute support. */ + BLI_assert(&inputs_[attr_len_ + ubo_len_ + uniform_len_] >= current_input); + current_input = &inputs_[attr_len_ + ubo_len_ + uniform_len_]; + + /* Map builtin uniform indices to uniform binding locations. */ + this->map_builtins(); +} + +void MTLShaderInterface::set_sampler_properties(bool use_argument_buffer, + uint32_t argument_buffer_bind_index_vert, + uint32_t argument_buffer_bind_index_frag) +{ + sampler_use_argument_buffer_ = use_argument_buffer; + sampler_argument_buffer_bind_index_vert_ = argument_buffer_bind_index_vert; + sampler_argument_buffer_bind_index_frag_ = argument_buffer_bind_index_frag; +} + +/* Attributes. */ +const MTLShaderInputAttribute &MTLShaderInterface::get_attribute(uint index) const +{ + BLI_assert(index < MTL_MAX_VERTEX_INPUT_ATTRIBUTES); + BLI_assert(index < get_total_attributes()); + return attributes_[index]; +} + +uint32_t MTLShaderInterface::get_total_attributes() const +{ + return total_attributes_; +} + +uint32_t MTLShaderInterface::get_total_vertex_stride() const +{ + return total_vert_stride_; +} + +uint32_t MTLShaderInterface::get_enabled_attribute_mask() const +{ + return enabled_attribute_mask_; +} + +/* Uniforms. */ +const MTLShaderUniform &MTLShaderInterface::get_uniform(uint index) const +{ + BLI_assert(index < MTL_MAX_UNIFORMS_PER_BLOCK); + BLI_assert(index < get_total_uniforms()); + return uniforms_[index]; +} + +uint32_t MTLShaderInterface::get_total_uniforms() const +{ + return total_uniforms_; +} + +/* Uniform Blocks. */ +const MTLShaderUniformBlock &MTLShaderInterface::get_uniform_block(uint index) const +{ + BLI_assert(index < MTL_MAX_UNIFORM_BUFFER_BINDINGS); + BLI_assert(index < get_total_uniform_blocks()); + return ubos_[index]; +} + +const MTLShaderUniformBlock &MTLShaderInterface::get_push_constant_block() const +{ + return push_constant_block_; +} + +uint32_t MTLShaderInterface::get_total_uniform_blocks() const +{ + return total_uniform_blocks_; +} + +bool MTLShaderInterface::has_uniform_block(uint32_t block_index) const +{ + return (block_index < total_uniform_blocks_); +} + +uint32_t MTLShaderInterface::get_uniform_block_size(uint32_t block_index) const +{ + return (block_index < total_uniform_blocks_) ? ubos_[block_index].size : 0; +} + +/* Textures. */ +const MTLShaderTexture &MTLShaderInterface::get_texture(uint index) const +{ + BLI_assert(index < MTL_MAX_TEXTURE_SLOTS); + BLI_assert(index <= get_max_texture_index()); + return textures_[index]; +} + +uint32_t MTLShaderInterface::get_total_textures() const +{ + return total_textures_; +} + +uint32_t MTLShaderInterface::get_max_texture_index() const +{ + return max_texture_index_; +} + +bool MTLShaderInterface::get_use_argument_buffer_for_samplers( + int *vertex_arg_buffer_bind_index, int *fragment_arg_buffer_bind_index) const +{ + /* Returns argument buffer binding slot for each shader stage. + * The exact bind slot may be different, as each stage has different buffer inputs. */ + *vertex_arg_buffer_bind_index = sampler_argument_buffer_bind_index_vert_; + *fragment_arg_buffer_bind_index = sampler_argument_buffer_bind_index_frag_; + return sampler_use_argument_buffer_; +} + +id<MTLArgumentEncoder> MTLShaderInterface::find_argument_encoder(int buffer_index) const +{ + id encoder = nil; + for (const int i : IndexRange(ARGUMENT_ENCODERS_CACHE_SIZE)) { + encoder = arg_encoders_[i].buffer_index == buffer_index ? arg_encoders_[i].encoder : encoder; + } + return encoder; +} + +void MTLShaderInterface::insert_argument_encoder(int buffer_index, id encoder) +{ + for (const int i : IndexRange(ARGUMENT_ENCODERS_CACHE_SIZE)) { + if (arg_encoders_[i].encoder == nil) { + arg_encoders_[i].encoder = encoder; + arg_encoders_[i].buffer_index = buffer_index; + return; + } + } + MTL_LOG_WARNING("could not insert encoder into cache!"); +} + +MTLVertexFormat mtl_datatype_to_vertex_type(eMTLDataType type) +{ + switch (type) { + case MTL_DATATYPE_CHAR: + return MTLVertexFormatChar; + case MTL_DATATYPE_UCHAR: + return MTLVertexFormatUChar; + case MTL_DATATYPE_BOOL: + return MTLVertexFormatUChar; + case MTL_DATATYPE_CHAR2: + return MTLVertexFormatChar2; + case MTL_DATATYPE_UCHAR2: + return MTLVertexFormatUChar2; + case MTL_DATATYPE_BOOL2: + return MTLVertexFormatUChar2; + case MTL_DATATYPE_SHORT: + return MTLVertexFormatShort; + case MTL_DATATYPE_USHORT: + return MTLVertexFormatUShort; + case MTL_DATATYPE_CHAR3: + return MTLVertexFormatChar3; + case MTL_DATATYPE_UCHAR3: + return MTLVertexFormatUChar3; + case MTL_DATATYPE_BOOL3: + return MTLVertexFormatUChar3; + case MTL_DATATYPE_CHAR4: + return MTLVertexFormatChar4; + case MTL_DATATYPE_UCHAR4: + return MTLVertexFormatUChar4; + case MTL_DATATYPE_INT: + return MTLVertexFormatInt; + case MTL_DATATYPE_UINT: + return MTLVertexFormatUInt; + case MTL_DATATYPE_BOOL4: + return MTLVertexFormatUChar4; + case MTL_DATATYPE_SHORT2: + return MTLVertexFormatShort2; + case MTL_DATATYPE_USHORT2: + return MTLVertexFormatUShort2; + case MTL_DATATYPE_FLOAT: + return MTLVertexFormatFloat; + case MTL_DATATYPE_HALF2x2: + case MTL_DATATYPE_HALF3x2: + case MTL_DATATYPE_HALF4x2: + BLI_assert_msg(false, "Unsupported raw vertex attribute types in Blender."); + return MTLVertexFormatInvalid; + + case MTL_DATATYPE_SHORT3: + return MTLVertexFormatShort3; + case MTL_DATATYPE_USHORT3: + return MTLVertexFormatUShort3; + case MTL_DATATYPE_SHORT4: + return MTLVertexFormatShort4; + case MTL_DATATYPE_USHORT4: + return MTLVertexFormatUShort4; + case MTL_DATATYPE_INT2: + return MTLVertexFormatInt2; + case MTL_DATATYPE_UINT2: + return MTLVertexFormatUInt2; + case MTL_DATATYPE_FLOAT2: + return MTLVertexFormatFloat2; + case MTL_DATATYPE_LONG: + return MTLVertexFormatInt; + case MTL_DATATYPE_ULONG: + return MTLVertexFormatUInt; + case MTL_DATATYPE_HALF2x3: + case MTL_DATATYPE_HALF2x4: + case MTL_DATATYPE_HALF3x3: + case MTL_DATATYPE_HALF3x4: + case MTL_DATATYPE_HALF4x3: + case MTL_DATATYPE_HALF4x4: + case MTL_DATATYPE_FLOAT2x2: + case MTL_DATATYPE_FLOAT3x2: + case MTL_DATATYPE_FLOAT4x2: + BLI_assert_msg(false, "Unsupported raw vertex attribute types in Blender."); + return MTLVertexFormatInvalid; + + case MTL_DATATYPE_INT3: + return MTLVertexFormatInt3; + case MTL_DATATYPE_INT4: + return MTLVertexFormatInt4; + case MTL_DATATYPE_UINT3: + return MTLVertexFormatUInt3; + case MTL_DATATYPE_UINT4: + return MTLVertexFormatUInt4; + case MTL_DATATYPE_FLOAT3: + return MTLVertexFormatFloat3; + case MTL_DATATYPE_FLOAT4: + return MTLVertexFormatFloat4; + case MTL_DATATYPE_LONG2: + return MTLVertexFormatInt2; + case MTL_DATATYPE_ULONG2: + return MTLVertexFormatUInt2; + case MTL_DATATYPE_FLOAT2x3: + case MTL_DATATYPE_FLOAT2x4: + case MTL_DATATYPE_FLOAT3x3: + case MTL_DATATYPE_FLOAT3x4: + case MTL_DATATYPE_FLOAT4x3: + case MTL_DATATYPE_FLOAT4x4: + BLI_assert_msg(false, "Unsupported raw vertex attribute types in Blender."); + return MTLVertexFormatInvalid; + + case MTL_DATATYPE_LONG3: + return MTLVertexFormatInt3; + case MTL_DATATYPE_LONG4: + return MTLVertexFormatInt4; + case MTL_DATATYPE_ULONG3: + return MTLVertexFormatUInt3; + case MTL_DATATYPE_ULONG4: + return MTLVertexFormatUInt4; + + /* Special Types */ + case MTL_DATATYPE_UINT1010102_NORM: + return MTLVertexFormatUInt1010102Normalized; + case MTL_DATATYPE_INT1010102_NORM: + return MTLVertexFormatInt1010102Normalized; + + default: + BLI_assert(false); + return MTLVertexFormatInvalid; + }; +} + +} // namespace blender::gpu |