blob: 8adc08c4867aa04ce8a653c5d44650995b914583 [file] [log] [blame]
//
// 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_format_utils.mm:
// Implements Format conversion utilities classes that convert from angle formats
// to respective MTLPixelFormat and MTLVertexFormat.
//
#include "libANGLE/renderer/metal/mtl_format_utils.h"
#include "common/debug.h"
#include "libANGLE/renderer/Format.h"
#include "libANGLE/renderer/load_functions_table.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
namespace rx
{
namespace mtl
{
namespace priv
{
template <typename T>
inline T *OffsetDataPointer(uint8_t *data, size_t y, size_t z, size_t rowPitch, size_t depthPitch)
{
return reinterpret_cast<T *>(data + (y * rowPitch) + (z * depthPitch));
}
template <typename T>
inline const T *OffsetDataPointer(const uint8_t *data,
size_t y,
size_t z,
size_t rowPitch,
size_t depthPitch)
{
return reinterpret_cast<const T *>(data + (y * rowPitch) + (z * depthPitch));
}
} // namespace priv
void LoadS8D24S8ToD32FX24S8(size_t width,
size_t height,
size_t depth,
const uint8_t *input,
size_t inputRowPitch,
size_t inputDepthPitch,
uint8_t *output,
size_t outputRowPitch,
size_t outputDepthPitch)
{
for (size_t z = 0; z < depth; z++)
{
for (size_t y = 0; y < height; y++)
{
const uint32_t *source =
priv::OffsetDataPointer<uint32_t>(input, y, z, inputRowPitch, inputDepthPitch);
float *destDepth =
priv::OffsetDataPointer<float>(output, y, z, outputRowPitch, outputDepthPitch);
uint32_t *destStencil =
priv::OffsetDataPointer<uint32_t>(output, y, z, outputRowPitch, outputDepthPitch) +
1;
for (size_t x = 0; x < width; x++)
{
destDepth[x * 2] = ((source[x] >> 8) & 0xFFFFFF) / static_cast<float>(0xFFFFFF);
destStencil[x * 2] = (source[x] & 0xFF);
}
}
}
}
static LoadImageFunctionInfo DEPTH24_STENCIL8_to_D32_FLOAT_X24S8_UINT(GLenum type)
{
switch (type)
{
case GL_UNSIGNED_INT_24_8:
return LoadImageFunctionInfo(LoadS8D24S8ToD32FX24S8, true);
default:
UNREACHABLE();
return LoadImageFunctionInfo(nullptr, true);
}
}
LoadFunctionMap GetLoadFunctionsMap(GLenum internalFormat, angle::FormatID angleFormat)
{
if (internalFormat == GL_DEPTH24_STENCIL8 &&
angleFormat == angle::FormatID::D32_FLOAT_S8X24_UINT)
{
return DEPTH24_STENCIL8_to_D32_FLOAT_X24S8_UINT;
}
return angle::GetLoadFunctionsMap(internalFormat, angleFormat);
}
namespace
{
bool OverrideTextureCaps(const DisplayMtl *display, angle::FormatID formatId, gl::TextureCaps *caps)
{
// NOTE(hqle): Auto generate this.
switch (formatId)
{
// NOTE: even though iOS devices don't support filtering depth textures, we still report as
// supported here in order for the OES_depth_texture extension to be enabled.
// During draw call, the filter modes will be converted to nearest.
case angle::FormatID::D16_UNORM:
case angle::FormatID::D24_UNORM_S8_UINT:
case angle::FormatID::D32_FLOAT_S8X24_UINT:
case angle::FormatID::D32_FLOAT:
case angle::FormatID::D32_UNORM:
caps->texturable = caps->filterable = caps->textureAttachment = caps->renderbuffer =
true;
return true;
default:
// NOTE(hqle): Handle more cases
return false;
}
}
void GenerateTextureCapsMap(const FormatTable &formatTable,
const DisplayMtl *display,
gl::TextureCapsMap *capsMapOut,
std::vector<GLenum> *compressedFormatsOut,
uint32_t *maxSamplesOut)
{
auto &textureCapsMap = *capsMapOut;
auto &compressedFormats = *compressedFormatsOut;
compressedFormats.clear();
auto formatVerifier = [&](const gl::InternalFormat &internalFormatInfo) {
angle::FormatID angleFormatId =
angle::Format::InternalFormatToID(internalFormatInfo.sizedInternalFormat);
const Format &mtlFormat = formatTable.getPixelFormat(angleFormatId);
if (!mtlFormat.valid())
{
return;
}
const FormatCaps &formatCaps = mtlFormat.getCaps();
const angle::Format &intendedAngleFormat = mtlFormat.intendedAngleFormat();
gl::TextureCaps textureCaps;
// First let check whether we can override certain special cases.
if (!OverrideTextureCaps(display, mtlFormat.intendedFormatId, &textureCaps))
{
// Fill the texture caps using pixel format's caps
textureCaps.filterable = mtlFormat.getCaps().filterable;
textureCaps.renderbuffer =
mtlFormat.getCaps().colorRenderable || mtlFormat.getCaps().depthRenderable;
textureCaps.texturable = true;
textureCaps.textureAttachment = textureCaps.renderbuffer;
textureCaps.blendable = mtlFormat.getCaps().blendable;
}
if (formatCaps.multisample)
{
constexpr uint32_t sampleCounts[] = {2, 4, 8};
for (auto sampleCount : sampleCounts)
{
if ([display->getMetalDevice() supportsTextureSampleCount:sampleCount])
{
textureCaps.sampleCounts.insert(sampleCount);
*maxSamplesOut = std::max(*maxSamplesOut, sampleCount);
}
}
}
textureCapsMap.set(mtlFormat.intendedFormatId, textureCaps);
if (intendedAngleFormat.isBlock)
{
compressedFormats.push_back(intendedAngleFormat.glInternalFormat);
}
};
// Texture caps map.
const gl::FormatSet &internalFormats = gl::GetAllSizedInternalFormats();
for (const auto internalFormat : internalFormats)
{
const gl::InternalFormat &internalFormatInfo =
gl::GetSizedInternalFormatInfo(internalFormat);
formatVerifier(internalFormatInfo);
}
}
} // namespace
// FormatBase implementation
const angle::Format &FormatBase::actualAngleFormat() const
{
return angle::Format::Get(actualFormatId);
}
const angle::Format &FormatBase::intendedAngleFormat() const
{
return angle::Format::Get(intendedFormatId);
}
// Format implementation
const gl::InternalFormat &Format::intendedInternalFormat() const
{
return gl::GetSizedInternalFormatInfo(intendedAngleFormat().glInternalFormat);
}
const gl::InternalFormat &Format::actualInternalFormat() const
{
return gl::GetSizedInternalFormatInfo(actualAngleFormat().glInternalFormat);
}
bool Format::needConversion(angle::FormatID srcFormatId) const
{
if ((srcFormatId == angle::FormatID::BC1_RGB_UNORM_BLOCK &&
actualFormatId == angle::FormatID::BC1_RGBA_UNORM_BLOCK) ||
(srcFormatId == angle::FormatID::BC1_RGB_UNORM_SRGB_BLOCK &&
actualFormatId == angle::FormatID::BC1_RGBA_UNORM_SRGB_BLOCK))
{
// When texture swizzling is available, DXT1 RGB format will be swizzled with RGB1.
// WebGL allows unswizzled mapping when swizzling is not available. No need to convert.
return false;
}
return srcFormatId != actualFormatId;
}
bool Format::isPVRTC() const
{
switch (metalFormat)
{
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || \
(TARGET_OS_OSX && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 110000))
case MTLPixelFormatPVRTC_RGB_2BPP:
case MTLPixelFormatPVRTC_RGB_2BPP_sRGB:
case MTLPixelFormatPVRTC_RGB_4BPP:
case MTLPixelFormatPVRTC_RGB_4BPP_sRGB:
case MTLPixelFormatPVRTC_RGBA_2BPP:
case MTLPixelFormatPVRTC_RGBA_2BPP_sRGB:
case MTLPixelFormatPVRTC_RGBA_4BPP:
case MTLPixelFormatPVRTC_RGBA_4BPP_sRGB:
return true;
#endif
default:
return false;
}
}
// FormatTable implementation
angle::Result FormatTable::initialize(const DisplayMtl *display)
{
mMaxSamples = 0;
// Initialize native format caps
initNativeFormatCaps(display);
for (size_t i = 0; i < angle::kNumANGLEFormats; ++i)
{
const auto formatId = static_cast<angle::FormatID>(i);
mPixelFormatTable[i].init(display, formatId);
mPixelFormatTable[i].caps = &mNativePixelFormatCapsTable[mPixelFormatTable[i].metalFormat];
if (mPixelFormatTable[i].actualFormatId != mPixelFormatTable[i].intendedFormatId)
{
mPixelFormatTable[i].textureLoadFunctions = mtl::GetLoadFunctionsMap(
mPixelFormatTable[i].intendedAngleFormat().glInternalFormat,
mPixelFormatTable[i].actualFormatId);
}
mVertexFormatTables[0][i].init(formatId, false);
mVertexFormatTables[1][i].init(formatId, true);
}
// TODO(anglebug.com/5505): unmerged change from WebKit was here -
// D24S8 fallback to D32_FLOAT_S8X24_UINT, since removed.
return angle::Result::Continue;
}
void FormatTable::generateTextureCaps(const DisplayMtl *display,
gl::TextureCapsMap *capsMapOut,
std::vector<GLenum> *compressedFormatsOut)
{
GenerateTextureCapsMap(*this, display, capsMapOut, compressedFormatsOut, &mMaxSamples);
}
const Format &FormatTable::getPixelFormat(angle::FormatID angleFormatId) const
{
return mPixelFormatTable[static_cast<size_t>(angleFormatId)];
}
const FormatCaps &FormatTable::getNativeFormatCaps(MTLPixelFormat mtlFormat) const
{
ASSERT(mNativePixelFormatCapsTable.count(mtlFormat));
return mNativePixelFormatCapsTable.at(mtlFormat);
}
const VertexFormat &FormatTable::getVertexFormat(angle::FormatID angleFormatId,
bool tightlyPacked) const
{
auto tableIdx = tightlyPacked ? 1 : 0;
return mVertexFormatTables[tableIdx][static_cast<size_t>(angleFormatId)];
}
void FormatTable::setFormatCaps(MTLPixelFormat formatId,
bool filterable,
bool writable,
bool blendable,
bool multisample,
bool resolve,
bool colorRenderable)
{
setFormatCaps(formatId, filterable, writable, blendable, multisample, resolve, colorRenderable,
false, 0);
}
void FormatTable::setFormatCaps(MTLPixelFormat formatId,
bool filterable,
bool writable,
bool blendable,
bool multisample,
bool resolve,
bool colorRenderable,
NSUInteger pixelBytes,
NSUInteger channels)
{
setFormatCaps(formatId, filterable, writable, blendable, multisample, resolve, colorRenderable,
false, pixelBytes, channels);
}
void FormatTable::setFormatCaps(MTLPixelFormat formatId,
bool filterable,
bool writable,
bool blendable,
bool multisample,
bool resolve,
bool colorRenderable,
bool depthRenderable)
{
setFormatCaps(formatId, filterable, writable, blendable, multisample, resolve, colorRenderable,
depthRenderable, 0, 0);
}
void FormatTable::setFormatCaps(MTLPixelFormat id,
bool filterable,
bool writable,
bool blendable,
bool multisample,
bool resolve,
bool colorRenderable,
bool depthRenderable,
NSUInteger pixelBytes,
NSUInteger channels)
{
mNativePixelFormatCapsTable[id].filterable = filterable;
mNativePixelFormatCapsTable[id].writable = writable;
mNativePixelFormatCapsTable[id].colorRenderable = colorRenderable;
mNativePixelFormatCapsTable[id].depthRenderable = depthRenderable;
mNativePixelFormatCapsTable[id].blendable = blendable;
mNativePixelFormatCapsTable[id].multisample = multisample;
mNativePixelFormatCapsTable[id].resolve = resolve;
mNativePixelFormatCapsTable[id].pixelBytes = pixelBytes;
mNativePixelFormatCapsTable[id].pixelBytesMSAA = pixelBytes;
mNativePixelFormatCapsTable[id].channels = channels;
if (channels != 0)
mNativePixelFormatCapsTable[id].alignment = MAX(pixelBytes / channels, 1U);
}
void FormatTable::setCompressedFormatCaps(MTLPixelFormat formatId, bool filterable)
{
setFormatCaps(formatId, filterable, false, false, false, false, false, false);
}
void FormatTable::adjustFormatCapsForDevice(const mtl::ContextDevice &device,
MTLPixelFormat id,
bool supportsiOS2,
bool supportsiOS4)
{
#if !(TARGET_OS_OSX || TARGET_OS_MACCATALYST)
NSUInteger pixelBytesRender = mNativePixelFormatCapsTable[id].pixelBytes;
NSUInteger pixelBytesRenderMSAA = mNativePixelFormatCapsTable[id].pixelBytesMSAA;
NSUInteger alignment = mNativePixelFormatCapsTable[id].alignment;
// Override the current pixelBytesRender
# define SPECIFIC(_pixelFormat, _pixelBytesRender) \
case _pixelFormat: \
pixelBytesRender = _pixelBytesRender; \
pixelBytesRenderMSAA = _pixelBytesRender; \
alignment = \
supportsiOS4 ? _pixelBytesRender / mNativePixelFormatCapsTable[id].channels : 4; \
break
// Override the current pixel bytes render, and MSAA
# define SPECIFIC_MSAA(_pixelFormat, _pixelBytesRender, _pixelBytesRenderMSAA) \
case _pixelFormat: \
pixelBytesRender = _pixelBytesRender; \
pixelBytesRenderMSAA = _pixelBytesRenderMSAA; \
alignment = \
supportsiOS4 ? _pixelBytesRender / mNativePixelFormatCapsTable[id].channels : 4; \
break
// Override the current pixelBytesRender, and alignment
# define SPECIFIC_ALIGN(_pixelFormat, _pixelBytesRender, _alignment) \
case _pixelFormat: \
pixelBytesRender = _pixelBytesRender; \
pixelBytesRenderMSAA = _pixelBytesRender; \
alignment = _alignment; \
break
if (!mNativePixelFormatCapsTable[id].compressed)
{
// On AppleGPUFamily4+, there is no 4byte minimum requirement for render targets
uint32_t minSize = supportsiOS4 ? 1U : 4U;
pixelBytesRender = MAX(mNativePixelFormatCapsTable[id].pixelBytes, minSize);
pixelBytesRenderMSAA = pixelBytesRender;
alignment =
supportsiOS4 ? MAX(pixelBytesRender / mNativePixelFormatCapsTable[id].channels, 1U) : 4;
}
// This list of tables starts from a general multi-platform table,
// to specific platforms (i.e. ios2, ios4) inheriting from the previous tables
// Start off with the general case
switch ((NSUInteger)id)
{
SPECIFIC(MTLPixelFormatB5G6R5Unorm, 4U);
SPECIFIC(MTLPixelFormatA1BGR5Unorm, 4U);
SPECIFIC(MTLPixelFormatABGR4Unorm, 4U);
SPECIFIC(MTLPixelFormatBGR5A1Unorm, 4U);
SPECIFIC(MTLPixelFormatRGBA8Unorm, 4U);
SPECIFIC(MTLPixelFormatBGRA8Unorm, 4U);
SPECIFIC_MSAA(MTLPixelFormatRGBA8Unorm_sRGB, 4U, 8U);
SPECIFIC_MSAA(MTLPixelFormatBGRA8Unorm_sRGB, 4U, 8U);
SPECIFIC_MSAA(MTLPixelFormatRGBA8Snorm, 4U, 8U);
SPECIFIC_MSAA(MTLPixelFormatRGB10A2Uint, 4U, 8U);
SPECIFIC(MTLPixelFormatRGB10A2Unorm, 8U);
SPECIFIC(MTLPixelFormatBGR10A2Unorm, 8U);
SPECIFIC(MTLPixelFormatRG11B10Float, 8U);
SPECIFIC(MTLPixelFormatRGB9E5Float, 8U);
SPECIFIC(MTLPixelFormatStencil8, 1U);
}
// Override based ios2
if (supportsiOS2)
{
switch ((NSUInteger)id)
{
SPECIFIC(MTLPixelFormatB5G6R5Unorm, 8U);
SPECIFIC(MTLPixelFormatA1BGR5Unorm, 8U);
SPECIFIC(MTLPixelFormatABGR4Unorm, 8U);
SPECIFIC(MTLPixelFormatBGR5A1Unorm, 8U);
SPECIFIC_MSAA(MTLPixelFormatRGBA8Unorm, 4U, 8U);
SPECIFIC_MSAA(MTLPixelFormatBGRA8Unorm, 4U, 8U);
}
}
// Override based on ios4
if (supportsiOS4)
{
switch ((NSUInteger)id)
{
SPECIFIC_ALIGN(MTLPixelFormatB5G6R5Unorm, 6U, 2U);
SPECIFIC(MTLPixelFormatRGBA8Unorm, 4U);
SPECIFIC(MTLPixelFormatBGRA8Unorm, 4U);
SPECIFIC(MTLPixelFormatRGBA8Unorm_sRGB, 4U);
SPECIFIC(MTLPixelFormatBGRA8Unorm_sRGB, 4U);
SPECIFIC(MTLPixelFormatRGBA8Snorm, 4U);
SPECIFIC_ALIGN(MTLPixelFormatRGB10A2Unorm, 4U, 4U);
SPECIFIC_ALIGN(MTLPixelFormatBGR10A2Unorm, 4U, 4U);
SPECIFIC(MTLPixelFormatRGB10A2Uint, 8U);
SPECIFIC_ALIGN(MTLPixelFormatRG11B10Float, 4U, 4U);
SPECIFIC_ALIGN(MTLPixelFormatRGB9E5Float, 4U, 4U);
}
}
mNativePixelFormatCapsTable[id].pixelBytes = pixelBytesRender;
mNativePixelFormatCapsTable[id].pixelBytesMSAA = pixelBytesRenderMSAA;
mNativePixelFormatCapsTable[id].alignment = alignment;
# undef SPECIFIC
# undef SPECIFIC_ALIGN
# undef SPECIFIC_MSAA
#endif
// macOS does not need to perform any additoinal adjustment. These values are only used to check
// valid MRT sizes on iOS.
}
void FormatTable::initNativeFormatCaps(const DisplayMtl *display)
{
initNativeFormatCapsAutogen(display);
}
} // namespace mtl
} // namespace rx