/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup gpu */ #include "BKE_global.h" #include "BLI_string.h" #include #include #include #include #include #include #include #include #include #include "GPU_platform.h" #include "GPU_vertex_format.h" #include "mtl_common.hh" #include "mtl_context.hh" #include "mtl_debug.hh" #include "mtl_pso_descriptor_state.hh" #include "mtl_shader.hh" #include "mtl_shader_generator.hh" #include "mtl_shader_interface.hh" #include "mtl_texture.hh" extern char datatoc_mtl_shader_common_msl[]; using namespace blender; using namespace blender::gpu; using namespace blender::gpu::shader; namespace blender::gpu { /* -------------------------------------------------------------------- */ /** \name Creation / Destruction. * \{ */ /* Create empty shader to be populated later. */ MTLShader::MTLShader(MTLContext *ctx, const char *name) : Shader(name) { context_ = ctx; /* Create SHD builder to hold temporary resources until compilation is complete. */ shd_builder_ = new MTLShaderBuilder(); #ifndef NDEBUG /* Remove invalid symbols from shader name to ensure debug entry-point function name is valid. */ for (uint i : IndexRange(strlen(this->name))) { char c = this->name[i]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { } else { this->name[i] = '_'; } } #endif } /* Create shader from MSL source. */ MTLShader::MTLShader(MTLContext *ctx, MTLShaderInterface *interface, const char *name, NSString *input_vertex_source, NSString *input_fragment_source, NSString *vert_function_name, NSString *frag_function_name) : MTLShader(ctx, name) { BLI_assert([vert_function_name length]); BLI_assert([frag_function_name length]); this->set_vertex_function_name(vert_function_name); this->set_fragment_function_name(frag_function_name); this->shader_source_from_msl(input_vertex_source, input_fragment_source); this->set_interface(interface); this->finalize(nullptr); } MTLShader::~MTLShader() { if (this->is_valid()) { /* Free uniform data block. */ if (push_constant_data_ != nullptr) { MEM_freeN(push_constant_data_); push_constant_data_ = nullptr; } /* Free Metal resources. */ if (shader_library_vert_ != nil) { [shader_library_vert_ release]; shader_library_vert_ = nil; } if (shader_library_frag_ != nil) { [shader_library_frag_ release]; shader_library_frag_ = nil; } if (pso_descriptor_ != nil) { [pso_descriptor_ release]; pso_descriptor_ = nil; } /* Free Pipeline Cache. */ for (const MTLRenderPipelineStateInstance *pso_inst : pso_cache_.values()) { if (pso_inst->vert) { [pso_inst->vert release]; } if (pso_inst->frag) { [pso_inst->frag release]; } if (pso_inst->pso) { [pso_inst->pso release]; } delete pso_inst; } pso_cache_.clear(); /* NOTE(Metal): #ShaderInterface deletion is handled in the super destructor `~Shader()`. */ } valid_ = false; if (shd_builder_ != nullptr) { delete shd_builder_; shd_builder_ = nullptr; } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Shader stage creation. * \{ */ void MTLShader::vertex_shader_from_glsl(MutableSpan sources) { /* Flag source as not being compiled from native MSL. */ BLI_assert(shd_builder_ != nullptr); shd_builder_->source_from_msl_ = false; /* Remove #version tag entry. */ sources[0] = ""; /* Consolidate GLSL vertex sources. */ std::stringstream ss; for (int i = 0; i < sources.size(); i++) { ss << sources[i] << std::endl; } shd_builder_->glsl_vertex_source_ = ss.str(); } void MTLShader::geometry_shader_from_glsl(MutableSpan sources) { MTL_LOG_ERROR("MTLShader::geometry_shader_from_glsl - Geometry shaders unsupported!\n"); } void MTLShader::fragment_shader_from_glsl(MutableSpan sources) { /* Flag source as not being compiled from native MSL. */ BLI_assert(shd_builder_ != nullptr); shd_builder_->source_from_msl_ = false; /* Remove #version tag entry. */ sources[0] = ""; /* Consolidate GLSL fragment sources. */ std::stringstream ss; for (int i = 0; i < sources.size(); i++) { ss << sources[i] << std::endl; } shd_builder_->glsl_fragment_source_ = ss.str(); } void MTLShader::compute_shader_from_glsl(MutableSpan sources) { /* Remove #version tag entry. */ sources[0] = ""; /* TODO(Metal): Support compute shaders in Metal. */ MTL_LOG_WARNING( "MTLShader::compute_shader_from_glsl - Compute shaders currently unsupported!\n"); } bool MTLShader::finalize(const shader::ShaderCreateInfo *info) { /* Check if Shader has already been finalized. */ if (this->is_valid()) { MTL_LOG_ERROR("Shader (%p) '%s' has already been finalized!\n", this, this->name_get()); } /* Perform GLSL to MSL source translation. */ BLI_assert(shd_builder_ != nullptr); if (!shd_builder_->source_from_msl_) { bool success = generate_msl_from_glsl(info); if (!success) { /* GLSL to MSL translation has failed, or is unsupported for this shader. */ valid_ = false; BLI_assert_msg(false, "Shader translation from GLSL to MSL has failed. \n"); /* Create empty interface to allow shader to be silently used. */ MTLShaderInterface *mtl_interface = new MTLShaderInterface(this->name_get()); this->set_interface(mtl_interface); /* Release temporary compilation resources. */ delete shd_builder_; shd_builder_ = nullptr; return false; } } /* Ensure we have a valid shader interface. */ MTLShaderInterface *mtl_interface = this->get_interface(); BLI_assert(mtl_interface != nullptr); /* Verify Context handle, fetch device and compile shader. */ BLI_assert(context_); id device = context_->device; BLI_assert(device != nil); /* Ensure source and stage entry-point names are set. */ BLI_assert([vertex_function_name_ length] > 0); if (transform_feedback_type_ == GPU_SHADER_TFB_NONE) { BLI_assert([fragment_function_name_ length] > 0); } BLI_assert(shd_builder_ != nullptr); BLI_assert([shd_builder_->msl_source_vert_ length] > 0); @autoreleasepool { MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease]; options.languageVersion = MTLLanguageVersion2_2; options.fastMathEnabled = YES; NSString *source_to_compile = shd_builder_->msl_source_vert_; for (int src_stage = 0; src_stage <= 1; src_stage++) { source_to_compile = (src_stage == 0) ? shd_builder_->msl_source_vert_ : shd_builder_->msl_source_frag_; /* Transform feedback, skip compilation. */ if (src_stage == 1 && (transform_feedback_type_ != GPU_SHADER_TFB_NONE)) { shader_library_frag_ = nil; break; } /* Concatenate common source. */ NSString *str = [NSString stringWithUTF8String:datatoc_mtl_shader_common_msl]; NSString *source_with_header_a = [str stringByAppendingString:source_to_compile]; /* Inject unique context ID to avoid cross-context shader cache collisions. * Required on macOS 11.0. */ NSString *source_with_header = source_with_header_a; if (@available(macos 11.0, *)) { /* Pass-through. Availability syntax requirement, expression cannot be negated. */ } else { source_with_header = [source_with_header_a stringByAppendingString:[NSString stringWithFormat:@"\n\n#define MTL_CONTEXT_IND %d\n", context_->context_id]]; } [source_with_header retain]; /* Prepare Shader Library. */ NSError *error = nullptr; id library = [device newLibraryWithSource:source_with_header options:options error:&error]; if (error) { /* Only exit out if genuine error and not warning. */ if ([[error localizedDescription] rangeOfString:@"Compilation succeeded"].location == NSNotFound) { NSLog( @"Compile Error - Metal Shader Library (Stage: %d), error %@ \n", src_stage, error); BLI_assert(false); /* Release temporary compilation resources. */ delete shd_builder_; shd_builder_ = nullptr; return false; } } MTL_LOG_INFO("Successfully compiled Metal Shader Library (Stage: %d) for shader; %s\n", src_stage, name); BLI_assert(library != nil); if (src_stage == 0) { /* Retain generated library and assign debug name. */ shader_library_vert_ = library; [shader_library_vert_ retain]; shader_library_vert_.label = [NSString stringWithUTF8String:this->name]; } else { /* Retain generated library for fragment shader and assign debug name. */ shader_library_frag_ = library; [shader_library_frag_ retain]; shader_library_frag_.label = [NSString stringWithUTF8String:this->name]; } [source_with_header autorelease]; } pso_descriptor_.label = [NSString stringWithUTF8String:this->name]; /* Prepare descriptor. */ pso_descriptor_ = [[MTLRenderPipelineDescriptor alloc] init]; [pso_descriptor_ retain]; /* Shader has successfully been created. */ valid_ = true; /* Prepare backing data storage for local uniforms. */ const MTLShaderUniformBlock &push_constant_block = mtl_interface->get_push_constant_block(); if (push_constant_block.size > 0) { push_constant_data_ = MEM_callocN(push_constant_block.size, __func__); this->push_constant_bindstate_mark_dirty(true); } else { push_constant_data_ = nullptr; } } /* Release temporary compilation resources. */ delete shd_builder_; shd_builder_ = nullptr; return true; } void MTLShader::transform_feedback_names_set(Span name_list, const eGPUShaderTFBType geom_type) { tf_output_name_list_.clear(); for (int i = 0; i < name_list.size(); i++) { tf_output_name_list_.append(std::string(name_list[i])); } transform_feedback_type_ = geom_type; } bool MTLShader::transform_feedback_enable(GPUVertBuf *buf) { BLI_assert(transform_feedback_type_ != GPU_SHADER_TFB_NONE); BLI_assert(buf); transform_feedback_active_ = true; transform_feedback_vertbuf_ = buf; /* TODO(Metal): Enable this assertion once #MTLVertBuf lands. */ // BLI_assert(static_cast(unwrap(transform_feedback_vertbuf_))->get_usage_type() == // GPU_USAGE_DEVICE_ONLY); return true; } void MTLShader::transform_feedback_disable() { transform_feedback_active_ = false; transform_feedback_vertbuf_ = nullptr; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Shader Binding. * \{ */ void MTLShader::bind() { MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); if (interface == nullptr || !this->is_valid()) { MTL_LOG_WARNING( "MTLShader::bind - Shader '%s' has no valid implementation in Metal, draw calls will be " "skipped.\n", this->name_get()); } ctx->pipeline_state.active_shader = this; } void MTLShader::unbind() { MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); ctx->pipeline_state.active_shader = nullptr; } void MTLShader::uniform_float(int location, int comp_len, int array_size, const float *data) { BLI_assert(this); if (!this->is_valid()) { return; } MTLShaderInterface *mtl_interface = get_interface(); if (location < 0 || location >= mtl_interface->get_total_uniforms()) { MTL_LOG_WARNING("Uniform location %d is not valid in Shader %s\n", location, this->name_get()); return; } /* Fetch more information about uniform from interface. */ const MTLShaderUniform &uniform = mtl_interface->get_uniform(location); /* Prepare to copy data into local shader push constant memory block. */ BLI_assert(push_constant_data_ != nullptr); uint8_t *dest_ptr = (uint8_t *)push_constant_data_; dest_ptr += uniform.byte_offset; uint32_t copy_size = sizeof(float) * comp_len * array_size; /* Test per-element size. It is valid to copy less array elements than the total, but each * array element needs to match. */ uint32_t source_per_element_size = sizeof(float) * comp_len; uint32_t dest_per_element_size = uniform.size_in_bytes / uniform.array_len; BLI_assert_msg( source_per_element_size <= dest_per_element_size, "source Per-array-element size must be smaller than destination storage capacity for " "that data"); if (source_per_element_size < dest_per_element_size) { switch (uniform.type) { /* Special case for handling 'vec3' array upload. */ case MTL_DATATYPE_FLOAT3: { int numvecs = uniform.array_len; uint8_t *data_c = (uint8_t *)data; /* It is more efficient on the host to only modify data if it has changed. * Data modifications are small, so memory comparison is cheap. * If uniforms have remained unchanged, then we avoid both copying * data into the local uniform struct, and upload of the modified uniform * contents in the command stream. */ bool changed = false; for (int i = 0; i < numvecs; i++) { changed = changed || (memcmp((void *)dest_ptr, (void *)data_c, sizeof(float) * 3) != 0); if (changed) { memcpy((void *)dest_ptr, (void *)data_c, sizeof(float) * 3); } data_c += sizeof(float) * 3; dest_ptr += sizeof(float) * 4; } if (changed) { this->push_constant_bindstate_mark_dirty(true); } return; } /* Special case for handling 'mat3' upload. */ case MTL_DATATYPE_FLOAT3x3: { int numvecs = 3 * uniform.array_len; uint8_t *data_c = (uint8_t *)data; /* It is more efficient on the host to only modify data if it has changed. * Data modifications are small, so memory comparison is cheap. * If uniforms have remained unchanged, then we avoid both copying * data into the local uniform struct, and upload of the modified uniform * contents in the command stream. */ bool changed = false; for (int i = 0; i < numvecs; i++) { changed = changed || (memcmp((void *)dest_ptr, (void *)data_c, sizeof(float) * 3) != 0); if (changed) { memcpy((void *)dest_ptr, (void *)data_c, sizeof(float) * 3); } data_c += sizeof(float) * 3; dest_ptr += sizeof(float) * 4; } if (changed) { this->push_constant_bindstate_mark_dirty(true); } return; } default: shader_debug_printf("INCOMPATIBLE UNIFORM TYPE: %d\n", uniform.type); break; } } /* Debug checks. */ BLI_assert_msg( copy_size <= uniform.size_in_bytes, "Size of provided uniform data is greater than size specified in Shader interface\n"); /* Only flag UBO as modified if data is different -- This can avoid re-binding of unmodified * local uniform data. */ bool data_changed = (memcmp((void *)dest_ptr, (void *)data, copy_size) != 0); if (data_changed) { this->push_constant_bindstate_mark_dirty(true); memcpy((void *)dest_ptr, (void *)data, copy_size); } } void MTLShader::uniform_int(int location, int comp_len, int array_size, const int *data) { BLI_assert(this); if (!this->is_valid()) { return; } /* NOTE(Metal): Invalidation warning for uniform re-mapping of texture slots, unsupported in * Metal, as we cannot point a texture binding at a different slot. */ MTLShaderInterface *mtl_interface = this->get_interface(); if (location >= mtl_interface->get_total_uniforms() && location < (mtl_interface->get_total_uniforms() + mtl_interface->get_total_textures())) { MTL_LOG_WARNING( "Texture uniform location re-mapping unsupported in Metal. (Possibly also bad uniform " "location %d)\n", location); return; } if (location < 0 || location >= mtl_interface->get_total_uniforms()) { MTL_LOG_WARNING( "Uniform is not valid at location %d - Shader %s\n", location, this->name_get()); return; } /* Fetch more information about uniform from interface. */ const MTLShaderUniform &uniform = mtl_interface->get_uniform(location); /* Determine data location in uniform block. */ BLI_assert(push_constant_data_ != nullptr); uint8_t *ptr = (uint8_t *)push_constant_data_; ptr += uniform.byte_offset; /* Copy data into local block. Only flag UBO as modified if data is different * This can avoid re-binding of unmodified local uniform data, reducing * the total number of copy operations needed and data transfers between * CPU and GPU. */ bool data_changed = (memcmp((void *)ptr, (void *)data, sizeof(int) * comp_len * array_size) != 0); if (data_changed) { this->push_constant_bindstate_mark_dirty(true); memcpy((void *)ptr, (void *)data, sizeof(int) * comp_len * array_size); } } bool MTLShader::get_push_constant_is_dirty() { return push_constant_modified_; } void MTLShader::push_constant_bindstate_mark_dirty(bool is_dirty) { push_constant_modified_ = is_dirty; } /** \} */ /* -------------------------------------------------------------------- */ /** \name METAL Custom Behavior * \{ */ void MTLShader::set_vertex_function_name(NSString *vert_function_name) { vertex_function_name_ = vert_function_name; } void MTLShader::set_fragment_function_name(NSString *frag_function_name) { fragment_function_name_ = frag_function_name; } void MTLShader::shader_source_from_msl(NSString *input_vertex_source, NSString *input_fragment_source) { BLI_assert(shd_builder_ != nullptr); shd_builder_->msl_source_vert_ = input_vertex_source; shd_builder_->msl_source_frag_ = input_fragment_source; shd_builder_->source_from_msl_ = true; } void MTLShader::set_interface(MTLShaderInterface *interface) { /* Assign gpu::Shader super-class interface. */ Shader::interface = interface; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Bake Pipeline State Objects * \{ */ /** * Bakes or fetches a pipeline state using the current * #MTLRenderPipelineStateDescriptor state. * * This state contains information on shader inputs/outputs, such * as the vertex descriptor, used to control vertex assembly for * current vertex data, and active render target information, * describing the output attachment pixel formats. * * Other rendering parameters such as global point-size, blend state, color mask * etc; are also used. See mtl_shader.h for full #MLRenderPipelineStateDescriptor. */ MTLRenderPipelineStateInstance *MTLShader::bake_current_pipeline_state( MTLContext *ctx, MTLPrimitiveTopologyClass prim_type) { /* NOTE(Metal): PSO cache can be accessed from multiple threads, though these operations should * be thread-safe due to organization of high-level renderer. If there are any issues, then * access can be guarded as appropriate. */ BLI_assert(this); MTLShaderInterface *mtl_interface = this->get_interface(); BLI_assert(mtl_interface); BLI_assert(this->is_valid()); /* NOTE(Metal): Vertex input assembly description will have been populated externally * via #MTLBatch or #MTLImmediate during binding or draw. */ /* Resolve Context Frame-buffer state. */ MTLFrameBuffer *framebuffer = ctx->get_current_framebuffer(); /* Update global pipeline descriptor. */ MTLStateManager *state_manager = static_cast( MTLContext::get()->state_manager); MTLRenderPipelineStateDescriptor &pipeline_descriptor = state_manager->get_pipeline_descriptor(); pipeline_descriptor.num_color_attachments = 0; for (int attachment = 0; attachment < GPU_FB_MAX_COLOR_ATTACHMENT; attachment++) { MTLAttachment color_attachment = framebuffer->get_color_attachment(attachment); if (color_attachment.used) { /* If SRGB is disabled and format is SRGB, use color data directly with no conversions * between linear and SRGB. */ MTLPixelFormat mtl_format = gpu_texture_format_to_metal( color_attachment.texture->format_get()); if (framebuffer->get_is_srgb() && !framebuffer->get_srgb_enabled()) { mtl_format = MTLPixelFormatRGBA8Unorm; } pipeline_descriptor.color_attachment_format[attachment] = mtl_format; } else { pipeline_descriptor.color_attachment_format[attachment] = MTLPixelFormatInvalid; } pipeline_descriptor.num_color_attachments += (color_attachment.used) ? 1 : 0; } MTLAttachment depth_attachment = framebuffer->get_depth_attachment(); MTLAttachment stencil_attachment = framebuffer->get_stencil_attachment(); pipeline_descriptor.depth_attachment_format = (depth_attachment.used) ? gpu_texture_format_to_metal( depth_attachment.texture->format_get()) : MTLPixelFormatInvalid; pipeline_descriptor.stencil_attachment_format = (stencil_attachment.used) ? gpu_texture_format_to_metal(stencil_attachment.texture->format_get()) : MTLPixelFormatInvalid; /* Resolve Context Pipeline State (required by PSO). */ pipeline_descriptor.color_write_mask = ctx->pipeline_state.color_write_mask; pipeline_descriptor.blending_enabled = ctx->pipeline_state.blending_enabled; pipeline_descriptor.alpha_blend_op = ctx->pipeline_state.alpha_blend_op; pipeline_descriptor.rgb_blend_op = ctx->pipeline_state.rgb_blend_op; pipeline_descriptor.dest_alpha_blend_factor = ctx->pipeline_state.dest_alpha_blend_factor; pipeline_descriptor.dest_rgb_blend_factor = ctx->pipeline_state.dest_rgb_blend_factor; pipeline_descriptor.src_alpha_blend_factor = ctx->pipeline_state.src_alpha_blend_factor; pipeline_descriptor.src_rgb_blend_factor = ctx->pipeline_state.src_rgb_blend_factor; pipeline_descriptor.point_size = ctx->pipeline_state.point_size; /* Primitive Type -- Primitive topology class needs to be specified for layered rendering. */ bool requires_specific_topology_class = uses_mtl_array_index_ || prim_type == MTLPrimitiveTopologyClassPoint; pipeline_descriptor.vertex_descriptor.prim_topology_class = (requires_specific_topology_class) ? prim_type : MTLPrimitiveTopologyClassUnspecified; /* Check if current PSO exists in the cache. */ MTLRenderPipelineStateInstance **pso_lookup = pso_cache_.lookup_ptr(pipeline_descriptor); MTLRenderPipelineStateInstance *pipeline_state = (pso_lookup) ? *pso_lookup : nullptr; if (pipeline_state != nullptr) { return pipeline_state; } shader_debug_printf("Baking new pipeline variant for shader: %s\n", this->name); /* Generate new Render Pipeline State Object (PSO). */ @autoreleasepool { /* Prepare Render Pipeline Descriptor. */ /* Setup function specialization constants, used to modify and optimize * generated code based on current render pipeline configuration. */ MTLFunctionConstantValues *values = [[MTLFunctionConstantValues new] autorelease]; /* Prepare Vertex descriptor based on current pipeline vertex binding state. */ MTLRenderPipelineStateDescriptor ¤t_state = pipeline_descriptor; MTLRenderPipelineDescriptor *desc = pso_descriptor_; [desc reset]; pso_descriptor_.label = [NSString stringWithUTF8String:this->name]; /* Offset the bind index for Uniform buffers such that they begin after the VBO * buffer bind slots. `MTL_uniform_buffer_base_index` is passed as a function * specialization constant, customized per unique pipeline state permutation. * * NOTE: For binding point compaction, we could use the number of VBOs present * in the current PSO configuration `current_state.vertex_descriptor.num_vert_buffers`). * However, it is more efficient to simply offset the uniform buffer base index to the * maximal number of VBO bind-points, as then UBO bind-points for similar draw calls * will align and avoid the requirement for additional binding. */ int MTL_uniform_buffer_base_index = GPU_BATCH_VBO_MAX_LEN; /* Null buffer index is used if an attribute is not found in the * bound VBOs #VertexFormat. */ int null_buffer_index = current_state.vertex_descriptor.num_vert_buffers; bool using_null_buffer = false; if (this->get_uses_ssbo_vertex_fetch()) { /* If using SSBO Vertex fetch mode, no vertex descriptor is required * as we wont be using stage-in. */ desc.vertexDescriptor = nil; desc.inputPrimitiveTopology = MTLPrimitiveTopologyClassUnspecified; /* We want to offset the uniform buffer base to allow for sufficient VBO binding slots - We * also require +1 slot for the Index buffer. */ MTL_uniform_buffer_base_index = MTL_SSBO_VERTEX_FETCH_IBO_INDEX + 1; } else { for (const uint i : IndexRange(current_state.vertex_descriptor.num_attributes)) { /* Metal back-end attribute descriptor state. */ MTLVertexAttributeDescriptorPSO &attribute_desc = current_state.vertex_descriptor.attributes[i]; /* Flag format conversion */ /* In some cases, Metal cannot implicitly convert between data types. * In these instances, the fetch mode #GPUVertFetchMode as provided in the vertex format * is passed in, and used to populate function constants named: MTL_AttributeConvert0..15. * * It is then the responsibility of the vertex shader to perform any necessary type * casting. * * See `mtl_shader.hh` for more information. Relevant Metal API documentation: * https://developer.apple.com/documentation/metal/mtlvertexattributedescriptor/1516081-format?language=objc */ if (attribute_desc.format == MTLVertexFormatInvalid) { MTL_LOG_WARNING( "MTLShader: baking pipeline state for '%s'- expected input attribute at " "index '%d' but none was specified in the current vertex state\n", mtl_interface->get_name(), i); /* Write out null conversion constant if attribute unused. */ int MTL_attribute_conversion_mode = 0; [values setConstantValue:&MTL_attribute_conversion_mode type:MTLDataTypeInt withName:[NSString stringWithFormat:@"MTL_AttributeConvert%d", i]]; continue; } int MTL_attribute_conversion_mode = (int)attribute_desc.format_conversion_mode; [values setConstantValue:&MTL_attribute_conversion_mode type:MTLDataTypeInt withName:[NSString stringWithFormat:@"MTL_AttributeConvert%d", i]]; if (MTL_attribute_conversion_mode == GPU_FETCH_INT_TO_FLOAT_UNIT || MTL_attribute_conversion_mode == GPU_FETCH_INT_TO_FLOAT) { shader_debug_printf( "TODO(Metal): Shader %s needs to support internal format conversion\n", mtl_interface->name); } /* Copy metal back-end attribute descriptor state into PSO descriptor. * NOTE: need to copy each element due to direct assignment restrictions. * Also note */ MTLVertexAttributeDescriptor *mtl_attribute = desc.vertexDescriptor.attributes[i]; mtl_attribute.format = attribute_desc.format; mtl_attribute.offset = attribute_desc.offset; mtl_attribute.bufferIndex = attribute_desc.buffer_index; } for (const uint i : IndexRange(current_state.vertex_descriptor.num_vert_buffers)) { /* Metal back-end state buffer layout. */ const MTLVertexBufferLayoutDescriptorPSO &buf_layout = current_state.vertex_descriptor.buffer_layouts[i]; /* Copy metal back-end buffer layout state into PSO descriptor. * NOTE: need to copy each element due to copying from internal * back-end descriptor to Metal API descriptor. */ MTLVertexBufferLayoutDescriptor *mtl_buf_layout = desc.vertexDescriptor.layouts[i]; mtl_buf_layout.stepFunction = buf_layout.step_function; mtl_buf_layout.stepRate = buf_layout.step_rate; mtl_buf_layout.stride = buf_layout.stride; } /* Mark empty attribute conversion. */ for (int i = current_state.vertex_descriptor.num_attributes; i < GPU_VERT_ATTR_MAX_LEN; i++) { int MTL_attribute_conversion_mode = 0; [values setConstantValue:&MTL_attribute_conversion_mode type:MTLDataTypeInt withName:[NSString stringWithFormat:@"MTL_AttributeConvert%d", i]]; } /* DEBUG: Missing/empty attributes. */ /* Attributes are normally mapped as part of the state setting based on the used * #GPUVertFormat, however, if attributes have not been set, we can sort them out here. */ for (const uint i : IndexRange(mtl_interface->get_total_attributes())) { const MTLShaderInputAttribute &attribute = mtl_interface->get_attribute(i); MTLVertexAttributeDescriptor *current_attribute = desc.vertexDescriptor.attributes[i]; if (current_attribute.format == MTLVertexFormatInvalid) { #if MTL_DEBUG_SHADER_ATTRIBUTES == 1 MTL_LOG_INFO("-> Filling in unbound attribute '%s' for shader PSO '%s' \n", attribute.name, mtl_interface->name); #endif current_attribute.format = attribute.format; current_attribute.offset = 0; current_attribute.bufferIndex = null_buffer_index; /* Add Null vert buffer binding for invalid attributes. */ if (!using_null_buffer) { MTLVertexBufferLayoutDescriptor *null_buf_layout = desc.vertexDescriptor.layouts[null_buffer_index]; /* Use constant step function such that null buffer can * contain just a singular dummy attribute. */ null_buf_layout.stepFunction = MTLVertexStepFunctionConstant; null_buf_layout.stepRate = 0; null_buf_layout.stride = max_ii(null_buf_layout.stride, attribute.size); /* If we are using the maximum number of vertex buffers, or tight binding indices, * MTL_uniform_buffer_base_index needs shifting to the bind slot after the null buffer * index. */ if (null_buffer_index >= MTL_uniform_buffer_base_index) { MTL_uniform_buffer_base_index = null_buffer_index + 1; } using_null_buffer = true; #if MTL_DEBUG_SHADER_ATTRIBUTES == 1 MTL_LOG_INFO("Setting up buffer binding for null attribute with buffer index %d\n", null_buffer_index); #endif } } } /* Primitive Topology */ desc.inputPrimitiveTopology = pipeline_descriptor.vertex_descriptor.prim_topology_class; } /* Update constant value for 'MTL_uniform_buffer_base_index' */ [values setConstantValue:&MTL_uniform_buffer_base_index type:MTLDataTypeInt withName:@"MTL_uniform_buffer_base_index"]; /* Transform feedback constant */ int MTL_transform_feedback_buffer_index = (this->transform_feedback_type_ != GPU_SHADER_TFB_NONE) ? MTL_uniform_buffer_base_index + mtl_interface->get_total_uniform_blocks() : -1; if (this->transform_feedback_type_ != GPU_SHADER_TFB_NONE) { [values setConstantValue:&MTL_transform_feedback_buffer_index type:MTLDataTypeInt withName:@"MTL_transform_feedback_buffer_index"]; } /* gl_PointSize constant */ bool null_pointsize = true; float MTL_pointsize = pipeline_descriptor.point_size; if (pipeline_descriptor.vertex_descriptor.prim_topology_class == MTLPrimitiveTopologyClassPoint) { /* `if pointsize is > 0.0`, PROGRAM_POINT_SIZE is enabled, and `gl_PointSize` shader keyword * overrides the value. Otherwise, if < 0.0, use global constant point size. */ if (MTL_pointsize < 0.0) { MTL_pointsize = fabsf(MTL_pointsize); [values setConstantValue:&MTL_pointsize type:MTLDataTypeFloat withName:@"MTL_global_pointsize"]; null_pointsize = false; } } if (null_pointsize) { MTL_pointsize = 0.0f; [values setConstantValue:&MTL_pointsize type:MTLDataTypeFloat withName:@"MTL_global_pointsize"]; } /* Compile functions */ NSError *error = nullptr; desc.vertexFunction = [shader_library_vert_ newFunctionWithName:vertex_function_name_ constantValues:values error:&error]; if (error) { NSLog(@"Compile Error - Metal Shader vertex function, error %@", error); /* Only exit out if genuine error and not warning */ if ([[error localizedDescription] rangeOfString:@"Compilation succeeded"].location == NSNotFound) { BLI_assert(false); return nullptr; } } /* If transform feedback is used, Vertex-only stage */ if (transform_feedback_type_ == GPU_SHADER_TFB_NONE) { desc.fragmentFunction = [shader_library_frag_ newFunctionWithName:fragment_function_name_ constantValues:values error:&error]; if (error) { NSLog(@"Compile Error - Metal Shader fragment function, error %@", error); /* Only exit out if genuine error and not warning */ if ([[error localizedDescription] rangeOfString:@"Compilation succeeded"].location == NSNotFound) { BLI_assert(false); return nullptr; } } } else { desc.fragmentFunction = nil; desc.rasterizationEnabled = false; } /* Setup pixel format state */ for (int color_attachment = 0; color_attachment < GPU_FB_MAX_COLOR_ATTACHMENT; color_attachment++) { /* Fetch color attachment pixel format in back-end pipeline state. */ MTLPixelFormat pixel_format = current_state.color_attachment_format[color_attachment]; /* Populate MTL API PSO attachment descriptor. */ MTLRenderPipelineColorAttachmentDescriptor *col_attachment = desc.colorAttachments[color_attachment]; col_attachment.pixelFormat = pixel_format; if (pixel_format != MTLPixelFormatInvalid) { bool format_supports_blending = mtl_format_supports_blending(pixel_format); col_attachment.writeMask = current_state.color_write_mask; col_attachment.blendingEnabled = current_state.blending_enabled && format_supports_blending; if (format_supports_blending && current_state.blending_enabled) { col_attachment.alphaBlendOperation = current_state.alpha_blend_op; col_attachment.rgbBlendOperation = current_state.rgb_blend_op; col_attachment.destinationAlphaBlendFactor = current_state.dest_alpha_blend_factor; col_attachment.destinationRGBBlendFactor = current_state.dest_rgb_blend_factor; col_attachment.sourceAlphaBlendFactor = current_state.src_alpha_blend_factor; col_attachment.sourceRGBBlendFactor = current_state.src_rgb_blend_factor; } else { if (current_state.blending_enabled && !format_supports_blending) { shader_debug_printf( "[Warning] Attempting to Bake PSO, but MTLPixelFormat %d does not support " "blending\n", *((int *)&pixel_format)); } } } } desc.depthAttachmentPixelFormat = current_state.depth_attachment_format; desc.stencilAttachmentPixelFormat = current_state.stencil_attachment_format; /* Compile PSO */ MTLAutoreleasedRenderPipelineReflection reflection_data; id pso = [ctx->device newRenderPipelineStateWithDescriptor:desc options:MTLPipelineOptionBufferTypeInfo reflection:&reflection_data error:&error]; if (error) { NSLog(@"Failed to create PSO for shader: %s error %@\n", this->name, error); BLI_assert(false); return nullptr; } else if (!pso) { NSLog(@"Failed to create PSO for shader: %s, but no error was provided!\n", this->name); BLI_assert(false); return nullptr; } else { NSLog(@"Successfully compiled PSO for shader: %s (Metal Context: %p)\n", this->name, ctx); } /* Prepare pipeline state instance. */ MTLRenderPipelineStateInstance *pso_inst = new MTLRenderPipelineStateInstance(); pso_inst->vert = desc.vertexFunction; pso_inst->frag = desc.fragmentFunction; pso_inst->pso = pso; pso_inst->base_uniform_buffer_index = MTL_uniform_buffer_base_index; pso_inst->null_attribute_buffer_index = (using_null_buffer) ? null_buffer_index : -1; pso_inst->transform_feedback_buffer_index = MTL_transform_feedback_buffer_index; pso_inst->shader_pso_index = pso_cache_.size(); pso_inst->reflection_data_available = (reflection_data != nil); if (reflection_data != nil) { /* Extract shader reflection data for buffer bindings. * This reflection data is used to contrast the binding information * we know about in the interface against the bindings in the finalized * PSO. This accounts for bindings which have been stripped out during * optimization, and allows us to both avoid over-binding and also * allows us to verify size-correctness for bindings, to ensure * that buffers bound are not smaller than the size of expected data. */ NSArray *vert_args = [reflection_data vertexArguments]; pso_inst->buffer_bindings_reflection_data_vert.clear(); int buffer_binding_max_ind = 0; for (int i = 0; i < [vert_args count]; i++) { MTLArgument *arg = [vert_args objectAtIndex:i]; if ([arg type] == MTLArgumentTypeBuffer) { int buf_index = [arg index] - MTL_uniform_buffer_base_index; if (buf_index >= 0) { buffer_binding_max_ind = max_ii(buffer_binding_max_ind, buf_index); } } } pso_inst->buffer_bindings_reflection_data_vert.resize(buffer_binding_max_ind + 1); for (int i = 0; i < buffer_binding_max_ind + 1; i++) { pso_inst->buffer_bindings_reflection_data_vert[i] = {0, 0, 0, false}; } for (int i = 0; i < [vert_args count]; i++) { MTLArgument *arg = [vert_args objectAtIndex:i]; if ([arg type] == MTLArgumentTypeBuffer) { int buf_index = [arg index] - MTL_uniform_buffer_base_index; if (buf_index >= 0) { pso_inst->buffer_bindings_reflection_data_vert[buf_index] = { (uint32_t)([arg index]), (uint32_t)([arg bufferDataSize]), (uint32_t)([arg bufferAlignment]), ([arg isActive] == YES) ? true : false}; } } } NSArray *frag_args = [reflection_data fragmentArguments]; pso_inst->buffer_bindings_reflection_data_frag.clear(); buffer_binding_max_ind = 0; for (int i = 0; i < [frag_args count]; i++) { MTLArgument *arg = [frag_args objectAtIndex:i]; if ([arg type] == MTLArgumentTypeBuffer) { int buf_index = [arg index] - MTL_uniform_buffer_base_index; if (buf_index >= 0) { buffer_binding_max_ind = max_ii(buffer_binding_max_ind, buf_index); } } } pso_inst->buffer_bindings_reflection_data_frag.resize(buffer_binding_max_ind + 1); for (int i = 0; i < buffer_binding_max_ind + 1; i++) { pso_inst->buffer_bindings_reflection_data_frag[i] = {0, 0, 0, false}; } for (int i = 0; i < [frag_args count]; i++) { MTLArgument *arg = [frag_args objectAtIndex:i]; if ([arg type] == MTLArgumentTypeBuffer) { int buf_index = [arg index] - MTL_uniform_buffer_base_index; shader_debug_printf(" BUF IND: %d (arg name: %s)\n", buf_index, [[arg name] UTF8String]); if (buf_index >= 0) { pso_inst->buffer_bindings_reflection_data_frag[buf_index] = { (uint32_t)([arg index]), (uint32_t)([arg bufferDataSize]), (uint32_t)([arg bufferAlignment]), ([arg isActive] == YES) ? true : false}; } } } } [pso_inst->vert retain]; [pso_inst->frag retain]; [pso_inst->pso retain]; /* Insert into pso cache. */ pso_cache_.add(pipeline_descriptor, pso_inst); shader_debug_printf("PSO CACHE: Stored new variant in PSO cache for shader '%s'\n", this->name); return pso_inst; } } /** \} */ /* -------------------------------------------------------------------- */ /** \name SSBO-vertex-fetch-mode attribute control. * \{ */ int MTLShader::ssbo_vertex_type_to_attr_type(MTLVertexFormat attribute_type) { switch (attribute_type) { case MTLVertexFormatFloat: return GPU_SHADER_ATTR_TYPE_FLOAT; case MTLVertexFormatInt: return GPU_SHADER_ATTR_TYPE_INT; case MTLVertexFormatUInt: return GPU_SHADER_ATTR_TYPE_UINT; case MTLVertexFormatShort: return GPU_SHADER_ATTR_TYPE_SHORT; case MTLVertexFormatUChar: return GPU_SHADER_ATTR_TYPE_CHAR; case MTLVertexFormatUChar2: return GPU_SHADER_ATTR_TYPE_CHAR2; case MTLVertexFormatUChar3: return GPU_SHADER_ATTR_TYPE_CHAR3; case MTLVertexFormatUChar4: return GPU_SHADER_ATTR_TYPE_CHAR4; case MTLVertexFormatFloat2: return GPU_SHADER_ATTR_TYPE_VEC2; case MTLVertexFormatFloat3: return GPU_SHADER_ATTR_TYPE_VEC3; case MTLVertexFormatFloat4: return GPU_SHADER_ATTR_TYPE_VEC4; case MTLVertexFormatUInt2: return GPU_SHADER_ATTR_TYPE_UVEC2; case MTLVertexFormatUInt3: return GPU_SHADER_ATTR_TYPE_UVEC3; case MTLVertexFormatUInt4: return GPU_SHADER_ATTR_TYPE_UVEC4; case MTLVertexFormatInt2: return GPU_SHADER_ATTR_TYPE_IVEC2; case MTLVertexFormatInt3: return GPU_SHADER_ATTR_TYPE_IVEC3; case MTLVertexFormatInt4: return GPU_SHADER_ATTR_TYPE_IVEC4; case MTLVertexFormatUCharNormalized: return GPU_SHADER_ATTR_TYPE_UCHAR_NORM; case MTLVertexFormatUChar2Normalized: return GPU_SHADER_ATTR_TYPE_UCHAR2_NORM; case MTLVertexFormatUChar3Normalized: return GPU_SHADER_ATTR_TYPE_UCHAR3_NORM; case MTLVertexFormatUChar4Normalized: return GPU_SHADER_ATTR_TYPE_UCHAR4_NORM; case MTLVertexFormatInt1010102Normalized: return GPU_SHADER_ATTR_TYPE_INT1010102_NORM; case MTLVertexFormatShort3Normalized: return GPU_SHADER_ATTR_TYPE_SHORT3_NORM; default: BLI_assert_msg(false, "Not yet supported attribute type for SSBO vertex fetch -- Add entry " "GPU_SHADER_ATTR_TYPE_** to shader defines, and in this table"); return -1; } return -1; } void MTLShader::ssbo_vertex_fetch_bind_attributes_begin() { MTLShaderInterface *mtl_interface = this->get_interface(); ssbo_vertex_attribute_bind_active_ = true; ssbo_vertex_attribute_bind_mask_ = (1 << mtl_interface->get_total_attributes()) - 1; /* Reset tracking of actively used VBO bind slots for SSBO vertex fetch mode. */ for (int i = 0; i < MTL_SSBO_VERTEX_FETCH_MAX_VBOS; i++) { ssbo_vbo_slot_used_[i] = false; } } void MTLShader::ssbo_vertex_fetch_bind_attribute(const MTLSSBOAttribute &ssbo_attr) { /* Fetch attribute. */ MTLShaderInterface *mtl_interface = this->get_interface(); BLI_assert(ssbo_attr.mtl_attribute_index >= 0 && ssbo_attr.mtl_attribute_index < mtl_interface->get_total_attributes()); UNUSED_VARS_NDEBUG(mtl_interface); /* Update bind-mask to verify this attribute has been used. */ BLI_assert((ssbo_vertex_attribute_bind_mask_ & (1 << ssbo_attr.mtl_attribute_index)) == (1 << ssbo_attr.mtl_attribute_index) && "Attribute has already been bound"); ssbo_vertex_attribute_bind_mask_ &= ~(1 << ssbo_attr.mtl_attribute_index); /* Fetch attribute uniform addresses from cache. */ ShaderSSBOAttributeBinding &cached_ssbo_attribute = cached_ssbo_attribute_bindings_[ssbo_attr.mtl_attribute_index]; BLI_assert(cached_ssbo_attribute.attribute_index >= 0); /* Write attribute descriptor properties to shader uniforms. */ this->uniform_int(cached_ssbo_attribute.uniform_offset, 1, 1, &ssbo_attr.attribute_offset); this->uniform_int(cached_ssbo_attribute.uniform_stride, 1, 1, &ssbo_attr.per_vertex_stride); int inst_val = (ssbo_attr.is_instance ? 1 : 0); this->uniform_int(cached_ssbo_attribute.uniform_fetchmode, 1, 1, &inst_val); this->uniform_int(cached_ssbo_attribute.uniform_vbo_id, 1, 1, &ssbo_attr.vbo_id); BLI_assert(ssbo_attr.attribute_format >= 0); this->uniform_int(cached_ssbo_attribute.uniform_attr_type, 1, 1, &ssbo_attr.attribute_format); ssbo_vbo_slot_used_[ssbo_attr.vbo_id] = true; } void MTLShader::ssbo_vertex_fetch_bind_attributes_end(id active_encoder) { ssbo_vertex_attribute_bind_active_ = false; /* If our mask is non-zero, we have unassigned attributes. */ if (ssbo_vertex_attribute_bind_mask_ != 0) { MTLShaderInterface *mtl_interface = this->get_interface(); /* Determine if there is a free slot we can bind the null buffer to -- We should have at * least ONE free slot in this instance. */ int null_attr_buffer_slot = -1; for (int i = 0; i < MTL_SSBO_VERTEX_FETCH_MAX_VBOS; i++) { if (!ssbo_vbo_slot_used_[i]) { null_attr_buffer_slot = i; break; } } BLI_assert_msg(null_attr_buffer_slot >= 0, "No suitable bind location for a NULL buffer was found"); for (int i = 0; i < mtl_interface->get_total_attributes(); i++) { if (ssbo_vertex_attribute_bind_mask_ & (1 << i)) { const MTLShaderInputAttribute *mtl_shader_attribute = &mtl_interface->get_attribute(i); #if MTL_DEBUG_SHADER_ATTRIBUTES == 1 MTL_LOG_WARNING( "SSBO Vertex Fetch missing attribute with index: %d. Shader: %s, Attr " "Name: " "%s - Null buffer bound\n", i, this->name_get(), mtl_shader_attribute->name); #endif /* Bind Attribute with NULL buffer index and stride zero (for constant access). */ MTLSSBOAttribute ssbo_attr( i, null_attr_buffer_slot, 0, 0, GPU_SHADER_ATTR_TYPE_FLOAT, false); ssbo_vertex_fetch_bind_attribute(ssbo_attr); MTL_LOG_WARNING( "Unassigned Shader attribute: %s, Attr Name: %s -- Binding NULL BUFFER to " "slot %d\n", this->name_get(), mtl_interface->get_name_at_offset(mtl_shader_attribute->name_offset), null_attr_buffer_slot); } } /* Bind NULL buffer to given VBO slot. */ MTLContext *ctx = reinterpret_cast(GPU_context_active_get()); id null_buf = ctx->get_null_attribute_buffer(); BLI_assert(null_buf); MTLRenderPassState &rps = ctx->main_command_buffer.get_render_pass_state(); rps.bind_vertex_buffer(null_buf, 0, null_attr_buffer_slot); } } GPUVertBuf *MTLShader::get_transform_feedback_active_buffer() { if (transform_feedback_type_ == GPU_SHADER_TFB_NONE || !transform_feedback_active_) { return nullptr; } return transform_feedback_vertbuf_; } bool MTLShader::has_transform_feedback_varying(std::string str) { if (this->transform_feedback_type_ == GPU_SHADER_TFB_NONE) { return false; } return (std::find(tf_output_name_list_.begin(), tf_output_name_list_.end(), str) != tf_output_name_list_.end()); } } // blender::gpu::shdaer