// // Copyright 2021 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // ProvokingVertexHelper.mm: // Implements the class methods for ProvokingVertexHelper. // #include "libANGLE/renderer/metal/ProvokingVertexHelper.h" #import #include "common/base/anglebase/numerics/checked_math.h" #include "libANGLE/Display.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/mtl_common.h" #include "libANGLE/renderer/metal/shaders/rewrite_indices_shared.h" namespace rx { namespace { constexpr size_t kInitialIndexBufferSize = 0xFFFF; // Initial 64k pool. } static inline uint32_t primCountForIndexCount(const uint fixIndexBufferKey, const GLsizei indexCount) { const uint fixIndexBufferMode = (fixIndexBufferKey >> MtlFixIndexBufferKeyModeShift) & MtlFixIndexBufferKeyModeMask; switch (fixIndexBufferMode) { case MtlFixIndexBufferKeyPoints: return indexCount; case MtlFixIndexBufferKeyLines: return indexCount / 2; case MtlFixIndexBufferKeyLineStrip: // Prevent underflow with subtraction and avoid casting to a signed type return std::max(indexCount - 1, 0); case MtlFixIndexBufferKeyLineLoop: return indexCount; case MtlFixIndexBufferKeyTriangles: return indexCount / 3; case MtlFixIndexBufferKeyTriangleStrip: // Prevent underflow with subtraction and avoid casting to a signed type return std::max(indexCount - 2, 0); case MtlFixIndexBufferKeyTriangleFan: // Prevent underflow with subtraction and avoid casting to a signed type return std::max(indexCount - 2, 0); default: ASSERT(false); return 0; } } static inline bool indexCountForPrimCount(const uint fixIndexBufferKey, const uint32_t primCount, uint32_t *outIndexCount) { const uint fixIndexBufferMode = (fixIndexBufferKey >> MtlFixIndexBufferKeyModeShift) & MtlFixIndexBufferKeyModeMask; uint32_t indicesPerPrimitive = 0; switch (fixIndexBufferMode) { case MtlFixIndexBufferKeyPoints: indicesPerPrimitive = 1; break; case MtlFixIndexBufferKeyLines: case MtlFixIndexBufferKeyLineStrip: case MtlFixIndexBufferKeyLineLoop: indicesPerPrimitive = 2; break; case MtlFixIndexBufferKeyTriangles: case MtlFixIndexBufferKeyTriangleStrip: case MtlFixIndexBufferKeyTriangleFan: indicesPerPrimitive = 3; break; default: UNREACHABLE(); return false; } angle::CheckedNumeric indexCount(primCount); indexCount *= indicesPerPrimitive; return indexCount.AssignIfValid(outIndexCount); } static inline gl::PrimitiveMode getNewPrimitiveMode(const uint fixIndexBufferKey) { const uint fixIndexBufferMode = (fixIndexBufferKey >> MtlFixIndexBufferKeyModeShift) & MtlFixIndexBufferKeyModeMask; switch (fixIndexBufferMode) { case MtlFixIndexBufferKeyPoints: return gl::PrimitiveMode::Points; case MtlFixIndexBufferKeyLines: return gl::PrimitiveMode::Lines; case MtlFixIndexBufferKeyLineStrip: return gl::PrimitiveMode::Lines; case MtlFixIndexBufferKeyLineLoop: return gl::PrimitiveMode::Lines; case MtlFixIndexBufferKeyTriangles: return gl::PrimitiveMode::Triangles; case MtlFixIndexBufferKeyTriangleStrip: return gl::PrimitiveMode::Triangles; case MtlFixIndexBufferKeyTriangleFan: return gl::PrimitiveMode::Triangles; default: ASSERT(false); return gl::PrimitiveMode::InvalidEnum; } } ProvokingVertexHelper::ProvokingVertexHelper(ContextMtl *context) : mIndexBuffers(false) { mIndexBuffers.initialize(context, kInitialIndexBufferSize, mtl::kIndexBufferOffsetAlignment, 0); } void ProvokingVertexHelper::onDestroy(ContextMtl *context) { mIndexBuffers.destroy(context); } void ProvokingVertexHelper::releaseInFlightBuffers(ContextMtl *contextMtl) { mIndexBuffers.releaseInFlightBuffers(contextMtl); } static uint buildIndexBufferKey(gl::DrawElementsType elementType, bool doPrimPrestart, gl::PrimitiveMode primMode) { uint indexBufferKey = 0; switch (elementType) { case gl::DrawElementsType::UnsignedShort: indexBufferKey |= MtlFixIndexBufferKeyUint16 << MtlFixIndexBufferKeyInShift; indexBufferKey |= MtlFixIndexBufferKeyUint16 << MtlFixIndexBufferKeyOutShift; break; case gl::DrawElementsType::UnsignedInt: indexBufferKey |= MtlFixIndexBufferKeyUint32 << MtlFixIndexBufferKeyInShift; indexBufferKey |= MtlFixIndexBufferKeyUint32 << MtlFixIndexBufferKeyOutShift; break; default: ASSERT(false); // Index type should only be short or int. break; } indexBufferKey |= (uint)primMode << MtlFixIndexBufferKeyModeShift; indexBufferKey |= doPrimPrestart ? MtlFixIndexBufferKeyPrimRestart : 0; // We only rewrite indices if we're switching the provoking vertex mode. indexBufferKey |= MtlFixIndexBufferKeyProvokingVertexLast; return indexBufferKey; } angle::Result ProvokingVertexHelper::prepareCommandEncoderForFunction( ContextMtl *context, mtl::ComputeCommandEncoder *encoder, uint32_t indexBufferKey, bool isForGenerateIndices) { auto &functionMap = isForGenerateIndices ? mGenIndexBufferFunctions : mFixIndexBufferFunctions; NSString *functionName = isForGenerateIndices ? @"genIndexBuffer" : @"fixIndexBuffer"; angle::ObjCPtr> computeShader; auto iter = functionMap.find(indexBufferKey); if (iter != functionMap.end()) { computeShader = iter->second; } else { id provokingVertexLibrary = context->getDisplay()->getDefaultShadersLib(); auto fcValues = angle::adoptObjCPtr([[MTLFunctionConstantValues alloc] init]); [fcValues setConstantValue:&indexBufferKey type:MTLDataTypeUInt withName:@"fixIndexBufferKey"]; ANGLE_TRY(CreateMslShader(context, provokingVertexLibrary, functionName, fcValues.get(), &computeShader)); functionMap[indexBufferKey] = computeShader; } angle::ObjCPtr> pipelineState; ANGLE_TRY( context->getPipelineCache().getComputePipeline(context, computeShader, &pipelineState)); encoder->setComputePipelineState(pipelineState); return angle::Result::Continue; } angle::Result ProvokingVertexHelper::preconditionIndexBuffer(ContextMtl *context, mtl::BufferRef indexBuffer, GLsizei glCount, size_t indexOffset, bool primitiveRestartEnabled, gl::PrimitiveMode primitiveMode, gl::DrawElementsType elementsType, uint32_t &outIndexCount, size_t &outIndexOffset, gl::PrimitiveMode &outPrimitiveMode, mtl::BufferRef &outNewBuffer) { // Get specialized program // Upload index buffer // dispatch per-primitive? uint indexBufferKey = buildIndexBufferKey(elementsType, primitiveRestartEnabled, primitiveMode); uint32_t primCount = primCountForIndexCount(indexBufferKey, glCount); uint32_t newIndexCount = 0; ANGLE_CHECK_GL_MATH(context, indexCountForPrimCount(indexBufferKey, primCount, &newIndexCount)); size_t indexSize = gl::GetDrawElementsTypeSize(elementsType); size_t newOffset = 0; mtl::BufferRef newBuffer; angle::CheckedNumeric checkedBufferSize(newIndexCount); checkedBufferSize *= indexSize; checkedBufferSize += indexOffset; ANGLE_CHECK_GL_MATH(context, checkedBufferSize.IsValid()); ANGLE_TRY(mIndexBuffers.allocate(context, checkedBufferSize.ValueOrDie(), nullptr, &newBuffer, &newOffset)); auto threadsPerThreadgroup = MTLSizeMake(MIN(primCount, 64u), 1, 1); mtl::ComputeCommandEncoder *encoder = context->getComputeCommandEncoderWithoutEndingRenderEncoder(); const bool isForGenerateIndices = false; ANGLE_TRY( prepareCommandEncoderForFunction(context, encoder, indexBufferKey, isForGenerateIndices)); encoder->setBuffer(indexBuffer, static_cast(indexOffset), 0); encoder->setBufferForWrite( newBuffer, static_cast(indexOffset) + static_cast(newOffset), 1); encoder->setData(static_cast(glCount), 2); encoder->setData(primCount, 3); encoder->dispatch( MTLSizeMake((static_cast(primCount) + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, 1, 1), threadsPerThreadgroup); outIndexCount = static_cast(newIndexCount); outIndexOffset = newOffset; outPrimitiveMode = getNewPrimitiveMode(indexBufferKey); outNewBuffer = newBuffer; return angle::Result::Continue; } angle::Result ProvokingVertexHelper::generateIndexBuffer(ContextMtl *context, size_t first, GLsizei glCount, gl::PrimitiveMode primitiveMode, gl::DrawElementsType elementsType, uint32_t &outIndexCount, size_t &outIndexOffset, gl::PrimitiveMode &outPrimitiveMode, mtl::BufferRef &outNewBuffer) { // Get specialized program // Upload index buffer // dispatch per-primitive? const bool primitiveRestartEnabled = false; uint indexBufferKey = buildIndexBufferKey(elementsType, primitiveRestartEnabled, primitiveMode); uint32_t primCount = primCountForIndexCount(indexBufferKey, glCount); uint32_t newIndexCount = 0; ANGLE_CHECK_GL_MATH(context, indexCountForPrimCount(indexBufferKey, primCount, &newIndexCount)); size_t indexSize = gl::GetDrawElementsTypeSize(elementsType); size_t newIndexOffset = 0; mtl::BufferRef newBuffer; angle::CheckedNumeric checkedBufferSize = newIndexCount; checkedBufferSize *= indexSize; ANGLE_CHECK_GL_MATH(context, checkedBufferSize.IsValid()); ANGLE_TRY(mIndexBuffers.allocate(context, checkedBufferSize.ValueOrDie(), nullptr, &newBuffer, &newIndexOffset)); auto threadsPerThreadgroup = MTLSizeMake(MIN(primCount, 64u), 1, 1); mtl::ComputeCommandEncoder *encoder = context->getComputeCommandEncoderWithoutEndingRenderEncoder(); const bool isForGenerateIndices = true; ANGLE_TRY( prepareCommandEncoderForFunction(context, encoder, indexBufferKey, isForGenerateIndices)); encoder->setBufferForWrite(newBuffer, static_cast(newIndexOffset), 1); encoder->setData(static_cast(glCount), 2); encoder->setData(primCount, 3); encoder->setData(static_cast(first), 4); encoder->dispatch( MTLSizeMake((static_cast(primCount) + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, 1, 1), threadsPerThreadgroup); outIndexCount = static_cast(newIndexCount); outIndexOffset = newIndexOffset; outPrimitiveMode = getNewPrimitiveMode(indexBufferKey); outNewBuffer = newBuffer; return angle::Result::Continue; } } // namespace rx