/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/mtl/GrMtlGpu.h" #include "include/private/GrTypesPriv.h" #include "src/core/SkCompressedDataUtils.h" #include "src/core/SkConvertPixels.h" #include "src/core/SkMathPriv.h" #include "src/core/SkMipmap.h" #include "src/gpu/GrBackendUtils.h" #include "src/gpu/GrDataUtils.h" #include "src/gpu/GrDirectContextPriv.h" #include "src/gpu/GrRenderTarget.h" #include "src/gpu/GrResourceProvider.h" #include "src/gpu/GrTexture.h" #include "src/gpu/GrThreadSafePipelineBuilder.h" #include "src/gpu/mtl/GrMtlBuffer.h" #include "src/gpu/mtl/GrMtlCommandBuffer.h" #include "src/gpu/mtl/GrMtlOpsRenderPass.h" #include "src/gpu/mtl/GrMtlPipelineStateBuilder.h" #include "src/gpu/mtl/GrMtlRenderCommandEncoder.h" #include "src/gpu/mtl/GrMtlSemaphore.h" #include "src/gpu/mtl/GrMtlTexture.h" #include "src/gpu/mtl/GrMtlTextureRenderTarget.h" #include "src/gpu/mtl/GrMtlUtil.h" #import #if !__has_feature(objc_arc) #error This file must be compiled with Arc. Use -fobjc-arc flag #endif GR_NORETAIN_BEGIN #if GR_TEST_UTILS // set to 1 if you want to do GPU capture of each commandBuffer #define GR_METAL_CAPTURE_COMMANDBUFFER 0 #endif sk_sp GrMtlGpu::Make(const GrMtlBackendContext& context, const GrContextOptions& options, GrDirectContext* direct) { if (!context.fDevice || !context.fQueue) { return nullptr; } if (@available(macOS 10.14, iOS 10.0, *)) { // no warning needed } else { SkDebugf("*** Error ***: Skia's Metal backend no longer supports this OS version.\n"); #ifdef SK_BUILD_FOR_IOS SkDebugf("Minimum supported version is iOS 10.0.\n"); #else SkDebugf("Minimum supported version is MacOS 10.14.\n"); #endif return nullptr; } id GR_NORETAIN device = (__bridge id)(context.fDevice.get()); id GR_NORETAIN queue = (__bridge id)(context.fQueue.get()); return sk_sp(new GrMtlGpu(direct, options, device, queue, context.fBinaryArchive.get())); } // This constant determines how many OutstandingCommandBuffers are allocated together as a block in // the deque. As such it needs to balance allocating too much memory vs. incurring // allocation/deallocation thrashing. It should roughly correspond to the max number of outstanding // command buffers we expect to see. static const int kDefaultOutstandingAllocCnt = 8; GrMtlGpu::GrMtlGpu(GrDirectContext* direct, const GrContextOptions& options, id device, id queue, GrMTLHandle binaryArchive) : INHERITED(direct) , fDevice(device) , fQueue(queue) , fOutstandingCommandBuffers(sizeof(OutstandingCommandBuffer), kDefaultOutstandingAllocCnt) , fResourceProvider(this) , fStagingBufferManager(this) , fUniformsRingBuffer(this, 128 * 1024, 256, GrGpuBufferType::kUniform) , fDisconnected(false) { fMtlCaps.reset(new GrMtlCaps(options, fDevice)); this->initCapsAndCompiler(fMtlCaps); #if GR_METAL_CAPTURE_COMMANDBUFFER this->testingOnly_startCapture(); #endif fCurrentCmdBuffer = GrMtlCommandBuffer::Make(fQueue); #if GR_METAL_SDK_VERSION >= 230 if (@available(macOS 11.0, iOS 14.0, *)) { fBinaryArchive = (__bridge id)(binaryArchive); } #endif } GrMtlGpu::~GrMtlGpu() { if (!fDisconnected) { this->destroyResources(); } } void GrMtlGpu::disconnect(DisconnectType type) { INHERITED::disconnect(type); if (!fDisconnected) { this->destroyResources(); fDisconnected = true; } } GrThreadSafePipelineBuilder* GrMtlGpu::pipelineBuilder() { return nullptr; } sk_sp GrMtlGpu::refPipelineBuilder() { return nullptr; } void GrMtlGpu::destroyResources() { this->submitCommandBuffer(SyncQueue::kForce_SyncQueue); // if there's no work we won't release the command buffer, so we do it here fCurrentCmdBuffer = nil; // We used a placement new for each object in fOutstandingCommandBuffers, so we're responsible // for calling the destructor on each of them as well. while (!fOutstandingCommandBuffers.empty()) { OutstandingCommandBuffer* buffer = (OutstandingCommandBuffer*)fOutstandingCommandBuffers.front(); // make sure we remove before deleting as deletion might try to kick off another submit fOutstandingCommandBuffers.pop_front(); buffer->~OutstandingCommandBuffer(); } fStagingBufferManager.reset(); fResourceProvider.destroyResources(); fQueue = nil; fDevice = nil; } GrOpsRenderPass* GrMtlGpu::onGetOpsRenderPass( GrRenderTarget* renderTarget, bool useMSAASurface, GrAttachment* stencil, GrSurfaceOrigin origin, const SkIRect& bounds, const GrOpsRenderPass::LoadAndStoreInfo& colorInfo, const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo, const SkTArray& sampledProxies, GrXferBarrierFlags renderPassXferBarriers) { // For the given render target and requested render pass features we need to find a compatible // framebuffer to use. GrMtlRenderTarget* mtlRT = static_cast(renderTarget); // TODO: support DMSAA SkASSERT(!useMSAASurface || (renderTarget->numSamples() > 1)); bool withResolve = false; // Figure out if we can use a Resolve store action for this render pass. When we set up // the render pass we'll update the color load/store ops since we don't want to ever load // or store the msaa color attachment, but may need to for the resolve attachment. if (useMSAASurface && this->mtlCaps().renderTargetSupportsDiscardableMSAA(mtlRT)) { withResolve = true; } sk_sp framebuffer = sk_ref_sp(mtlRT->getFramebuffer(withResolve, SkToBool(stencil))); if (!framebuffer) { return nullptr; } return new GrMtlOpsRenderPass(this, renderTarget, std::move(framebuffer), origin, colorInfo, stencilInfo); } GrMtlCommandBuffer* GrMtlGpu::commandBuffer() { if (!fCurrentCmdBuffer) { #if GR_METAL_CAPTURE_COMMANDBUFFER this->testingOnly_startCapture(); #endif // Create a new command buffer for the next submit fCurrentCmdBuffer = GrMtlCommandBuffer::Make(fQueue); } SkASSERT(fCurrentCmdBuffer); return fCurrentCmdBuffer.get(); } void GrMtlGpu::takeOwnershipOfBuffer(sk_sp buffer) { SkASSERT(buffer); this->commandBuffer()->addGrBuffer(std::move(buffer)); } void GrMtlGpu::submit(GrOpsRenderPass* renderPass) { GrMtlOpsRenderPass* mtlRenderPass = reinterpret_cast(renderPass); mtlRenderPass->submit(); delete renderPass; } bool GrMtlGpu::submitCommandBuffer(SyncQueue sync) { if (!fCurrentCmdBuffer || !fCurrentCmdBuffer->hasWork()) { if (sync == SyncQueue::kForce_SyncQueue) { this->finishOutstandingGpuWork(); this->checkForFinishedCommandBuffers(); } // We need to manually call the finishedCallbacks since we don't add this // to the OutstandingCommandBuffer list if (fCurrentCmdBuffer) { fCurrentCmdBuffer->callFinishedCallbacks(); } return true; } SkASSERT(fCurrentCmdBuffer); bool didCommit = fCurrentCmdBuffer->commit(sync == SyncQueue::kForce_SyncQueue); if (didCommit) { new (fOutstandingCommandBuffers.push_back()) OutstandingCommandBuffer(fCurrentCmdBuffer); } // We don't create a new command buffer here because we may end up using it // in the next frame, and that confuses the GPU debugger. Instead we // create when we next need one. fCurrentCmdBuffer.reset(); // If the freeing of any resources held by a finished command buffer causes us to send // a new command to the gpu we'll create the new command buffer in commandBuffer(), above. this->checkForFinishedCommandBuffers(); #if GR_METAL_CAPTURE_COMMANDBUFFER this->testingOnly_endCapture(); #endif return didCommit; } void GrMtlGpu::checkForFinishedCommandBuffers() { // Iterate over all the outstanding command buffers to see if any have finished. The command // buffers are in order from oldest to newest, so we start at the front to check if their fence // has signaled. If so we pop it off and move onto the next. // Repeat till we find a command list that has not finished yet (and all others afterwards are // also guaranteed to not have finished). OutstandingCommandBuffer* front = (OutstandingCommandBuffer*)fOutstandingCommandBuffers.front(); while (front && (*front)->isCompleted()) { // Make sure we remove before deleting as deletion might try to kick off another submit fOutstandingCommandBuffers.pop_front(); // Since we used placement new we are responsible for calling the destructor manually. front->~OutstandingCommandBuffer(); front = (OutstandingCommandBuffer*)fOutstandingCommandBuffers.front(); } } void GrMtlGpu::finishOutstandingGpuWork() { // wait for the last command buffer we've submitted to finish OutstandingCommandBuffer* back = (OutstandingCommandBuffer*)fOutstandingCommandBuffers.back(); if (back) { (*back)->waitUntilCompleted(); } } void GrMtlGpu::addFinishedProc(GrGpuFinishedProc finishedProc, GrGpuFinishedContext finishedContext) { SkASSERT(finishedProc); this->addFinishedCallback(GrRefCntedCallback::Make(finishedProc, finishedContext)); } void GrMtlGpu::addFinishedCallback(sk_sp finishedCallback) { SkASSERT(finishedCallback); // Besides the current commandbuffer, we also add the finishedCallback to the newest outstanding // commandbuffer. Our contract for calling the proc is that all previous submitted cmdbuffers // have finished when we call it. However, if our current command buffer has no work when it is // flushed it will drop its ref to the callback immediately. But the previous work may not have // finished. It is safe to only add the proc to the newest outstanding commandbuffer cause that // must finish after all previously submitted command buffers. OutstandingCommandBuffer* back = (OutstandingCommandBuffer*)fOutstandingCommandBuffers.back(); if (back) { (*back)->addFinishedCallback(finishedCallback); } commandBuffer()->addFinishedCallback(std::move(finishedCallback)); } bool GrMtlGpu::onSubmitToGpu(bool syncCpu) { if (syncCpu) { return this->submitCommandBuffer(kForce_SyncQueue); } else { return this->submitCommandBuffer(kSkip_SyncQueue); } } std::unique_ptr GrMtlGpu::prepareTextureForCrossContextUsage(GrTexture*) { this->submitToGpu(false); return nullptr; } sk_sp GrMtlGpu::onCreateBuffer(size_t size, GrGpuBufferType type, GrAccessPattern accessPattern, const void* data) { return GrMtlBuffer::Make(this, size, type, accessPattern, data); } static bool check_max_blit_width(int widthInPixels) { if (widthInPixels > 32767) { SkASSERT(false); // surfaces should not be this wide anyway return false; } return true; } bool GrMtlGpu::uploadToTexture(GrMtlTexture* tex, SkIRect rect, GrColorType dataColorType, const GrMipLevel texels[], int mipLevelCount) { SkASSERT(this->mtlCaps().isFormatTexturable(tex->mtlTexture().pixelFormat)); // The assumption is either that we have no mipmaps, or that our rect is the entire texture SkASSERT(mipLevelCount == 1 || rect == SkIRect::MakeSize(tex->dimensions())); // We assume that if the texture has mip levels, we either upload to all the levels or just the // first. SkASSERT(mipLevelCount == 1 || mipLevelCount == (tex->maxMipmapLevel() + 1)); if (!check_max_blit_width(rect.width())) { return false; } if (rect.isEmpty()) { return false; } SkASSERT(this->mtlCaps().surfaceSupportsWritePixels(tex)); SkASSERT(this->mtlCaps().areColorTypeAndFormatCompatible(dataColorType, tex->backendFormat())); id GR_NORETAIN mtlTexture = tex->mtlTexture(); SkASSERT(mtlTexture); // Either upload only the first miplevel or all miplevels SkASSERT(1 == mipLevelCount || mipLevelCount == (int)mtlTexture.mipmapLevelCount); if (mipLevelCount == 1 && !texels[0].fPixels) { return true; // no data to upload } for (int i = 0; i < mipLevelCount; ++i) { // We do not allow any gaps in the mip data if (!texels[i].fPixels) { return false; } } size_t bpp = GrColorTypeBytesPerPixel(dataColorType); SkTArray individualMipOffsets(mipLevelCount); size_t combinedBufferSize = GrComputeTightCombinedBufferSize(bpp, rect.size(), &individualMipOffsets, mipLevelCount); SkASSERT(combinedBufferSize); // offset value must be a multiple of the destination texture's pixel size in bytes size_t alignment = std::max(bpp, this->mtlCaps().getMinBufferAlignment()); GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice( combinedBufferSize, alignment); if (!slice.fBuffer) { return false; } char* bufferData = (char*)slice.fOffsetMapPtr; GrMtlBuffer* mtlBuffer = static_cast(slice.fBuffer); int currentWidth = rect.width(); int currentHeight = rect.height(); SkDEBUGCODE(int layerHeight = tex->height()); MTLOrigin origin = MTLOriginMake(rect.left(), rect.top(), 0); auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"uploadToTexture"]; #endif for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { if (texels[currentMipLevel].fPixels) { SkASSERT(1 == mipLevelCount || currentHeight == layerHeight); const size_t trimRowBytes = currentWidth * bpp; const size_t rowBytes = texels[currentMipLevel].fRowBytes; // copy data into the buffer, skipping any trailing bytes char* dst = bufferData + individualMipOffsets[currentMipLevel]; const char* src = (const char*)texels[currentMipLevel].fPixels; SkRectMemcpy(dst, trimRowBytes, src, rowBytes, trimRowBytes, currentHeight); [blitCmdEncoder copyFromBuffer: mtlBuffer->mtlBuffer() sourceOffset: slice.fOffset + individualMipOffsets[currentMipLevel] sourceBytesPerRow: trimRowBytes sourceBytesPerImage: trimRowBytes*currentHeight sourceSize: MTLSizeMake(currentWidth, currentHeight, 1) toTexture: mtlTexture destinationSlice: 0 destinationLevel: currentMipLevel destinationOrigin: origin]; } currentWidth = std::max(1, currentWidth/2); currentHeight = std::max(1, currentHeight/2); SkDEBUGCODE(layerHeight = currentHeight); } #ifdef SK_BUILD_FOR_MAC if (this->mtlCaps().isMac()) { [mtlBuffer->mtlBuffer() didModifyRange: NSMakeRange(slice.fOffset, combinedBufferSize)]; } #endif #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder popDebugGroup]; #endif if (mipLevelCount < (int) tex->mtlTexture().mipmapLevelCount) { tex->markMipmapsDirty(); } return true; } bool GrMtlGpu::clearTexture(GrMtlTexture* tex, size_t bpp, uint32_t levelMask) { SkASSERT(this->mtlCaps().isFormatTexturable(tex->mtlTexture().pixelFormat)); if (!levelMask) { return true; } id GR_NORETAIN mtlTexture = tex->mtlTexture(); SkASSERT(mtlTexture); // Either upload only the first miplevel or all miplevels int mipLevelCount = (int)mtlTexture.mipmapLevelCount; SkTArray individualMipOffsets(mipLevelCount); size_t combinedBufferSize = 0; int currentWidth = tex->width(); int currentHeight = tex->height(); // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image // config. This works with the assumption that the bytes in pixel config is always a power of 2. // TODO: can we just copy from a single buffer the size of the largest cleared level w/o a perf // penalty? SkASSERT((bpp & (bpp - 1)) == 0); const size_t alignmentMask = 0x3 | (bpp - 1); for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { if (levelMask & (1 << currentMipLevel)) { const size_t trimmedSize = currentWidth * bpp * currentHeight; const size_t alignmentDiff = combinedBufferSize & alignmentMask; if (alignmentDiff != 0) { combinedBufferSize += alignmentMask - alignmentDiff + 1; } individualMipOffsets.push_back(combinedBufferSize); combinedBufferSize += trimmedSize; } currentWidth = std::max(1, currentWidth/2); currentHeight = std::max(1, currentHeight/2); } SkASSERT(combinedBufferSize > 0 && !individualMipOffsets.empty()); size_t alignment = std::max(bpp, this->mtlCaps().getMinBufferAlignment()); GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice( combinedBufferSize, alignment); if (!slice.fBuffer) { return false; } GrMtlBuffer* mtlBuffer = static_cast(slice.fBuffer); id transferBuffer = mtlBuffer->mtlBuffer(); auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"clearTexture"]; #endif // clear the buffer to transparent black NSRange clearRange; clearRange.location = 0; clearRange.length = combinedBufferSize; [blitCmdEncoder fillBuffer: transferBuffer range: clearRange value: 0]; // now copy buffer to texture currentWidth = tex->width(); currentHeight = tex->height(); MTLOrigin origin = MTLOriginMake(0, 0, 0); for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) { if (levelMask & (1 << currentMipLevel)) { const size_t rowBytes = currentWidth * bpp; [blitCmdEncoder copyFromBuffer: transferBuffer sourceOffset: individualMipOffsets[currentMipLevel] sourceBytesPerRow: rowBytes sourceBytesPerImage: rowBytes * currentHeight sourceSize: MTLSizeMake(currentWidth, currentHeight, 1) toTexture: mtlTexture destinationSlice: 0 destinationLevel: currentMipLevel destinationOrigin: origin]; } currentWidth = std::max(1, currentWidth/2); currentHeight = std::max(1, currentHeight/2); } // Don't need didModifyRange: here because fillBuffer: happens on the GPU #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder popDebugGroup]; #endif if (mipLevelCount < (int) tex->mtlTexture().mipmapLevelCount) { tex->markMipmapsDirty(); } return true; } sk_sp GrMtlGpu::makeStencilAttachment(const GrBackendFormat& /*colorFormat*/, SkISize dimensions, int numStencilSamples) { MTLPixelFormat sFmt = this->mtlCaps().preferredStencilFormat(); fStats.incStencilAttachmentCreates(); return GrMtlAttachment::GrMtlAttachment::MakeStencil(this, dimensions, numStencilSamples, sFmt); } sk_sp GrMtlGpu::makeMSAAAttachment(SkISize dimensions, const GrBackendFormat& format, int numSamples, GrProtected isProtected, GrMemoryless isMemoryless) { // Metal doesn't support protected textures SkASSERT(isProtected == GrProtected::kNo); // TODO: add memoryless support SkASSERT(isMemoryless == GrMemoryless::kNo); MTLPixelFormat pixelFormat = (MTLPixelFormat) format.asMtlFormat(); SkASSERT(pixelFormat != MTLPixelFormatInvalid); SkASSERT(!GrMtlFormatIsCompressed(pixelFormat)); SkASSERT(this->mtlCaps().isFormatRenderable(pixelFormat, numSamples)); fStats.incMSAAAttachmentCreates(); return GrMtlAttachment::MakeMSAA(this, dimensions, numSamples, pixelFormat); } sk_sp GrMtlGpu::onCreateTexture(SkISize dimensions, const GrBackendFormat& format, GrRenderable renderable, int renderTargetSampleCnt, SkBudgeted budgeted, GrProtected isProtected, int mipLevelCount, uint32_t levelClearMask) { // We don't support protected textures in Metal. if (isProtected == GrProtected::kYes) { return nullptr; } SkASSERT(mipLevelCount > 0); MTLPixelFormat mtlPixelFormat = GrBackendFormatAsMTLPixelFormat(format); SkASSERT(mtlPixelFormat != MTLPixelFormatInvalid); SkASSERT(!this->caps()->isFormatCompressed(format)); sk_sp tex; GrMipmapStatus mipmapStatus = mipLevelCount > 1 ? GrMipmapStatus::kDirty : GrMipmapStatus::kNotAllocated; if (renderable == GrRenderable::kYes) { tex = GrMtlTextureRenderTarget::MakeNewTextureRenderTarget( this, budgeted, dimensions, renderTargetSampleCnt, mtlPixelFormat, mipLevelCount, mipmapStatus); } else { tex = GrMtlTexture::MakeNewTexture(this, budgeted, dimensions, mtlPixelFormat, mipLevelCount, mipmapStatus); } if (!tex) { return nullptr; } if (levelClearMask) { this->clearTexture(tex.get(), GrMtlFormatBytesPerBlock(mtlPixelFormat), levelClearMask); } return std::move(tex); } sk_sp GrMtlGpu::onCreateCompressedTexture(SkISize dimensions, const GrBackendFormat& format, SkBudgeted budgeted, GrMipmapped mipMapped, GrProtected isProtected, const void* data, size_t dataSize) { // We don't support protected textures in Metal. if (isProtected == GrProtected::kYes) { return nullptr; } SkASSERT(this->caps()->isFormatTexturable(format, GrTextureType::k2D)); SkASSERT(data); if (!check_max_blit_width(dimensions.width())) { return nullptr; } MTLPixelFormat mtlPixelFormat = GrBackendFormatAsMTLPixelFormat(format); SkASSERT(this->caps()->isFormatCompressed(format)); int numMipLevels = 1; if (mipMapped == GrMipmapped::kYes) { numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1; } GrMipmapStatus mipmapStatus = (mipMapped == GrMipmapped::kYes) ? GrMipmapStatus::kValid : GrMipmapStatus::kNotAllocated; auto tex = GrMtlTexture::MakeNewTexture(this, budgeted, dimensions, mtlPixelFormat, numMipLevels, mipmapStatus); if (!tex) { return nullptr; } // Upload to texture id GR_NORETAIN mtlTexture = tex->mtlTexture(); SkASSERT(mtlTexture); auto compressionType = GrBackendFormatToCompressionType(format); SkASSERT(compressionType != SkImage::CompressionType::kNone); SkTArray individualMipOffsets(numMipLevels); SkDEBUGCODE(size_t combinedBufferSize =) SkCompressedDataSize(compressionType, dimensions, &individualMipOffsets, mipMapped == GrMipmapped::kYes); SkASSERT(individualMipOffsets.count() == numMipLevels); SkASSERT(dataSize == combinedBufferSize); // offset value must be a multiple of the destination texture's pixel size in bytes // for compressed textures, this is the block size size_t alignment = SkCompressedBlockSize(compressionType); GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice( dataSize, alignment); if (!slice.fBuffer) { return nullptr; } char* bufferData = (char*)slice.fOffsetMapPtr; GrMtlBuffer* mtlBuffer = static_cast(slice.fBuffer); MTLOrigin origin = MTLOriginMake(0, 0, 0); auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return nullptr; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"onCreateCompressedTexture"]; #endif // copy data into the buffer, skipping any trailing bytes memcpy(bufferData, data, dataSize); SkISize levelDimensions = dimensions; for (int currentMipLevel = 0; currentMipLevel < numMipLevels; currentMipLevel++) { const size_t levelRowBytes = GrCompressedRowBytes(compressionType, levelDimensions.width()); size_t levelSize = SkCompressedDataSize(compressionType, levelDimensions, nullptr, false); // TODO: can this all be done in one go? [blitCmdEncoder copyFromBuffer: mtlBuffer->mtlBuffer() sourceOffset: slice.fOffset + individualMipOffsets[currentMipLevel] sourceBytesPerRow: levelRowBytes sourceBytesPerImage: levelSize sourceSize: MTLSizeMake(levelDimensions.width(), levelDimensions.height(), 1) toTexture: mtlTexture destinationSlice: 0 destinationLevel: currentMipLevel destinationOrigin: origin]; levelDimensions = {std::max(1, levelDimensions.width() /2), std::max(1, levelDimensions.height()/2)}; } #ifdef SK_BUILD_FOR_MAC if (this->mtlCaps().isMac()) { [mtlBuffer->mtlBuffer() didModifyRange: NSMakeRange(slice.fOffset, dataSize)]; } #endif #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder popDebugGroup]; #endif return std::move(tex); } // TODO: Extra retain/release can't be avoided here because of getMtlTextureInfo copying the // sk_cfp. It would be useful to have a (possibly-internal-only?) API to get the raw pointer. static id get_texture_from_backend(const GrBackendTexture& backendTex) { GrMtlTextureInfo textureInfo; if (!backendTex.getMtlTextureInfo(&textureInfo)) { return nil; } return GrGetMTLTexture(textureInfo.fTexture.get()); } static id get_texture_from_backend(const GrBackendRenderTarget& backendRT) { GrMtlTextureInfo textureInfo; if (!backendRT.getMtlTextureInfo(&textureInfo)) { return nil; } return GrGetMTLTexture(textureInfo.fTexture.get()); } sk_sp GrMtlGpu::onWrapBackendTexture(const GrBackendTexture& backendTex, GrWrapOwnership, GrWrapCacheable cacheable, GrIOType ioType) { id mtlTexture = get_texture_from_backend(backendTex); if (!mtlTexture) { return nullptr; } // We don't currently support sampling from a MSAA texture in shaders. if (mtlTexture.sampleCount != 1) { return nullptr; } return GrMtlTexture::MakeWrappedTexture(this, backendTex.dimensions(), mtlTexture, cacheable, ioType); } sk_sp GrMtlGpu::onWrapCompressedBackendTexture(const GrBackendTexture& backendTex, GrWrapOwnership, GrWrapCacheable cacheable) { id mtlTexture = get_texture_from_backend(backendTex); if (!mtlTexture) { return nullptr; } // We don't currently support sampling from a MSAA texture in shaders. if (mtlTexture.sampleCount != 1) { return nullptr; } return GrMtlTexture::MakeWrappedTexture(this, backendTex.dimensions(), mtlTexture, cacheable, kRead_GrIOType); } sk_sp GrMtlGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex, int sampleCnt, GrWrapOwnership, GrWrapCacheable cacheable) { id mtlTexture = get_texture_from_backend(backendTex); if (!mtlTexture) { return nullptr; } // We don't currently support sampling from a MSAA texture in shaders. if (mtlTexture.sampleCount != 1) { return nullptr; } const GrMtlCaps& caps = this->mtlCaps(); MTLPixelFormat format = mtlTexture.pixelFormat; if (!caps.isFormatRenderable(format, sampleCnt)) { return nullptr; } if (@available(macOS 10.11, iOS 9.0, *)) { SkASSERT(MTLTextureUsageRenderTarget & mtlTexture.usage); } sampleCnt = caps.getRenderTargetSampleCount(sampleCnt, format); SkASSERT(sampleCnt); return GrMtlTextureRenderTarget::MakeWrappedTextureRenderTarget( this, backendTex.dimensions(), sampleCnt, mtlTexture, cacheable); } sk_sp GrMtlGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& backendRT) { if (!this->caps()->isFormatRenderable(backendRT.getBackendFormat(), backendRT.sampleCnt())) { return nullptr; } id mtlTexture = get_texture_from_backend(backendRT); if (!mtlTexture) { return nullptr; } if (@available(macOS 10.11, iOS 9.0, *)) { SkASSERT(MTLTextureUsageRenderTarget & mtlTexture.usage); } return GrMtlRenderTarget::MakeWrappedRenderTarget(this, backendRT.dimensions(), backendRT.sampleCnt(), mtlTexture); } bool GrMtlGpu::onRegenerateMipMapLevels(GrTexture* texture) { GrMtlTexture* grMtlTexture = static_cast(texture); id GR_NORETAIN mtlTexture = grMtlTexture->mtlTexture(); // Automatic mipmap generation is only supported by color-renderable formats if (!fMtlCaps->isFormatRenderable(mtlTexture.pixelFormat, 1) && // We have pixel configs marked as textureable-only that use RGBA8 as the internal format MTLPixelFormatRGBA8Unorm != mtlTexture.pixelFormat) { return false; } auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } [blitCmdEncoder generateMipmapsForTexture: mtlTexture]; this->commandBuffer()->addGrSurface(sk_ref_sp(grMtlTexture->attachment())); return true; } // Used to "clear" a backend texture to a constant color by transferring. static GrColorType mtl_format_to_backend_tex_clear_colortype(MTLPixelFormat format) { switch(format) { case MTLPixelFormatA8Unorm: return GrColorType::kAlpha_8; case MTLPixelFormatR8Unorm: return GrColorType::kR_8; #ifdef SK_BUILD_FOR_IOS case MTLPixelFormatB5G6R5Unorm: return GrColorType::kBGR_565; case MTLPixelFormatABGR4Unorm: return GrColorType::kABGR_4444; #endif case MTLPixelFormatRGBA8Unorm: return GrColorType::kRGBA_8888; case MTLPixelFormatRGBA8Unorm_sRGB: return GrColorType::kRGBA_8888_SRGB; case MTLPixelFormatRG8Unorm: return GrColorType::kRG_88; case MTLPixelFormatBGRA8Unorm: return GrColorType::kBGRA_8888; case MTLPixelFormatRGB10A2Unorm: return GrColorType::kRGBA_1010102; #ifdef SK_BUILD_FOR_MAC case MTLPixelFormatBGR10A2Unorm: return GrColorType::kBGRA_1010102; #endif case MTLPixelFormatR16Float: return GrColorType::kR_F16; case MTLPixelFormatRGBA16Float: return GrColorType::kRGBA_F16; case MTLPixelFormatR16Unorm: return GrColorType::kR_16; case MTLPixelFormatRG16Unorm: return GrColorType::kRG_1616; case MTLPixelFormatRGBA16Unorm: return GrColorType::kRGBA_16161616; case MTLPixelFormatRG16Float: return GrColorType::kRG_F16; default: return GrColorType::kUnknown; } SkUNREACHABLE; } void copy_src_data(char* dst, size_t bytesPerPixel, const SkTArray& individualMipOffsets, const GrPixmap srcData[], int numMipLevels, size_t bufferSize) { SkASSERT(srcData && numMipLevels); SkASSERT(individualMipOffsets.count() == numMipLevels); for (int level = 0; level < numMipLevels; ++level) { const size_t trimRB = srcData[level].width() * bytesPerPixel; SkASSERT(individualMipOffsets[level] + trimRB * srcData[level].height() <= bufferSize); SkRectMemcpy(dst + individualMipOffsets[level], trimRB, srcData[level].addr(), srcData[level].rowBytes(), trimRB, srcData[level].height()); } } bool GrMtlGpu::createMtlTextureForBackendSurface(MTLPixelFormat mtlFormat, SkISize dimensions, int sampleCnt, GrTexturable texturable, GrRenderable renderable, GrMipmapped mipMapped, GrMtlTextureInfo* info) { SkASSERT(texturable == GrTexturable::kYes || renderable == GrRenderable::kYes); if (texturable == GrTexturable::kYes && !fMtlCaps->isFormatTexturable(mtlFormat)) { return false; } if (renderable == GrRenderable::kYes && !fMtlCaps->isFormatRenderable(mtlFormat, 1)) { return false; } if (!check_max_blit_width(dimensions.width())) { return false; } auto desc = [[MTLTextureDescriptor alloc] init]; desc.pixelFormat = mtlFormat; desc.width = dimensions.width(); desc.height = dimensions.height(); if (mipMapped == GrMipMapped::kYes) { desc.mipmapLevelCount = 1 + SkPrevLog2(std::max(dimensions.width(), dimensions.height())); } if (@available(macOS 10.11, iOS 9.0, *)) { desc.storageMode = MTLStorageModePrivate; MTLTextureUsage usage = texturable == GrTexturable::kYes ? MTLTextureUsageShaderRead : 0; usage |= renderable == GrRenderable::kYes ? MTLTextureUsageRenderTarget : 0; desc.usage = usage; } if (sampleCnt != 1) { desc.sampleCount = sampleCnt; desc.textureType = MTLTextureType2DMultisample; } id testTexture = [fDevice newTextureWithDescriptor: desc]; #ifdef SK_ENABLE_MTL_DEBUG_INFO testTexture.label = @"testTexture"; #endif info->fTexture.reset(GrRetainPtrFromId(testTexture)); return true; } GrBackendTexture GrMtlGpu::onCreateBackendTexture(SkISize dimensions, const GrBackendFormat& format, GrRenderable renderable, GrMipmapped mipMapped, GrProtected isProtected) { const MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); GrMtlTextureInfo info; if (!this->createMtlTextureForBackendSurface(mtlFormat, dimensions, 1, GrTexturable::kYes, renderable, mipMapped, &info)) { return {}; } GrBackendTexture backendTex(dimensions.width(), dimensions.height(), mipMapped, info); return backendTex; } bool GrMtlGpu::onClearBackendTexture(const GrBackendTexture& backendTexture, sk_sp finishedCallback, std::array color) { GrMtlTextureInfo info; SkAssertResult(backendTexture.getMtlTextureInfo(&info)); id GR_NORETAIN mtlTexture = GrGetMTLTexture(info.fTexture.get()); const MTLPixelFormat mtlFormat = mtlTexture.pixelFormat; // Create a transfer buffer and fill with data. size_t bytesPerPixel = GrMtlFormatBytesPerBlock(mtlFormat); size_t combinedBufferSize; // Reuse the same buffer for all levels. Should be ok since we made the row bytes tight. combinedBufferSize = bytesPerPixel*backendTexture.width()*backendTexture.height(); size_t alignment = std::max(bytesPerPixel, this->mtlCaps().getMinBufferAlignment()); GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice( combinedBufferSize, alignment); if (!slice.fBuffer) { return false; } char* buffer = (char*)slice.fOffsetMapPtr; auto colorType = mtl_format_to_backend_tex_clear_colortype(mtlFormat); if (colorType == GrColorType::kUnknown) { return false; } GrImageInfo ii(colorType, kUnpremul_SkAlphaType, nullptr, backendTexture.dimensions()); auto rb = ii.minRowBytes(); SkASSERT(rb == bytesPerPixel*backendTexture.width()); if (!GrClearImage(ii, buffer, rb, color)) { return false; } // Transfer buffer contents to texture MTLOrigin origin = MTLOriginMake(0, 0, 0); GrMtlCommandBuffer* cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"onClearBackendTexture"]; #endif GrMtlBuffer* mtlBuffer = static_cast(slice.fBuffer); SkISize levelDimensions(backendTexture.dimensions()); int numMipLevels = mtlTexture.mipmapLevelCount; for (int currentMipLevel = 0; currentMipLevel < numMipLevels; currentMipLevel++) { size_t levelRowBytes; size_t levelSize; levelRowBytes = levelDimensions.width() * bytesPerPixel; levelSize = levelRowBytes * levelDimensions.height(); // TODO: can this all be done in one go? [blitCmdEncoder copyFromBuffer: mtlBuffer->mtlBuffer() sourceOffset: slice.fOffset sourceBytesPerRow: levelRowBytes sourceBytesPerImage: levelSize sourceSize: MTLSizeMake(levelDimensions.width(), levelDimensions.height(), 1) toTexture: mtlTexture destinationSlice: 0 destinationLevel: currentMipLevel destinationOrigin: origin]; levelDimensions = {std::max(1, levelDimensions.width() / 2), std::max(1, levelDimensions.height() / 2)}; } #ifdef SK_BUILD_FOR_MAC if (this->mtlCaps().isMac()) { [mtlBuffer->mtlBuffer() didModifyRange: NSMakeRange(slice.fOffset, combinedBufferSize)]; } #endif [blitCmdEncoder popDebugGroup]; if (finishedCallback) { this->addFinishedCallback(std::move(finishedCallback)); } return true; } GrBackendTexture GrMtlGpu::onCreateCompressedBackendTexture( SkISize dimensions, const GrBackendFormat& format, GrMipmapped mipMapped, GrProtected isProtected) { const MTLPixelFormat mtlFormat = GrBackendFormatAsMTLPixelFormat(format); GrMtlTextureInfo info; if (!this->createMtlTextureForBackendSurface(mtlFormat, dimensions, 1, GrTexturable::kYes, GrRenderable::kNo, mipMapped, &info)) { return {}; } return GrBackendTexture(dimensions.width(), dimensions.height(), mipMapped, info); } bool GrMtlGpu::onUpdateCompressedBackendTexture(const GrBackendTexture& backendTexture, sk_sp finishedCallback, const void* data, size_t size) { GrMtlTextureInfo info; SkAssertResult(backendTexture.getMtlTextureInfo(&info)); id mtlTexture = GrGetMTLTexture(info.fTexture.get()); int numMipLevels = mtlTexture.mipmapLevelCount; GrMipmapped mipMapped = numMipLevels > 1 ? GrMipmapped::kYes : GrMipmapped::kNo; SkImage::CompressionType compression = GrBackendFormatToCompressionType(backendTexture.getBackendFormat()); SkASSERT(compression != SkImage::CompressionType::kNone); // Create a transfer buffer and fill with data. SkSTArray<16, size_t> individualMipOffsets; size_t combinedBufferSize; combinedBufferSize = SkCompressedDataSize(compression, backendTexture.dimensions(), &individualMipOffsets, mipMapped == GrMipmapped::kYes); SkASSERT(individualMipOffsets.count() == numMipLevels); size_t alignment = std::max(SkCompressedBlockSize(compression), this->mtlCaps().getMinBufferAlignment()); GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice(combinedBufferSize, alignment); if (!slice.fBuffer) { return false; } char* buffer = (char*)slice.fOffsetMapPtr; memcpy(buffer, data, size); // Transfer buffer contents to texture MTLOrigin origin = MTLOriginMake(0, 0, 0); GrMtlCommandBuffer* cmdBuffer = this->commandBuffer(); id blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"onUpdateCompressedBackendTexture"]; #endif GrMtlBuffer* mtlBuffer = static_cast(slice.fBuffer); SkISize levelDimensions(backendTexture.dimensions()); for (int currentMipLevel = 0; currentMipLevel < numMipLevels; currentMipLevel++) { size_t levelRowBytes; size_t levelSize; levelRowBytes = GrCompressedRowBytes(compression, levelDimensions.width()); levelSize = SkCompressedDataSize(compression, levelDimensions, nullptr, false); // TODO: can this all be done in one go? [blitCmdEncoder copyFromBuffer: mtlBuffer->mtlBuffer() sourceOffset: slice.fOffset + individualMipOffsets[currentMipLevel] sourceBytesPerRow: levelRowBytes sourceBytesPerImage: levelSize sourceSize: MTLSizeMake(levelDimensions.width(), levelDimensions.height(), 1) toTexture: mtlTexture destinationSlice: 0 destinationLevel: currentMipLevel destinationOrigin: origin]; levelDimensions = {std::max(1, levelDimensions.width() / 2), std::max(1, levelDimensions.height() / 2)}; } #ifdef SK_BUILD_FOR_MAC if (this->mtlCaps().isMac()) { [mtlBuffer->mtlBuffer() didModifyRange:NSMakeRange(slice.fOffset, combinedBufferSize)]; } #endif [blitCmdEncoder popDebugGroup]; if (finishedCallback) { this->addFinishedCallback(std::move(finishedCallback)); } return true; } void GrMtlGpu::deleteBackendTexture(const GrBackendTexture& tex) { SkASSERT(GrBackendApi::kMetal == tex.backend()); // Nothing to do here, will get cleaned up when the GrBackendTexture object goes away } bool GrMtlGpu::compile(const GrProgramDesc& desc, const GrProgramInfo& programInfo) { GrThreadSafePipelineBuilder::Stats::ProgramCacheResult stat; auto pipelineState = this->resourceProvider().findOrCreateCompatiblePipelineState( desc, programInfo, &stat); if (!pipelineState) { return false; } return stat != GrThreadSafePipelineBuilder::Stats::ProgramCacheResult::kHit; } bool GrMtlGpu::precompileShader(const SkData& key, const SkData& data) { return this->resourceProvider().precompileShader(key, data); } #if GR_TEST_UTILS bool GrMtlGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const { SkASSERT(GrBackendApi::kMetal == tex.backend()); GrMtlTextureInfo info; if (!tex.getMtlTextureInfo(&info)) { return false; } id mtlTexture = GrGetMTLTexture(info.fTexture.get()); if (!mtlTexture) { return false; } if (@available(macOS 10.11, iOS 9.0, *)) { return mtlTexture.usage & MTLTextureUsageShaderRead; } else { return true; // best we can do } } GrBackendRenderTarget GrMtlGpu::createTestingOnlyBackendRenderTarget(SkISize dimensions, GrColorType ct, int sampleCnt, GrProtected isProtected) { if (dimensions.width() > this->caps()->maxRenderTargetSize() || dimensions.height() > this->caps()->maxRenderTargetSize()) { return {}; } if (isProtected == GrProtected::kYes) { return {}; } MTLPixelFormat format = this->mtlCaps().getFormatFromColorType(ct); sampleCnt = this->mtlCaps().getRenderTargetSampleCount(sampleCnt, format); if (sampleCnt == 0) { return {}; } GrMtlTextureInfo info; if (!this->createMtlTextureForBackendSurface(format, dimensions, sampleCnt, GrTexturable::kNo, GrRenderable::kYes, GrMipmapped::kNo, &info)) { return {}; } return GrBackendRenderTarget(dimensions.width(), dimensions.height(), info); } void GrMtlGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget& rt) { SkASSERT(GrBackendApi::kMetal == rt.backend()); GrMtlTextureInfo info; if (rt.getMtlTextureInfo(&info)) { this->submitToGpu(true); // Nothing else to do here, will get cleaned up when the GrBackendRenderTarget // is deleted. } } #endif // GR_TEST_UTILS void GrMtlGpu::copySurfaceAsResolve(GrSurface* dst, GrSurface* src) { // TODO: Add support for subrectangles GrMtlRenderTarget* srcRT = static_cast(src->asRenderTarget()); GrRenderTarget* dstRT = dst->asRenderTarget(); GrMtlAttachment* dstAttachment; if (dstRT) { GrMtlRenderTarget* mtlRT = static_cast(dstRT); dstAttachment = mtlRT->colorAttachment(); } else { SkASSERT(dst->asTexture()); dstAttachment = static_cast(dst->asTexture())->attachment(); } this->resolve(dstAttachment, srcRT->colorAttachment()); } void GrMtlGpu::copySurfaceAsBlit(GrSurface* dst, GrSurface* src, GrMtlAttachment* dstAttachment, GrMtlAttachment* srcAttachment, const SkIRect& srcRect, const SkIPoint& dstPoint) { #ifdef SK_DEBUG SkASSERT(this->mtlCaps().canCopyAsBlit(dstAttachment->mtlFormat(), dstAttachment->numSamples(), srcAttachment->mtlFormat(), dstAttachment->numSamples(), srcRect, dstPoint, dst == src)); #endif id GR_NORETAIN dstTex = dstAttachment->mtlTexture(); id GR_NORETAIN srcTex = srcAttachment->mtlTexture(); auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"copySurfaceAsBlit"]; #endif [blitCmdEncoder copyFromTexture: srcTex sourceSlice: 0 sourceLevel: 0 sourceOrigin: MTLOriginMake(srcRect.x(), srcRect.y(), 0) sourceSize: MTLSizeMake(srcRect.width(), srcRect.height(), 1) toTexture: dstTex destinationSlice: 0 destinationLevel: 0 destinationOrigin: MTLOriginMake(dstPoint.fX, dstPoint.fY, 0)]; #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder popDebugGroup]; #endif cmdBuffer->addGrSurface(sk_ref_sp(dst)); cmdBuffer->addGrSurface(sk_ref_sp(src)); } bool GrMtlGpu::onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, const SkIPoint& dstPoint) { SkASSERT(!src->isProtected() && !dst->isProtected()); GrMtlAttachment* dstAttachment; GrMtlAttachment* srcAttachment; GrRenderTarget* dstRT = dst->asRenderTarget(); if (dstRT) { GrMtlRenderTarget* mtlRT = static_cast(dstRT); // This will technically return true for single sample rts that used DMSAA in which case we // don't have to pick the resolve attachment. But in that case the resolve and color // attachments will be the same anyways. if (this->mtlCaps().renderTargetSupportsDiscardableMSAA(mtlRT)) { dstAttachment = mtlRT->resolveAttachment(); } else { dstAttachment = mtlRT->colorAttachment(); } } else if (dst->asTexture()) { dstAttachment = static_cast(dst->asTexture())->attachment(); } else { // The surface in a GrAttachment already dstAttachment = static_cast(dst); } GrRenderTarget* srcRT = src->asRenderTarget(); if (srcRT) { GrMtlRenderTarget* mtlRT = static_cast(srcRT); // This will technically return true for single sample rts that used DMSAA in which case we // don't have to pick the resolve attachment. But in that case the resolve and color // attachments will be the same anyways. if (this->mtlCaps().renderTargetSupportsDiscardableMSAA(mtlRT)) { srcAttachment = mtlRT->resolveAttachment(); } else { srcAttachment = mtlRT->colorAttachment(); } } else if (src->asTexture()) { SkASSERT(src->asTexture()); srcAttachment = static_cast(src->asTexture())->attachment(); } else { // The surface in a GrAttachment already srcAttachment = static_cast(src); } MTLPixelFormat dstFormat = dstAttachment->mtlFormat(); MTLPixelFormat srcFormat = srcAttachment->mtlFormat(); int dstSampleCnt = dstAttachment->sampleCount(); int srcSampleCnt = srcAttachment->sampleCount(); if (this->mtlCaps().canCopyAsResolve(dstFormat, dstSampleCnt, srcFormat, srcSampleCnt, SkToBool(srcRT), src->dimensions(), srcRect, dstPoint, dstAttachment == srcAttachment)) { this->copySurfaceAsResolve(dst, src); return true; } if (srcAttachment->framebufferOnly() || dstAttachment->framebufferOnly()) { return false; } if (this->mtlCaps().canCopyAsBlit(dstFormat, dstSampleCnt, srcFormat, srcSampleCnt, srcRect, dstPoint, dstAttachment == srcAttachment)) { this->copySurfaceAsBlit(dst, src, dstAttachment, srcAttachment, srcRect, dstPoint); return true; } return false; } bool GrMtlGpu::onWritePixels(GrSurface* surface, SkIRect rect, GrColorType surfaceColorType, GrColorType srcColorType, const GrMipLevel texels[], int mipLevelCount, bool prepForTexSampling) { GrMtlTexture* mtlTexture = static_cast(surface->asTexture()); // TODO: In principle we should be able to support pure rendertargets as well, but // until we find a use case we'll only support texture rendertargets. if (!mtlTexture) { return false; } if (!mipLevelCount) { return false; } #ifdef SK_DEBUG for (int i = 0; i < mipLevelCount; i++) { SkASSERT(texels[i].fPixels); } #endif return this->uploadToTexture(mtlTexture, rect, srcColorType, texels, mipLevelCount); } bool GrMtlGpu::onReadPixels(GrSurface* surface, SkIRect rect, GrColorType surfaceColorType, GrColorType dstColorType, void* buffer, size_t rowBytes) { SkASSERT(surface); if (surfaceColorType != dstColorType) { return false; } int bpp = GrColorTypeBytesPerPixel(dstColorType); size_t transBufferRowBytes = bpp*rect.width(); size_t transBufferImageBytes = transBufferRowBytes*rect.height(); GrResourceProvider* resourceProvider = this->getContext()->priv().resourceProvider(); sk_sp transferBuffer = resourceProvider->createBuffer( transBufferImageBytes, GrGpuBufferType::kXferGpuToCpu, kDynamic_GrAccessPattern); if (!transferBuffer) { return false; } GrMtlBuffer* grMtlBuffer = static_cast(transferBuffer.get()); if (!this->readOrTransferPixels(surface, rect, dstColorType, grMtlBuffer->mtlBuffer(), 0, transBufferImageBytes, transBufferRowBytes)) { return false; } this->submitCommandBuffer(kForce_SyncQueue); const void* mappedMemory = grMtlBuffer->mtlBuffer().contents; SkRectMemcpy(buffer, rowBytes, mappedMemory, transBufferRowBytes, transBufferRowBytes, rect.height()); return true; } bool GrMtlGpu::onTransferPixelsTo(GrTexture* texture, SkIRect rect, GrColorType textureColorType, GrColorType bufferColorType, sk_sp transferBuffer, size_t offset, size_t rowBytes) { SkASSERT(texture); SkASSERT(transferBuffer); if (textureColorType != bufferColorType) { return false; } GrMtlTexture* grMtlTexture = static_cast(texture); id GR_NORETAIN mtlTexture = grMtlTexture->mtlTexture(); SkASSERT(mtlTexture); GrMtlBuffer* grMtlBuffer = static_cast(transferBuffer.get()); id GR_NORETAIN mtlBuffer = grMtlBuffer->mtlBuffer(); SkASSERT(mtlBuffer); size_t bpp = GrColorTypeBytesPerPixel(bufferColorType); if (offset % bpp) { return false; } if (GrBackendFormatBytesPerPixel(texture->backendFormat()) != bpp) { return false; } MTLOrigin origin = MTLOriginMake(rect.left(), rect.top(), 0); auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"onTransferPixelsTo"]; #endif [blitCmdEncoder copyFromBuffer: mtlBuffer sourceOffset: offset sourceBytesPerRow: rowBytes sourceBytesPerImage: rowBytes*rect.height() sourceSize: MTLSizeMake(rect.width(), rect.height(), 1) toTexture: mtlTexture destinationSlice: 0 destinationLevel: 0 destinationOrigin: origin]; #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder popDebugGroup]; #endif return true; } bool GrMtlGpu::onTransferPixelsFrom(GrSurface* surface, SkIRect rect, GrColorType surfaceColorType, GrColorType bufferColorType, sk_sp transferBuffer, size_t offset) { SkASSERT(surface); SkASSERT(transferBuffer); if (surfaceColorType != bufferColorType) { return false; } // Metal only supports offsets that are aligned to a pixel. size_t bpp = GrColorTypeBytesPerPixel(bufferColorType); if (offset % bpp) { return false; } if (GrBackendFormatBytesPerPixel(surface->backendFormat()) != bpp) { return false; } GrMtlBuffer* grMtlBuffer = static_cast(transferBuffer.get()); size_t transBufferRowBytes = bpp*rect.width(); size_t transBufferImageBytes = transBufferRowBytes*rect.height(); return this->readOrTransferPixels(surface, rect, bufferColorType, grMtlBuffer->mtlBuffer(), offset, transBufferImageBytes, transBufferRowBytes); } bool GrMtlGpu::readOrTransferPixels(GrSurface* surface, SkIRect rect, GrColorType dstColorType, id transferBuffer, size_t offset, size_t imageBytes, size_t rowBytes) { if (!check_max_blit_width(rect.width())) { return false; } id mtlTexture; if (GrMtlRenderTarget* rt = static_cast(surface->asRenderTarget())) { if (rt->numSamples() > 1) { SkASSERT(rt->requiresManualMSAAResolve()); // msaa-render-to-texture not yet supported. mtlTexture = rt->resolveMTLTexture(); } else { SkASSERT(!rt->requiresManualMSAAResolve()); mtlTexture = rt->colorMTLTexture(); } } else if (GrMtlTexture* texture = static_cast(surface->asTexture())) { mtlTexture = texture->mtlTexture(); } if (!mtlTexture) { return false; } auto cmdBuffer = this->commandBuffer(); id GR_NORETAIN blitCmdEncoder = cmdBuffer->getBlitCommandEncoder(); if (!blitCmdEncoder) { return false; } #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder pushDebugGroup:@"readOrTransferPixels"]; #endif [blitCmdEncoder copyFromTexture: mtlTexture sourceSlice: 0 sourceLevel: 0 sourceOrigin: MTLOriginMake(rect.left(), rect.top(), 0) sourceSize: MTLSizeMake(rect.width(), rect.height(), 1) toBuffer: transferBuffer destinationOffset: offset destinationBytesPerRow: rowBytes destinationBytesPerImage: imageBytes]; #ifdef SK_BUILD_FOR_MAC if (this->mtlCaps().isMac()) { // Sync GPU data back to the CPU [blitCmdEncoder synchronizeResource: transferBuffer]; } #endif #ifdef SK_ENABLE_MTL_DEBUG_INFO [blitCmdEncoder popDebugGroup]; #endif return true; } GrFence SK_WARN_UNUSED_RESULT GrMtlGpu::insertFence() { GrMtlCommandBuffer* cmdBuffer = this->commandBuffer(); // We create a semaphore and signal it within the current // command buffer's completion handler. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); cmdBuffer->addCompletedHandler(^(id commandBuffer) { dispatch_semaphore_signal(semaphore); }); const void* cfFence = (__bridge_retained const void*) semaphore; return (GrFence) cfFence; } bool GrMtlGpu::waitFence(GrFence fence) { const void* cfFence = (const void*) fence; dispatch_semaphore_t semaphore = (__bridge dispatch_semaphore_t)cfFence; long result = dispatch_semaphore_wait(semaphore, 0); return !result; } void GrMtlGpu::deleteFence(GrFence fence) const { const void* cfFence = (const void*) fence; // In this case it's easier to release in CoreFoundation than depend on ARC CFRelease(cfFence); } std::unique_ptr SK_WARN_UNUSED_RESULT GrMtlGpu::makeSemaphore(bool /*isOwned*/) { SkASSERT(this->caps()->semaphoreSupport()); return GrMtlSemaphore::Make(this); } std::unique_ptr GrMtlGpu::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, GrSemaphoreWrapType /* wrapType */, GrWrapOwnership /*ownership*/) { SkASSERT(this->caps()->semaphoreSupport()); return GrMtlSemaphore::MakeWrapped(semaphore.mtlSemaphore(), semaphore.mtlValue()); } void GrMtlGpu::insertSemaphore(GrSemaphore* semaphore) { if (@available(macOS 10.14, iOS 12.0, *)) { SkASSERT(semaphore); GrMtlSemaphore* mtlSem = static_cast(semaphore); this->commandBuffer()->encodeSignalEvent(mtlSem->event(), mtlSem->value()); } } void GrMtlGpu::waitSemaphore(GrSemaphore* semaphore) { if (@available(macOS 10.14, iOS 12.0, *)) { SkASSERT(semaphore); GrMtlSemaphore* mtlSem = static_cast(semaphore); this->commandBuffer()->encodeWaitForEvent(mtlSem->event(), mtlSem->value()); } } void GrMtlGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect&) { SkASSERT(target->numSamples() > 1); GrMtlRenderTarget* rt = static_cast(target); if (rt->resolveAttachment() && this->mtlCaps().renderTargetSupportsDiscardableMSAA(rt)) { // We would have resolved the RT during the render pass. return; } this->resolve(static_cast(target)->resolveAttachment(), static_cast(target)->colorAttachment()); } void GrMtlGpu::resolve(GrMtlAttachment* resolveAttachment, GrMtlAttachment* msaaAttachment) { auto renderPassDesc = [[MTLRenderPassDescriptor alloc] init]; auto colorAttachment = renderPassDesc.colorAttachments[0]; colorAttachment.texture = msaaAttachment->mtlTexture(); colorAttachment.resolveTexture = resolveAttachment->mtlTexture(); colorAttachment.loadAction = MTLLoadActionLoad; colorAttachment.storeAction = MTLStoreActionMultisampleResolve; GrMtlRenderCommandEncoder* cmdEncoder = this->commandBuffer()->getRenderCommandEncoder(renderPassDesc, nullptr, nullptr); if (cmdEncoder) { cmdEncoder->setLabel(@"resolveTexture"); this->commandBuffer()->addGrSurface(sk_ref_sp(resolveAttachment)); this->commandBuffer()->addGrSurface(sk_ref_sp(msaaAttachment)); } } GrMtlRenderCommandEncoder* GrMtlGpu::loadMSAAFromResolve( GrAttachment* dst, GrMtlAttachment* src, const SkIRect& srcRect, MTLRenderPassStencilAttachmentDescriptor* stencil) { if (!dst) { return nil; } if (!src || src->framebufferOnly()) { return nil; } GrMtlAttachment* mtlDst = static_cast(dst); MTLPixelFormat stencilFormat = stencil.texture.pixelFormat; auto renderPipeline = this->resourceProvider().findOrCreateMSAALoadPipeline(mtlDst->mtlFormat(), dst->numSamples(), stencilFormat); // Set up rendercommandencoder auto renderPassDesc = [MTLRenderPassDescriptor new]; auto colorAttachment = renderPassDesc.colorAttachments[0]; colorAttachment.texture = mtlDst->mtlTexture(); colorAttachment.loadAction = MTLLoadActionDontCare; colorAttachment.storeAction = MTLStoreActionMultisampleResolve; colorAttachment.resolveTexture = src->mtlTexture(); renderPassDesc.stencilAttachment = stencil; // We know in this case that the preceding renderCommandEncoder will not be compatible. // Either it's using a different rendertarget, or we are reading from the resolve and // hence we need to let the previous resolve finish. So we create a new one without checking. auto renderCmdEncoder = this->commandBuffer()->getRenderCommandEncoder(renderPassDesc, nullptr); if (!renderCmdEncoder) { return nullptr; } // Bind pipeline renderCmdEncoder->setRenderPipelineState(renderPipeline->mtlPipelineState()); this->commandBuffer()->addResource(sk_ref_sp(renderPipeline)); // Bind src as input texture renderCmdEncoder->setFragmentTexture(src->mtlTexture(), 0); // No sampler needed this->commandBuffer()->addGrSurface(sk_ref_sp(src)); // Scissor and viewport should default to size of color attachment // Update and bind uniform data int w = srcRect.width(); int h = srcRect.height(); // dst rect edges in NDC (-1 to 1) int dw = dst->width(); int dh = dst->height(); float dx0 = 2.f * srcRect.fLeft / dw - 1.f; float dx1 = 2.f * (srcRect.fLeft + w) / dw - 1.f; float dy0 = 2.f * srcRect.fTop / dh - 1.f; float dy1 = 2.f * (srcRect.fTop + h) / dh - 1.f; struct { float posXform[4]; int textureSize[2]; int pad[2]; } uniData = {{dx1 - dx0, dy1 - dy0, dx0, dy0}, {dw, dh}, {0, 0}}; constexpr size_t uniformSize = 32; if (@available(macOS 10.11, iOS 8.3, *)) { SkASSERT(uniformSize <= this->caps()->maxPushConstantsSize()); renderCmdEncoder->setVertexBytes(&uniData, uniformSize, 0); } else { // upload the data GrRingBuffer::Slice slice = this->uniformsRingBuffer()->suballocate(uniformSize); GrMtlBuffer* buffer = (GrMtlBuffer*) slice.fBuffer; char* destPtr = static_cast(slice.fBuffer->map()) + slice.fOffset; memcpy(destPtr, &uniData, uniformSize); renderCmdEncoder->setVertexBuffer(buffer->mtlBuffer(), slice.fOffset, 0); } renderCmdEncoder->drawPrimitives(MTLPrimitiveTypeTriangleStrip, (NSUInteger)0, (NSUInteger)4); return renderCmdEncoder; } #if GR_TEST_UTILS void GrMtlGpu::testingOnly_startCapture() { if (@available(macOS 10.13, iOS 11.0, *)) { // TODO: add Metal 3 interface as well MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; if (captureManager.isCapturing) { return; } if (@available(macOS 10.15, iOS 13.0, *)) { MTLCaptureDescriptor* captureDescriptor = [[MTLCaptureDescriptor alloc] init]; captureDescriptor.captureObject = fQueue; NSError *error; if (![captureManager startCaptureWithDescriptor: captureDescriptor error:&error]) { NSLog(@"Failed to start capture, error %@", error); } } else { [captureManager startCaptureWithCommandQueue: fQueue]; } } } void GrMtlGpu::testingOnly_endCapture() { if (@available(macOS 10.13, iOS 11.0, *)) { MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; if (captureManager.isCapturing) { [captureManager stopCapture]; } } } #endif #ifdef SK_ENABLE_DUMP_GPU #include "src/utils/SkJSONWriter.h" void GrMtlGpu::onDumpJSON(SkJSONWriter* writer) const { // We are called by the base class, which has already called beginObject(). We choose to nest // all of our caps information in a named sub-object. writer->beginObject("Metal GPU"); writer->beginObject("Device"); writer->appendString("name", fDevice.name.UTF8String); #ifdef SK_BUILD_FOR_MAC if (@available(macOS 10.11, *)) { writer->appendBool("isHeadless", fDevice.isHeadless); writer->appendBool("isLowPower", fDevice.isLowPower); } if (@available(macOS 10.13, *)) { writer->appendBool("isRemovable", fDevice.isRemovable); } #endif if (@available(macOS 10.13, iOS 11.0, *)) { writer->appendU64("registryID", fDevice.registryID); } #if defined(SK_BUILD_FOR_MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 if (@available(macOS 10.15, *)) { switch (fDevice.location) { case MTLDeviceLocationBuiltIn: writer->appendString("location", "builtIn"); break; case MTLDeviceLocationSlot: writer->appendString("location", "slot"); break; case MTLDeviceLocationExternal: writer->appendString("location", "external"); break; case MTLDeviceLocationUnspecified: writer->appendString("location", "unspecified"); break; default: writer->appendString("location", "unknown"); break; } writer->appendU64("locationNumber", fDevice.locationNumber); writer->appendU64("maxTransferRate", fDevice.maxTransferRate); } #endif // SK_BUILD_FOR_MAC #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(macOS 10.15, iOS 13.0, *)) { writer->appendBool("hasUnifiedMemory", fDevice.hasUnifiedMemory); } #endif #ifdef SK_BUILD_FOR_MAC #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 if (@available(macOS 10.15, *)) { writer->appendU64("peerGroupID", fDevice.peerGroupID); writer->appendU32("peerCount", fDevice.peerCount); writer->appendU32("peerIndex", fDevice.peerIndex); } #endif if (@available(macOS 10.12, *)) { writer->appendU64("recommendedMaxWorkingSetSize", fDevice.recommendedMaxWorkingSetSize); } #endif // SK_BUILD_FOR_MAC if (@available(macOS 10.13, iOS 11.0, *)) { writer->appendU64("currentAllocatedSize", fDevice.currentAllocatedSize); writer->appendU64("maxThreadgroupMemoryLength", fDevice.maxThreadgroupMemoryLength); } if (@available(macOS 10.11, iOS 9.0, *)) { writer->beginObject("maxThreadsPerThreadgroup"); writer->appendU64("width", fDevice.maxThreadsPerThreadgroup.width); writer->appendU64("height", fDevice.maxThreadsPerThreadgroup.height); writer->appendU64("depth", fDevice.maxThreadsPerThreadgroup.depth); writer->endObject(); } if (@available(macOS 10.13, iOS 11.0, *)) { writer->appendBool("areProgrammableSamplePositionsSupported", fDevice.areProgrammableSamplePositionsSupported); writer->appendBool("areRasterOrderGroupsSupported", fDevice.areRasterOrderGroupsSupported); } #ifdef SK_BUILD_FOR_MAC if (@available(macOS 10.11, *)) { writer->appendBool("isDepth24Stencil8PixelFormatSupported", fDevice.isDepth24Stencil8PixelFormatSupported); } #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 if (@available(macOS 10.15, *)) { writer->appendBool("areBarycentricCoordsSupported", fDevice.areBarycentricCoordsSupported); writer->appendBool("supportsShaderBarycentricCoordinates", fDevice.supportsShaderBarycentricCoordinates); } #endif #endif // SK_BUILD_FOR_MAC if (@available(macOS 10.14, iOS 12.0, *)) { writer->appendU64("maxBufferLength", fDevice.maxBufferLength); } if (@available(macOS 10.13, iOS 11.0, *)) { switch (fDevice.readWriteTextureSupport) { case MTLReadWriteTextureTier1: writer->appendString("readWriteTextureSupport", "tier1"); break; case MTLReadWriteTextureTier2: writer->appendString("readWriteTextureSupport", "tier2"); break; case MTLReadWriteTextureTierNone: writer->appendString("readWriteTextureSupport", "tierNone"); break; default: writer->appendString("readWriteTextureSupport", "unknown"); break; } switch (fDevice.argumentBuffersSupport) { case MTLArgumentBuffersTier1: writer->appendString("argumentBuffersSupport", "tier1"); break; case MTLArgumentBuffersTier2: writer->appendString("argumentBuffersSupport", "tier2"); break; default: writer->appendString("argumentBuffersSupport", "unknown"); break; } } if (@available(macOS 10.14, iOS 12.0, *)) { writer->appendU64("maxArgumentBufferSamplerCount", fDevice.maxArgumentBufferSamplerCount); } #ifdef SK_BUILD_FOR_IOS if (@available(iOS 13.0, *)) { writer->appendU64("sparseTileSizeInBytes", fDevice.sparseTileSizeInBytes); } #endif writer->endObject(); writer->appendString("queue", fQueue.label.UTF8String); writer->appendBool("disconnected", fDisconnected); writer->endObject(); } #endif GR_NORETAIN_END