in filament/backend/src/metal/MetalDriver.mm [1152:1406]
void MetalDriver::draw(backend::PipelineState ps, Handle<HwRenderPrimitive> rph) {
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
"Attempted to draw without a valid command encoder.");
auto primitive = handle_cast<MetalRenderPrimitive>(rph);
auto program = handle_cast<MetalProgram>(ps.program);
const auto& rs = ps.rasterState;
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
// during the draw call when the program is invalid. The shader compile error has already been
// dumped to the console at this point, so it's fine to simply return early.
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!program->isValid)) {
return;
}
ASSERT_PRECONDITION(program->isValid, "Attempting to draw with an invalid Metal program.");
// Pipeline state
MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid };
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i);
if (!attachment) {
continue;
}
colorPixelFormat[i] = attachment.getPixelFormat();
}
MTLPixelFormat depthPixelFormat = MTLPixelFormatInvalid;
const auto& depthAttachment = mContext->currentRenderTarget->getDepthAttachment();
if (depthAttachment) {
depthPixelFormat = depthAttachment.getPixelFormat();
}
metal::PipelineState pipelineState {
.vertexFunction = program->vertexFunction,
.fragmentFunction = program->fragmentFunction,
.vertexDescription = primitive->vertexDescription,
.colorAttachmentPixelFormat = {
colorPixelFormat[0],
colorPixelFormat[1],
colorPixelFormat[2],
colorPixelFormat[3],
colorPixelFormat[4],
colorPixelFormat[5],
colorPixelFormat[6],
colorPixelFormat[7]
},
.depthAttachmentPixelFormat = depthPixelFormat,
.sampleCount = mContext->currentRenderTarget->getSamples(),
.blendState = BlendState {
.blendingEnabled = rs.hasBlending(),
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
.destinationRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionDstRGB),
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha)
},
.colorWrite = rs.colorWrite
};
mContext->pipelineState.updateState(pipelineState);
if (mContext->pipelineState.stateChanged()) {
id<MTLRenderPipelineState> pipeline =
mContext->pipelineStateCache.getOrCreateState(pipelineState);
assert_invariant(pipeline != nil);
[mContext->currentRenderPassEncoder setRenderPipelineState:pipeline];
}
// Cull mode
MTLCullMode cullMode = getMetalCullMode(rs.culling);
mContext->cullModeState.updateState(cullMode);
if (mContext->cullModeState.stateChanged()) {
[mContext->currentRenderPassEncoder setCullMode:cullMode];
}
// Front face winding
MTLWinding winding = rs.inverseFrontFaces ? MTLWindingClockwise : MTLWindingCounterClockwise;
mContext->windingState.updateState(winding);
if (mContext->windingState.stateChanged()) {
[mContext->currentRenderPassEncoder setFrontFacingWinding:winding];
}
// Set the depth-stencil state, if a state change is needed.
DepthStencilState depthState;
if (depthAttachment) {
depthState.compareFunction = getMetalCompareFunction(rs.depthFunc);
depthState.depthWriteEnabled = rs.depthWrite;
}
mContext->depthStencilState.updateState(depthState);
if (mContext->depthStencilState.stateChanged()) {
id<MTLDepthStencilState> state =
mContext->depthStencilStateCache.getOrCreateState(depthState);
assert_invariant(state != nil);
[mContext->currentRenderPassEncoder setDepthStencilState:state];
}
if (ps.polygonOffset.constant != 0.0 || ps.polygonOffset.slope != 0.0) {
[mContext->currentRenderPassEncoder setDepthBias:ps.polygonOffset.constant
slopeScale:ps.polygonOffset.slope
clamp:0.0];
}
// FIXME: implement take ps.scissor into account
// must be intersected with viewport (see OpenGLDriver.cpp for implementation details)
// Bind uniform buffers.
MetalBuffer* uniformsToBind[Program::BINDING_COUNT] = { nil };
NSUInteger offsets[Program::BINDING_COUNT] = { 0 };
enumerateBoundUniformBuffers([&uniformsToBind, &offsets](const UniformBufferState& state,
MetalBuffer* buffer, uint32_t index) {
uniformsToBind[index] = buffer;
offsets[index] = state.offset;
});
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
0, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT, uniformsToBind, offsets,
Program::BINDING_COUNT);
// Enumerate all the sampler buffers for the program and check which textures and samplers need
// to be bound.
auto getTextureToBind = [this](const SamplerGroup::Sampler* sampler) {
const auto metalTexture = handle_const_cast<MetalTexture>(sampler->t);
id<MTLTexture> textureToBind = metalTexture->swizzledTextureView ? metalTexture->swizzledTextureView
: metalTexture->texture;
if (metalTexture->externalImage.isValid()) {
textureToBind = metalTexture->externalImage.getMetalTextureForDraw();
}
return textureToBind;
};
auto getSamplerToBind = [this](const SamplerGroup::Sampler* sampler) {
const auto metalTexture = handle_const_cast<MetalTexture>(sampler->t);
SamplerState s {
.samplerParams = sampler->s,
.minLod = metalTexture->minLod,
.maxLod = metalTexture->maxLod
};
return mContext->samplerStateCache.getOrCreateState(s);
};
id<MTLTexture> texturesToBindVertex[backend::MAX_VERTEX_SAMPLER_COUNT] = {};
id<MTLSamplerState> samplersToBindVertex[backend::MAX_VERTEX_SAMPLER_COUNT] = {};
enumerateSamplerGroups(program, ShaderType::VERTEX,
[this, &getTextureToBind, &getSamplerToBind, &texturesToBindVertex, &samplersToBindVertex](
const SamplerGroup::Sampler* sampler, uint8_t binding) {
// We currently only support a max of MAX_VERTEX_SAMPLER_COUNT samplers. Ignore any additional
// samplers that may be bound.
if (binding >= backend::MAX_VERTEX_SAMPLER_COUNT) {
return;
}
auto& textureToBind = texturesToBindVertex[binding];
textureToBind = getTextureToBind(sampler);
if (!textureToBind) {
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
<< " at the vertex shader." << utils::io::endl;
textureToBind = getOrCreateEmptyTexture(mContext);
}
auto& samplerToBind = samplersToBindVertex[binding];
samplerToBind = getSamplerToBind(sampler);
});
// Assign a default sampler to empty slots, in case Filament hasn't bound all samplers.
// Metal requires all samplers referenced in shaders to be bound.
for (auto& sampler : samplersToBindVertex) {
if (!sampler) {
sampler = mContext->samplerStateCache.getOrCreateState({});
}
}
NSRange vertexSamplerRange = NSMakeRange(0, backend::MAX_VERTEX_SAMPLER_COUNT);
[mContext->currentRenderPassEncoder setVertexTextures:texturesToBindVertex
withRange:vertexSamplerRange];
[mContext->currentRenderPassEncoder setVertexSamplerStates:samplersToBindVertex
withRange:vertexSamplerRange];
id<MTLTexture> texturesToBindFragment[backend::MAX_FRAGMENT_SAMPLER_COUNT] = {};
id<MTLSamplerState> samplersToBindFragment[backend::MAX_FRAGMENT_SAMPLER_COUNT] = {};
enumerateSamplerGroups(program, ShaderType::FRAGMENT,
[this, &getTextureToBind, &getSamplerToBind, &texturesToBindFragment, &samplersToBindFragment](
const SamplerGroup::Sampler* sampler, uint8_t binding) {
// We currently only support a max of MAX_FRAGMENT_SAMPLER_COUNT samplers. Ignore any additional
// samplers that may be bound.
if (binding >= backend::MAX_FRAGMENT_SAMPLER_COUNT) {
return;
}
auto& textureToBind = texturesToBindFragment[binding];
textureToBind = getTextureToBind(sampler);
if (!textureToBind) {
utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding
<< " at the fragment shader." << utils::io::endl;
textureToBind = getOrCreateEmptyTexture(mContext);
}
auto& samplerToBind = samplersToBindFragment[binding];
samplerToBind = getSamplerToBind(sampler);
});
// Assign a default sampler to empty slots, in case Filament hasn't bound all samplers.
// Metal requires all samplers referenced in shaders to be bound.
for (auto& sampler : samplersToBindFragment) {
if (!sampler) {
sampler = mContext->samplerStateCache.getOrCreateState({});
}
}
NSRange fragmentSamplerRange = NSMakeRange(0, backend::MAX_FRAGMENT_SAMPLER_COUNT);
[mContext->currentRenderPassEncoder setFragmentTextures:texturesToBindFragment
withRange:fragmentSamplerRange];
[mContext->currentRenderPassEncoder setFragmentSamplerStates:samplersToBindFragment
withRange:fragmentSamplerRange];
// Bind the vertex buffers.
MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT];
size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT];
size_t bufferIndex = 0;
auto vb = primitive->vertexBuffer;
for (uint32_t attributeIndex = 0; attributeIndex < vb->attributes.size(); attributeIndex++) {
const auto& attribute = vb->attributes[attributeIndex];
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
continue;
}
assert_invariant(vb->buffers[attribute.buffer]);
buffers[bufferIndex] = vb->buffers[attribute.buffer];
vertexBufferOffsets[bufferIndex] = attribute.offset;
bufferIndex++;
}
const auto bufferCount = bufferIndex;
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
VERTEX_BUFFER_START, MetalBuffer::Stage::VERTEX, buffers,
vertexBufferOffsets, bufferCount);
// Bind the zero buffer, used for missing vertex attributes.
static const char bytes[16] = { 0 };
[mContext->currentRenderPassEncoder setVertexBytes:bytes
length:16
atIndex:(VERTEX_BUFFER_START + ZERO_VERTEX_BUFFER)];
MetalIndexBuffer* indexBuffer = primitive->indexBuffer;
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
id<MTLBuffer> metalIndexBuffer = indexBuffer->buffer.getGpuBufferForDraw(cmdBuffer);
size_t offset = indexBuffer->buffer.getGpuBufferStreamOffset();
[mContext->currentRenderPassEncoder drawIndexedPrimitives:getMetalPrimitiveType(primitive->type)
indexCount:primitive->count
indexType:getIndexType(indexBuffer->elementSize)
indexBuffer:metalIndexBuffer
indexBufferOffset:primitive->offset + offset];
}