| /* |
| * Copyright 2021 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/graphite/mtl/MtlGraphicsPipeline.h" |
| |
| #include "include/gpu/graphite/TextureInfo.h" |
| #include "src/gpu/graphite/Attribute.h" |
| #include "src/gpu/graphite/ContextUtils.h" |
| #include "src/gpu/graphite/GraphicsPipelineDesc.h" |
| #include "src/gpu/graphite/Log.h" |
| #include "src/gpu/graphite/RenderPassDesc.h" |
| #include "src/gpu/graphite/RendererProvider.h" |
| #include "src/gpu/graphite/mtl/MtlGraphiteTypesPriv.h" |
| #include "src/gpu/graphite/mtl/MtlGraphiteUtilsPriv.h" |
| #include "src/gpu/graphite/mtl/MtlResourceProvider.h" |
| #include "src/gpu/graphite/mtl/MtlSharedContext.h" |
| #include "src/gpu/mtl/MtlUtilsPriv.h" |
| #include "src/sksl/SkSLCompiler.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| |
| namespace skgpu::graphite { |
| |
| namespace { |
| |
| inline MTLVertexFormat attribute_type_to_mtlformat(VertexAttribType type) { |
| switch (type) { |
| case VertexAttribType::kFloat: |
| return MTLVertexFormatFloat; |
| case VertexAttribType::kFloat2: |
| return MTLVertexFormatFloat2; |
| case VertexAttribType::kFloat3: |
| return MTLVertexFormatFloat3; |
| case VertexAttribType::kFloat4: |
| return MTLVertexFormatFloat4; |
| case VertexAttribType::kHalf: |
| if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { |
| return MTLVertexFormatHalf; |
| } else { |
| return MTLVertexFormatInvalid; |
| } |
| case VertexAttribType::kHalf2: |
| return MTLVertexFormatHalf2; |
| case VertexAttribType::kHalf4: |
| return MTLVertexFormatHalf4; |
| case VertexAttribType::kInt2: |
| return MTLVertexFormatInt2; |
| case VertexAttribType::kInt3: |
| return MTLVertexFormatInt3; |
| case VertexAttribType::kInt4: |
| return MTLVertexFormatInt4; |
| case VertexAttribType::kUInt2: |
| return MTLVertexFormatUInt2; |
| case VertexAttribType::kByte: |
| if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { |
| return MTLVertexFormatChar; |
| } else { |
| return MTLVertexFormatInvalid; |
| } |
| case VertexAttribType::kByte2: |
| return MTLVertexFormatChar2; |
| case VertexAttribType::kByte4: |
| return MTLVertexFormatChar4; |
| case VertexAttribType::kUByte: |
| if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { |
| return MTLVertexFormatUChar; |
| } else { |
| return MTLVertexFormatInvalid; |
| } |
| case VertexAttribType::kUByte2: |
| return MTLVertexFormatUChar2; |
| case VertexAttribType::kUByte4: |
| return MTLVertexFormatUChar4; |
| case VertexAttribType::kUByte_norm: |
| if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { |
| return MTLVertexFormatUCharNormalized; |
| } else { |
| return MTLVertexFormatInvalid; |
| } |
| case VertexAttribType::kUByte4_norm: |
| return MTLVertexFormatUChar4Normalized; |
| case VertexAttribType::kShort2: |
| return MTLVertexFormatShort2; |
| case VertexAttribType::kShort4: |
| return MTLVertexFormatShort4; |
| case VertexAttribType::kUShort2: |
| return MTLVertexFormatUShort2; |
| case VertexAttribType::kUShort2_norm: |
| return MTLVertexFormatUShort2Normalized; |
| case VertexAttribType::kInt: |
| return MTLVertexFormatInt; |
| case VertexAttribType::kUInt: |
| return MTLVertexFormatUInt; |
| case VertexAttribType::kUShort_norm: |
| if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) { |
| return MTLVertexFormatUShortNormalized; |
| } else { |
| return MTLVertexFormatInvalid; |
| } |
| case VertexAttribType::kUShort4_norm: |
| return MTLVertexFormatUShort4Normalized; |
| } |
| SK_ABORT("Unknown vertex attribute type"); |
| } |
| |
| MTLVertexDescriptor* create_vertex_descriptor(SkSpan<const Attribute> vertexAttrs, |
| SkSpan<const Attribute> instanceAttrs) { |
| auto vertexDescriptor = [[MTLVertexDescriptor alloc] init]; |
| int attributeIndex = 0; |
| |
| size_t vertexAttributeOffset = 0; |
| for (const auto& attribute : vertexAttrs) { |
| MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex]; |
| MTLVertexFormat format = attribute_type_to_mtlformat(attribute.cpuType()); |
| SkASSERT(MTLVertexFormatInvalid != format); |
| mtlAttribute.format = format; |
| mtlAttribute.offset = vertexAttributeOffset; |
| mtlAttribute.bufferIndex = MtlGraphicsPipeline::kVertexBufferIndex; |
| |
| vertexAttributeOffset += attribute.sizeAlign4(); |
| attributeIndex++; |
| } |
| |
| if (vertexAttributeOffset) { |
| MTLVertexBufferLayoutDescriptor* vertexBufferLayout = |
| vertexDescriptor.layouts[MtlGraphicsPipeline::kVertexBufferIndex]; |
| vertexBufferLayout.stepFunction = MTLVertexStepFunctionPerVertex; |
| vertexBufferLayout.stepRate = 1; |
| vertexBufferLayout.stride = vertexAttributeOffset; |
| } |
| |
| size_t instanceAttributeOffset = 0; |
| for (const auto& attribute : instanceAttrs) { |
| MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex]; |
| MTLVertexFormat format = attribute_type_to_mtlformat(attribute.cpuType()); |
| SkASSERT(MTLVertexFormatInvalid != format); |
| mtlAttribute.format = format; |
| mtlAttribute.offset = instanceAttributeOffset; |
| mtlAttribute.bufferIndex = MtlGraphicsPipeline::kInstanceBufferIndex; |
| |
| instanceAttributeOffset += attribute.sizeAlign4(); |
| attributeIndex++; |
| } |
| |
| if (instanceAttributeOffset) { |
| MTLVertexBufferLayoutDescriptor* instanceBufferLayout = |
| vertexDescriptor.layouts[MtlGraphicsPipeline::kInstanceBufferIndex]; |
| instanceBufferLayout.stepFunction = MTLVertexStepFunctionPerInstance; |
| instanceBufferLayout.stepRate = 1; |
| instanceBufferLayout.stride = instanceAttributeOffset; |
| } |
| return vertexDescriptor; |
| } |
| |
| // TODO: share this w/ Ganesh Metal backend? |
| static MTLBlendFactor blend_coeff_to_mtl_blend(skgpu::BlendCoeff coeff) { |
| switch (coeff) { |
| case skgpu::BlendCoeff::kZero: |
| return MTLBlendFactorZero; |
| case skgpu::BlendCoeff::kOne: |
| return MTLBlendFactorOne; |
| case skgpu::BlendCoeff::kSC: |
| return MTLBlendFactorSourceColor; |
| case skgpu::BlendCoeff::kISC: |
| return MTLBlendFactorOneMinusSourceColor; |
| case skgpu::BlendCoeff::kDC: |
| return MTLBlendFactorDestinationColor; |
| case skgpu::BlendCoeff::kIDC: |
| return MTLBlendFactorOneMinusDestinationColor; |
| case skgpu::BlendCoeff::kSA: |
| return MTLBlendFactorSourceAlpha; |
| case skgpu::BlendCoeff::kISA: |
| return MTLBlendFactorOneMinusSourceAlpha; |
| case skgpu::BlendCoeff::kDA: |
| return MTLBlendFactorDestinationAlpha; |
| case skgpu::BlendCoeff::kIDA: |
| return MTLBlendFactorOneMinusDestinationAlpha; |
| case skgpu::BlendCoeff::kConstC: |
| return MTLBlendFactorBlendColor; |
| case skgpu::BlendCoeff::kIConstC: |
| return MTLBlendFactorOneMinusBlendColor; |
| case skgpu::BlendCoeff::kS2C: |
| if (@available(macOS 10.12, iOS 11.0, tvOS 11.0, *)) { |
| return MTLBlendFactorSource1Color; |
| } else { |
| return MTLBlendFactorZero; |
| } |
| case skgpu::BlendCoeff::kIS2C: |
| if (@available(macOS 10.12, iOS 11.0, tvOS 11.0, *)) { |
| return MTLBlendFactorOneMinusSource1Color; |
| } else { |
| return MTLBlendFactorZero; |
| } |
| case skgpu::BlendCoeff::kS2A: |
| if (@available(macOS 10.12, iOS 11.0, tvOS 11.0, *)) { |
| return MTLBlendFactorSource1Alpha; |
| } else { |
| return MTLBlendFactorZero; |
| } |
| case skgpu::BlendCoeff::kIS2A: |
| if (@available(macOS 10.12, iOS 11.0, tvOS 11.0, *)) { |
| return MTLBlendFactorOneMinusSource1Alpha; |
| } else { |
| return MTLBlendFactorZero; |
| } |
| case skgpu::BlendCoeff::kIllegal: |
| return MTLBlendFactorZero; |
| } |
| |
| SK_ABORT("Unknown blend coefficient"); |
| } |
| |
| // TODO: share this w/ Ganesh Metal backend? |
| static MTLBlendOperation blend_equation_to_mtl_blend_op(skgpu::BlendEquation equation) { |
| static const MTLBlendOperation gTable[] = { |
| MTLBlendOperationAdd, // skgpu::BlendEquation::kAdd |
| MTLBlendOperationSubtract, // skgpu::BlendEquation::kSubtract |
| MTLBlendOperationReverseSubtract, // skgpu::BlendEquation::kReverseSubtract |
| }; |
| static_assert(std::size(gTable) == (int)skgpu::BlendEquation::kFirstAdvanced); |
| static_assert(0 == (int)skgpu::BlendEquation::kAdd); |
| static_assert(1 == (int)skgpu::BlendEquation::kSubtract); |
| static_assert(2 == (int)skgpu::BlendEquation::kReverseSubtract); |
| |
| SkASSERT((unsigned)equation < skgpu::kBlendEquationCnt); |
| return gTable[(int)equation]; |
| } |
| |
| static MTLRenderPipelineColorAttachmentDescriptor* create_color_attachment( |
| MTLPixelFormat format, |
| const BlendInfo& blendInfo) { |
| |
| skgpu::BlendEquation equation = blendInfo.fEquation; |
| skgpu::BlendCoeff srcCoeff = blendInfo.fSrcBlend; |
| skgpu::BlendCoeff dstCoeff = blendInfo.fDstBlend; |
| bool blendOn = !skgpu::BlendShouldDisable(equation, srcCoeff, dstCoeff); |
| |
| // TODO: I *think* this gets cleaned up by the pipelineDescriptor? |
| auto mtlColorAttachment = [[MTLRenderPipelineColorAttachmentDescriptor alloc] init]; |
| |
| mtlColorAttachment.pixelFormat = format; |
| |
| mtlColorAttachment.blendingEnabled = blendOn; |
| |
| if (blendOn) { |
| mtlColorAttachment.sourceRGBBlendFactor = blend_coeff_to_mtl_blend(srcCoeff); |
| mtlColorAttachment.destinationRGBBlendFactor = blend_coeff_to_mtl_blend(dstCoeff); |
| mtlColorAttachment.rgbBlendOperation = blend_equation_to_mtl_blend_op(equation); |
| mtlColorAttachment.sourceAlphaBlendFactor = blend_coeff_to_mtl_blend(srcCoeff); |
| mtlColorAttachment.destinationAlphaBlendFactor = blend_coeff_to_mtl_blend(dstCoeff); |
| mtlColorAttachment.alphaBlendOperation = blend_equation_to_mtl_blend_op(equation); |
| } |
| |
| mtlColorAttachment.writeMask = blendInfo.fWritesColor ? MTLColorWriteMaskAll |
| : MTLColorWriteMaskNone; |
| |
| return mtlColorAttachment; |
| } |
| |
| } // anonymous namespace |
| |
| sk_sp<MtlGraphicsPipeline> MtlGraphicsPipeline::Make(const MtlSharedContext* sharedContext, |
| MtlResourceProvider* resourceProvider, |
| const RuntimeEffectDictionary* runtimeDict, |
| const GraphicsPipelineDesc& pipelineDesc, |
| const RenderPassDesc& renderPassDesc) { |
| std::string vsMSL, fsMSL; |
| SkSL::Program::Interface vsInterface, fsInterface; |
| |
| SkSL::ProgramSettings settings; |
| settings.fSharpenTextures = true; |
| settings.fForceNoRTFlip = true; |
| |
| SkSL::Compiler skslCompiler; |
| ShaderErrorHandler* errorHandler = sharedContext->caps()->shaderErrorHandler(); |
| |
| const RenderStep* step = |
| sharedContext->rendererProvider()->lookup(pipelineDesc.renderStepID()); |
| const bool useStorageBuffers = sharedContext->caps()->storageBufferSupport(); |
| |
| UniquePaintParamsID paintID = pipelineDesc.paintParamsID(); |
| FragSkSLInfo fsSkSLInfo = BuildFragmentSkSL(sharedContext->caps(), |
| sharedContext->shaderCodeDictionary(), |
| runtimeDict, |
| step, |
| paintID, |
| useStorageBuffers, |
| renderPassDesc.fWriteSwizzle); |
| std::string& fsSkSL = fsSkSLInfo.fSkSL; |
| const BlendInfo& blendInfo = fsSkSLInfo.fBlendInfo; |
| const bool localCoordsNeeded = fsSkSLInfo.fRequiresLocalCoords; |
| if (!SkSLToMSL(sharedContext->caps()->shaderCaps(), |
| fsSkSL, |
| SkSL::ProgramKind::kGraphiteFragment, |
| settings, |
| &fsMSL, |
| &fsInterface, |
| errorHandler)) { |
| return nullptr; |
| } |
| |
| VertSkSLInfo vsSkSLInfo = BuildVertexSkSL(sharedContext->caps()->resourceBindingRequirements(), |
| step, |
| useStorageBuffers, |
| localCoordsNeeded); |
| const std::string& vsSkSL = vsSkSLInfo.fSkSL; |
| if (!SkSLToMSL(sharedContext->caps()->shaderCaps(), |
| vsSkSL, |
| SkSL::ProgramKind::kGraphiteVertex, |
| settings, |
| &vsMSL, |
| &vsInterface, |
| errorHandler)) { |
| return nullptr; |
| } |
| |
| auto vsLibrary = MtlCompileShaderLibrary(sharedContext, vsSkSLInfo.fLabel, vsMSL, errorHandler); |
| auto fsLibrary = MtlCompileShaderLibrary(sharedContext, fsSkSLInfo.fLabel, fsMSL, errorHandler); |
| |
| sk_cfp<id<MTLDepthStencilState>> dss = |
| resourceProvider->findOrCreateCompatibleDepthStencilState(step->depthStencilSettings()); |
| |
| PipelineInfo pipelineInfo{vsSkSLInfo, fsSkSLInfo}; |
| #if defined(GPU_TEST_UTILS) |
| pipelineInfo.fNativeVertexShader = std::move(vsMSL); |
| pipelineInfo.fNativeFragmentShader = std::move(fsMSL); |
| #endif |
| std::string pipelineLabel = |
| GetPipelineLabel(sharedContext->shaderCodeDictionary(), renderPassDesc, step, paintID); |
| return Make(sharedContext, |
| pipelineLabel, |
| pipelineInfo, |
| {vsLibrary.get(), "vertexMain"}, |
| step->vertexAttributes(), |
| step->instanceAttributes(), |
| {fsLibrary.get(), "fragmentMain"}, |
| std::move(dss), |
| step->depthStencilSettings().fStencilReferenceValue, |
| blendInfo, |
| renderPassDesc); |
| } |
| |
| sk_sp<MtlGraphicsPipeline> MtlGraphicsPipeline::MakeLoadMSAAPipeline( |
| const MtlSharedContext* sharedContext, |
| MtlResourceProvider* resourceProvider, |
| const RenderPassDesc& renderPassDesc) { |
| static const char* kLoadMSAAShaderText = R"( |
| #include <metal_stdlib> |
| #include <simd/simd.h> |
| using namespace metal; |
| |
| typedef struct { |
| float4 position [[position]]; |
| } VertexOutput; |
| |
| vertex VertexOutput vertexMain(uint vertexID [[vertex_id]]) { |
| VertexOutput out; |
| float2 position = float2(float(vertexID >> 1), float(vertexID & 1)); |
| out.position = float4(2.0 * position - 1.0, 0.0, 1.0); |
| return out; |
| } |
| |
| fragment float4 fragmentMain(VertexOutput in [[stage_in]], |
| texture2d<half> colorMap [[texture(0)]]) { |
| uint2 coords = uint2(in.position.x, in.position.y); |
| half4 colorSample = colorMap.read(coords); |
| return float4(colorSample); |
| } |
| )"; |
| |
| auto mtlLibrary = MtlCompileShaderLibrary(sharedContext, |
| "LoadMSAAFromResolve", |
| kLoadMSAAShaderText, |
| sharedContext->caps()->shaderErrorHandler()); |
| BlendInfo noBlend{}; // default is equivalent to kSrc blending |
| sk_cfp<id<MTLDepthStencilState>> ignoreDS = |
| resourceProvider->findOrCreateCompatibleDepthStencilState({}); |
| |
| std::string pipelineLabel = "LoadMSAAFromResolve + "; |
| pipelineLabel += renderPassDesc.toString().c_str(); |
| |
| PipelineInfo pipelineInfo; |
| pipelineInfo.fNumFragTexturesAndSamplers = 1; |
| // This is an internal shader, leave off filling out the test-utils shader code |
| return Make(sharedContext, |
| pipelineLabel, |
| pipelineInfo, |
| {mtlLibrary.get(), "vertexMain"}, |
| /*vertexAttrs=*/{}, |
| /*instanceAttrs=*/{}, |
| {mtlLibrary.get(), "fragmentMain"}, |
| std::move(ignoreDS), |
| /*stencilRefValue=*/0, |
| noBlend, |
| renderPassDesc); |
| } |
| |
| sk_sp<MtlGraphicsPipeline> MtlGraphicsPipeline::Make(const MtlSharedContext* sharedContext, |
| const std::string& label, |
| const PipelineInfo& pipelineInfo, |
| MSLFunction vertexMain, |
| SkSpan<const Attribute> vertexAttrs, |
| SkSpan<const Attribute> instanceAttrs, |
| MSLFunction fragmentMain, |
| sk_cfp<id<MTLDepthStencilState>> dss, |
| uint32_t stencilRefValue, |
| const BlendInfo& blendInfo, |
| const RenderPassDesc& renderPassDesc) { |
| id<MTLLibrary> vsLibrary = std::get<0>(vertexMain); |
| id<MTLLibrary> fsLibrary = std::get<0>(fragmentMain); |
| if (!vsLibrary || !fsLibrary) { |
| return nullptr; |
| } |
| |
| sk_cfp<MTLRenderPipelineDescriptor*> psoDescriptor([[MTLRenderPipelineDescriptor alloc] init]); |
| |
| NSString* labelName = [NSString stringWithUTF8String: label.c_str()]; |
| NSString* vsFuncName = [NSString stringWithUTF8String: std::get<1>(vertexMain).c_str()]; |
| NSString* fsFuncName = [NSString stringWithUTF8String: std::get<1>(fragmentMain).c_str()]; |
| |
| (*psoDescriptor).label = labelName; |
| (*psoDescriptor).vertexFunction = [vsLibrary newFunctionWithName: vsFuncName]; |
| (*psoDescriptor).fragmentFunction = [fsLibrary newFunctionWithName: fsFuncName]; |
| |
| // TODO: I *think* this gets cleaned up by the pipelineDescriptor? |
| (*psoDescriptor).vertexDescriptor = create_vertex_descriptor(vertexAttrs, instanceAttrs); |
| |
| MTLPixelFormat pixelFormat = |
| TextureInfos::GetMTLPixelFormat(renderPassDesc.fColorAttachment.fTextureInfo); |
| auto mtlColorAttachment = create_color_attachment(pixelFormat, blendInfo); |
| (*psoDescriptor).colorAttachments[0] = mtlColorAttachment; |
| |
| (*psoDescriptor).rasterSampleCount = |
| renderPassDesc.fColorAttachment.fTextureInfo.numSamples(); |
| |
| MTLPixelFormat depthStencilFormat = |
| TextureInfos::GetMTLPixelFormat(renderPassDesc.fDepthStencilAttachment.fTextureInfo); |
| if (MtlFormatIsStencil(depthStencilFormat)) { |
| (*psoDescriptor).stencilAttachmentPixelFormat = depthStencilFormat; |
| } else { |
| (*psoDescriptor).stencilAttachmentPixelFormat = MTLPixelFormatInvalid; |
| } |
| if (MtlFormatIsDepth(depthStencilFormat)) { |
| (*psoDescriptor).depthAttachmentPixelFormat = depthStencilFormat; |
| } else { |
| (*psoDescriptor).depthAttachmentPixelFormat = MTLPixelFormatInvalid; |
| } |
| |
| NSError* error; |
| sk_cfp<id<MTLRenderPipelineState>> pso( |
| [sharedContext->device() newRenderPipelineStateWithDescriptor:psoDescriptor.get() |
| error:&error]); |
| if (!pso) { |
| SKGPU_LOG_E("Render pipeline creation failure:\n%s", error.debugDescription.UTF8String); |
| return nullptr; |
| } |
| |
| return sk_sp<MtlGraphicsPipeline>(new MtlGraphicsPipeline(sharedContext, |
| pipelineInfo, |
| std::move(pso), |
| std::move(dss), |
| stencilRefValue)); |
| } |
| |
| MtlGraphicsPipeline::MtlGraphicsPipeline(const skgpu::graphite::SharedContext* sharedContext, |
| const PipelineInfo& pipelineInfo, |
| sk_cfp<id<MTLRenderPipelineState>> pso, |
| sk_cfp<id<MTLDepthStencilState>> dss, |
| uint32_t refValue) |
| : GraphicsPipeline(sharedContext, pipelineInfo) |
| , fPipelineState(std::move(pso)) |
| , fDepthStencilState(std::move(dss)) |
| , fStencilReferenceValue(refValue) {} |
| |
| void MtlGraphicsPipeline::freeGpuData() { |
| fPipelineState.reset(); |
| } |
| |
| } // namespace skgpu::graphite |