| /* |
| * MVKPipeline.mm |
| * |
| * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "MVKPipeline.h" |
| #include <MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h> |
| #include "MVKRenderPass.h" |
| #include "MVKCommandBuffer.h" |
| #include "MVKFoundation.h" |
| #include "MVKOSExtensions.h" |
| #include "MVKStrings.h" |
| #include "MTLRenderPipelineDescriptor+MoltenVK.h" |
| #include "mvk_datatypes.h" |
| |
| #include <cereal/archives/binary.hpp> |
| #include <cereal/types/string.hpp> |
| #include <cereal/types/vector.hpp> |
| |
| using namespace std; |
| |
| |
| #pragma mark MVKPipelineLayout |
| |
| void MVKPipelineLayout::bindDescriptorSets(MVKCommandEncoder* cmdEncoder, |
| vector<MVKDescriptorSet*>& descriptorSets, |
| uint32_t firstSet, |
| vector<uint32_t>& dynamicOffsets) { |
| |
| uint32_t pDynamicOffsetIndex = 0; |
| uint32_t dsCnt = (uint32_t)descriptorSets.size(); |
| for (uint32_t dsIdx = 0; dsIdx < dsCnt; dsIdx++) { |
| MVKDescriptorSet* descSet = descriptorSets[dsIdx]; |
| uint32_t dslIdx = firstSet + dsIdx; |
| _descriptorSetLayouts[dslIdx].bindDescriptorSet(cmdEncoder, descSet, |
| _dslMTLResourceIndexOffsets[dslIdx], |
| dynamicOffsets, &pDynamicOffsetIndex); |
| } |
| cmdEncoder->getPushConstants(VK_SHADER_STAGE_VERTEX_BIT)->setMTLBufferIndex(_pushConstantsMTLResourceIndexOffsets.vertexStage.bufferIndex); |
| cmdEncoder->getPushConstants(VK_SHADER_STAGE_FRAGMENT_BIT)->setMTLBufferIndex(_pushConstantsMTLResourceIndexOffsets.fragmentStage.bufferIndex); |
| cmdEncoder->getPushConstants(VK_SHADER_STAGE_COMPUTE_BIT)->setMTLBufferIndex(_pushConstantsMTLResourceIndexOffsets.computeStage.bufferIndex); |
| } |
| |
| void MVKPipelineLayout::populateShaderConverterContext(SPIRVToMSLConverterContext& context) { |
| context.resourceBindings.clear(); |
| |
| // Add resource bindings defined in the descriptor set layouts |
| uint32_t dslCnt = (uint32_t)_descriptorSetLayouts.size(); |
| for (uint32_t dslIdx = 0; dslIdx < dslCnt; dslIdx++) { |
| _descriptorSetLayouts[dslIdx].populateShaderConverterContext(context, |
| _dslMTLResourceIndexOffsets[dslIdx], |
| dslIdx); |
| } |
| |
| // Add any resource bindings used by push-constants |
| mvkPopulateShaderConverterContext(context, |
| _pushConstantsMTLResourceIndexOffsets.vertexStage, |
| spv::ExecutionModelVertex, |
| kPushConstDescSet, |
| kPushConstBinding); |
| |
| mvkPopulateShaderConverterContext(context, |
| _pushConstantsMTLResourceIndexOffsets.fragmentStage, |
| spv::ExecutionModelFragment, |
| kPushConstDescSet, |
| kPushConstBinding); |
| |
| mvkPopulateShaderConverterContext(context, |
| _pushConstantsMTLResourceIndexOffsets.computeStage, |
| spv::ExecutionModelGLCompute, |
| kPushConstDescSet, |
| kPushConstBinding); |
| } |
| |
| MVKPipelineLayout::MVKPipelineLayout(MVKDevice* device, |
| const VkPipelineLayoutCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) { |
| |
| // Add descriptor set layouts, accumulating the resource index offsets used by the |
| // corresponding DSL, and associating the current accumulated resource index offsets |
| // with each DSL as it is added. The final accumulation of resource index offsets |
| // becomes the resource index offsets that will be used for push contants. |
| |
| // According to the Vulkan spec, VkDescriptorSetLayout is intended to be consumed when |
| // passed to any Vulkan function, and may be safely destroyed by app immediately after. |
| // In order for this pipeline layout to retain the content of a VkDescriptorSetLayout, |
| // this pipeline holds onto copies of the MVKDescriptorSetLayout instances, so that the |
| // originals created by the app can be safely destroyed. |
| |
| _descriptorSetLayouts.reserve(pCreateInfo->setLayoutCount); |
| for (uint32_t i = 0; i < pCreateInfo->setLayoutCount; i++) { |
| MVKDescriptorSetLayout* pDescSetLayout = (MVKDescriptorSetLayout*)pCreateInfo->pSetLayouts[i]; |
| _descriptorSetLayouts.push_back(*pDescSetLayout); |
| _dslMTLResourceIndexOffsets.push_back(_pushConstantsMTLResourceIndexOffsets); |
| _pushConstantsMTLResourceIndexOffsets += pDescSetLayout->_mtlResourceCounts; |
| } |
| |
| // Add push constants |
| _pushConstants.reserve(pCreateInfo->pushConstantRangeCount); |
| for (uint32_t i = 0; i < pCreateInfo->pushConstantRangeCount; i++) { |
| _pushConstants.push_back(pCreateInfo->pPushConstantRanges[i]); |
| } |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKGraphicsPipeline |
| |
| void MVKGraphicsPipeline::encode(MVKCommandEncoder* cmdEncoder) { |
| |
| id<MTLRenderCommandEncoder> mtlCmdEnc = cmdEncoder->_mtlRenderEncoder; |
| if ( !mtlCmdEnc ) { return; } // Pre-renderpass. Come back later. |
| |
| // Render pipeline state |
| [mtlCmdEnc setRenderPipelineState: _mtlPipelineState]; |
| |
| // Depth stencil state |
| if (_hasDepthStencilInfo) { |
| cmdEncoder->_depthStencilState.setDepthStencilState(_depthStencilInfo); |
| cmdEncoder->_stencilReferenceValueState.setReferenceValues(_depthStencilInfo); |
| } else { |
| cmdEncoder->_depthStencilState.reset(); |
| cmdEncoder->_stencilReferenceValueState.reset(); |
| } |
| |
| // Rasterization |
| cmdEncoder->_blendColorState.setBlendColor(_blendConstants[0], _blendConstants[1], |
| _blendConstants[2], _blendConstants[3], false); |
| cmdEncoder->_depthBiasState.setDepthBias(_rasterInfo); |
| cmdEncoder->_viewportState.setViewports(_mtlViewports, 0, false); |
| cmdEncoder->_scissorState.setScissors(_mtlScissors, 0, false); |
| cmdEncoder->_mtlPrimitiveType = _mtlPrimitiveType; |
| |
| [mtlCmdEnc setCullMode: _mtlCullMode]; |
| [mtlCmdEnc setFrontFacingWinding: _mtlFrontWinding]; |
| [mtlCmdEnc setTriangleFillMode: _mtlFillMode]; |
| |
| if (_device->_pMetalFeatures->depthClipMode) { |
| [mtlCmdEnc setDepthClipMode: _mtlDepthClipMode]; |
| } |
| } |
| |
| bool MVKGraphicsPipeline::supportsDynamicState(VkDynamicState state) { |
| |
| // First test if this dynamic state is explicitly turned off |
| if ( (state >= VK_DYNAMIC_STATE_RANGE_SIZE) || !_dynamicStateEnabled[state] ) { return false; } |
| |
| // Some dynamic states have other restrictions |
| switch (state) { |
| case VK_DYNAMIC_STATE_DEPTH_BIAS: |
| return _rasterInfo.depthBiasEnable; |
| default: |
| return true; |
| } |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKGraphicsPipeline::MVKGraphicsPipeline(MVKDevice* device, |
| MVKPipelineCache* pipelineCache, |
| MVKPipeline* parent, |
| const VkGraphicsPipelineCreateInfo* pCreateInfo) : MVKPipeline(device, pipelineCache, parent) { |
| |
| // Track dynamic state in _dynamicStateEnabled array |
| memset(&_dynamicStateEnabled, false, sizeof(_dynamicStateEnabled)); // start with all dynamic state disabled |
| const VkPipelineDynamicStateCreateInfo* pDS = pCreateInfo->pDynamicState; |
| if (pDS) { |
| for (uint32_t i = 0; i < pDS->dynamicStateCount; i++) { |
| VkDynamicState ds = pDS->pDynamicStates[i]; |
| _dynamicStateEnabled[ds] = true; |
| } |
| } |
| |
| if (pCreateInfo->pColorBlendState) { |
| memcpy(&_blendConstants, &pCreateInfo->pColorBlendState->blendConstants, sizeof(_blendConstants)); |
| } |
| |
| if (pCreateInfo->pInputAssemblyState) { |
| _mtlPrimitiveType = mvkMTLPrimitiveTypeFromVkPrimitiveTopology(pCreateInfo->pInputAssemblyState->topology); |
| } |
| |
| // Add raster content - must occur before initMTLRenderPipelineState() for rasterizerDiscardEnable |
| _mtlCullMode = MTLCullModeNone; |
| _mtlFrontWinding = MTLWindingCounterClockwise; |
| _mtlFillMode = MTLTriangleFillModeFill; |
| _mtlDepthClipMode = MTLDepthClipModeClip; |
| bool hasRasterInfo = mvkSetOrClear(&_rasterInfo, pCreateInfo->pRasterizationState); |
| if (hasRasterInfo) { |
| _mtlCullMode = mvkMTLCullModeFromVkCullModeFlags(_rasterInfo.cullMode); |
| _mtlFrontWinding = mvkMTLWindingFromVkFrontFace(_rasterInfo.frontFace); |
| _mtlFillMode = mvkMTLTriangleFillModeFromVkPolygonMode(_rasterInfo.polygonMode); |
| if (_rasterInfo.depthClampEnable) { |
| if (_device->_pMetalFeatures->depthClipMode) { |
| _mtlDepthClipMode = MTLDepthClipModeClamp; |
| } else { |
| setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "This device does not support depth clamping.")); |
| } |
| } |
| } |
| |
| // Render pipeline state |
| initMTLRenderPipelineState(pCreateInfo); |
| |
| // Depth stencil content |
| _hasDepthStencilInfo = mvkSetOrClear(&_depthStencilInfo, pCreateInfo->pDepthStencilState); |
| |
| // Add viewports and scissors |
| if (pCreateInfo->pViewportState) { |
| _mtlViewports.reserve(pCreateInfo->pViewportState->viewportCount); |
| for (uint32_t i = 0; i < pCreateInfo->pViewportState->viewportCount; i++) { |
| // If viewport is dyanamic, we still add a dummy so that the count will be tracked. |
| MTLViewport mtlVP; |
| if ( !_dynamicStateEnabled[VK_DYNAMIC_STATE_VIEWPORT] ) { |
| mtlVP = mvkMTLViewportFromVkViewport(pCreateInfo->pViewportState->pViewports[i]); |
| } |
| _mtlViewports.push_back(mtlVP); |
| } |
| _mtlScissors.reserve(pCreateInfo->pViewportState->scissorCount); |
| for (uint32_t i = 0; i < pCreateInfo->pViewportState->scissorCount; i++) { |
| // If scissor is dyanamic, we still add a dummy so that the count will be tracked. |
| MTLScissorRect mtlSc; |
| if ( !_dynamicStateEnabled[VK_DYNAMIC_STATE_SCISSOR] ) { |
| mtlSc = mvkMTLScissorRectFromVkRect2D(pCreateInfo->pViewportState->pScissors[i]); |
| } |
| _mtlScissors.push_back(mtlSc); |
| } |
| } |
| } |
| |
| /** Constructs the underlying Metal render pipeline. */ |
| void MVKGraphicsPipeline::initMTLRenderPipelineState(const VkGraphicsPipelineCreateInfo* pCreateInfo) { |
| _mtlPipelineState = nil; |
| MTLRenderPipelineDescriptor* plDesc = getMTLRenderPipelineDescriptor(pCreateInfo); |
| if (plDesc) { |
| MVKRenderPipelineCompiler* plc = new MVKRenderPipelineCompiler(_device); |
| _mtlPipelineState = plc->newMTLRenderPipelineState(plDesc); // retained |
| setConfigurationResult(plc->getConfigurationResult()); |
| plc->destroy(); |
| } |
| } |
| |
| // Returns a MTLRenderPipelineDescriptor constructed from this instance, or nil if an error occurs. |
| MTLRenderPipelineDescriptor* MVKGraphicsPipeline::getMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo) { |
| SPIRVToMSLConverterContext shaderContext; |
| initMVKShaderConverterContext(shaderContext, pCreateInfo); |
| |
| // Retrieve the render subpass for which this pipeline is being constructed |
| MVKRenderPass* mvkRendPass = (MVKRenderPass*)pCreateInfo->renderPass; |
| MVKRenderSubpass* mvkRenderSubpass = mvkRendPass->getSubpass(pCreateInfo->subpass); |
| |
| MTLRenderPipelineDescriptor* plDesc = [[MTLRenderPipelineDescriptor new] autorelease]; |
| |
| // Add shader stages |
| for (uint32_t i = 0; i < pCreateInfo->stageCount; i++) { |
| const VkPipelineShaderStageCreateInfo* pSS = &pCreateInfo->pStages[i]; |
| shaderContext.options.entryPointName = pSS->pName; |
| |
| MVKShaderModule* mvkShdrMod = (MVKShaderModule*)pSS->module; |
| |
| // Vertex shader |
| if (mvkAreFlagsEnabled(pSS->stage, VK_SHADER_STAGE_VERTEX_BIT)) { |
| shaderContext.options.entryPointStage = spv::ExecutionModelVertex; |
| id<MTLFunction> mtlFunction = mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache).mtlFunction; |
| if ( !mtlFunction ) { |
| setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader function could not be compiled into pipeline. See previous error.")); |
| return nil; |
| } |
| plDesc.vertexFunction = mtlFunction; |
| } |
| |
| // Fragment shader |
| if (mvkAreFlagsEnabled(pSS->stage, VK_SHADER_STAGE_FRAGMENT_BIT)) { |
| shaderContext.options.entryPointStage = spv::ExecutionModelFragment; |
| id<MTLFunction> mtlFunction = mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache).mtlFunction; |
| if ( !mtlFunction ) { |
| setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader function could not be compiled into pipeline. See previous error.")); |
| } |
| plDesc.fragmentFunction = mtlFunction; |
| } |
| } |
| |
| // Vertex attributes |
| uint32_t vaCnt = pCreateInfo->pVertexInputState->vertexAttributeDescriptionCount; |
| for (uint32_t i = 0; i < vaCnt; i++) { |
| const VkVertexInputAttributeDescription* pVKVA = &pCreateInfo->pVertexInputState->pVertexAttributeDescriptions[i]; |
| if (shaderContext.isVertexAttributeLocationUsed(pVKVA->location)) { |
| MTLVertexAttributeDescriptor* vaDesc = plDesc.vertexDescriptor.attributes[pVKVA->location]; |
| vaDesc.format = mvkMTLVertexFormatFromVkFormat(pVKVA->format); |
| vaDesc.bufferIndex = _device->getMetalBufferIndexForVertexAttributeBinding(pVKVA->binding); |
| vaDesc.offset = pVKVA->offset; |
| } |
| } |
| |
| // Vertex buffer bindings |
| uint32_t vbCnt = pCreateInfo->pVertexInputState->vertexBindingDescriptionCount; |
| for (uint32_t i = 0; i < vbCnt; i++) { |
| const VkVertexInputBindingDescription* pVKVB = &pCreateInfo->pVertexInputState->pVertexBindingDescriptions[i]; |
| uint32_t vbIdx = _device->getMetalBufferIndexForVertexAttributeBinding(pVKVB->binding); |
| if (shaderContext.isVertexBufferUsed(vbIdx)) { |
| MTLVertexBufferLayoutDescriptor* vbDesc = plDesc.vertexDescriptor.layouts[vbIdx]; |
| vbDesc.stride = (pVKVB->stride == 0) ? sizeof(simd::float4) : pVKVB->stride; // Vulkan allows zero stride but Metal doesn't. Default to float4 |
| vbDesc.stepFunction = mvkMTLVertexStepFunctionFromVkVertexInputRate(pVKVB->inputRate); |
| vbDesc.stepRate = 1; |
| } |
| } |
| |
| // Color attachments |
| if (pCreateInfo->pColorBlendState) { |
| for (uint32_t caIdx = 0; caIdx < pCreateInfo->pColorBlendState->attachmentCount; caIdx++) { |
| const VkPipelineColorBlendAttachmentState* pCA = &pCreateInfo->pColorBlendState->pAttachments[caIdx]; |
| |
| MTLRenderPipelineColorAttachmentDescriptor* colorDesc = plDesc.colorAttachments[caIdx]; |
| colorDesc.pixelFormat = mtlPixelFormatFromVkFormat(mvkRenderSubpass->getColorAttachmentFormat(caIdx)); |
| colorDesc.writeMask = mvkMTLColorWriteMaskFromVkChannelFlags(pCA->colorWriteMask); |
| colorDesc.blendingEnabled = pCA->blendEnable; |
| colorDesc.rgbBlendOperation = mvkMTLBlendOperationFromVkBlendOp(pCA->colorBlendOp); |
| colorDesc.sourceRGBBlendFactor = mvkMTLBlendFactorFromVkBlendFactor(pCA->srcColorBlendFactor); |
| colorDesc.destinationRGBBlendFactor = mvkMTLBlendFactorFromVkBlendFactor(pCA->dstColorBlendFactor); |
| colorDesc.alphaBlendOperation = mvkMTLBlendOperationFromVkBlendOp(pCA->alphaBlendOp); |
| colorDesc.sourceAlphaBlendFactor = mvkMTLBlendFactorFromVkBlendFactor(pCA->srcAlphaBlendFactor); |
| colorDesc.destinationAlphaBlendFactor = mvkMTLBlendFactorFromVkBlendFactor(pCA->dstAlphaBlendFactor); |
| } |
| } |
| |
| // Depth & stencil attachments |
| MTLPixelFormat mtlDSFormat = mtlPixelFormatFromVkFormat(mvkRenderSubpass->getDepthStencilFormat()); |
| if (mvkMTLPixelFormatIsDepthFormat(mtlDSFormat)) { plDesc.depthAttachmentPixelFormat = mtlDSFormat; } |
| if (mvkMTLPixelFormatIsStencilFormat(mtlDSFormat)) { plDesc.stencilAttachmentPixelFormat = mtlDSFormat; } |
| |
| // Rasterization |
| plDesc.rasterizationEnabled = !_rasterInfo.rasterizerDiscardEnable; |
| if (pCreateInfo->pMultisampleState) { |
| plDesc.sampleCount = mvkSampleCountFromVkSampleCountFlagBits(pCreateInfo->pMultisampleState->rasterizationSamples); |
| plDesc.alphaToCoverageEnabled = pCreateInfo->pMultisampleState->alphaToCoverageEnable; |
| plDesc.alphaToOneEnabled = pCreateInfo->pMultisampleState->alphaToOneEnable; |
| } |
| |
| if (pCreateInfo->pInputAssemblyState) { |
| plDesc.inputPrimitiveTopologyMVK = mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopology(pCreateInfo->pInputAssemblyState->topology); |
| } |
| |
| return plDesc; |
| } |
| |
| /** Initializes the context used to prepare the MSL library used by this pipeline. */ |
| void MVKGraphicsPipeline::initMVKShaderConverterContext(SPIRVToMSLConverterContext& shaderContext, |
| const VkGraphicsPipelineCreateInfo* pCreateInfo) { |
| |
| shaderContext.options.mslVersion = _device->_pMetalFeatures->mslVersion; |
| shaderContext.options.texelBufferTextureWidth = _device->_pMetalFeatures->maxTextureDimension; |
| |
| MVKPipelineLayout* layout = (MVKPipelineLayout*)pCreateInfo->layout; |
| layout->populateShaderConverterContext(shaderContext); |
| |
| shaderContext.options.isRenderingPoints = (pCreateInfo->pInputAssemblyState && (pCreateInfo->pInputAssemblyState->topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST)); |
| shaderContext.options.shouldFlipVertexY = _device->_mvkConfig.shaderConversionFlipVertexY; |
| |
| // Set the shader context vertex attribute information |
| shaderContext.vertexAttributes.clear(); |
| uint32_t vaCnt = pCreateInfo->pVertexInputState->vertexAttributeDescriptionCount; |
| for (uint32_t vaIdx = 0; vaIdx < vaCnt; vaIdx++) { |
| const VkVertexInputAttributeDescription* pVKVA = &pCreateInfo->pVertexInputState->pVertexAttributeDescriptions[vaIdx]; |
| |
| // Set binding and offset from Vulkan vertex attribute |
| MSLVertexAttribute va; |
| va.location = pVKVA->location; |
| va.mslBuffer = _device->getMetalBufferIndexForVertexAttributeBinding(pVKVA->binding); |
| va.mslOffset = pVKVA->offset; |
| |
| // Set stride and input rate of vertex attribute from corresponding Vulkan vertex bindings |
| uint32_t vbCnt = pCreateInfo->pVertexInputState->vertexBindingDescriptionCount; |
| for (uint32_t vbIdx = 0; vbIdx < vbCnt; vbIdx++) { |
| const VkVertexInputBindingDescription* pVKVB = &pCreateInfo->pVertexInputState->pVertexBindingDescriptions[vbIdx]; |
| if (pVKVB->binding == pVKVA->binding) { |
| va.mslStride = pVKVB->stride; |
| va.isPerInstance = (pVKVB->inputRate == VK_VERTEX_INPUT_RATE_INSTANCE); |
| break; |
| } |
| } |
| |
| shaderContext.vertexAttributes.push_back(va); |
| } |
| } |
| |
| MVKGraphicsPipeline::~MVKGraphicsPipeline() { |
| [_mtlPipelineState release]; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKComputePipeline |
| |
| void MVKComputePipeline::encode(MVKCommandEncoder* cmdEncoder) { |
| [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch) setComputePipelineState: _mtlPipelineState]; |
| cmdEncoder->_mtlThreadgroupSize = _mtlThreadgroupSize; |
| } |
| |
| MVKComputePipeline::MVKComputePipeline(MVKDevice* device, |
| MVKPipelineCache* pipelineCache, |
| MVKPipeline* parent, |
| const VkComputePipelineCreateInfo* pCreateInfo) : MVKPipeline(device, pipelineCache, parent) { |
| MVKMTLFunction shaderFunc = getMTLFunction(pCreateInfo); |
| _mtlThreadgroupSize = shaderFunc.threadGroupSize; |
| _mtlPipelineState = nil; |
| |
| if (shaderFunc.mtlFunction) { |
| MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(_device); |
| _mtlPipelineState = plc->newMTLComputePipelineState(shaderFunc.mtlFunction); // retained |
| setConfigurationResult(plc->getConfigurationResult()); |
| plc->destroy(); |
| } else { |
| setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Compute shader function could not be compiled into pipeline. See previous error.")); |
| } |
| } |
| |
| // Returns a MTLFunction to use when creating the MTLComputePipelineState. |
| MVKMTLFunction MVKComputePipeline::getMTLFunction(const VkComputePipelineCreateInfo* pCreateInfo) { |
| |
| const VkPipelineShaderStageCreateInfo* pSS = &pCreateInfo->stage; |
| if ( !mvkAreFlagsEnabled(pSS->stage, VK_SHADER_STAGE_COMPUTE_BIT) ) { return MVKMTLFunctionNull; } |
| |
| SPIRVToMSLConverterContext shaderContext; |
| shaderContext.options.entryPointName = pCreateInfo->stage.pName; |
| shaderContext.options.entryPointStage = spv::ExecutionModelGLCompute; |
| shaderContext.options.mslVersion = _device->_pMetalFeatures->mslVersion; |
| |
| MVKPipelineLayout* layout = (MVKPipelineLayout*)pCreateInfo->layout; |
| layout->populateShaderConverterContext(shaderContext); |
| |
| MVKShaderModule* mvkShdrMod = (MVKShaderModule*)pSS->module; |
| return mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache); |
| } |
| |
| |
| MVKComputePipeline::~MVKComputePipeline() { |
| [_mtlPipelineState release]; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKPipelineCache |
| |
| /** Return a shader library from the specified shader context sourced from the specified shader module. */ |
| MVKShaderLibrary* MVKPipelineCache::getShaderLibrary(SPIRVToMSLConverterContext* pContext, MVKShaderModule* shaderModule) { |
| lock_guard<mutex> lock(_shaderCacheLock); |
| |
| bool wasAdded = false; |
| MVKShaderLibraryCache* slCache = getShaderLibraryCache(shaderModule->getKey()); |
| MVKShaderLibrary* shLib = slCache->getShaderLibrary(pContext, shaderModule, &wasAdded); |
| if (wasAdded) { markDirty(); } |
| return shLib; |
| } |
| |
| // Returns a shader library cache for the specified shader module key, creating it if necessary. |
| MVKShaderLibraryCache* MVKPipelineCache::getShaderLibraryCache(MVKShaderModuleKey smKey) { |
| MVKShaderLibraryCache* slCache = _shaderCache[smKey]; |
| if ( !slCache ) { |
| slCache = new MVKShaderLibraryCache(_device); |
| _shaderCache[smKey] = slCache; |
| } |
| return slCache; |
| } |
| |
| |
| #pragma mark Streaming pipeline cache to and from offline memory |
| |
| static uint32_t kDataHeaderSize = (sizeof(uint32_t) * 4) + VK_UUID_SIZE; |
| |
| // Entry type markers to be inserted into data stream |
| typedef enum { |
| MVKPipelineCacheEntryTypeEOF = 0, |
| MVKPipelineCacheEntryTypeShaderLibrary = 1, |
| } MVKPipelineCacheEntryType; |
| |
| // Ceral archive definitions |
| namespace mvk { |
| |
| template<class Archive> |
| void serialize(Archive & archive, SPIRVWorkgroupSizeDimension& wsd) { |
| archive(wsd.size, |
| wsd.specializationID, |
| wsd.isSpecialized); |
| } |
| |
| template<class Archive> |
| void serialize(Archive & archive, SPIRVEntryPoint& ep) { |
| archive(ep.mtlFunctionName, |
| ep.workgroupSize.width, |
| ep.workgroupSize.height, |
| ep.workgroupSize.depth); |
| } |
| |
| template<class Archive> |
| void serialize(Archive & archive, SPIRVToMSLConverterOptions& opt) { |
| archive(opt.entryPointName, |
| opt.entryPointStage, |
| opt.mslVersion, |
| opt.texelBufferTextureWidth, |
| opt.shouldFlipVertexY, |
| opt.isRenderingPoints); |
| } |
| |
| template<class Archive> |
| void serialize(Archive & archive, MSLVertexAttribute& va) { |
| archive(va.location, |
| va.mslBuffer, |
| va.mslOffset, |
| va.mslStride, |
| va.isPerInstance, |
| va.isUsedByShader); |
| } |
| |
| template<class Archive> |
| void serialize(Archive & archive, MSLResourceBinding& rb) { |
| archive(rb.stage, |
| rb.descriptorSet, |
| rb.binding, |
| rb.mslBuffer, |
| rb.mslTexture, |
| rb.mslSampler, |
| rb.isUsedByShader); |
| } |
| |
| template<class Archive> |
| void serialize(Archive & archive, SPIRVToMSLConverterContext& ctx) { |
| archive(ctx.options, ctx.vertexAttributes, ctx.resourceBindings); |
| } |
| |
| } |
| |
| template<class Archive> |
| void serialize(Archive & archive, MVKShaderModuleKey& k) { |
| archive(k.codeSize, k.codeHash); |
| } |
| |
| // Helper class to iterate through the shader libraries in a shader library cache in order to serialize them. |
| // Needs to support input of null shader library cache. |
| class MVKShaderCacheIterator : MVKBaseObject { |
| protected: |
| friend MVKPipelineCache; |
| |
| bool next() { return (++_index < (_pSLCache ? _pSLCache->_shaderLibraries.size() : 0)); } |
| SPIRVToMSLConverterContext& getShaderContext() { return _pSLCache->_shaderLibraries[_index].first; } |
| std::string& getMSL() { return _pSLCache->_shaderLibraries[_index].second->_msl; } |
| SPIRVEntryPoint& getEntryPoint() { return _pSLCache->_shaderLibraries[_index].second->_entryPoint; } |
| MVKShaderCacheIterator(MVKShaderLibraryCache* pSLCache) : _pSLCache(pSLCache) {} |
| |
| MVKShaderLibraryCache* _pSLCache; |
| size_t _count = 0; |
| int32_t _index = -1; |
| }; |
| |
| // If pData is not null, serializes at most pDataSize bytes of the contents of the cache into that |
| // memory location, and returns the number of bytes serialized in pDataSize. If pData is null, |
| // returns the number of bytes required to serialize the contents of this pipeline cache. |
| // This is the compliment of the readData() function. The two must be kept aligned. |
| VkResult MVKPipelineCache::writeData(size_t* pDataSize, void* pData) { |
| lock_guard<mutex> lock(_shaderCacheLock); |
| |
| try { |
| |
| if ( !pDataSize ) { return VK_SUCCESS; } |
| |
| if (pData) { |
| if (*pDataSize >= _dataSize) { |
| mvk::membuf mb((char*)pData, _dataSize); |
| ostream outStream(&mb); |
| writeData(outStream); |
| *pDataSize = _dataSize; |
| return VK_SUCCESS; |
| } else { |
| *pDataSize = 0; |
| return VK_INCOMPLETE; |
| } |
| } else { |
| if (_dataSize == 0) { |
| mvk::countbuf cb; |
| ostream outStream(&cb); |
| writeData(outStream, true); |
| _dataSize = cb.buffSize; |
| } |
| *pDataSize = _dataSize; |
| return VK_SUCCESS; |
| } |
| |
| } catch (cereal::Exception& ex) { |
| *pDataSize = 0; |
| return mvkNotifyErrorWithText(VK_INCOMPLETE, "Error writing pipeline cache data: %s", ex.what()); |
| } |
| } |
| |
| // Serializes the data in this cache to a stream |
| void MVKPipelineCache::writeData(ostream& outstream, bool isCounting) { |
| |
| MVKPerformanceTracker& shaderCompilationEvent = isCounting |
| ? _device->_performanceStatistics.pipelineCache.sizePipelineCache |
| : _device->_performanceStatistics.pipelineCache.writePipelineCache; |
| |
| uint32_t cacheEntryType; |
| cereal::BinaryOutputArchive writer(outstream); |
| |
| // Write the data header...after ensuring correct byte-order. |
| const VkPhysicalDeviceProperties* pDevProps = _device->_pProperties; |
| writer(NSSwapHostIntToLittle(kDataHeaderSize)); |
| writer(NSSwapHostIntToLittle(VK_PIPELINE_CACHE_HEADER_VERSION_ONE)); |
| writer(NSSwapHostIntToLittle(pDevProps->vendorID)); |
| writer(NSSwapHostIntToLittle(pDevProps->deviceID)); |
| writer(pDevProps->pipelineCacheUUID); |
| |
| // Shader libraries |
| // Output a cache entry for each shader library, including the shader module key in each entry. |
| cacheEntryType = MVKPipelineCacheEntryTypeShaderLibrary; |
| for (auto& scPair : _shaderCache) { |
| MVKShaderModuleKey smKey = scPair.first; |
| MVKShaderCacheIterator cacheIter(scPair.second); |
| while (cacheIter.next()) { |
| uint64_t startTime = _device->getPerformanceTimestamp(); |
| writer(cacheEntryType); |
| writer(smKey); |
| writer(cacheIter.getShaderContext()); |
| writer(cacheIter.getEntryPoint()); |
| writer(cacheIter.getMSL()); |
| _device->addActivityPerformance(shaderCompilationEvent, startTime); |
| } |
| } |
| |
| // Mark the end of the archive |
| cacheEntryType = MVKPipelineCacheEntryTypeEOF; |
| writer(cacheEntryType); |
| } |
| |
| // Loads any data indicated by the creation info. |
| // This is the compliment of the writeData() function. The two must be kept aligned. |
| void MVKPipelineCache::readData(const VkPipelineCacheCreateInfo* pCreateInfo) { |
| try { |
| |
| size_t byteCount = pCreateInfo->initialDataSize; |
| uint32_t cacheEntryType; |
| |
| // Must be able to read the header and at least one cache entry type. |
| if (byteCount < kDataHeaderSize + sizeof(cacheEntryType)) { return; } |
| |
| mvk::membuf mb((char*)pCreateInfo->pInitialData, byteCount); |
| istream inStream(&mb); |
| cereal::BinaryInputArchive reader(inStream); |
| |
| // Read the data header...and ensure correct byte-order. |
| uint32_t hdrComponent; |
| uint8_t pcUUID[VK_UUID_SIZE]; |
| const VkPhysicalDeviceProperties* pDevProps = _device->_pProperties; |
| |
| reader(hdrComponent); // Header size |
| if (NSSwapLittleIntToHost(hdrComponent) != kDataHeaderSize) { return; } |
| |
| reader(hdrComponent); // Header version |
| if (NSSwapLittleIntToHost(hdrComponent) != VK_PIPELINE_CACHE_HEADER_VERSION_ONE) { return; } |
| |
| reader(hdrComponent); // Vendor ID |
| if (NSSwapLittleIntToHost(hdrComponent) != pDevProps->vendorID) { return; } |
| |
| reader(hdrComponent); // Device ID |
| if (NSSwapLittleIntToHost(hdrComponent) != pDevProps->deviceID) { return; } |
| |
| reader(pcUUID); // Pipeline cache UUID |
| if (memcmp(pcUUID, pDevProps->pipelineCacheUUID, VK_UUID_SIZE) != 0) { return; } |
| |
| bool done = false; |
| while ( !done ) { |
| reader(cacheEntryType); |
| switch (cacheEntryType) { |
| case MVKPipelineCacheEntryTypeShaderLibrary: { |
| uint64_t startTime = _device->getPerformanceTimestamp(); |
| |
| MVKShaderModuleKey smKey; |
| reader(smKey); |
| |
| SPIRVToMSLConverterContext shaderContext; |
| reader(shaderContext); |
| |
| SPIRVEntryPoint entryPoint; |
| reader(entryPoint); |
| |
| string msl; |
| reader(msl); |
| |
| // Add the shader library to the staging cache. |
| MVKShaderLibraryCache* slCache = getShaderLibraryCache(smKey); |
| _device->addActivityPerformance(_device->_performanceStatistics.pipelineCache.readPipelineCache, startTime); |
| slCache->addShaderLibrary(&shaderContext, msl, entryPoint); |
| |
| break; |
| } |
| |
| default: { |
| done = true; |
| break; |
| } |
| } |
| } |
| |
| } catch (cereal::Exception& ex) { |
| setConfigurationResult(mvkNotifyErrorWithText(VK_SUCCESS, "Error reading pipeline cache data: %s", ex.what())); |
| } |
| } |
| |
| // Mark the cache as dirty, so that existing streaming info is released |
| void MVKPipelineCache::markDirty() { |
| _dataSize = 0; |
| } |
| |
| VkResult MVKPipelineCache::mergePipelineCaches(uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches) { |
| for (uint32_t srcIdx = 0; srcIdx < srcCacheCount; srcIdx++) { |
| MVKPipelineCache* srcPLC = (MVKPipelineCache*)pSrcCaches[srcIdx]; |
| for (auto& srcPair : srcPLC->_shaderCache) { |
| getShaderLibraryCache(srcPair.first)->merge(srcPair.second); |
| } |
| } |
| markDirty(); |
| |
| return VK_SUCCESS; |
| } |
| |
| |
| #pragma mark Construction |
| |
| MVKPipelineCache::MVKPipelineCache(MVKDevice* device, const VkPipelineCacheCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) { |
| readData(pCreateInfo); |
| } |
| |
| MVKPipelineCache::~MVKPipelineCache() { |
| for (auto& pair : _shaderCache) { pair.second->destroy(); } |
| _shaderCache.clear(); |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKRenderPipelineCompiler |
| |
| id<MTLRenderPipelineState> MVKRenderPipelineCompiler::newMTLRenderPipelineState(MTLRenderPipelineDescriptor* mtlRPLDesc) { |
| unique_lock<mutex> lock(_completionLock); |
| |
| compile(lock, ^{ |
| [getMTLDevice() newRenderPipelineStateWithDescriptor: mtlRPLDesc |
| completionHandler: ^(id<MTLRenderPipelineState> ps, NSError* error) { |
| bool isLate = compileComplete(ps, error); |
| if (isLate) { destroy(); } |
| }]; |
| }); |
| |
| return [_mtlRenderPipelineState retain]; |
| } |
| |
| bool MVKRenderPipelineCompiler::compileComplete(id<MTLRenderPipelineState> mtlRenderPipelineState, NSError* compileError) { |
| lock_guard<mutex> lock(_completionLock); |
| |
| _mtlRenderPipelineState = [mtlRenderPipelineState retain]; // retained |
| return endCompile(compileError); |
| } |
| |
| #pragma mark Construction |
| |
| MVKRenderPipelineCompiler::~MVKRenderPipelineCompiler() { |
| [_mtlRenderPipelineState release]; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark MVKComputePipelineCompiler |
| |
| id<MTLComputePipelineState> MVKComputePipelineCompiler::newMTLComputePipelineState(id<MTLFunction> mtlFunction) { |
| unique_lock<mutex> lock(_completionLock); |
| |
| compile(lock, ^{ |
| [getMTLDevice() newComputePipelineStateWithFunction: mtlFunction |
| completionHandler: ^(id<MTLComputePipelineState> ps, NSError* error) { |
| bool isLate = compileComplete(ps, error); |
| if (isLate) { destroy(); } |
| }]; |
| }); |
| |
| return [_mtlComputePipelineState retain]; |
| } |
| |
| bool MVKComputePipelineCompiler::compileComplete(id<MTLComputePipelineState> mtlComputePipelineState, NSError* compileError) { |
| lock_guard<mutex> lock(_completionLock); |
| |
| _mtlComputePipelineState = [mtlComputePipelineState retain]; // retained |
| return endCompile(compileError); |
| } |
| |
| #pragma mark Construction |
| |
| MVKComputePipelineCompiler::~MVKComputePipelineCompiler() { |
| [_mtlComputePipelineState release]; |
| } |
| |