1// 2// Copyright 2021 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// ProvokingVertexHelper.mm: 7// Implements the class methods for ProvokingVertexHelper. 8// 9 10#include "libANGLE/renderer/metal/ProvokingVertexHelper.h" 11#import <Foundation/Foundation.h> 12#include "libANGLE/Display.h" 13#include "libANGLE/renderer/metal/ContextMtl.h" 14#include "libANGLE/renderer/metal/DisplayMtl.h" 15#include "libANGLE/renderer/metal/mtl_common.h" 16#include "libANGLE/renderer/metal/shaders/rewrite_indices_shared.h" 17namespace rx 18{ 19 20namespace 21{ 22constexpr size_t kInitialIndexBufferSize = 0xFFFF; // Initial 64k pool. 23} 24static inline uint primCountForIndexCount(const uint fixIndexBufferKey, const uint indexCount) 25{ 26 const uint fixIndexBufferMode = 27 (fixIndexBufferKey >> MtlFixIndexBufferKeyModeShift) & MtlFixIndexBufferKeyModeMask; 28 29 switch (fixIndexBufferMode) 30 { 31 case MtlFixIndexBufferKeyPoints: 32 return indexCount; 33 case MtlFixIndexBufferKeyLines: 34 return indexCount / 2; 35 case MtlFixIndexBufferKeyLineStrip: 36 return (uint)MAX(0, (int)indexCount - 1); 37 case MtlFixIndexBufferKeyLineLoop: 38 return (uint)MAX(0, (int)indexCount); 39 case MtlFixIndexBufferKeyTriangles: 40 return indexCount / 3; 41 case MtlFixIndexBufferKeyTriangleStrip: 42 return (uint)MAX(0, (int)indexCount - 2); 43 case MtlFixIndexBufferKeyTriangleFan: 44 return (uint)MAX(0, (int)indexCount - 2); 45 default: 46 ASSERT(false); 47 return 0; 48 } 49} 50 51static inline uint indexCountForPrimCount(const uint fixIndexBufferKey, const uint primCount) 52{ 53 const uint fixIndexBufferMode = 54 (fixIndexBufferKey >> MtlFixIndexBufferKeyModeShift) & MtlFixIndexBufferKeyModeMask; 55 switch (fixIndexBufferMode) 56 { 57 case MtlFixIndexBufferKeyPoints: 58 return primCount; 59 case MtlFixIndexBufferKeyLines: 60 return primCount * 2; 61 case MtlFixIndexBufferKeyLineStrip: 62 return primCount * 2; 63 case MtlFixIndexBufferKeyLineLoop: 64 return primCount * 2; 65 case MtlFixIndexBufferKeyTriangles: 66 return primCount * 3; 67 case MtlFixIndexBufferKeyTriangleStrip: 68 return primCount * 3; 69 case MtlFixIndexBufferKeyTriangleFan: 70 return primCount * 3; 71 default: 72 ASSERT(false); 73 return 0; 74 } 75} 76 77static inline gl::PrimitiveMode getNewPrimitiveMode(const uint fixIndexBufferKey) 78{ 79 const uint fixIndexBufferMode = 80 (fixIndexBufferKey >> MtlFixIndexBufferKeyModeShift) & MtlFixIndexBufferKeyModeMask; 81 switch (fixIndexBufferMode) 82 { 83 case MtlFixIndexBufferKeyPoints: 84 return gl::PrimitiveMode::Points; 85 case MtlFixIndexBufferKeyLines: 86 return gl::PrimitiveMode::Lines; 87 case MtlFixIndexBufferKeyLineStrip: 88 return gl::PrimitiveMode::Lines; 89 case MtlFixIndexBufferKeyLineLoop: 90 return gl::PrimitiveMode::Lines; 91 case MtlFixIndexBufferKeyTriangles: 92 return gl::PrimitiveMode::Triangles; 93 case MtlFixIndexBufferKeyTriangleStrip: 94 return gl::PrimitiveMode::Triangles; 95 case MtlFixIndexBufferKeyTriangleFan: 96 return gl::PrimitiveMode::Triangles; 97 default: 98 ASSERT(false); 99 return gl::PrimitiveMode::InvalidEnum; 100 } 101} 102ProvokingVertexHelper::ProvokingVertexHelper(ContextMtl *context, 103 mtl::CommandQueue *commandQueue, 104 DisplayMtl *display) 105 : mCommandBuffer(commandQueue), 106 mIndexBuffers(false), 107 mPipelineCache(this), 108 mCurrentEncoder(&mCommandBuffer) 109{ 110 id<MTLLibrary> mtlLib = display->getDefaultShadersLib(); 111 mProvokingVertexLibrary = mtlLib; 112 mIndexBuffers.initialize(context, kInitialIndexBufferSize, mtl::kIndexBufferOffsetAlignment, 0); 113} 114 115void ProvokingVertexHelper::onDestroy(ContextMtl *context) 116{ 117 mIndexBuffers.destroy(context); 118 mPipelineCache.clear(); 119} 120 121void ProvokingVertexHelper::commitPreconditionCommandBuffer(ContextMtl *contextMtl) 122{ 123 if (mCurrentEncoder.valid()) 124 { 125 mCurrentEncoder.endEncoding(); 126 } 127 mCommandBuffer.commit(mtl::NoWait); 128 129 mIndexBuffers.releaseInFlightBuffers(contextMtl); 130} 131 132mtl::ComputeCommandEncoder *ProvokingVertexHelper::getComputeCommandEncoder() 133{ 134 if (mCurrentEncoder.valid()) 135 { 136 return &mCurrentEncoder; 137 } 138 139 ensureCommandBufferReady(); 140 return &mCurrentEncoder.restart(); 141} 142 143void ProvokingVertexHelper::ensureCommandBufferReady() 144{ 145 if (!mCommandBuffer.ready()) 146 { 147 mCommandBuffer.restart(); 148 } 149 ASSERT(mCommandBuffer.ready()); 150} 151 152static uint buildIndexBufferKey(const mtl::ProvokingVertexComputePipelineDesc &pipelineDesc) 153{ 154 uint indexBufferKey = 0; 155 gl::DrawElementsType elementType = (gl::DrawElementsType)pipelineDesc.elementType; 156 bool doPrimPrestart = pipelineDesc.primitiveRestartEnabled; 157 gl::PrimitiveMode primMode = pipelineDesc.primitiveMode; 158 switch (elementType) 159 { 160 case gl::DrawElementsType::UnsignedShort: 161 indexBufferKey |= MtlFixIndexBufferKeyUint16 << MtlFixIndexBufferKeyInShift; 162 indexBufferKey |= MtlFixIndexBufferKeyUint16 << MtlFixIndexBufferKeyOutShift; 163 break; 164 case gl::DrawElementsType::UnsignedInt: 165 indexBufferKey |= MtlFixIndexBufferKeyUint32 << MtlFixIndexBufferKeyInShift; 166 indexBufferKey |= MtlFixIndexBufferKeyUint32 << MtlFixIndexBufferKeyOutShift; 167 break; 168 default: 169 ASSERT(false); // Index type should only be short or int. 170 break; 171 } 172 indexBufferKey |= (uint)primMode << MtlFixIndexBufferKeyModeShift; 173 indexBufferKey |= doPrimPrestart ? MtlFixIndexBufferKeyPrimRestart : 0; 174 // We only rewrite indices if we're switching the provoking vertex mode. 175 indexBufferKey |= MtlFixIndexBufferKeyProvokingVertexLast; 176 return indexBufferKey; 177} 178 179bool ProvokingVertexHelper::hasSpecializedShader( 180 gl::ShaderType shaderType, 181 const mtl::ProvokingVertexComputePipelineDesc &renderPipelineDesc) 182{ 183 return true; 184} 185 186angle::Result ProvokingVertexHelper::getSpecializedShader( 187 rx::mtl::Context *context, 188 gl::ShaderType shaderType, 189 const mtl::ProvokingVertexComputePipelineDesc &pipelineDesc, 190 id<MTLFunction> *shaderOut) 191{ 192 uint indexBufferKey = buildIndexBufferKey(pipelineDesc); 193 auto fcValues = mtl::adoptObjCObj([[MTLFunctionConstantValues alloc] init]); 194 [fcValues setConstantValue:&indexBufferKey type:MTLDataTypeUInt withName:@"fixIndexBufferKey"]; 195 if (pipelineDesc.generateIndices) 196 { 197 return CreateMslShader(context, mProvokingVertexLibrary, @"genIndexBuffer", fcValues.get(), 198 shaderOut); 199 } 200 else 201 { 202 return CreateMslShader(context, mProvokingVertexLibrary, @"fixIndexBuffer", fcValues.get(), 203 shaderOut); 204 } 205} 206 207void ProvokingVertexHelper::prepareCommandEncoderForDescriptor( 208 ContextMtl *context, 209 mtl::ComputeCommandEncoder *encoder, 210 mtl::ProvokingVertexComputePipelineDesc desc) 211{ 212 auto pipelineState = mPipelineCache.getComputePipelineState(context, desc); 213 214 encoder->setComputePipelineState(pipelineState); 215 mCachedDesc = desc; 216} 217mtl::BufferRef ProvokingVertexHelper::preconditionIndexBuffer(ContextMtl *context, 218 mtl::BufferRef indexBuffer, 219 size_t indexCount, 220 size_t indexOffset, 221 bool primitiveRestartEnabled, 222 gl::PrimitiveMode primitiveMode, 223 gl::DrawElementsType elementsType, 224 size_t &outIndexCount, 225 size_t &outIndexOffset, 226 gl::PrimitiveMode &outPrimitiveMode) 227{ 228 // Get specialized program 229 // Upload index buffer 230 // dispatch per-primitive? 231 ensureCommandBufferReady(); 232 mtl::ProvokingVertexComputePipelineDesc pipelineDesc; 233 pipelineDesc.elementType = (uint8_t)elementsType; 234 pipelineDesc.primitiveMode = primitiveMode; 235 pipelineDesc.primitiveRestartEnabled = primitiveRestartEnabled; 236 pipelineDesc.generateIndices = false; 237 uint indexBufferKey = buildIndexBufferKey(pipelineDesc); 238 uint primCount = primCountForIndexCount(indexBufferKey, (uint32_t)indexCount); 239 uint newIndexCount = indexCountForPrimCount(indexBufferKey, primCount); 240 size_t indexSize = gl::GetDrawElementsTypeSize(elementsType); 241 size_t newOffset = 0; 242 mtl::BufferRef newBuffer; 243 if (mIndexBuffers.allocate(context, newIndexCount * indexSize + indexOffset, nullptr, 244 &newBuffer, &newOffset) == angle::Result::Stop) 245 { 246 return nullptr; 247 } 248 uint indexCountEncoded = (uint)indexCount; 249 auto threadsPerThreadgroup = MTLSizeMake(MIN(primCount, 64u), 1, 1); 250 251 mtl::ComputeCommandEncoder *encoder = getComputeCommandEncoder(); 252 prepareCommandEncoderForDescriptor(context, encoder, pipelineDesc); 253 encoder->setBuffer(indexBuffer, static_cast<uint32_t>(indexOffset), 0); 254 encoder->setBufferForWrite( 255 newBuffer, static_cast<uint32_t>(indexOffset) + static_cast<uint32_t>(newOffset), 1); 256 encoder->setData(&indexCountEncoded, 2); 257 encoder->setData(&primCount, 3); 258 encoder->dispatch( 259 MTLSizeMake((primCount + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, 1, 260 1), 261 threadsPerThreadgroup); 262 outIndexCount = newIndexCount; 263 outIndexOffset = newOffset; 264 outPrimitiveMode = getNewPrimitiveMode(indexBufferKey); 265 return newBuffer; 266} 267 268mtl::BufferRef ProvokingVertexHelper::generateIndexBuffer(ContextMtl *context, 269 size_t first, 270 size_t indexCount, 271 gl::PrimitiveMode primitiveMode, 272 gl::DrawElementsType elementsType, 273 size_t &outIndexCount, 274 size_t &outIndexOffset, 275 gl::PrimitiveMode &outPrimitiveMode) 276{ 277 // Get specialized program 278 // Upload index buffer 279 // dispatch per-primitive? 280 ensureCommandBufferReady(); 281 mtl::ProvokingVertexComputePipelineDesc pipelineDesc; 282 pipelineDesc.elementType = (uint8_t)elementsType; 283 pipelineDesc.primitiveMode = primitiveMode; 284 pipelineDesc.primitiveRestartEnabled = false; 285 pipelineDesc.generateIndices = true; 286 uint indexBufferKey = buildIndexBufferKey(pipelineDesc); 287 uint primCount = primCountForIndexCount(indexBufferKey, (uint32_t)indexCount); 288 uint newIndexCount = indexCountForPrimCount(indexBufferKey, primCount); 289 size_t indexSize = gl::GetDrawElementsTypeSize(elementsType); 290 size_t newIndexOffset = 0; 291 mtl::BufferRef newBuffer; 292 if (mIndexBuffers.allocate(context, newIndexCount * indexSize, nullptr, &newBuffer, 293 &newIndexOffset) == angle::Result::Stop) 294 { 295 return nullptr; 296 } 297 uint indexCountEncoded = static_cast<uint>(indexCount); 298 uint firstVertexEncoded = static_cast<uint>(first); 299 uint indexOffsetEncoded = static_cast<uint>(newIndexOffset); 300 auto threadsPerThreadgroup = MTLSizeMake(MIN(primCount, 64u), 1, 1); 301 302 mtl::ComputeCommandEncoder *encoder = getComputeCommandEncoder(); 303 prepareCommandEncoderForDescriptor(context, encoder, pipelineDesc); 304 encoder->setBufferForWrite(newBuffer, indexOffsetEncoded, 1); 305 encoder->setData(indexCountEncoded, 2); 306 encoder->setData(primCount, 3); 307 encoder->setData(firstVertexEncoded, 4); 308 encoder->dispatch( 309 MTLSizeMake((primCount + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, 1, 310 1), 311 threadsPerThreadgroup); 312 outIndexCount = newIndexCount; 313 outIndexOffset = newIndexOffset; 314 outPrimitiveMode = getNewPrimitiveMode(indexBufferKey); 315 return newBuffer; 316} 317 318} // namespace rx 319