blob: 2438a1e85b985b05989a43ab0f38dcbb9fc529c2 [file] [log] [blame]
/*
* 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 "experimental/graphite/src/mtl/MtlGraphicsPipeline.h"
#include "experimental/graphite/include/TextureInfo.h"
#include "experimental/graphite/src/ContextPriv.h"
#include "experimental/graphite/src/GraphicsPipelineDesc.h"
#include "experimental/graphite/src/Log.h"
#include "experimental/graphite/src/Renderer.h"
#include "experimental/graphite/src/mtl/MtlGpu.h"
#include "experimental/graphite/src/mtl/MtlResourceProvider.h"
#include "experimental/graphite/src/mtl/MtlUtils.h"
#include "include/core/SkSpan.h"
#include "include/gpu/ShaderErrorHandler.h"
#include "include/private/SkSLString.h"
#include "include/private/SkShaderCodeDictionary.h"
namespace skgpu::mtl {
namespace {
SkSL::String emit_SKSL_uniforms(int bufferID, const char* name, SkSpan<const Uniform> uniforms) {
SkSL::String result;
result.appendf("layout (binding=%d) uniform %sUniforms {\n", bufferID, name);
int offset = 0;
for (auto u : uniforms) {
int count = u.count() ? u.count() : 1;
// TODO: this is sufficient for the sprint but should be changed to use SkSL's
// machinery
result.appendf(" layout(offset=%d) ", offset);
switch (u.type()) {
case SLType::kFloat4:
result.append("float4");
offset += 16 * count;
break;
case SLType::kFloat2:
result.append("float2");
offset += 8 * count;
break;
case SLType::kFloat:
result.append("float");
offset += 4 * count;
break;
case SLType::kFloat4x4:
result.append("float4x4");
offset += 64 * count;
break;
case SLType::kHalf4:
result.append("half4");
offset += 8 * count;
break;
default:
SkASSERT(0);
}
result.append(" ");
result.append(u.name());
if (u.count()) {
result.append("[");
result.append(std::to_string(u.count()));
result.append("]");
}
result.append(";\n");
}
result.append("};\n\n");
return result;
}
SkSL::String emit_SkSL_attributes(SkSpan<const Attribute> vertexAttrs,
SkSpan<const Attribute> instanceAttrs) {
SkSL::String result;
int attr = 0;
auto add_attrs = [&](SkSpan<const Attribute> attrs) {
for (auto a : attrs) {
// TODO: this is sufficient for the sprint but should be changed to use SkSL's
// machinery
result.appendf(" layout(location=%d) in ", attr++);
switch (a.gpuType()) {
case SLType::kFloat4:
result.append("float4");
break;
case SLType::kFloat2:
result.append("float2");
break;
case SLType::kFloat3:
result.append("float3");
break;
case SLType::kFloat:
result.append("float");
break;
case SLType::kHalf4:
result.append("half4");
break;
default:
SkASSERT(0);
}
result.appendf(" %s;\n", a.name());
}
};
if (!vertexAttrs.empty()) {
result.append("// vertex attrs\n");
add_attrs(vertexAttrs);
}
if (!instanceAttrs.empty()) {
result.append("// instance attrs\n");
add_attrs(instanceAttrs);
}
return result;
}
SkSL::String get_sksl_vs(const GraphicsPipelineDesc& desc) {
const RenderStep* step = desc.renderStep();
// TODO: To more completely support end-to-end rendering, this will need to be updated so that
// the RenderStep shader snippet can produce a device coord, a local coord, and depth.
// If the paint combination doesn't need the local coord it can be ignored, otherwise we need
// a varying for it. The fragment function's output will need to be updated to have a color and
// the depth, or when there's no combination, just the depth. Lastly, we also should add the
// static/intrinsic uniform binding point so that we can handle normalizing the device position
// produced by the RenderStep automatically.
// Fixed program header
SkSL::String sksl =
"layout (binding=0) uniform intrinsicUniforms {\n"
" layout(offset=0) float4 rtAdjust;\n"
"};\n"
"\n";
if (step->numVertexAttributes() > 0 || step->numInstanceAttributes() > 0) {
sksl += emit_SkSL_attributes(step->vertexAttributes(), step->instanceAttributes());
}
// Uniforms needed by RenderStep
if (step->numUniforms() > 0) {
sksl += emit_SKSL_uniforms(1, "Step", step->uniforms());
}
// Vertex shader function declaration
sksl += "void main() {\n";
// Vertex shader body
sksl += step->vertexSkSL();
sksl += "sk_Position = float4(devPosition.xy * rtAdjust.xy + rtAdjust.zw, devPosition.zw);\n"
"}\n";
return sksl;
}
SkSL::String get_sksl_fs(const Context* context,
const GraphicsPipelineDesc& desc,
bool* writesColor) {
SkSL::String sksl;
SkPaintParamsKey key;
auto entry = context->priv().shaderCodeDictionary()->lookup(desc.paintParamsID());
if (entry) {
key = entry->paintParamsKey();
}
*writesColor = false;
// TODO: make this more flexible so the individual blocks can be linked together. Right now
// this loop relies on only one shader snippet and a blend mode being added to a key.
int curHeaderOffset = 0;
while (curHeaderOffset < key.sizeInBytes()) {
auto [codeSnippetID, blockSize] = key.readCodeSnippetID(curHeaderOffset);
if (codeSnippetID == CodeSnippetID::kSimpleBlendMode) {
curHeaderOffset += blockSize;
continue;
}
// Typedefs needed for painting
auto paintUniforms = GetUniforms(codeSnippetID);
if (!paintUniforms.empty()) {
sksl += emit_SKSL_uniforms(2, "FS", paintUniforms);
}
sksl += "layout(location = 0, index = 0) out half4 sk_FragColor;\n";
sksl += "void main() {\n"
" half4 outColor;\n";
sksl += GetShaderSkSL(codeSnippetID);
sksl += " sk_FragColor = outColor;\n"
"}\n";
*writesColor = codeSnippetID != CodeSnippetID::kDepthStencilOnlyDraw;
curHeaderOffset += blockSize;
}
return sksl;
}
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, *)) {
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::kByte:
if (@available(macOS 10.13, iOS 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, *)) {
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, *)) {
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, *)) {
return MTLVertexFormatUShortNormalized;
} else {
return MTLVertexFormatInvalid;
}
case VertexAttribType::kUShort4_norm:
return MTLVertexFormatUShort4Normalized;
}
SK_ABORT("Unknown vertex attribute type");
}
MTLVertexDescriptor* create_vertex_descriptor(const RenderStep* step) {
auto vertexDescriptor = [[MTLVertexDescriptor alloc] init];
int attributeIndex = 0;
int vertexAttributeCount = step->numVertexAttributes();
size_t vertexAttributeOffset = 0;
for (const auto& attribute : step->vertexAttributes()) {
MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex];
MTLVertexFormat format = attribute_type_to_mtlformat(attribute.cpuType());
SkASSERT(MTLVertexFormatInvalid != format);
mtlAttribute.format = format;
mtlAttribute.offset = vertexAttributeOffset;
mtlAttribute.bufferIndex = GraphicsPipeline::kVertexBufferIndex;
vertexAttributeOffset += attribute.sizeAlign4();
attributeIndex++;
}
SkASSERT(vertexAttributeOffset == step->vertexStride());
if (vertexAttributeCount) {
MTLVertexBufferLayoutDescriptor* vertexBufferLayout =
vertexDescriptor.layouts[GraphicsPipeline::kVertexBufferIndex];
vertexBufferLayout.stepFunction = MTLVertexStepFunctionPerVertex;
vertexBufferLayout.stepRate = 1;
vertexBufferLayout.stride = vertexAttributeOffset;
}
int instanceAttributeCount = step->numInstanceAttributes();
size_t instanceAttributeOffset = 0;
for (const auto& attribute : step->instanceAttributes()) {
MTLVertexAttributeDescriptor* mtlAttribute = vertexDescriptor.attributes[attributeIndex];
MTLVertexFormat format = attribute_type_to_mtlformat(attribute.cpuType());
SkASSERT(MTLVertexFormatInvalid != format);
mtlAttribute.format = format;
mtlAttribute.offset = instanceAttributeOffset;
mtlAttribute.bufferIndex = GraphicsPipeline::kInstanceBufferIndex;
instanceAttributeOffset += attribute.sizeAlign4();
attributeIndex++;
}
SkASSERT(instanceAttributeOffset == step->instanceStride());
if (instanceAttributeCount) {
MTLVertexBufferLayoutDescriptor* instanceBufferLayout =
vertexDescriptor.layouts[GraphicsPipeline::kInstanceBufferIndex];
instanceBufferLayout.stepFunction = MTLVertexStepFunctionPerInstance;
instanceBufferLayout.stepRate = 1;
instanceBufferLayout.stride = instanceAttributeOffset;
}
return vertexDescriptor;
}
} // anonymous namespace
enum ShaderType {
kVertex_ShaderType = 0,
kFragment_ShaderType = 1,
kLast_ShaderType = kFragment_ShaderType
};
static const int kShaderTypeCount = kLast_ShaderType + 1;
sk_sp<GraphicsPipeline> GraphicsPipeline::Make(const Context* context,
const Gpu* gpu,
const skgpu::GraphicsPipelineDesc& pipelineDesc,
const skgpu::RenderPassDesc& renderPassDesc) {
sk_cfp<MTLRenderPipelineDescriptor*> psoDescriptor([[MTLRenderPipelineDescriptor alloc] init]);
SkSL::String msl[kShaderTypeCount];
SkSL::Program::Inputs inputs[kShaderTypeCount];
SkSL::Program::Settings settings;
ShaderErrorHandler* errorHandler = DefaultShaderErrorHandler();
if (!SkSLToMSL(gpu,
get_sksl_vs(pipelineDesc),
SkSL::ProgramKind::kVertex,
settings,
&msl[kVertex_ShaderType],
&inputs[kVertex_ShaderType],
errorHandler)) {
return nullptr;
}
bool writesColor;
if (!SkSLToMSL(gpu,
get_sksl_fs(context, pipelineDesc, &writesColor),
SkSL::ProgramKind::kFragment,
settings,
&msl[kFragment_ShaderType],
&inputs[kFragment_ShaderType],
errorHandler)) {
return nullptr;
}
sk_cfp<id<MTLLibrary>> shaderLibraries[kShaderTypeCount];
shaderLibraries[kVertex_ShaderType] = CompileShaderLibrary(gpu,
msl[kVertex_ShaderType],
errorHandler);
shaderLibraries[kFragment_ShaderType] = CompileShaderLibrary(gpu,
msl[kFragment_ShaderType],
errorHandler);
if (!shaderLibraries[kVertex_ShaderType] || !shaderLibraries[kFragment_ShaderType]) {
return nullptr;
}
(*psoDescriptor).label = @(pipelineDesc.renderStep()->name());
(*psoDescriptor).vertexFunction =
[shaderLibraries[kVertex_ShaderType].get() newFunctionWithName: @"vertexMain"];
(*psoDescriptor).fragmentFunction =
[shaderLibraries[kFragment_ShaderType].get() newFunctionWithName: @"fragmentMain"];
// TODO: I *think* this gets cleaned up by the pipelineDescriptor?
(*psoDescriptor).vertexDescriptor = create_vertex_descriptor(pipelineDesc.renderStep());
// TODO: I *think* this gets cleaned up by the pipelineDescriptor as well?
auto mtlColorAttachment = [[MTLRenderPipelineColorAttachmentDescriptor alloc] init];
mtl::TextureInfo mtlTexInfo;
renderPassDesc.fColorAttachment.fTextureInfo.getMtlTextureInfo(&mtlTexInfo);
mtlColorAttachment.pixelFormat = (MTLPixelFormat)mtlTexInfo.fFormat;
mtlColorAttachment.blendingEnabled = FALSE;
mtlColorAttachment.writeMask = writesColor ? MTLColorWriteMaskAll : MTLColorWriteMaskNone;
(*psoDescriptor).colorAttachments[0] = mtlColorAttachment;
renderPassDesc.fDepthStencilAttachment.fTextureInfo.getMtlTextureInfo(&mtlTexInfo);
MTLPixelFormat depthStencilFormat = (MTLPixelFormat)mtlTexInfo.fFormat;
if (FormatIsStencil(depthStencilFormat)) {
(*psoDescriptor).stencilAttachmentPixelFormat = depthStencilFormat;
} else {
(*psoDescriptor).stencilAttachmentPixelFormat = MTLPixelFormatInvalid;
}
if (FormatIsDepth(depthStencilFormat)) {
(*psoDescriptor).depthAttachmentPixelFormat = depthStencilFormat;
} else {
(*psoDescriptor).depthAttachmentPixelFormat = MTLPixelFormatInvalid;
}
NSError* error;
sk_cfp<id<MTLRenderPipelineState>> pso(
[gpu->device() newRenderPipelineStateWithDescriptor:psoDescriptor.get()
error:&error]);
if (!pso) {
SKGPU_LOG_E("Pipeline creation failure:\n%s", error.debugDescription.UTF8String);
return nullptr;
}
auto resourceProvider = (skgpu::mtl::ResourceProvider*) gpu->resourceProvider();
const DepthStencilSettings& depthStencilSettings =
pipelineDesc.renderStep()->depthStencilSettings();
id<MTLDepthStencilState> dss = resourceProvider->findOrCreateCompatibleDepthStencilState(
depthStencilSettings);
return sk_sp<GraphicsPipeline>(
new GraphicsPipeline(gpu,
std::move(pso),
dss,
depthStencilSettings.fStencilReferenceValue,
pipelineDesc.renderStep()->vertexStride(),
pipelineDesc.renderStep()->instanceStride()));
}
void GraphicsPipeline::onFreeGpuData() {
fPipelineState.reset();
}
} // namespace skgpu::mtl