// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // mtl_utils.mm: // Implements utilities functions that create Metal shaders, convert from angle enums // to Metal enums and so on. // #include "libANGLE/renderer/metal/mtl_utils.h" #include #include "common/MemoryBuffer.h" #include "libANGLE/renderer/metal/ContextMtl.h" namespace rx { namespace mtl { angle::Result InitializeTextureContents(const gl::Context *context, const TextureRef &texture, const Format &textureObjFormat, const gl::ImageIndex &index) { ASSERT(texture && texture->valid()); ASSERT(texture->textureType() == MTLTextureType2D || texture->textureType() == MTLTextureTypeCube); ContextMtl *contextMtl = mtl::GetImpl(context); const gl::InternalFormat &intendedInternalFormat = textureObjFormat.intendedInternalFormat(); // This function is called in many places to initialize the content of a texture. // So it's better we do the sanity check here instead of let the callers do it themselves: if (!textureObjFormat.valid() || intendedInternalFormat.compressed || intendedInternalFormat.depthBits > 0 || intendedInternalFormat.stencilBits > 0) { return angle::Result::Continue; } gl::Extents size = texture->size(index); // Intialize the content to black const angle::Format &srcFormat = angle::Format::Get(intendedInternalFormat.alphaBits > 0 ? angle::FormatID::R8G8B8A8_UNORM : angle::FormatID::R8G8B8_UNORM); const size_t srcRowPitch = srcFormat.pixelBytes * size.width; angle::MemoryBuffer srcRow; ANGLE_CHECK_GL_ALLOC(contextMtl, srcRow.resize(srcRowPitch)); memset(srcRow.data(), 0, srcRowPitch); const angle::Format &dstFormat = angle::Format::Get(textureObjFormat.actualFormatId); const size_t dstRowPitch = dstFormat.pixelBytes * size.width; angle::MemoryBuffer conversionRow; ANGLE_CHECK_GL_ALLOC(contextMtl, conversionRow.resize(dstRowPitch)); CopyImageCHROMIUM(srcRow.data(), srcRowPitch, srcFormat.pixelBytes, 0, srcFormat.pixelReadFunction, conversionRow.data(), dstRowPitch, dstFormat.pixelBytes, 0, dstFormat.pixelWriteFunction, intendedInternalFormat.format, dstFormat.componentType, size.width, 1, 1, false, false, false); auto mtlRowRegion = MTLRegionMake2D(0, 0, size.width, 1); for (NSUInteger r = 0; r < static_cast(size.height); ++r) { mtlRowRegion.origin.y = r; // Upload to texture texture->replaceRegion(contextMtl, mtlRowRegion, index.getLevelIndex(), index.hasLayer() ? index.cubeMapFaceIndex() : 0, conversionRow.data(), dstRowPitch); } return angle::Result::Continue; } MTLViewport GetViewport(const gl::Rectangle &rect, double znear, double zfar) { MTLViewport re; re.originX = rect.x; re.originY = rect.y; re.width = rect.width; re.height = rect.height; re.znear = znear; re.zfar = zfar; return re; } MTLViewport GetViewportFlipY(const gl::Rectangle &rect, NSUInteger screenHeight, double znear, double zfar) { MTLViewport re; re.originX = rect.x; re.originY = static_cast(screenHeight) - rect.y1(); re.width = rect.width; re.height = rect.height; re.znear = znear; re.zfar = zfar; return re; } MTLViewport GetViewport(const gl::Rectangle &rect, NSUInteger screenHeight, bool flipY, double znear, double zfar) { if (flipY) { return GetViewportFlipY(rect, screenHeight, znear, zfar); } return GetViewport(rect, znear, zfar); } MTLScissorRect GetScissorRect(const gl::Rectangle &rect, NSUInteger screenHeight, bool flipY) { MTLScissorRect re; re.x = rect.x; re.y = flipY ? (screenHeight - rect.y1()) : rect.y; re.width = rect.width; re.height = rect.height; return re; } AutoObjCPtr> CreateShaderLibrary(id metalDevice, const std::string &source, AutoObjCPtr *error) { return CreateShaderLibrary(metalDevice, source.c_str(), source.size(), error); } AutoObjCPtr> CreateShaderLibrary(id metalDevice, const char *source, size_t sourceLen, AutoObjCPtr *errorOut) { ANGLE_MTL_OBJC_SCOPE { NSError *nsError = nil; auto nsSource = [[NSString alloc] initWithBytesNoCopy:const_cast(source) length:sourceLen encoding:NSUTF8StringEncoding freeWhenDone:NO]; auto options = [[[MTLCompileOptions alloc] init] ANGLE_MTL_AUTORELEASE]; auto library = [metalDevice newLibraryWithSource:nsSource options:options error:&nsError]; [nsSource ANGLE_MTL_AUTORELEASE]; *errorOut = std::move(nsError); return [library ANGLE_MTL_AUTORELEASE]; } } AutoObjCPtr> CreateShaderLibraryFromBinary(id metalDevice, const uint8_t *binarySource, size_t binarySourceLen, AutoObjCPtr *errorOut) { ANGLE_MTL_OBJC_SCOPE { NSError *nsError = nil; auto shaderSourceData = dispatch_data_create(binarySource, binarySourceLen, dispatch_get_main_queue(), ^{ }); auto library = [metalDevice newLibraryWithData:shaderSourceData error:&nsError]; [shaderSourceData ANGLE_MTL_AUTORELEASE]; *errorOut = std::move(nsError); return [library ANGLE_MTL_AUTORELEASE]; } } MTLTextureType GetTextureType(gl::TextureType glType) { switch (glType) { case gl::TextureType::_2D: return MTLTextureType2D; case gl::TextureType::CubeMap: return MTLTextureTypeCube; default: return MTLTextureTypeInvalid; } } MTLSamplerMinMagFilter GetFilter(GLenum filter) { switch (filter) { case GL_LINEAR_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_NEAREST: case GL_LINEAR: return MTLSamplerMinMagFilterLinear; case GL_NEAREST_MIPMAP_LINEAR: case GL_NEAREST_MIPMAP_NEAREST: case GL_NEAREST: return MTLSamplerMinMagFilterNearest; default: UNIMPLEMENTED(); return MTLSamplerMinMagFilterNearest; } } MTLSamplerMipFilter GetMipmapFilter(GLenum filter) { switch (filter) { case GL_LINEAR: case GL_NEAREST: return MTLSamplerMipFilterNotMipmapped; case GL_LINEAR_MIPMAP_LINEAR: case GL_NEAREST_MIPMAP_LINEAR: return MTLSamplerMipFilterLinear; case GL_NEAREST_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_NEAREST: return MTLSamplerMipFilterNearest; default: UNIMPLEMENTED(); return MTLSamplerMipFilterNotMipmapped; } } MTLSamplerAddressMode GetSamplerAddressMode(GLenum wrap) { switch (wrap) { case GL_REPEAT: return MTLSamplerAddressModeRepeat; case GL_MIRRORED_REPEAT: return MTLSamplerAddressModeMirrorRepeat; case GL_CLAMP_TO_BORDER: // ES doesn't have border support return MTLSamplerAddressModeClampToEdge; case GL_CLAMP_TO_EDGE: return MTLSamplerAddressModeClampToEdge; default: UNIMPLEMENTED(); return MTLSamplerAddressModeClampToEdge; } } MTLBlendFactor GetBlendFactor(GLenum factor) { switch (factor) { case GL_ZERO: return MTLBlendFactorZero; case GL_ONE: return MTLBlendFactorOne; case GL_SRC_COLOR: return MTLBlendFactorSourceColor; case GL_DST_COLOR: return MTLBlendFactorDestinationColor; case GL_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor; case GL_SRC_ALPHA: return MTLBlendFactorSourceAlpha; case GL_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha; case GL_DST_ALPHA: return MTLBlendFactorDestinationAlpha; case GL_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha; case GL_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor; case GL_SRC_ALPHA_SATURATE: return MTLBlendFactorSourceAlphaSaturated; case GL_CONSTANT_COLOR: return MTLBlendFactorBlendColor; case GL_CONSTANT_ALPHA: return MTLBlendFactorBlendAlpha; case GL_ONE_MINUS_CONSTANT_COLOR: return MTLBlendFactorOneMinusBlendColor; case GL_ONE_MINUS_CONSTANT_ALPHA: return MTLBlendFactorOneMinusBlendAlpha; default: UNREACHABLE(); return MTLBlendFactorZero; } } MTLBlendOperation GetBlendOp(GLenum op) { switch (op) { case GL_FUNC_ADD: return MTLBlendOperationAdd; case GL_FUNC_SUBTRACT: return MTLBlendOperationSubtract; case GL_FUNC_REVERSE_SUBTRACT: return MTLBlendOperationReverseSubtract; case GL_MIN: return MTLBlendOperationMin; case GL_MAX: return MTLBlendOperationMax; default: UNREACHABLE(); return MTLBlendOperationAdd; } } MTLCompareFunction GetCompareFunc(GLenum func) { switch (func) { case GL_NEVER: return MTLCompareFunctionNever; case GL_ALWAYS: return MTLCompareFunctionAlways; case GL_LESS: return MTLCompareFunctionLess; case GL_LEQUAL: return MTLCompareFunctionLessEqual; case GL_EQUAL: return MTLCompareFunctionEqual; case GL_GREATER: return MTLCompareFunctionGreater; case GL_GEQUAL: return MTLCompareFunctionGreaterEqual; case GL_NOTEQUAL: return MTLCompareFunctionNotEqual; default: UNREACHABLE(); return MTLCompareFunctionAlways; } } MTLStencilOperation GetStencilOp(GLenum op) { switch (op) { case GL_KEEP: return MTLStencilOperationKeep; case GL_ZERO: return MTLStencilOperationZero; case GL_REPLACE: return MTLStencilOperationReplace; case GL_INCR: return MTLStencilOperationIncrementClamp; case GL_DECR: return MTLStencilOperationDecrementClamp; case GL_INCR_WRAP: return MTLStencilOperationIncrementWrap; case GL_DECR_WRAP: return MTLStencilOperationDecrementWrap; case GL_INVERT: return MTLStencilOperationInvert; default: UNREACHABLE(); return MTLStencilOperationKeep; } } MTLWinding GetFontfaceWinding(GLenum frontFaceMode, bool invert) { switch (frontFaceMode) { case GL_CW: return invert ? MTLWindingCounterClockwise : MTLWindingClockwise; case GL_CCW: return invert ? MTLWindingClockwise : MTLWindingCounterClockwise; default: UNREACHABLE(); return MTLWindingClockwise; } } #if ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE PrimitiveTopologyClass GetPrimitiveTopologyClass(gl::PrimitiveMode mode) { // NOTE(hqle): Support layered renderring in future. // In non-layered rendering mode, unspecified is enough. return MTLPrimitiveTopologyClassUnspecified; } #else // ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE PrimitiveTopologyClass GetPrimitiveTopologyClass(gl::PrimitiveMode mode) { return kPrimitiveTopologyClassTriangle; } #endif MTLPrimitiveType GetPrimitiveType(gl::PrimitiveMode mode) { switch (mode) { case gl::PrimitiveMode::Triangles: return MTLPrimitiveTypeTriangle; case gl::PrimitiveMode::Points: return MTLPrimitiveTypePoint; case gl::PrimitiveMode::Lines: return MTLPrimitiveTypeLine; case gl::PrimitiveMode::LineStrip: case gl::PrimitiveMode::LineLoop: return MTLPrimitiveTypeLineStrip; case gl::PrimitiveMode::TriangleStrip: return MTLPrimitiveTypeTriangleStrip; case gl::PrimitiveMode::TriangleFan: // NOTE(hqle): Emulate triangle fan. default: return MTLPrimitiveTypeInvalid; } } MTLIndexType GetIndexType(gl::DrawElementsType type) { switch (type) { case gl::DrawElementsType::UnsignedShort: return MTLIndexTypeUInt16; case gl::DrawElementsType::UnsignedInt: return MTLIndexTypeUInt32; case gl::DrawElementsType::UnsignedByte: // NOTE(hqle): Convert to supported type default: return MTLIndexTypeInvalid; } } MTLClearColor EmulatedAlphaClearColor(MTLClearColor color, MTLColorWriteMask colorMask) { MTLClearColor re = color; if (!(colorMask & MTLColorWriteMaskAlpha)) { re.alpha = kEmulatedAlphaValue; } return re; } } // namespace mtl } // namespace rx