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// mtl_resources.mm: 7// Implements wrapper classes for Metal's MTLTexture and MTLBuffer. 8// 9 10#include "libANGLE/renderer/metal/mtl_resources.h" 11 12#include <TargetConditionals.h> 13 14#include <algorithm> 15 16#include "common/debug.h" 17#include "libANGLE/renderer/metal/ContextMtl.h" 18#include "libANGLE/renderer/metal/DisplayMtl.h" 19#include "libANGLE/renderer/metal/mtl_command_buffer.h" 20#include "libANGLE/renderer/metal/mtl_format_utils.h" 21 22namespace rx 23{ 24namespace mtl 25{ 26namespace 27{ 28inline NSUInteger GetMipSize(NSUInteger baseSize, NSUInteger level) 29{ 30 return std::max<NSUInteger>(1, baseSize >> level); 31} 32 33void SetTextureSwizzle(ContextMtl *context, 34 const Format &format, 35 MTLTextureDescriptor *textureDescOut) 36{ 37// Texture swizzle functions's declarations are only available if macos 10.15 sdk is present 38#if defined(__MAC_10_15) 39 if (context->getDisplay()->getFeatures().hasTextureSwizzle.enabled) 40 { 41 // Work around Metal doesn't have native support for DXT1 without alpha. 42 switch (format.intendedFormatId) 43 { 44 case angle::FormatID::BC1_RGB_UNORM_BLOCK: 45 case angle::FormatID::BC1_RGB_UNORM_SRGB_BLOCK: 46 textureDescOut.swizzle = 47 MTLTextureSwizzleChannelsMake(MTLTextureSwizzleRed, MTLTextureSwizzleGreen, 48 MTLTextureSwizzleBlue, MTLTextureSwizzleOne); 49 break; 50 default: 51 break; 52 } 53 } 54#endif 55} 56} // namespace 57// Resource implementation 58Resource::Resource() : mUsageRef(std::make_shared<UsageRef>()) {} 59 60// Share the GPU usage ref with other resource 61Resource::Resource(Resource *other) : mUsageRef(other->mUsageRef) 62{ 63 ASSERT(mUsageRef); 64} 65 66bool Resource::isBeingUsedByGPU(Context *context) const 67{ 68 return context->cmdQueue().isResourceBeingUsedByGPU(this); 69} 70 71void Resource::setUsedByCommandBufferWithQueueSerial(uint64_t serial, bool writing) 72{ 73 auto curSerial = mUsageRef->cmdBufferQueueSerial.load(std::memory_order_relaxed); 74 do 75 { 76 if (curSerial >= serial) 77 { 78 return; 79 } 80 } while (!mUsageRef->cmdBufferQueueSerial.compare_exchange_weak( 81 curSerial, serial, std::memory_order_release, std::memory_order_relaxed)); 82 83 // NOTE(hqle): This is not thread safe, if multiple command buffers on multiple threads 84 // are writing to it. 85 if (writing) 86 { 87 mUsageRef->cpuReadMemDirty = true; 88 } 89} 90 91// Texture implemenetation 92/** static */ 93angle::Result Texture::Make2DTexture(ContextMtl *context, 94 const Format &format, 95 uint32_t width, 96 uint32_t height, 97 uint32_t mips, 98 bool renderTargetOnly, 99 bool allowTextureView, 100 TextureRef *refOut) 101{ 102 ANGLE_MTL_OBJC_SCOPE 103 { 104 MTLTextureDescriptor *desc = 105 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat 106 width:width 107 height:height 108 mipmapped:mips == 0 || mips > 1]; 109 110 SetTextureSwizzle(context, format, desc); 111 refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView)); 112 } // ANGLE_MTL_OBJC_SCOPE 113 114 if (!refOut || !refOut->get()) 115 { 116 ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); 117 } 118 119 return angle::Result::Continue; 120} 121 122/** static */ 123angle::Result Texture::MakeCubeTexture(ContextMtl *context, 124 const Format &format, 125 uint32_t size, 126 uint32_t mips, 127 bool renderTargetOnly, 128 bool allowTextureView, 129 TextureRef *refOut) 130{ 131 ANGLE_MTL_OBJC_SCOPE 132 { 133 MTLTextureDescriptor *desc = 134 [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:format.metalFormat 135 size:size 136 mipmapped:mips == 0 || mips > 1]; 137 SetTextureSwizzle(context, format, desc); 138 refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView)); 139 } // ANGLE_MTL_OBJC_SCOPE 140 141 if (!refOut || !refOut->get()) 142 { 143 ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); 144 } 145 146 return angle::Result::Continue; 147} 148 149/** static */ 150TextureRef Texture::MakeFromMetal(id<MTLTexture> metalTexture) 151{ 152 ANGLE_MTL_OBJC_SCOPE { return TextureRef(new Texture(metalTexture)); } 153} 154 155Texture::Texture(id<MTLTexture> metalTexture) 156 : mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll)) 157{ 158 set(metalTexture); 159} 160 161Texture::Texture(ContextMtl *context, 162 MTLTextureDescriptor *desc, 163 uint32_t mips, 164 bool renderTargetOnly, 165 bool supportTextureView) 166 : mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll)) 167{ 168 ANGLE_MTL_OBJC_SCOPE 169 { 170 id<MTLDevice> metalDevice = context->getMetalDevice(); 171 172 if (mips > 1 && mips < desc.mipmapLevelCount) 173 { 174 desc.mipmapLevelCount = mips; 175 } 176 177 // Every texture will support being rendered for now 178 desc.usage = 0; 179 180 if (Format::FormatRenderable(desc.pixelFormat)) 181 { 182 desc.usage |= MTLTextureUsageRenderTarget; 183 } 184 185 if (!Format::FormatCPUReadable(desc.pixelFormat)) 186 { 187 desc.resourceOptions = MTLResourceStorageModePrivate; 188 } 189 190 if (!renderTargetOnly) 191 { 192 desc.usage = desc.usage | MTLTextureUsageShaderRead; 193 } 194 195 if (supportTextureView) 196 { 197 desc.usage = desc.usage | MTLTextureUsagePixelFormatView; 198 } 199 200 set([[metalDevice newTextureWithDescriptor:desc] ANGLE_MTL_AUTORELEASE]); 201 } 202} 203 204Texture::Texture(Texture *original, MTLPixelFormat format) 205 : Resource(original), 206 mColorWritableMask(original->mColorWritableMask) // Share color write mask property 207{ 208 ANGLE_MTL_OBJC_SCOPE 209 { 210 auto view = [original->get() newTextureViewWithPixelFormat:format]; 211 212 set([view ANGLE_MTL_AUTORELEASE]); 213 } 214} 215 216Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, uint32_t slice) 217 : Resource(original), 218 mColorWritableMask(original->mColorWritableMask) // Share color write mask property 219{ 220 ANGLE_MTL_OBJC_SCOPE 221 { 222 auto view = [original->get() newTextureViewWithPixelFormat:original->pixelFormat() 223 textureType:type 224 levels:mipmapLevelRange 225 slices:NSMakeRange(slice, 1)]; 226 227 set([view ANGLE_MTL_AUTORELEASE]); 228 } 229} 230 231void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder) 232{ 233#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 234 if (blitEncoder) 235 { 236 blitEncoder->synchronizeResource(shared_from_this()); 237 } 238#endif 239} 240 241void Texture::syncContent(ContextMtl *context) 242{ 243#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 244 // Make sure GPU & CPU contents are synchronized. 245 // NOTE: Only MacOS has separated storage for resource on CPU and GPU and needs explicit 246 // synchronization 247 if (this->isCPUReadMemDirty()) 248 { 249 mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder(); 250 syncContent(context, blitEncoder); 251 252 this->resetCPUReadMemDirty(); 253 } 254#endif 255} 256 257bool Texture::isCPUAccessible() const 258{ 259#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 260 if (get().storageMode == MTLStorageModeManaged) 261 { 262 return true; 263 } 264#endif 265 return get().storageMode == MTLStorageModeShared; 266} 267 268void Texture::replaceRegion(ContextMtl *context, 269 MTLRegion region, 270 uint32_t mipmapLevel, 271 uint32_t slice, 272 const uint8_t *data, 273 size_t bytesPerRow) 274{ 275 if (mipmapLevel >= this->mipmapLevels()) 276 { 277 return; 278 } 279 280 ASSERT(isCPUAccessible()); 281 282 CommandQueue &cmdQueue = context->cmdQueue(); 283 284 syncContent(context); 285 286 // NOTE(hqle): what if multiple contexts on multiple threads are using this texture? 287 if (this->isBeingUsedByGPU(context)) 288 { 289 context->flushCommandBufer(); 290 } 291 292 cmdQueue.ensureResourceReadyForCPU(this); 293 294 [get() replaceRegion:region 295 mipmapLevel:mipmapLevel 296 slice:slice 297 withBytes:data 298 bytesPerRow:bytesPerRow 299 bytesPerImage:0]; 300} 301 302void Texture::getBytes(ContextMtl *context, 303 size_t bytesPerRow, 304 MTLRegion region, 305 uint32_t mipmapLevel, 306 uint8_t *dataOut) 307{ 308 ASSERT(isCPUAccessible()); 309 310 CommandQueue &cmdQueue = context->cmdQueue(); 311 312 syncContent(context); 313 314 // NOTE(hqle): what if multiple contexts on multiple threads are using this texture? 315 if (this->isBeingUsedByGPU(context)) 316 { 317 context->flushCommandBufer(); 318 } 319 320 cmdQueue.ensureResourceReadyForCPU(this); 321 322 [get() getBytes:dataOut bytesPerRow:bytesPerRow fromRegion:region mipmapLevel:mipmapLevel]; 323} 324 325TextureRef Texture::createCubeFaceView(uint32_t face) 326{ 327 ANGLE_MTL_OBJC_SCOPE 328 { 329 switch (textureType()) 330 { 331 case MTLTextureTypeCube: 332 return TextureRef( 333 new Texture(this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), face)); 334 default: 335 UNREACHABLE(); 336 return nullptr; 337 } 338 } 339} 340 341TextureRef Texture::createSliceMipView(uint32_t slice, uint32_t level) 342{ 343 ANGLE_MTL_OBJC_SCOPE 344 { 345 switch (textureType()) 346 { 347 case MTLTextureTypeCube: 348 case MTLTextureType2D: 349 return TextureRef( 350 new Texture(this, MTLTextureType2D, NSMakeRange(level, 1), slice)); 351 default: 352 UNREACHABLE(); 353 return nullptr; 354 } 355 } 356} 357 358TextureRef Texture::createViewWithDifferentFormat(MTLPixelFormat format) 359{ 360 return TextureRef(new Texture(this, format)); 361} 362 363MTLPixelFormat Texture::pixelFormat() const 364{ 365 return get().pixelFormat; 366} 367 368MTLTextureType Texture::textureType() const 369{ 370 return get().textureType; 371} 372 373uint32_t Texture::mipmapLevels() const 374{ 375 return static_cast<uint32_t>(get().mipmapLevelCount); 376} 377 378uint32_t Texture::width(uint32_t level) const 379{ 380 return static_cast<uint32_t>(GetMipSize(get().width, level)); 381} 382 383uint32_t Texture::height(uint32_t level) const 384{ 385 return static_cast<uint32_t>(GetMipSize(get().height, level)); 386} 387 388gl::Extents Texture::size(uint32_t level) const 389{ 390 gl::Extents re; 391 392 re.width = width(level); 393 re.height = height(level); 394 re.depth = static_cast<uint32_t>(GetMipSize(get().depth, level)); 395 396 return re; 397} 398 399gl::Extents Texture::size(const gl::ImageIndex &index) const 400{ 401 // Only support these texture types for now 402 ASSERT(!get() || textureType() == MTLTextureType2D || textureType() == MTLTextureTypeCube); 403 404 return size(index.getLevelIndex()); 405} 406 407void Texture::set(id<MTLTexture> metalTexture) 408{ 409 ParentClass::set(metalTexture); 410} 411 412// Buffer implementation 413angle::Result Buffer::MakeBuffer(ContextMtl *context, 414 size_t size, 415 const uint8_t *data, 416 BufferRef *bufferOut) 417{ 418 bufferOut->reset(new Buffer(context, size, data)); 419 420 if (!bufferOut || !bufferOut->get()) 421 { 422 ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); 423 } 424 425 return angle::Result::Continue; 426} 427 428Buffer::Buffer(ContextMtl *context, size_t size, const uint8_t *data) 429{ 430 (void)reset(context, size, data); 431} 432 433angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *data) 434{ 435 ANGLE_MTL_OBJC_SCOPE 436 { 437 MTLResourceOptions options; 438 439 id<MTLBuffer> newBuffer; 440 id<MTLDevice> metalDevice = context->getMetalDevice(); 441 442 options = 0; 443#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 444 options |= MTLResourceStorageModeManaged; 445#endif 446 447 if (data) 448 { 449 newBuffer = [metalDevice newBufferWithBytes:data length:size options:options]; 450 } 451 else 452 { 453 newBuffer = [metalDevice newBufferWithLength:size options:options]; 454 } 455 456 set([newBuffer ANGLE_MTL_AUTORELEASE]); 457 458 return angle::Result::Continue; 459 } 460} 461 462uint8_t *Buffer::map(ContextMtl *context) 463{ 464 CommandQueue &cmdQueue = context->cmdQueue(); 465 466 // NOTE(hqle): what if multiple contexts on multiple threads are using this buffer? 467 if (this->isBeingUsedByGPU(context)) 468 { 469 context->flushCommandBufer(); 470 } 471 472 // NOTE(hqle): currently not support reading data written by GPU 473 cmdQueue.ensureResourceReadyForCPU(this); 474 475 return reinterpret_cast<uint8_t *>([get() contents]); 476} 477 478void Buffer::unmap(ContextMtl *context) 479{ 480#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 481 [get() didModifyRange:NSMakeRange(0, size())]; 482#endif 483} 484 485size_t Buffer::size() const 486{ 487 return get().length; 488} 489} 490} 491