blob: 8c5f9ddf67b141dd608a8e1409321aef286b70d5 [file] [log] [blame]
/*
* 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 "GrMtlGpu.h"
#include "GrMtlTexture.h"
#include "GrMtlTextureRenderTarget.h"
#include "GrMtlUtil.h"
#include "SkConvertPixels.h"
#if !__has_feature(objc_arc)
#error This file must be compiled with Arc. Use -fobjc-arc flag
#endif
static bool get_feature_set(id<MTLDevice> device, MTLFeatureSet* featureSet) {
// Mac OSX
#ifdef SK_BUILD_FOR_MAC
if ([device supportsFeatureSet:MTLFeatureSet_OSX_GPUFamily1_v2]) {
*featureSet = MTLFeatureSet_OSX_GPUFamily1_v2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_OSX_GPUFamily1_v1]) {
*featureSet = MTLFeatureSet_OSX_GPUFamily1_v1;
return true;
}
#endif
// iOS Family group 3
#ifdef SK_BUILD_FOR_IOS
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v2]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily3_v2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily3_v1;
return true;
}
// iOS Family group 2
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v3]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily2_v3;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily2_v2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily2_v1;
return true;
}
// iOS Family group 1
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v3]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily1_v3;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily1_v2;
return true;
}
if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v1]) {
*featureSet = MTLFeatureSet_iOS_GPUFamily1_v1;
return true;
}
#endif
// No supported feature sets were found
return false;
}
sk_sp<GrGpu> GrMtlGpu::Make(GrContext* context, const GrContextOptions& options,
id<MTLDevice> device, id<MTLCommandQueue> queue) {
if (!device || !queue) {
return nullptr;
}
MTLFeatureSet featureSet;
if (!get_feature_set(device, &featureSet)) {
return nullptr;
}
return sk_sp<GrGpu>(new GrMtlGpu(context, options, device, queue, featureSet));
}
GrMtlGpu::GrMtlGpu(GrContext* context, const GrContextOptions& options,
id<MTLDevice> device, id<MTLCommandQueue> queue, MTLFeatureSet featureSet)
: INHERITED(context)
, fDevice(device)
, fQueue(queue) {
fMtlCaps.reset(new GrMtlCaps(options, fDevice, featureSet));
fCaps = fMtlCaps;
fCmdBuffer = [fQueue commandBuffer];
MTLTextureDescriptor* txDesc = [[MTLTextureDescriptor alloc] init];
txDesc.textureType = MTLTextureType3D;
txDesc.height = 64;
txDesc.width = 64;
txDesc.depth = 64;
txDesc.pixelFormat = MTLPixelFormatRGBA8Unorm;
txDesc.arrayLength = 1;
txDesc.mipmapLevelCount = 1;
id<MTLTexture> testTexture = [fDevice newTextureWithDescriptor:txDesc];
// To get ride of unused var warning
int width = [testTexture width];
SkDebugf("width: %d\n", width);
// Unused queue warning fix
SkDebugf("ptr to queue: %p\n", fQueue);
}
void GrMtlGpu::submitCommandBuffer(SyncQueue sync) {
SkASSERT(fCmdBuffer);
[fCmdBuffer commit];
if (SyncQueue::kForce_SyncQueue == sync) {
[fCmdBuffer waitUntilCompleted];
}
fCmdBuffer = [fQueue commandBuffer];
}
sk_sp<GrTexture> GrMtlGpu::onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
const GrMipLevel texels[], int mipLevelCount) {
int mipLevels = !mipLevelCount ? 1 : mipLevelCount;
if (!fMtlCaps->isConfigTexturable(desc.fConfig)) {
return nullptr;
}
MTLPixelFormat format;
if (!GrPixelConfigToMTLFormat(desc.fConfig, &format)) {
return nullptr;
}
bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag);
// This TexDesc refers to the texture that will be read by the client. Thus even if msaa is
// requested, this TexDesc describes the resolved texture. Therefore we always have samples set
// to 1.
MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
texDesc.textureType = MTLTextureType2D;
texDesc.pixelFormat = format;
texDesc.width = desc.fWidth;
texDesc.height = desc.fHeight;
texDesc.depth = 1;
texDesc.mipmapLevelCount = mipLevels;
texDesc.sampleCount = 1;
texDesc.arrayLength = 1;
texDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
// Make all textures have private gpu only access. We can use transfer buffers or textures
// to copy to them.
texDesc.storageMode = MTLStorageModePrivate;
texDesc.usage = MTLTextureUsageShaderRead | renderTarget ? MTLTextureUsageRenderTarget : 0;
GrMipMapsStatus mipMapsStatus = GrMipMapsStatus::kNotAllocated;
if (mipLevels > 1) {
mipMapsStatus = GrMipMapsStatus::kValid;
for (int i = 0; i < mipLevels; ++i) {
if (!texels[i].fPixels) {
mipMapsStatus = GrMipMapsStatus::kDirty;
break;
}
}
}
sk_sp<GrMtlTexture> tex;
if (renderTarget) {
tex = GrMtlTextureRenderTarget::CreateNewTextureRenderTarget(this, budgeted,
desc, texDesc, mipMapsStatus);
} else {
tex = GrMtlTexture::CreateNewTexture(this, budgeted, desc, texDesc, mipMapsStatus);
}
if (!tex) {
return nullptr;
}
if (mipLevelCount) {
// Perform initial data upload here
}
if (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) {
// Do initial clear of the texture
}
return tex;
}
static id<MTLTexture> get_texture_from_backend(const GrBackendTexture& backendTex,
GrWrapOwnership ownership) {
GrMtlTextureInfo textureInfo;
if (!backendTex.getMtlTextureInfo(&textureInfo)) {
return nil;
}
return GrGetMTLTexture(textureInfo.fTexture, ownership);
}
static id<MTLTexture> get_texture_from_backend(const GrBackendRenderTarget& backendRT) {
GrMtlTextureInfo textureInfo;
if (!backendRT.getMtlTextureInfo(&textureInfo)) {
return nil;
}
return GrGetMTLTexture(textureInfo.fTexture, GrWrapOwnership::kBorrow_GrWrapOwnership);
}
static inline void init_surface_desc(GrSurfaceDesc* surfaceDesc, id<MTLTexture> mtlTexture,
bool isRenderTarget, GrPixelConfig config) {
if (isRenderTarget) {
SkASSERT(MTLTextureUsageRenderTarget & mtlTexture.usage);
}
surfaceDesc->fFlags = isRenderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
surfaceDesc->fWidth = mtlTexture.width;
surfaceDesc->fHeight = mtlTexture.height;
surfaceDesc->fConfig = config;
surfaceDesc->fSampleCnt = 1;
}
sk_sp<GrTexture> GrMtlGpu::onWrapBackendTexture(const GrBackendTexture& backendTex,
GrWrapOwnership ownership) {
id<MTLTexture> mtlTexture = get_texture_from_backend(backendTex, ownership);
if (!mtlTexture) {
return nullptr;
}
GrSurfaceDesc surfDesc;
init_surface_desc(&surfDesc, mtlTexture, false, backendTex.config());
return GrMtlTexture::MakeWrappedTexture(this, surfDesc, mtlTexture);
}
sk_sp<GrTexture> GrMtlGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex,
int sampleCnt,
GrWrapOwnership ownership) {
id<MTLTexture> mtlTexture = get_texture_from_backend(backendTex, ownership);
if (!mtlTexture) {
return nullptr;
}
GrSurfaceDesc surfDesc;
init_surface_desc(&surfDesc, mtlTexture, true, backendTex.config());
surfDesc.fSampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, surfDesc.fConfig);
if (!surfDesc.fSampleCnt) {
return nullptr;
}
return GrMtlTextureRenderTarget::MakeWrappedTextureRenderTarget(this, surfDesc, mtlTexture);
}
sk_sp<GrRenderTarget> GrMtlGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& backendRT) {
// TODO: Revisit this when the Metal backend is completed. It may support MSAA render targets.
if (backendRT.sampleCnt() > 1) {
return nullptr;
}
id<MTLTexture> mtlTexture = get_texture_from_backend(backendRT);
if (!mtlTexture) {
return nullptr;
}
GrSurfaceDesc surfDesc;
init_surface_desc(&surfDesc, mtlTexture, true, backendRT.config());
return GrMtlRenderTarget::MakeWrappedRenderTarget(this, surfDesc, mtlTexture);
}
sk_sp<GrRenderTarget> GrMtlGpu::onWrapBackendTextureAsRenderTarget(
const GrBackendTexture& backendTex, int sampleCnt) {
id<MTLTexture> mtlTexture = get_texture_from_backend(backendTex,
GrWrapOwnership::kBorrow_GrWrapOwnership);
if (!mtlTexture) {
return nullptr;
}
GrSurfaceDesc surfDesc;
init_surface_desc(&surfDesc, mtlTexture, true, backendTex.config());
surfDesc.fSampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, surfDesc.fConfig);
if (!surfDesc.fSampleCnt) {
return nullptr;
}
return GrMtlRenderTarget::MakeWrappedRenderTarget(this, surfDesc, mtlTexture);
}
bool GrMtlGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height,
GrColorType dstColorType, void* buffer, size_t rowBytes) {
static const int MAX_BLIT_WIDTH = 32767; // in pixels
SkASSERT(surface);
if (width > MAX_BLIT_WIDTH) {
SkASSERT(false); // A texture/RT shouldn't be this wide anyway.
return false;
}
if (GrPixelConfigToColorType(surface->config()) != dstColorType) {
return false;
}
id<MTLTexture> mtlTexture = nil;
GrMtlRenderTarget* renderTarget = static_cast<GrMtlRenderTarget*>(surface->asRenderTarget());
GrMtlTexture* texture;
if (renderTarget) {
// TODO: Handle resolving rt here when MSAA is supported. Right now we just grab the render
// texture since we cannot currently have a multi-sampled rt.
mtlTexture = renderTarget->mtlRenderTexture();
} else {
texture = static_cast<GrMtlTexture*>(surface->asTexture());
if (texture) {
mtlTexture = texture->mtlTexture();
}
}
if (!mtlTexture) {
return false;
}
int bpp = GrColorTypeBytesPerPixel(dstColorType);
size_t transBufferRowBytes = bpp * width;
size_t transBufferImageBytes = transBufferRowBytes * height;
// TODO: implement some way of reusing buffers instead of making a new one every time.
id<MTLBuffer> transferBuffer = [fDevice newBufferWithLength: transBufferImageBytes
options: MTLResourceStorageModeShared];
id<MTLBlitCommandEncoder> blitCmdEncoder = [fCmdBuffer blitCommandEncoder];
[blitCmdEncoder copyFromTexture: mtlTexture
sourceSlice: 0
sourceLevel: 0
sourceOrigin: MTLOriginMake(left, top, 0)
sourceSize: MTLSizeMake(width, height, 1)
toBuffer: transferBuffer
destinationOffset: 0
destinationBytesPerRow: transBufferRowBytes
destinationBytesPerImage: transBufferImageBytes];
[blitCmdEncoder endEncoding];
this->submitCommandBuffer(kForce_SyncQueue);
const void* mappedMemory = transferBuffer.contents;
SkRectMemcpy(buffer, rowBytes, mappedMemory, transBufferRowBytes, transBufferRowBytes, height);
return true;
}