1// 2// Copyright 2019 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// BufferMtl.mm: 7// Implements the class methods for BufferMtl. 8// 9 10#include "libANGLE/renderer/metal/BufferMtl.h" 11 12#include "common/debug.h" 13#include "common/utilities.h" 14#include "libANGLE/renderer/metal/ContextMtl.h" 15 16namespace rx 17{ 18 19namespace 20{ 21 22// Start with a fairly small buffer size. We can increase this dynamically as we convert more data. 23constexpr size_t kConvertedElementArrayBufferInitialSize = 1024 * 8; 24 25template <typename IndexType> 26angle::Result GetFirstLastIndices(const IndexType *indices, 27 size_t count, 28 std::pair<uint32_t, uint32_t> *outIndices) 29{ 30 IndexType first, last; 31 // Use memcpy to avoid unaligned memory access crash: 32 memcpy(&first, &indices[0], sizeof(first)); 33 memcpy(&last, &indices[count - 1], sizeof(last)); 34 35 outIndices->first = first; 36 outIndices->second = last; 37 38 return angle::Result::Continue; 39} 40 41} // namespace 42 43// ConversionBufferMtl implementation. 44ConversionBufferMtl::ConversionBufferMtl(const gl::Context *context, 45 size_t initialSize, 46 size_t alignment) 47 : dirty(true), convertedBuffer(nullptr), convertedOffset(0) 48{ 49 ContextMtl *contextMtl = mtl::GetImpl(context); 50 data.initialize(contextMtl, initialSize, alignment); 51} 52 53ConversionBufferMtl::~ConversionBufferMtl() = default; 54 55// IndexConversionBufferMtl implementation. 56IndexConversionBufferMtl::IndexConversionBufferMtl(const gl::Context *context, 57 gl::DrawElementsType typeIn, 58 size_t offsetIn) 59 : ConversionBufferMtl(context, 60 kConvertedElementArrayBufferInitialSize, 61 mtl::kIndexBufferOffsetAlignment), 62 type(typeIn), 63 offset(offsetIn) 64{} 65 66// BufferMtl::VertexConversionBuffer implementation. 67BufferMtl::VertexConversionBuffer::VertexConversionBuffer(const gl::Context *context, 68 angle::FormatID formatIDIn, 69 GLuint strideIn, 70 size_t offsetIn) 71 : ConversionBufferMtl(context, 0, mtl::kVertexAttribBufferStrideAlignment), 72 formatID(formatIDIn), 73 stride(strideIn), 74 offset(offsetIn) 75{ 76 // Due to Metal's strict requirement for offset and stride, we need to always allocate new 77 // buffer for every conversion. 78 data.setAlwaysAllocateNewBuffer(true); 79} 80 81// BufferMtl implementation 82BufferMtl::BufferMtl(const gl::BufferState &state) 83 : BufferImpl(state), mBufferPool(/** alwaysAllocNewBuffer */ true) 84{} 85 86BufferMtl::~BufferMtl() {} 87 88void BufferMtl::destroy(const gl::Context *context) 89{ 90 ContextMtl *contextMtl = mtl::GetImpl(context); 91 mShadowCopy.clear(); 92 mBufferPool.destroy(contextMtl); 93 mBuffer = nullptr; 94 95 clearConversionBuffers(); 96} 97 98angle::Result BufferMtl::setData(const gl::Context *context, 99 gl::BufferBinding target, 100 const void *data, 101 size_t intendedSize, 102 gl::BufferUsage usage) 103{ 104 ContextMtl *contextMtl = mtl::GetImpl(context); 105 106 // Invalidate conversion buffers 107 if (mState.getSize() != static_cast<GLint64>(intendedSize)) 108 { 109 clearConversionBuffers(); 110 } 111 else 112 { 113 markConversionBuffersDirty(); 114 } 115 116 size_t adjustedSize = std::max<size_t>(1, intendedSize); 117 118 if (!mShadowCopy.size() || intendedSize > mShadowCopy.size() || usage != mState.getUsage()) 119 { 120 // Re-create the buffer 121 ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(adjustedSize), GL_OUT_OF_MEMORY); 122 123 size_t maxBuffers; 124 switch (usage) 125 { 126 case gl::BufferUsage::StaticCopy: 127 case gl::BufferUsage::StaticDraw: 128 case gl::BufferUsage::StaticRead: 129 maxBuffers = 1; // static buffer doesn't need high speed data update 130 break; 131 default: 132 // dynamic buffer, allow up to 2 update per frame/encoding without 133 // waiting for GPU. 134 maxBuffers = 2; 135 break; 136 } 137 138 mBufferPool.initialize(contextMtl, adjustedSize, 1, maxBuffers); 139 } 140 141 // Transfer data to shadow copy buffer 142 if (data) 143 { 144 auto ptr = static_cast<const uint8_t *>(data); 145 std::copy(ptr, ptr + intendedSize, mShadowCopy.data()); 146 } 147 148 // Transfer data from shadow copy buffer to GPU buffer. 149 return commitShadowCopy(context, adjustedSize); 150} 151 152angle::Result BufferMtl::setSubData(const gl::Context *context, 153 gl::BufferBinding target, 154 const void *data, 155 size_t size, 156 size_t offset) 157{ 158 return setSubDataImpl(context, data, size, offset); 159} 160 161angle::Result BufferMtl::copySubData(const gl::Context *context, 162 BufferImpl *source, 163 GLintptr sourceOffset, 164 GLintptr destOffset, 165 GLsizeiptr size) 166{ 167 if (!source) 168 { 169 return angle::Result::Continue; 170 } 171 172 ASSERT(mShadowCopy.size()); 173 174 auto srcMtl = GetAs<BufferMtl>(source); 175 176 // NOTE(hqle): use blit command. 177 return setSubDataImpl(context, srcMtl->getClientShadowCopyData(context) + sourceOffset, size, 178 destOffset); 179} 180 181angle::Result BufferMtl::map(const gl::Context *context, GLenum access, void **mapPtr) 182{ 183 ASSERT(mShadowCopy.size()); 184 return mapRange(context, 0, size(), 0, mapPtr); 185} 186 187angle::Result BufferMtl::mapRange(const gl::Context *context, 188 size_t offset, 189 size_t length, 190 GLbitfield access, 191 void **mapPtr) 192{ 193 ASSERT(mShadowCopy.size()); 194 195 // NOTE(hqle): use access flags 196 if (mapPtr) 197 { 198 *mapPtr = mShadowCopy.data() + offset; 199 } 200 201 return angle::Result::Continue; 202} 203 204angle::Result BufferMtl::unmap(const gl::Context *context, GLboolean *result) 205{ 206 ASSERT(mShadowCopy.size()); 207 208 markConversionBuffersDirty(); 209 210 ANGLE_TRY(commitShadowCopy(context)); 211 212 return angle::Result::Continue; 213} 214 215angle::Result BufferMtl::getIndexRange(const gl::Context *context, 216 gl::DrawElementsType type, 217 size_t offset, 218 size_t count, 219 bool primitiveRestartEnabled, 220 gl::IndexRange *outRange) 221{ 222 ASSERT(mShadowCopy.size()); 223 224 const uint8_t *indices = mShadowCopy.data() + offset; 225 226 *outRange = gl::ComputeIndexRange(type, indices, count, primitiveRestartEnabled); 227 228 return angle::Result::Continue; 229} 230 231angle::Result BufferMtl::getFirstLastIndices(const gl::Context *context, 232 gl::DrawElementsType type, 233 size_t offset, 234 size_t count, 235 std::pair<uint32_t, uint32_t> *outIndices) const 236{ 237 ASSERT(mShadowCopy.size()); 238 239 const uint8_t *indices = mShadowCopy.data() + offset; 240 241 switch (type) 242 { 243 case gl::DrawElementsType::UnsignedByte: 244 return GetFirstLastIndices(static_cast<const GLubyte *>(indices), count, outIndices); 245 case gl::DrawElementsType::UnsignedShort: 246 return GetFirstLastIndices(reinterpret_cast<const GLushort *>(indices), count, 247 outIndices); 248 case gl::DrawElementsType::UnsignedInt: 249 return GetFirstLastIndices(reinterpret_cast<const GLuint *>(indices), count, 250 outIndices); 251 default: 252 UNREACHABLE(); 253 return angle::Result::Stop; 254 } 255 256 return angle::Result::Continue; 257} 258 259const uint8_t *BufferMtl::getClientShadowCopyData(const gl::Context *context) 260{ 261 // NOTE(hqle): Support buffer update from GPU. 262 // Which mean we have to stall the GPU by calling finish and copy 263 // data back to shadow copy. 264 return mShadowCopy.data(); 265} 266 267ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(const gl::Context *context, 268 angle::FormatID formatID, 269 GLuint stride, 270 size_t offset) 271{ 272 for (VertexConversionBuffer &buffer : mVertexConversionBuffers) 273 { 274 if (buffer.formatID == formatID && buffer.stride == stride && buffer.offset == offset) 275 { 276 return &buffer; 277 } 278 } 279 280 mVertexConversionBuffers.emplace_back(context, formatID, stride, offset); 281 return &mVertexConversionBuffers.back(); 282} 283 284IndexConversionBufferMtl *BufferMtl::getIndexConversionBuffer(const gl::Context *context, 285 gl::DrawElementsType type, 286 size_t offset) 287{ 288 for (auto &buffer : mIndexConversionBuffers) 289 { 290 if (buffer.type == type && buffer.offset == offset) 291 { 292 return &buffer; 293 } 294 } 295 296 mIndexConversionBuffers.emplace_back(context, type, offset); 297 return &mIndexConversionBuffers.back(); 298} 299 300void BufferMtl::markConversionBuffersDirty() 301{ 302 for (VertexConversionBuffer &buffer : mVertexConversionBuffers) 303 { 304 buffer.dirty = true; 305 } 306 307 for (auto &buffer : mIndexConversionBuffers) 308 { 309 buffer.dirty = true; 310 buffer.convertedBuffer = nullptr; 311 buffer.convertedOffset = 0; 312 } 313} 314 315void BufferMtl::clearConversionBuffers() 316{ 317 mVertexConversionBuffers.clear(); 318 mIndexConversionBuffers.clear(); 319} 320 321angle::Result BufferMtl::setSubDataImpl(const gl::Context *context, 322 const void *data, 323 size_t size, 324 size_t offset) 325{ 326 if (!data) 327 { 328 return angle::Result::Continue; 329 } 330 ContextMtl *contextMtl = mtl::GetImpl(context); 331 332 ASSERT(mShadowCopy.size()); 333 334 ANGLE_MTL_TRY(contextMtl, offset <= this->size()); 335 336 auto srcPtr = static_cast<const uint8_t *>(data); 337 auto sizeToCopy = std::min<size_t>(size, this->size() - offset); 338 std::copy(srcPtr, srcPtr + sizeToCopy, mShadowCopy.data() + offset); 339 340 markConversionBuffersDirty(); 341 342 ANGLE_TRY(commitShadowCopy(context)); 343 344 return angle::Result::Continue; 345} 346 347angle::Result BufferMtl::commitShadowCopy(const gl::Context *context) 348{ 349 return commitShadowCopy(context, size()); 350} 351 352angle::Result BufferMtl::commitShadowCopy(const gl::Context *context, size_t size) 353{ 354 ContextMtl *contextMtl = mtl::GetImpl(context); 355 356 uint8_t *ptr = nullptr; 357 ANGLE_TRY(mBufferPool.allocate(contextMtl, size, &ptr, &mBuffer, nullptr, nullptr)); 358 359 std::copy(mShadowCopy.data(), mShadowCopy.data() + size, ptr); 360 361 ANGLE_TRY(mBufferPool.commit(contextMtl)); 362 363#ifndef NDEBUG 364 ANGLE_MTL_OBJC_SCOPE { mBuffer->get().label = [NSString stringWithFormat:@"%p", this]; } 365#endif 366 367 return angle::Result::Continue; 368} 369 370// SimpleWeakBufferHolderMtl implementation 371SimpleWeakBufferHolderMtl::SimpleWeakBufferHolderMtl() 372{ 373 mIsWeak = true; 374} 375 376} // namespace rx 377