Commit 9277ee74 by Le Hoang Quyen Committed by Commit Bot

Metal: Implement MSAA default framebuffer.

GL_SAMPLE_COVERAGE_VALUE is implemented by inserting gl_SampleMask writing logic to fragment shader. New test added: MultisampleTest.ContentPresevedAfterInterruption. - Skip on D3D11 (Bug: angleproject:4609) Bug: angleproject:2634 Change-Id: Ib44daf0baccc36ea320596d81713156047da059c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2281783 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent f9e01f12
......@@ -26,7 +26,7 @@
// Version number for shader translation API.
// It is incremented every time the API changes.
#define ANGLE_SH_VERSION 230
#define ANGLE_SH_VERSION 231
enum ShShaderSpec
{
......@@ -790,6 +790,12 @@ extern const char kAtomicCountersBlockName[];
extern const char kLineRasterEmulationPosition[];
} // namespace vk
namespace mtl
{
// Specialization constant to enable GL_SAMPLE_COVERAGE_VALUE emulation.
extern const char kCoverageMaskEnabledConstName[];
} // namespace mtl
} // namespace sh
#endif // GLSLANG_SHADERLANG_H_
......@@ -41,6 +41,10 @@ struct FeaturesMtl : FeatureSetBase
"Some Apple platforms such as iOS allows separate depth & stencil buffers, "
"whereas others such as macOS don't",
&members};
Feature allowMultisampleStoreAndResolve = {
"allow_msaa_store_and_resolve", FeatureCategory::MetalFeatures,
"The renderer supports MSAA store and resolve in the same pass", &members};
};
} // namespace angle
......
{
"src/libANGLE/renderer/metal/shaders/blit.metal":
"9281aba529ceb4ed5131b7f9c0515362",
"1a12b22f56799bd38cf1c6b301b720ee",
"src/libANGLE/renderer/metal/shaders/clear.metal":
"1c231afc6100433a79fce49046aa5965",
"src/libANGLE/renderer/metal/shaders/common.h":
"569171e345ef36dd6a3b12aeebfae4a6",
"7330bd3f7ab21214e4fe16bc526749bb",
"src/libANGLE/renderer/metal/shaders/compiled/mtl_default_shaders.inc":
"c42c37285c375de9a065b925ed54aebd",
"445538e99fb3679c3c8436f565911c69",
"src/libANGLE/renderer/metal/shaders/constants.h":
"9bb6e63bf2b48a7a56978c787bde4850",
"src/libANGLE/renderer/metal/shaders/gen_indices.metal":
"002511e2b980a7fca7e80cbda6a82712",
"src/libANGLE/renderer/metal/shaders/gen_mtl_internal_shaders.py":
"8de75752bb966cdbe575defc04fa7a7a",
"0e599fb113dbc3f714291383d85c39c2",
"src/libANGLE/renderer/metal/shaders/master_source.metal":
"fbe6f4bfb49a48ae87791a4cff5fab0a",
"src/libANGLE/renderer/metal/shaders/mtl_default_shaders_src_autogen.inc":
"8a94beb0d979f472d71686b6f95b624f"
"ee7ff414da20e7b84f1187d2bea1e84d"
}
\ No newline at end of file
......@@ -25,9 +25,31 @@
namespace sh
{
namespace mtl
{
/** extern */
const char kCoverageMaskEnabledConstName[] = "ANGLECoverageMaskEnabled";
} // namespace mtl
namespace
{
constexpr ImmutableString kCoverageMaskField = ImmutableString("coverageMask");
constexpr ImmutableString kSampleMaskWriteFuncName = ImmutableString("ANGLEWriteSampleMask");
TIntermBinary *CreateDriverUniformRef(const TVariable *driverUniforms, const char *fieldName)
{
size_t fieldIndex =
FindFieldIndex(driverUniforms->getType().getInterfaceBlock()->fields(), fieldName);
TIntermSymbol *angleUniformsRef = new TIntermSymbol(driverUniforms);
TConstantUnion *uniformIndex = new TConstantUnion;
uniformIndex->setIConst(static_cast<int>(fieldIndex));
TIntermConstantUnion *indexRef =
new TIntermConstantUnion(uniformIndex, *StaticType::GetBasic<EbtInt>());
return new TIntermBinary(EOpIndexDirectInterfaceBlock, angleUniformsRef, indexRef);
}
// Unlike Vulkan having auto viewport flipping extension, in Metal we have to flip gl_Position.y
// manually.
// This operation performs flipping the gl_Position.y using this expression:
......@@ -89,6 +111,13 @@ bool TranslatorMetal::translate(TIntermBlock *root,
return false;
}
}
else if (getShaderType() == GL_FRAGMENT_SHADER)
{
if (!insertSampleMaskWritingLogic(root, driverUniforms))
{
return false;
}
}
// Write translated shader.
root->traverse(&outputGLSL);
......@@ -124,4 +153,70 @@ bool TranslatorMetal::transformDepthBeforeCorrection(TIntermBlock *root,
return RunAtTheEndOfShader(this, root, assignment, &getSymbolTable());
}
void TranslatorMetal::createAdditionalGraphicsDriverUniformFields(std::vector<TField *> *fieldsOut)
{
// Add coverage mask to driver uniform. Metal doesn't have built-in GL_SAMPLE_COVERAGE_VALUE
// equivalent functionality, needs to emulate it using fragment shader's [[sample_mask]] output
// value.
TField *coverageMaskField =
new TField(new TType(EbtUInt), kCoverageMaskField, TSourceLoc(), SymbolType::AngleInternal);
fieldsOut->push_back(coverageMaskField);
}
// Add sample_mask writing to main, guarded by the specialization constant
// kCoverageMaskEnabledConstName
ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(TIntermBlock *root,
const TVariable *driverUniforms)
{
TInfoSinkBase &sink = getInfoSink().obj;
TSymbolTable *symbolTable = &getSymbolTable();
// Insert coverageMaskEnabled specialization constant and sample_mask writing function.
sink << "layout (constant_id=0) const bool " << mtl::kCoverageMaskEnabledConstName;
sink << " = false;\n";
sink << "void " << kSampleMaskWriteFuncName << "(uint mask)\n";
sink << "{\n";
sink << " if (" << mtl::kCoverageMaskEnabledConstName << ")\n";
sink << " {\n";
sink << " gl_SampleMask[0] = int(mask);\n";
sink << " }\n";
sink << "}\n";
// Create kCoverageMaskEnabledConstName and kSampleMaskWriteFuncName variable references.
TType *boolType = new TType(EbtBool);
boolType->setQualifier(EvqConst);
TVariable *coverageMaskEnabledVar =
new TVariable(symbolTable, ImmutableString(mtl::kCoverageMaskEnabledConstName), boolType,
SymbolType::AngleInternal);
TFunction *sampleMaskWriteFunc =
new TFunction(symbolTable, kSampleMaskWriteFuncName, SymbolType::AngleInternal,
StaticType::GetBasic<EbtVoid>(), false);
TType *uintType = new TType(EbtUInt);
TVariable *maskArg =
new TVariable(symbolTable, ImmutableString("mask"), uintType, SymbolType::AngleInternal);
sampleMaskWriteFunc->addParameter(maskArg);
// coverageMask
TIntermBinary *coverageMask = CreateDriverUniformRef(driverUniforms, kCoverageMaskField.data());
// Insert this code to the end of main()
// if (ANGLECoverageMaskEnabled)
// {
// ANGLEWriteSampleMask(ANGLEUniforms.coverageMask);
// }
TIntermSequence *args = new TIntermSequence;
args->push_back(coverageMask);
TIntermAggregate *callSampleMaskWriteFunc =
TIntermAggregate::CreateFunctionCall(*sampleMaskWriteFunc, args);
TIntermBlock *callBlock = new TIntermBlock;
callBlock->appendStatement(callSampleMaskWriteFunc);
TIntermSymbol *coverageMaskEnabled = new TIntermSymbol(coverageMaskEnabledVar);
TIntermIfElse *ifCall = new TIntermIfElse(coverageMaskEnabled, callBlock, nullptr);
return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
}
} // namespace sh
......@@ -32,6 +32,11 @@ class TranslatorMetal : public TranslatorVulkan
ANGLE_NO_DISCARD bool transformDepthBeforeCorrection(TIntermBlock *root,
const TVariable *driverUniforms) override;
void createAdditionalGraphicsDriverUniformFields(std::vector<TField *> *fieldsOut) override;
ANGLE_NO_DISCARD bool insertSampleMaskWritingLogic(TIntermBlock *root,
const TVariable *driverUniforms);
};
} // namespace sh
......
......@@ -196,19 +196,6 @@ constexpr size_t kNumComputeDriverUniforms
constexpr std::array<const char *, kNumComputeDriverUniforms> kComputeDriverUniformNames = {
{kAcbBufferOffsets}};
size_t FindFieldIndex(const TFieldList &fieldList, const char *fieldName)
{
for (size_t fieldIndex = 0; fieldIndex < fieldList.size(); ++fieldIndex)
{
if (strcmp(fieldList[fieldIndex]->name().data(), fieldName) == 0)
{
return fieldIndex;
}
}
UNREACHABLE();
return 0;
}
TIntermBinary *CreateDriverUniformRef(const TVariable *driverUniforms, const char *fieldName)
{
size_t fieldIndex =
......@@ -389,7 +376,9 @@ ANGLE_NO_DISCARD bool AppendVertexShaderTransformFeedbackOutputToMain(TCompiler
// variable.
//
// There are Graphics and Compute variations as they require different uniforms.
const TVariable *AddGraphicsDriverUniformsToShader(TIntermBlock *root, TSymbolTable *symbolTable)
const TVariable *AddGraphicsDriverUniformsToShader(TIntermBlock *root,
TSymbolTable *symbolTable,
const std::vector<TField *> &additionalFields)
{
// Init the depth range type.
TFieldList *depthRangeParamsFields = new TFieldList();
......@@ -445,6 +434,10 @@ const TVariable *AddGraphicsDriverUniformsToShader(TIntermBlock *root, TSymbolTa
driverFieldList->push_back(driverUniformField);
}
// Back-end specific fields
driverFieldList->insert(driverFieldList->end(), additionalFields.begin(),
additionalFields.end());
// Define a driver uniform block "ANGLEUniformBlock" with instance name "ANGLEUniforms".
return DeclareInterfaceBlock(
root, symbolTable, driverFieldList, EvqUniform, TMemoryQualifier::Create(), 0,
......@@ -865,7 +858,10 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root,
}
else
{
driverUniforms = AddGraphicsDriverUniformsToShader(root, &getSymbolTable());
std::vector<TField *> additionalFields;
createAdditionalGraphicsDriverUniformFields(&additionalFields);
driverUniforms =
AddGraphicsDriverUniformsToShader(root, &getSymbolTable(), additionalFields);
}
if (atomicCounterCount > 0)
......
......@@ -48,6 +48,9 @@ class TranslatorVulkan : public TCompiler
{
return true;
}
// Back-end specific fields to be added to driver uniform. See TranslatorMetal.cpp.
virtual void createAdditionalGraphicsDriverUniformFields(std::vector<TField *> *fieldsOut) {}
};
} // namespace sh
......
......@@ -962,4 +962,17 @@ bool IsValidImplicitConversion(sh::ImplicitTypeConversion conversion, TOperator
return false;
}
size_t FindFieldIndex(const TFieldList &fieldList, const char *fieldName)
{
for (size_t fieldIndex = 0; fieldIndex < fieldList.size(); ++fieldIndex)
{
if (strcmp(fieldList[fieldIndex]->name().data(), fieldName) == 0)
{
return fieldIndex;
}
}
UNREACHABLE();
return 0;
}
} // namespace sh
......@@ -88,6 +88,8 @@ bool IsSpecWithFunctionBodyNewScope(ShShaderSpec shaderSpec, int shaderVersion);
ImplicitTypeConversion GetConversion(TBasicType t1, TBasicType t2);
bool IsValidImplicitConversion(ImplicitTypeConversion conversion, TOperator op);
size_t FindFieldIndex(const TFieldList &fieldList, const char *fieldName);
} // namespace sh
#endif // COMPILER_TRANSLATOR_UTIL_H_
......@@ -55,6 +55,7 @@ _metal_backend_sources = [
"mtl_utils.h",
"mtl_utils.mm",
"shaders/compiled/mtl_default_shaders.inc",
"shaders/constants.h",
"shaders/mtl_default_shaders_src_autogen.inc",
]
......
......@@ -27,6 +27,7 @@ class DisplayMtl;
class FramebufferMtl;
class VertexArrayMtl;
class ProgramMtl;
class WindowSurfaceMtl;
class ContextMtl : public ContextImpl, public mtl::Context
{
......@@ -266,6 +267,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
// Call this to notify ContextMtl whenever FramebufferMtl's state changed
void onDrawFrameBufferChange(const gl::Context *context, FramebufferMtl *framebuffer);
void onBackbufferResized(const gl::Context *context, WindowSurfaceMtl *backbuffer);
const MTLClearColor &getClearColorValue() const;
MTLColorWriteMask getColorMask() const;
......@@ -296,13 +298,10 @@ class ContextMtl : public ContextImpl, public mtl::Context
// Check whether compatible render pass has been started.
bool hasStartedRenderPass(const mtl::RenderPassDesc &desc);
bool hasStartedRenderPass(FramebufferMtl *framebuffer);
// Get current render encoder. May be nullptr if no render pass has been started.
mtl::RenderCommandEncoder *getRenderCommandEncoder();
mtl::RenderCommandEncoder *getCurrentFramebufferRenderCommandEncoder();
// Will end current command encoder if it is valid, then start new encoder.
// Unless hasStartedRenderPass(desc) returns true.
mtl::RenderCommandEncoder *getRenderCommandEncoder(const mtl::RenderPassDesc &desc);
......@@ -448,6 +447,10 @@ class ContextMtl : public ContextImpl, public mtl::Context
// Used to pre-rotate gl_FragCoord for Vulkan swapchain images on Android (a mat2, which is
// padded to the size of two vec4's).
float fragRotation[8];
uint32_t coverageMask;
float padding2[3];
};
struct DefaultAttribute
......@@ -466,12 +469,6 @@ class ContextMtl : public ContextImpl, public mtl::Context
VertexArrayMtl *mVertexArray = nullptr;
ProgramMtl *mProgram = nullptr;
// Special flag to indicate current draw framebuffer is default framebuffer.
// We need this instead of calling mDrawFramebuffer->getState().isDefault() because
// mDrawFramebuffer might point to a deleted object, ContextMtl only knows about this very late,
// only during syncState() function call.
bool mDrawFramebufferIsDefault = true;
using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
gl::AttributesMask mDirtyDefaultAttribsMask;
......
......@@ -654,19 +654,19 @@ angle::Result ContextMtl::syncState(const gl::Context *context,
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_COVERAGE_ENABLED:
// NOTE(hqle): MSAA support
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_SAMPLE_COVERAGE_ENABLED:
// NOTE(hqle): MSAA support
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_SAMPLE_COVERAGE:
// NOTE(hqle): MSAA support
invalidateDriverUniforms();
break;
case gl::State::DIRTY_BIT_SAMPLE_MASK_ENABLED:
// NOTE(hqle): MSAA support
// NOTE(hqle): 3.1 MSAA support
break;
case gl::State::DIRTY_BIT_SAMPLE_MASK:
// NOTE(hqle): MSAA support
// NOTE(hqle): 3.1 MSAA support
break;
case gl::State::DIRTY_BIT_DEPTH_TEST_ENABLED:
mDepthStencilDesc.updateDepthTestEnabled(glState.getDepthStencilState());
......@@ -801,7 +801,7 @@ angle::Result ContextMtl::syncState(const gl::Context *context,
invalidateCurrentTextures();
break;
case gl::State::DIRTY_BIT_MULTISAMPLING:
// NOTE(hqle): MSAA feature.
// NOTE(hqle): MSAA on/off.
break;
case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_ONE:
// NOTE(hqle): this is part of EXT_multisample_compatibility.
......@@ -896,7 +896,7 @@ ProgramImpl *ContextMtl::createProgram(const gl::ProgramState &state)
// Framebuffer creation
FramebufferImpl *ContextMtl::createFramebuffer(const gl::FramebufferState &state)
{
return new FramebufferMtl(state, false);
return new FramebufferMtl(state, false, nullptr);
}
// Texture creation
......@@ -1170,16 +1170,10 @@ void ContextMtl::present(const gl::Context *context, id<CAMetalDrawable> present
{
ensureCommandBufferReady();
// Always discard default FBO's depth stencil buffers at the end of the frame:
if (mDrawFramebufferIsDefault && hasStartedRenderPass(mDrawFramebuffer))
FramebufferMtl *currentframebuffer = mtl::GetImpl(getState().getDrawFramebuffer());
if (currentframebuffer)
{
constexpr GLenum dsAttachments[] = {GL_DEPTH, GL_STENCIL};
(void)mDrawFramebuffer->invalidate(context, 2, dsAttachments);
endEncoding(false);
// Reset discard flag by notify framebuffer that a new render pass has started.
mDrawFramebuffer->onStartedDrawingToFrameBuffer(context);
currentframebuffer->onFrameEnd(context);
}
endEncoding(false);
......@@ -1202,11 +1196,6 @@ bool ContextMtl::hasStartedRenderPass(const mtl::RenderPassDesc &desc)
mRenderEncoder.renderPassDesc().equalIgnoreLoadStoreOptions(desc);
}
bool ContextMtl::hasStartedRenderPass(FramebufferMtl *framebuffer)
{
return framebuffer && hasStartedRenderPass(framebuffer->getRenderPassDesc(this));
}
// Get current render encoder
mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder()
{
......@@ -1218,16 +1207,6 @@ mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder()
return &mRenderEncoder;
}
mtl::RenderCommandEncoder *ContextMtl::getCurrentFramebufferRenderCommandEncoder()
{
if (!mDrawFramebuffer)
{
return nullptr;
}
return getRenderCommandEncoder(mDrawFramebuffer->getRenderPassDesc(this));
}
mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder(const mtl::RenderPassDesc &desc)
{
if (hasStartedRenderPass(desc))
......@@ -1256,10 +1235,10 @@ mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder(
mtl::RenderPassDesc rpDesc;
rpDesc.colorAttachments[0].texture = textureTarget;
rpDesc.colorAttachments[0].level = index.getLevelIndex();
rpDesc.colorAttachments[0].slice = index.hasLayer() ? index.getLayerIndex() : 0;
rpDesc.numColorAttachments = 1;
rpDesc.colorAttachments[0].texture = textureTarget;
rpDesc.colorAttachments[0].level = index.getLevelIndex();
rpDesc.colorAttachments[0].sliceOrDepth = index.hasLayer() ? index.getLayerIndex() : 0;
rpDesc.numColorAttachments = 1;
if (clearColor.valid())
{
......@@ -1415,8 +1394,7 @@ void ContextMtl::updateDrawFrameBufferBinding(const gl::Context *context)
{
const gl::State &glState = getState();
mDrawFramebuffer = mtl::GetImpl(glState.getDrawFramebuffer());
mDrawFramebufferIsDefault = mDrawFramebuffer->getState().isDefault();
mDrawFramebuffer = mtl::GetImpl(glState.getDrawFramebuffer());
mDrawFramebuffer->onStartedDrawingToFrameBuffer(context);
......@@ -1435,10 +1413,26 @@ void ContextMtl::onDrawFrameBufferChange(const gl::Context *context, Framebuffer
updateFrontFace(glState);
updateScissor(glState);
// End any render encoding using the old render pass.
endEncoding(false);
// Need to re-apply state to RenderCommandEncoder
invalidateState(context);
}
void ContextMtl::onBackbufferResized(const gl::Context *context, WindowSurfaceMtl *backbuffer)
{
const gl::State &glState = getState();
FramebufferMtl *framebuffer = mtl::GetImpl(glState.getDrawFramebuffer());
if (framebuffer->getAttachedBackbuffer() != backbuffer)
{
return;
}
updateViewport(framebuffer, glState.getViewport(), glState.getNearPlane(),
glState.getFarPlane());
updateScissor(glState);
}
void ContextMtl::updateProgramExecutable(const gl::Context *context)
{
// Need to rebind textures
......@@ -1519,8 +1513,7 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
if (mDirtyBits.test(DIRTY_BIT_DRAW_FRAMEBUFFER))
{
// Start new render command encoder
const mtl::RenderPassDesc &rpDesc = mDrawFramebuffer->getRenderPassDesc(this);
ANGLE_MTL_TRY(this, getRenderCommandEncoder(rpDesc));
ANGLE_MTL_TRY(this, mDrawFramebuffer->ensureRenderPassStarted(context));
// re-apply everything
invalidateState(context);
......@@ -1729,6 +1722,19 @@ angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context)
mDriverUniforms.fragRotation[6] = 0.0f;
mDriverUniforms.fragRotation[7] = 0.0f;
// Sample coverage mask
uint32_t sampleBitCount = mDrawFramebuffer->getSamples();
uint32_t coverageSampleBitCount =
static_cast<uint32_t>(std::round(mState.getSampleCoverageValue() * sampleBitCount));
ASSERT(sampleBitCount < 32);
uint32_t coverageMask = (1u << coverageSampleBitCount) - 1;
uint32_t sampleMask = (1u << sampleBitCount) - 1;
if (mState.getSampleCoverageInvert())
{
coverageMask = sampleMask & (~coverageMask);
}
mDriverUniforms.coverageMask = coverageMask;
ASSERT(mRenderEncoder.valid());
mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
......@@ -1742,7 +1748,7 @@ angle::Result ContextMtl::handleDirtyDepthStencilState(const gl::Context *contex
// Need to handle the case when render pass doesn't have depth/stencil attachment.
mtl::DepthStencilDesc dsDesc = mDepthStencilDesc;
const mtl::RenderPassDesc &renderPassDesc = mDrawFramebuffer->getRenderPassDesc(this);
const mtl::RenderPassDesc &renderPassDesc = mRenderEncoder.renderPassDesc();
if (!renderPassDesc.depthAttachment.texture)
{
......@@ -1796,12 +1802,14 @@ angle::Result ContextMtl::checkIfPipelineChanged(
if (rppChange)
{
const mtl::RenderPassDesc &renderPassDesc = mDrawFramebuffer->getRenderPassDesc(this);
const mtl::RenderPassDesc &renderPassDesc = mRenderEncoder.renderPassDesc();
// Obtain RenderPipelineDesc's output descriptor.
renderPassDesc.populateRenderPipelineOutputDesc(mBlendDesc,
&mRenderPipelineDesc.outputDescriptor);
mRenderPipelineDesc.inputPrimitiveTopology = topologyClass;
mRenderPipelineDesc.alphaToCoverageEnabled = mState.isSampleAlphaToCoverageEnabled();
mRenderPipelineDesc.emulateCoverageMask = mState.isSampleCoverageEnabled();
*changedPipelineDesc = mRenderPipelineDesc;
}
......
......@@ -295,8 +295,13 @@ egl::ConfigSet DisplayMtl::generateConfigs()
config.surfaceType = EGL_WINDOW_BIT;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
config.minSwapInterval = 0;
config.maxSwapInterval = 1;
#else
config.minSwapInterval = 1;
config.maxSwapInterval = 1;
#endif
config.renderTargetFormat = GL_RGBA8;
config.depthStencilFormat = GL_DEPTH24_STENCIL8;
......@@ -308,40 +313,52 @@ egl::ConfigSet DisplayMtl::generateConfigs()
config.colorComponentType = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;
// Buffer sizes
config.redSize = 8;
config.greenSize = 8;
config.blueSize = 8;
config.alphaSize = 8;
config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize;
// With DS
config.depthSize = 24;
config.stencilSize = 8;
configs.add(config);
// With D
config.depthSize = 24;
config.stencilSize = 0;
configs.add(config);
// With S
config.depthSize = 0;
config.stencilSize = 8;
configs.add(config);
// No DS
config.depthSize = 0;
config.stencilSize = 0;
configs.add(config);
constexpr int samplesSupported[] = {0, 4};
for (int samples : samplesSupported)
{
config.samples = samples;
config.sampleBuffers = (samples == 0) ? 0 : 1;
// Buffer sizes
config.redSize = 8;
config.greenSize = 8;
config.blueSize = 8;
config.alphaSize = 8;
config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize;
// With DS
config.depthSize = 24;
config.stencilSize = 8;
configs.add(config);
// With D
config.depthSize = 24;
config.stencilSize = 0;
configs.add(config);
// With S
config.depthSize = 0;
config.stencilSize = 8;
configs.add(config);
// No DS
config.depthSize = 0;
config.stencilSize = 0;
configs.add(config);
}
return configs;
}
bool DisplayMtl::isValidNativeWindow(EGLNativeWindowType window) const
{
NSObject *layer = (__bridge NSObject *)(window);
return [layer isKindOfClass:[CALayer class]];
ANGLE_MTL_OBJC_SCOPE
{
NSObject *layer = (__bridge NSObject *)(window);
return [layer isKindOfClass:[CALayer class]];
}
}
std::string DisplayMtl::getRendererDescription() const
......@@ -611,7 +628,8 @@ void DisplayMtl::initializeFeatures()
mFeatures.allowSeparatedDepthStencilBuffers.enabled = false;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
mFeatures.hasDepthTextureFiltering.enabled = true;
mFeatures.hasDepthTextureFiltering.enabled = true;
mFeatures.allowMultisampleStoreAndResolve.enabled = true;
// Texture swizzle is only supported if macos sdk 10.15 is present
# if defined(__MAC_10_15)
......@@ -630,6 +648,9 @@ void DisplayMtl::initializeFeatures()
ANGLE_FEATURE_CONDITION((&mFeatures), hasNonUniformDispatch,
[getMetalDevice() supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]);
ANGLE_FEATURE_CONDITION((&mFeatures), allowMultisampleStoreAndResolve,
[getMetalDevice() supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]);
# if !TARGET_OS_SIMULATOR
mFeatures.allowSeparatedDepthStencilBuffers.enabled = true;
# endif
......@@ -637,6 +658,8 @@ void DisplayMtl::initializeFeatures()
angle::PlatformMethods *platform = ANGLEPlatformCurrent();
platform->overrideFeaturesMtl(platform, &mFeatures);
ApplyFeatureOverrides(&mFeatures, getState());
}
angle::Result DisplayMtl::initializeShaderLibrary()
......
......@@ -18,13 +18,17 @@
namespace rx
{
namespace mtl
{
class RenderCommandEncoder;
} // namespace mtl
class ContextMtl;
class SurfaceMtl;
class WindowSurfaceMtl;
class FramebufferMtl : public FramebufferImpl
{
public:
explicit FramebufferMtl(const gl::FramebufferState &state, bool flipY);
FramebufferMtl(const gl::FramebufferState &state, bool flipY, WindowSurfaceMtl *backbuffer);
~FramebufferMtl() override;
void destroy(const gl::Context *context) override;
......@@ -84,19 +88,26 @@ class FramebufferMtl : public FramebufferImpl
size_t index,
GLfloat *xy) const override;
RenderTargetMtl *getColorReadRenderTarget() const;
RenderTargetMtl *getColorReadRenderTarget(const gl::Context *context) const;
RenderTargetMtl *getDepthRenderTarget() const { return mDepthRenderTarget; }
RenderTargetMtl *getStencilRenderTarget() const { return mStencilRenderTarget; }
bool flipY() const { return mFlipY; }
gl::Rectangle getCompleteRenderArea() const;
int getSamples() const;
WindowSurfaceMtl *getAttachedBackbuffer() const { return mBackbuffer; }
const mtl::RenderPassDesc &getRenderPassDesc(ContextMtl *context);
bool renderPassHasStarted(ContextMtl *contextMtl) const;
mtl::RenderCommandEncoder *ensureRenderPassStarted(const gl::Context *context);
// Call this to notify FramebufferMtl whenever its render pass has started.
void onStartedDrawingToFrameBuffer(const gl::Context *context);
void onFrameEnd(const gl::Context *context);
// The actual area will be adjusted based on framebuffer flipping property.
gl::Rectangle getReadPixelArea(const gl::Rectangle &glArea);
gl::Rectangle getCorrectFlippedReadArea(const gl::Context *context,
const gl::Rectangle &glArea) const;
// NOTE: this method doesn't do the flipping of area. Caller must do it if needed before
// callling this. See getReadPixelsArea().
......@@ -121,9 +132,17 @@ class FramebufferMtl : public FramebufferImpl
gl::DrawBufferMask clearColorBuffers,
const mtl::ClearRectParams &clearOpts);
angle::Result prepareRenderPass(const gl::Context *context,
gl::DrawBufferMask drawColorBuffers,
mtl::RenderPassDesc *descOut);
// Initialize load store options for a render pass's first start (i.e. not render pass resuming
// from interruptions such as those caused by a conversion compute pass)
void setLoadStoreActionOnRenderPassFirstStart(mtl::RenderPassAttachmentDesc *attachmentOut);
// Fill RenderPassDesc with relevant attachment's info from GL front end.
angle::Result prepareRenderPass(const gl::Context *context, mtl::RenderPassDesc *descOut);
// Check if a render pass specified by the given RenderPassDesc has started or not, if not this
// method will start the render pass and return its render encoder.
mtl::RenderCommandEncoder *ensureRenderPassStarted(const gl::Context *context,
const mtl::RenderPassDesc &desc);
void overrideClearColor(const mtl::TextureRef &texture,
MTLClearColor clearColor,
......@@ -143,7 +162,13 @@ class FramebufferMtl : public FramebufferImpl
RenderTargetMtl *mDepthRenderTarget = nullptr;
RenderTargetMtl *mStencilRenderTarget = nullptr;
mtl::RenderPassDesc mRenderPassDesc;
const bool mFlipY = false;
// Flag indicating the render pass start is a clean start or a resume from interruption such
// as by a compute pass.
bool mRenderPassCleanStart = false;
WindowSurfaceMtl *mBackbuffer = nullptr;
const bool mFlipY = false;
};
} // namespace rx
......
......@@ -27,7 +27,18 @@ namespace rx
{
class ContextMtl;
class ProgramMtl : public ProgramImpl
// Represents a specialized shader variant. For example, a shader variant with fragment coverage
// mask enabled and a shader variant without.
struct ProgramShaderVariantMtl
{
void reset(ContextMtl *contextMtl);
mtl::AutoObjCPtr<id<MTLFunction>> metalShader;
// NOTE(hqle): might need additional info such as uniform buffer encoder, fragment coverage mask
// enabled or not, etc.
};
class ProgramMtl : public ProgramImpl, public mtl::RenderPipelineCacheSpecializeShaderFactory
{
public:
ProgramMtl(const gl::ProgramState &state);
......@@ -100,6 +111,14 @@ class ProgramMtl : public ProgramImpl
void getUniformiv(const gl::Context *context, GLint location, GLint *params) const override;
void getUniformuiv(const gl::Context *context, GLint location, GLuint *params) const override;
// Override mtl::RenderPipelineCacheSpecializeShaderFactory
angle::Result getSpecializedShader(mtl::Context *context,
gl::ShaderType shaderType,
const mtl::RenderPipelineDesc &renderPipelineDesc,
id<MTLFunction> *shaderOut) override;
bool hasSpecializedShader(gl::ShaderType shaderType,
const mtl::RenderPipelineDesc &renderPipelineDesc) override;
// Calls this before drawing, changedPipelineDesc is passed when vertex attributes desc and/or
// shader program changed.
angle::Result setupDraw(const gl::Context *glContext,
......@@ -132,10 +151,10 @@ class ProgramMtl : public ProgramImpl
const gl::ProgramLinkedResources &resources,
gl::InfoLog &infoLog);
angle::Result createMslShader(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
const std::string &translatedSource);
angle::Result createMslShaderLib(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
const std::string &translatedSource);
// State for the default uniform blocks.
struct DefaultUniformBlock final : private angle::NonCopyable
......@@ -158,6 +177,13 @@ class ProgramMtl : public ProgramImpl
gl::ShaderMap<std::string> mTranslatedMslShader;
gl::ShaderMap<mtl::TranslatedShaderInfo> mMslShaderTranslateInfo;
gl::ShaderMap<mtl::AutoObjCPtr<id<MTLLibrary>>> mMslShaderLibrary;
// Shader variants:
// - Vertex shader: One variant for now.
std::array<ProgramShaderVariantMtl, 1> mVertexShaderVariants;
// - Fragment shader: One with sample coverage mask enabled, one with it disabled.
std::array<ProgramShaderVariantMtl, 2> mFragmentShaderVariants;
mtl::RenderPipelineCache mMetalRenderPipelineCache;
};
......
......@@ -30,6 +30,7 @@ namespace
{
#define SHADER_ENTRY_NAME @"main0"
constexpr char kSpirvCrossSpecConstSuffix[] = "_tmp";
void InitDefaultUniformBlock(const std::vector<sh::Uniform> &uniforms,
gl::Shader *shader,
......@@ -124,14 +125,59 @@ class Std140BlockLayoutEncoderFactory : public gl::CustomBlockLayoutEncoderFacto
sh::BlockLayoutEncoder *makeEncoder() override { return new sh::Std140BlockEncoder(); }
};
angle::Result CreateMslShader(mtl::Context *context,
id<MTLLibrary> shaderLib,
MTLFunctionConstantValues *funcConstants,
mtl::AutoObjCPtr<id<MTLFunction>> *shaderOut)
{
NSError *nsErr = nil;
id<MTLFunction> mtlShader;
if (funcConstants)
{
mtlShader = [shaderLib newFunctionWithName:SHADER_ENTRY_NAME
constantValues:funcConstants
error:&nsErr];
}
else
{
mtlShader = [shaderLib newFunctionWithName:SHADER_ENTRY_NAME];
}
[mtlShader ANGLE_MTL_AUTORELEASE];
if (nsErr && !mtlShader)
{
std::ostringstream ss;
ss << "Internal error compiling Metal shader:\n"
<< nsErr.localizedDescription.UTF8String << "\n";
ERR() << ss.str();
ANGLE_MTL_CHECK(context, false, GL_INVALID_OPERATION);
}
shaderOut->retainAssign(mtlShader);
return angle::Result::Continue;
}
} // namespace
// ProgramShaderVariantMtl implementation
void ProgramShaderVariantMtl::reset(ContextMtl *contextMtl)
{
metalShader = nil;
}
// ProgramMtl implementation
ProgramMtl::DefaultUniformBlock::DefaultUniformBlock() {}
ProgramMtl::DefaultUniformBlock::~DefaultUniformBlock() = default;
ProgramMtl::ProgramMtl(const gl::ProgramState &state) : ProgramImpl(state) {}
ProgramMtl::ProgramMtl(const gl::ProgramState &state)
: ProgramImpl(state), mMetalRenderPipelineCache(this)
{}
ProgramMtl::~ProgramMtl() {}
......@@ -149,8 +195,10 @@ void ProgramMtl::reset(ContextMtl *context)
block.uniformLayout.clear();
}
for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes())
for (gl::ShaderType shaderType : gl::AllShaderTypes())
{
mMslShaderLibrary[shaderType] = nil;
for (mtl::SamplerBinding &binding :
mMslShaderTranslateInfo[shaderType].actualSamplerBindings)
{
......@@ -158,6 +206,15 @@ void ProgramMtl::reset(ContextMtl *context)
}
}
for (ProgramShaderVariantMtl &var : mVertexShaderVariants)
{
var.reset(context);
}
for (ProgramShaderVariantMtl &var : mFragmentShaderVariants)
{
var.reset(context);
}
mMetalRenderPipelineCache.clear();
}
......@@ -228,7 +285,7 @@ angle::Result ProgramMtl::linkImpl(const gl::Context *glContext,
{
// Create actual Metal shader
ANGLE_TRY(
createMslShader(glContext, shaderType, infoLog, mTranslatedMslShader[shaderType]));
createMslShaderLib(glContext, shaderType, infoLog, mTranslatedMslShader[shaderType]));
}
return angle::Result::Continue;
......@@ -325,10 +382,76 @@ angle::Result ProgramMtl::initDefaultUniformBlocks(const gl::Context *glContext)
return angle::Result::Continue;
}
angle::Result ProgramMtl::createMslShader(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
const std::string &translatedMsl)
angle::Result ProgramMtl::getSpecializedShader(mtl::Context *context,
gl::ShaderType shaderType,
const mtl::RenderPipelineDesc &renderPipelineDesc,
id<MTLFunction> *shaderOut)
{
static_assert(YES == 1, "YES should have value of 1");
mtl::AutoObjCPtr<id<MTLLibrary>> mtlShaderLib = mMslShaderLibrary[shaderType];
if (shaderType == gl::ShaderType::Vertex)
{
// NOTE(hqle): Only one vertex shader variant for now. In future, there should be a variant
// with rasterization discard enabled.
ProgramShaderVariantMtl &shaderVariant = mVertexShaderVariants[0];
if (shaderVariant.metalShader)
{
// Already created.
*shaderOut = shaderVariant.metalShader;
return angle::Result::Continue;
}
ANGLE_MTL_OBJC_SCOPE
{
ANGLE_TRY(CreateMslShader(context, mtlShaderLib, nil, &shaderVariant.metalShader));
}
*shaderOut = shaderVariant.metalShader;
}
else if (shaderType == gl::ShaderType::Fragment)
{
// For fragment shader, we need to create 2 variants, one with sample coverage mask
// disabled, one with the mask enabled.
BOOL emulateCoverageMask = renderPipelineDesc.emulateCoverageMask;
ProgramShaderVariantMtl &shaderVariant = mFragmentShaderVariants[emulateCoverageMask];
if (shaderVariant.metalShader)
{
// Already created.
*shaderOut = shaderVariant.metalShader;
return angle::Result::Continue;
}
ANGLE_MTL_OBJC_SCOPE
{
NSString *coverageMaskEnabledStr =
[NSString stringWithFormat:@"%s%s", sh::mtl::kCoverageMaskEnabledConstName,
kSpirvCrossSpecConstSuffix];
auto funcConstants = [[[MTLFunctionConstantValues alloc] init] ANGLE_MTL_AUTORELEASE];
[funcConstants setConstantValue:&emulateCoverageMask
type:MTLDataTypeBool
withName:coverageMaskEnabledStr];
ANGLE_TRY(
CreateMslShader(context, mtlShaderLib, funcConstants, &shaderVariant.metalShader));
}
*shaderOut = shaderVariant.metalShader;
} // gl::ShaderType::Fragment
return angle::Result::Continue;
}
bool ProgramMtl::hasSpecializedShader(gl::ShaderType shaderType,
const mtl::RenderPipelineDesc &renderPipelineDesc)
{
return true;
}
angle::Result ProgramMtl::createMslShaderLib(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
const std::string &translatedMsl)
{
ANGLE_MTL_OBJC_SCOPE
{
......@@ -338,9 +461,8 @@ angle::Result ProgramMtl::createMslShader(const gl::Context *glContext,
// Convert to actual binary shader
mtl::AutoObjCPtr<NSError *> err = nil;
mtl::AutoObjCPtr<id<MTLLibrary>> mtlShaderLib =
mtl::CreateShaderLibrary(mtlDevice, translatedMsl, &err);
if (err && !mtlShaderLib)
mMslShaderLibrary[shaderType] = mtl::CreateShaderLibrary(mtlDevice, translatedMsl, &err);
if (err && !mMslShaderLibrary[shaderType])
{
std::ostringstream ss;
ss << "Internal error compiling Metal shader:\n"
......@@ -353,18 +475,6 @@ angle::Result ProgramMtl::createMslShader(const gl::Context *glContext,
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION);
}
auto mtlShader = [mtlShaderLib.get() newFunctionWithName:SHADER_ENTRY_NAME];
[mtlShader ANGLE_MTL_AUTORELEASE];
ASSERT(mtlShader);
if (shaderType == gl::ShaderType::Vertex)
{
mMetalRenderPipelineCache.setVertexShader(contextMtl, mtlShader);
}
else if (shaderType == gl::ShaderType::Fragment)
{
mMetalRenderPipelineCache.setFragmentShader(contextMtl, mtlShader);
}
return angle::Result::Continue;
}
}
......
......@@ -32,21 +32,33 @@ class RenderTargetMtl final : public FramebufferAttachmentRenderTarget
// Used in std::vector initialization.
RenderTargetMtl(RenderTargetMtl &&other);
void set(const mtl::TextureRef &texture, size_t level, size_t layer, const mtl::Format &format);
void set(const mtl::TextureRef &texture);
void set(const mtl::TextureRef &texture,
uint32_t level,
uint32_t layer,
const mtl::Format &format);
void set(const mtl::TextureRef &texture,
const mtl::TextureRef &implicitMSTexture,
uint32_t level,
uint32_t layer,
const mtl::Format &format);
void setTexture(const mtl::TextureRef &texture);
void setImplicitMSTexture(const mtl::TextureRef &implicitMSTexture);
void reset();
const mtl::TextureRef &getTexture() const { return mTexture; }
size_t getLevelIndex() const { return mLevelIndex; }
size_t getLayerIndex() const { return mLayerIndex; }
mtl::TextureRef getTexture() const { return mTexture; }
mtl::TextureRef getImplicitMSTexture() const { return mImplicitMSTexture; }
uint32_t getLevelIndex() const { return mLevelIndex; }
uint32_t getLayerIndex() const { return mLayerIndex; }
uint32_t getRenderSamples() const;
const mtl::Format *getFormat() const { return mFormat; }
void toRenderPassAttachmentDesc(mtl::RenderPassAttachmentDesc *rpaDescOut) const;
private:
mtl::TextureRef mTexture;
size_t mLevelIndex = 0;
size_t mLayerIndex = 0;
mtl::TextureRef mImplicitMSTexture;
uint32_t mLevelIndex = 0;
uint32_t mLayerIndex = 0;
const mtl::Format *mFormat = nullptr;
};
} // namespace rx
......
......@@ -20,38 +20,61 @@ RenderTargetMtl::~RenderTargetMtl()
RenderTargetMtl::RenderTargetMtl(RenderTargetMtl &&other)
: mTexture(std::move(other.mTexture)),
mImplicitMSTexture(std::move(other.mImplicitMSTexture)),
mLevelIndex(other.mLevelIndex),
mLayerIndex(other.mLayerIndex)
{}
void RenderTargetMtl::set(const mtl::TextureRef &texture,
size_t level,
size_t layer,
uint32_t level,
uint32_t layer,
const mtl::Format &format)
{
mTexture = texture;
mLevelIndex = level;
mLayerIndex = layer;
mFormat = &format;
set(texture, nullptr, level, layer, format);
}
void RenderTargetMtl::set(const mtl::TextureRef &texture)
void RenderTargetMtl::set(const mtl::TextureRef &texture,
const mtl::TextureRef &implicitMSTexture,
uint32_t level,
uint32_t layer,
const mtl::Format &format)
{
mTexture = texture;
mImplicitMSTexture = implicitMSTexture;
mLevelIndex = level;
mLayerIndex = layer;
mFormat = &format;
}
void RenderTargetMtl::setTexture(const mtl::TextureRef &texture)
{
mTexture = texture;
}
void RenderTargetMtl::setImplicitMSTexture(const mtl::TextureRef &implicitMSTexture)
{
mImplicitMSTexture = implicitMSTexture;
}
void RenderTargetMtl::reset()
{
mTexture.reset();
mImplicitMSTexture.reset();
mLevelIndex = 0;
mLayerIndex = 0;
mFormat = nullptr;
}
uint32_t RenderTargetMtl::getRenderSamples() const
{
return mImplicitMSTexture ? mImplicitMSTexture->samples()
: (mTexture ? mTexture->samples() : 1);
}
void RenderTargetMtl::toRenderPassAttachmentDesc(mtl::RenderPassAttachmentDesc *rpaDescOut) const
{
rpaDescOut->texture = mTexture;
rpaDescOut->level = static_cast<uint32_t>(mLevelIndex);
rpaDescOut->slice = static_cast<uint32_t>(mLayerIndex);
rpaDescOut->texture = mTexture;
rpaDescOut->implicitMSTexture = mImplicitMSTexture;
rpaDescOut->level = mLevelIndex;
rpaDescOut->sliceOrDepth = mLayerIndex;
}
}
......@@ -64,6 +64,10 @@ class SurfaceMtl : public SurfaceImpl
EGLint isPostSubBufferSupported() const override;
EGLint getSwapBehavior() const override;
const mtl::TextureRef &getColorTexture() { return mColorTexture; }
const mtl::Format &getColorFormat() const { return mColorFormat; }
int getSamples() const { return mSamples; }
angle::Result getAttachmentRenderTarget(const gl::Context *context,
GLenum binding,
const gl::ImageIndex &imageIndex,
......@@ -71,20 +75,29 @@ class SurfaceMtl : public SurfaceImpl
FramebufferAttachmentRenderTarget **rtOut) override;
protected:
// Ensure companion (depth, stencil) textures' size is correct w.r.t color texture.
angle::Result ensureDepthStencilSizeCorrect(const gl::Context *context,
gl::Framebuffer::DirtyBits *fboDirtyBits);
// Ensure companion (MS, depth, stencil) textures' size is correct w.r.t color texture.
angle::Result ensureCompanionTexturesSizeCorrect(const gl::Context *context,
const gl::Extents &size);
angle::Result resolveColorTextureIfNeeded(const gl::Context *context);
// Normal textures
mtl::TextureRef mColorTexture;
mtl::TextureRef mDepthTexture;
mtl::TextureRef mStencilTexture;
// Implicit multisample texture
mtl::TextureRef mMSColorTexture;
bool mUsePackedDepthStencil = false;
// Auto resolve MS texture at the end of render pass or requires a separate blitting pass?
bool mAutoResolveMSColorTexture = false;
mtl::Format mColorFormat;
mtl::Format mDepthFormat;
mtl::Format mStencilFormat;
int mSamples = 0;
RenderTargetMtl mColorRenderTarget;
RenderTargetMtl mDepthRenderTarget;
RenderTargetMtl mStencilRenderTarget;
......@@ -105,9 +118,9 @@ class WindowSurfaceMtl : public SurfaceMtl
FramebufferImpl *createDefaultFramebuffer(const gl::Context *context,
const gl::FramebufferState &state) override;
egl::Error makeCurrent(const gl::Context *context) override;
egl::Error swap(const gl::Context *context) override;
void setSwapInterval(EGLint interval) override;
EGLint getSwapBehavior() const override;
// width and height can change with client window resizing
......@@ -120,17 +133,29 @@ class WindowSurfaceMtl : public SurfaceMtl
GLsizei samples,
FramebufferAttachmentRenderTarget **rtOut) override;
angle::Result ensureCurrentDrawableObtained(const gl::Context *context);
// Ensure the the texture returned from getColorTexture() is ready for glReadPixels(). This
// implicitly calls ensureCurrentDrawableObtained().
angle::Result ensureColorTextureReadyForReadPixels(const gl::Context *context);
private:
angle::Result swapImpl(const gl::Context *context);
angle::Result ensureRenderTargetsCreated(const gl::Context *context);
angle::Result obtainNextDrawable(const gl::Context *context);
angle::Result ensureCompanionTexturesSizeCorrect(const gl::Context *context);
CGSize calcExpectedDrawableSize() const;
// Check if metal layer has been resized.
void checkIfLayerResized();
bool checkIfLayerResized(const gl::Context *context);
mtl::AutoObjCObj<CAMetalLayer> mMetalLayer = nil;
CALayer *mLayer;
mtl::AutoObjCPtr<id<CAMetalDrawable>> mCurrentDrawable = nil;
// Cache last known drawable size that is used by GL context. Can be used to detect resize
// event. We don't use mMetalLayer.drawableSize directly since it might be changed internally by
// metal runtime.
CGSize mCurrentKnownDrawableSize;
};
} // namespace rx
......
......@@ -337,7 +337,7 @@ void TextureMtl::releaseTexture(bool releaseImages)
for (RenderTargetMtl &rt : mLayeredRenderTargets)
{
rt.set(nullptr);
rt.reset();
}
if (releaseImages)
......@@ -1153,7 +1153,7 @@ angle::Result TextureMtl::copySubImageWithDraw(const gl::Context *context,
DisplayMtl *displayMtl = contextMtl->getDisplay();
FramebufferMtl *framebufferMtl = mtl::GetImpl(source);
RenderTargetMtl *colorReadRT = framebufferMtl->getColorReadRenderTarget();
RenderTargetMtl *colorReadRT = framebufferMtl->getColorReadRenderTarget(context);
if (!colorReadRT || !colorReadRT->getTexture())
{
......@@ -1192,7 +1192,7 @@ angle::Result TextureMtl::copySubImageCPU(const gl::Context *context,
ContextMtl *contextMtl = mtl::GetImpl(context);
FramebufferMtl *framebufferMtl = mtl::GetImpl(source);
RenderTargetMtl *colorReadRT = framebufferMtl->getColorReadRenderTarget();
RenderTargetMtl *colorReadRT = framebufferMtl->getColorReadRenderTarget(context);
if (!colorReadRT || !colorReadRT->getTexture())
{
......@@ -1216,10 +1216,10 @@ angle::Result TextureMtl::copySubImageCPU(const gl::Context *context,
PackPixelsParams packParams(srcRowArea, dstFormat, dstRowPitch, false, nullptr, 0);
// Read pixels from framebuffer to memory:
gl::Rectangle flippedSrcRowArea = framebufferMtl->getReadPixelArea(srcRowArea);
gl::Rectangle flippedSrcRowArea =
framebufferMtl->getCorrectFlippedReadArea(context, srcRowArea);
ANGLE_TRY(framebufferMtl->readPixelsImpl(context, flippedSrcRowArea, packParams,
framebufferMtl->getColorReadRenderTarget(),
conversionRow.data()));
colorReadRT, conversionRow.data()));
// Upload to texture
ANGLE_TRY(UploadTextureContents(context, image, dstFormat, mtlDstRowArea, 0, 0,
......
......@@ -788,6 +788,24 @@ void RenderCommandEncoder::finalizeLoadStoreAction(
return;
}
if (objCRenderPassAttachment.resolveTexture)
{
if (objCRenderPassAttachment.storeAction == MTLStoreActionStore)
{
// NOTE(hqle): Currently if the store action with implicit MS texture is
// MTLStoreActionStore, it is automatically convert to store and resolve action. It
// might introduce unnecessary overhead. Consider an improvement such as only store the
// MS texture, and resolve only at the end of real render pass (not render pass the is
// interrupted by compute pass) or before glBlitFramebuffer operation starts.
objCRenderPassAttachment.storeAction = MTLStoreActionStoreAndMultisampleResolve;
}
else if (objCRenderPassAttachment.storeAction == MTLStoreActionDontCare)
{
// Ignore resolve texture if the store action is not a resolve action.
objCRenderPassAttachment.resolveTexture = nil;
}
}
if (objCRenderPassAttachment.storeAction == MTLStoreActionUnknown)
{
// If storeAction hasn't been set for this attachment, we set to dontcare.
......
......@@ -171,6 +171,21 @@ void GetAssignedSamplerBindings(const spirv_cross::CompilerMSL &compilerMsl,
}
}
std::string PostProcessTranslatedMsl(const std::string &translatedSource)
{
// Add function_constant attribute to gl_SampleMask.
// Even though this varying is only used when ANGLECoverageMaskEnabled is true,
// the spirv-cross doesn't assign function_constant attribute to it. Thus it won't be dead-code
// removed when ANGLECoverageMaskEnabled=false.
std::string sampleMaskReplaceStr = std::string("[[sample_mask, function_constant(") +
sh::mtl::kCoverageMaskEnabledConstName + ")]]";
// This replaces "gl_SampleMask [[sample_mask]]"
// with "gl_SampleMask [[sample_mask, function_constant(ANGLECoverageMaskEnabled)]]"
std::regex sampleMaskDeclareRegex(R"(\[\s*\[\s*sample_mask\s*\]\s*\])");
return std::regex_replace(translatedSource, sampleMaskDeclareRegex, sampleMaskReplaceStr);
}
// Customized spirv-cross compiler
class SpirvToMslCompiler : public spirv_cross::CompilerMSL
{
......@@ -210,7 +225,7 @@ class SpirvToMslCompiler : public spirv_cross::CompilerMSL
spirv_cross::CompilerMSL::set_msl_options(compOpt);
// Actual compilation
std::string translatedMsl = spirv_cross::CompilerMSL::compile();
std::string translatedMsl = PostProcessTranslatedMsl(spirv_cross::CompilerMSL::compile());
// Retrieve automatic texture slot assignments
GetAssignedSamplerBindings(*this, originalSamplerBindings,
......
......@@ -15,6 +15,7 @@
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/metal/mtl_command_buffer.h"
#include "libANGLE/renderer/metal/mtl_state_cache.h"
#include "libANGLE/renderer/metal/shaders/constants.h"
namespace rx
{
......@@ -129,6 +130,7 @@ class ColorBlitUtils
// Defer loading of render pipeline state cache.
void ensureRenderPipelineStateCacheInitialized(ContextMtl *ctx,
int alphaPremultiplyType,
int textureType,
RenderPipelineCache *cacheOut);
void setupBlitWithDraw(const gl::Context *context,
......@@ -139,9 +141,13 @@ class ColorBlitUtils
RenderCommandEncoder *cmdEncoder,
const BlitParams &params);
RenderPipelineCache mBlitRenderPipelineCache;
RenderPipelineCache mBlitPremultiplyAlphaRenderPipelineCache;
RenderPipelineCache mBlitUnmultiplyAlphaRenderPipelineCache;
// Blit with draw pipeline caches:
// - array dimension: source texture type (2d, ms, array, 3d, etc)
using ColorBlitRenderPipelineCacheArray =
std::array<RenderPipelineCache, mtl_shader::kTextureTypeCount>;
ColorBlitRenderPipelineCacheArray mBlitRenderPipelineCache;
ColorBlitRenderPipelineCacheArray mBlitPremultiplyAlphaRenderPipelineCache;
ColorBlitRenderPipelineCacheArray mBlitUnmultiplyAlphaRenderPipelineCache;
};
// util class for generating index buffer
......@@ -231,6 +237,11 @@ class RenderUtils : public Context, angle::NonCopyable
angle::Result blitWithDraw(const gl::Context *context,
RenderCommandEncoder *cmdEncoder,
const BlitParams &params);
// Same as above but blit the whole texture to the whole of current framebuffer.
// This function assumes the framebuffer and the source texture have same size.
angle::Result blitWithDraw(const gl::Context *context,
RenderCommandEncoder *cmdEncoder,
const TextureRef &srcTexture);
// See IndexGeneratorUtils
angle::Result convertIndexBufferGPU(ContextMtl *contextMtl,
......
......@@ -29,6 +29,9 @@ namespace
#define SOURCE_IDX_IS_U8_CONSTANT_NAME @"kSourceIndexIsU8"
#define SOURCE_IDX_IS_U16_CONSTANT_NAME @"kSourceIndexIsU16"
#define SOURCE_IDX_IS_U32_CONSTANT_NAME @"kSourceIndexIsU32"
#define PREMULTIPLY_ALPHA_CONSTANT_NAME @"kPremultiplyAlpha"
#define UNMULTIPLY_ALPHA_CONSTANT_NAME @"kUnmultiplyAlpha"
#define SOURCE_TEXTURE_TYPE_CONSTANT_NAME @"kSourceTextureType"
// See libANGLE/renderer/metal/shaders/clear.metal
struct ClearParamsUniform
......@@ -44,10 +47,12 @@ struct BlitParamsUniform
// 0: lower left, 1: lower right, 2: upper left
float srcTexCoords[3][2];
int srcLevel = 0;
uint8_t srcLuminance = 0; // source texture is luminance texture
int srcLayer = 0;
uint8_t dstFlipX = 0;
uint8_t dstFlipY = 0;
uint8_t dstLuminance = 0; // dest texture is luminace
uint8_t padding;
uint8_t padding1;
float padding2[3];
};
struct IndexConversionUniform
......@@ -131,6 +136,31 @@ void GetFirstLastIndicesFromClientElements(GLsizei count,
memcpy(lastOut, indices + count - 1, sizeof(indices[0]));
}
int GetShaderTextureType(const TextureRef &texture)
{
if (!texture)
{
return -1;
}
switch (texture->textureType())
{
case MTLTextureType2D:
return mtl_shader::kTextureType2D;
case MTLTextureType2DArray:
return mtl_shader::kTextureType2DArray;
case MTLTextureType2DMultisample:
return mtl_shader::kTextureType2DMultisample;
case MTLTextureTypeCube:
return mtl_shader::kTextureTypeCube;
case MTLTextureType3D:
return mtl_shader::kTextureType3D;
default:
UNREACHABLE();
}
return 0;
}
ANGLE_INLINE
void EnsureComputePipelineInitialized(DisplayMtl *display,
NSString *functionName,
......@@ -208,6 +238,15 @@ void EnsureSpecializedComputePipelineInitialized(
}
template <typename T>
void ClearRenderPipelineCacheArray(T *pipelineCacheArray)
{
for (RenderPipelineCache &pipelineCache : *pipelineCacheArray)
{
pipelineCache.clear();
}
}
template <typename T>
void ClearPipelineStateArray(T *pipelineCacheArray)
{
for (auto &pipeline : *pipelineCacheArray)
......@@ -353,6 +392,20 @@ angle::Result RenderUtils::blitWithDraw(const gl::Context *context,
return mColorBlitUtils.blitWithDraw(context, cmdEncoder, params);
}
angle::Result RenderUtils::blitWithDraw(const gl::Context *context,
RenderCommandEncoder *cmdEncoder,
const TextureRef &srcTexture)
{
if (!srcTexture)
{
return angle::Result::Continue;
}
BlitParams params;
params.src = srcTexture;
params.srcRect = gl::Rectangle(0, 0, srcTexture->width(), srcTexture->height());
return blitWithDraw(context, cmdEncoder, params);
}
angle::Result RenderUtils::convertIndexBufferGPU(ContextMtl *contextMtl,
const IndexConversionParams &params)
{
......@@ -570,13 +623,14 @@ ColorBlitUtils::ColorBlitUtils() = default;
void ColorBlitUtils::onDestroy()
{
mBlitRenderPipelineCache.clear();
mBlitPremultiplyAlphaRenderPipelineCache.clear();
mBlitUnmultiplyAlphaRenderPipelineCache.clear();
ClearRenderPipelineCacheArray(&mBlitRenderPipelineCache);
ClearRenderPipelineCacheArray(&mBlitPremultiplyAlphaRenderPipelineCache);
ClearRenderPipelineCacheArray(&mBlitUnmultiplyAlphaRenderPipelineCache);
}
void ColorBlitUtils::ensureRenderPipelineStateCacheInitialized(ContextMtl *ctx,
int alphaPremultiplyType,
int textureType,
RenderPipelineCache *cacheOut)
{
RenderPipelineCache &pipelineCache = *cacheOut;
......@@ -588,24 +642,43 @@ void ColorBlitUtils::ensureRenderPipelineStateCacheInitialized(ContextMtl *ctx,
ANGLE_MTL_OBJC_SCOPE
{
NSString *const fragmentShaderNames[] = {// Normal blit
@"blitFS",
// Blit premultiply-alpha
@"blitPremultiplyAlphaFS",
// Blit unmultiply alpha
@"blitUnmultiplyAlphaFS"};
NSError *err = nil;
id<MTLLibrary> shaderLib = ctx->getDisplay()->getDefaultShadersLib();
id<MTLFunction> vertexShader =
[[shaderLib newFunctionWithName:@"blitVS"] ANGLE_MTL_AUTORELEASE];
id<MTLFunction> fragmentShader = [[shaderLib
newFunctionWithName:fragmentShaderNames[alphaPremultiplyType]] ANGLE_MTL_AUTORELEASE];
MTLFunctionConstantValues *funcConstants =
[[[MTLFunctionConstantValues alloc] init] ANGLE_MTL_AUTORELEASE];
constexpr BOOL multiplyAlphaFlags[][2] = {// premultiply, unmultiply
// Normal blit
{NO, NO},
// Blit premultiply-alpha
{YES, NO},
// Blit unmultiply alpha
{NO, YES}};
// Set alpha multiply flags
[funcConstants setConstantValue:&multiplyAlphaFlags[alphaPremultiplyType][0]
type:MTLDataTypeBool
withName:PREMULTIPLY_ALPHA_CONSTANT_NAME];
[funcConstants setConstantValue:&multiplyAlphaFlags[alphaPremultiplyType][1]
type:MTLDataTypeBool
withName:UNMULTIPLY_ALPHA_CONSTANT_NAME];
// Set texture type constant
[funcConstants setConstantValue:&textureType
type:MTLDataTypeInt
withName:SOURCE_TEXTURE_TYPE_CONSTANT_NAME];
id<MTLFunction> fragmentShader =
[[shaderLib newFunctionWithName:@"blitFS" constantValues:funcConstants
error:&err] ANGLE_MTL_AUTORELEASE];
ASSERT(vertexShader);
ASSERT(fragmentShader);
mBlitRenderPipelineCache.setVertexShader(ctx, vertexShader);
mBlitRenderPipelineCache.setFragmentShader(ctx, fragmentShader);
pipelineCache.setVertexShader(ctx, vertexShader);
pipelineCache.setFragmentShader(ctx, fragmentShader);
}
}
......@@ -625,23 +698,25 @@ id<MTLRenderPipelineState> ColorBlitUtils::getBlitRenderPipelineState(
RenderPipelineCache *pipelineCache;
int alphaPremultiplyType;
int textureType = GetShaderTextureType(params.src);
if (params.unpackPremultiplyAlpha == params.unpackUnmultiplyAlpha)
{
alphaPremultiplyType = 0;
pipelineCache = &mBlitRenderPipelineCache;
pipelineCache = &mBlitRenderPipelineCache[textureType];
}
else if (params.unpackPremultiplyAlpha)
{
alphaPremultiplyType = 1;
pipelineCache = &mBlitPremultiplyAlphaRenderPipelineCache;
pipelineCache = &mBlitPremultiplyAlphaRenderPipelineCache[textureType];
}
else
{
alphaPremultiplyType = 2;
pipelineCache = &mBlitUnmultiplyAlphaRenderPipelineCache;
pipelineCache = &mBlitUnmultiplyAlphaRenderPipelineCache[textureType];
}
ensureRenderPipelineStateCacheInitialized(contextMtl, alphaPremultiplyType, pipelineCache);
ensureRenderPipelineStateCacheInitialized(contextMtl, alphaPremultiplyType, textureType,
pipelineCache);
return pipelineCache->getRenderPipelineState(contextMtl, pipelineDesc);
}
......
......@@ -111,6 +111,15 @@ class Texture final : public Resource,
bool allowTextureView,
TextureRef *refOut);
static angle::Result Make2DMSTexture(ContextMtl *context,
const Format &format,
uint32_t width,
uint32_t height,
uint32_t samples,
bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut);
static TextureRef MakeFromMetal(id<MTLTexture> metalTexture);
// Allow CPU to read & write data directly to this texture?
......@@ -148,6 +157,8 @@ class Texture final : public Resource,
gl::Extents size(uint32_t level = 0) const;
gl::Extents size(const gl::ImageIndex &index) const;
uint32_t samples() const;
// For render target
MTLColorWriteMask getColorWritableMask() const { return *mColorWritableMask; }
void setColorWritableMask(MTLColorWriteMask mask) { *mColorWritableMask = mask; }
......
......@@ -148,6 +148,38 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context,
}
/** static */
angle::Result Texture::Make2DMSTexture(ContextMtl *context,
const Format &format,
uint32_t width,
uint32_t height,
uint32_t samples,
bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut)
{
ANGLE_MTL_OBJC_SCOPE
{
MTLTextureDescriptor *desc = [[MTLTextureDescriptor new] ANGLE_MTL_AUTORELEASE];
desc.textureType = MTLTextureType2DMultisample;
desc.pixelFormat = format.metalFormat;
desc.width = width;
desc.height = height;
desc.mipmapLevelCount = 1;
desc.sampleCount = samples;
SetTextureSwizzle(context, format, desc);
refOut->reset(new Texture(context, desc, 1, renderTargetOnly, allowTextureView));
} // ANGLE_MTL_OBJC_SCOPE
if (!refOut || !refOut->get())
{
ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
}
return angle::Result::Continue;
}
/** static */
TextureRef Texture::MakeFromMetal(id<MTLTexture> metalTexture)
{
ANGLE_MTL_OBJC_SCOPE { return TextureRef(new Texture(metalTexture)); }
......@@ -183,7 +215,8 @@ Texture::Texture(ContextMtl *context,
desc.usage |= MTLTextureUsageRenderTarget;
}
if (!Format::FormatCPUReadable(desc.pixelFormat))
if (!Format::FormatCPUReadable(desc.pixelFormat) ||
desc.textureType == MTLTextureType2DMultisample)
{
desc.resourceOptions = MTLResourceStorageModePrivate;
}
......@@ -405,6 +438,11 @@ gl::Extents Texture::size(const gl::ImageIndex &index) const
return size(index.getLevelIndex());
}
uint32_t Texture::samples() const
{
return static_cast<uint32_t>(get().sampleCount);
}
void Texture::set(id<MTLTexture> metalTexture)
{
ParentClass::set(metalTexture);
......
......@@ -214,6 +214,7 @@ struct RenderPipelineOutputDesc
static_assert(kMaxRenderTargets <= 4, "kMaxRenderTargets must be <= 4");
uint8_t numColorAttachments : 3;
uint8_t sampleCount : 5;
};
// Some SDK levels don't declare MTLPrimitiveTopologyClass. Needs to do compile time check here:
......@@ -249,7 +250,13 @@ struct alignas(4) RenderPipelineDesc
// Use uint8_t instead of PrimitiveTopologyClass to compact space.
uint8_t inputPrimitiveTopology : 2;
bool rasterizationEnabled;
bool rasterizationEnabled : 1;
bool alphaToCoverageEnabled : 1;
// These flags are for emulation and do not correspond to any flags in
// MTLRenderPipelineDescriptor descriptor. These flags should be used by
// RenderPipelineCacheSpecializeShaderFactory.
bool emulateCoverageMask : 1;
};
struct RenderPassAttachmentDesc
......@@ -261,9 +268,15 @@ struct RenderPassAttachmentDesc
bool equalIgnoreLoadStoreOptions(const RenderPassAttachmentDesc &other) const;
bool operator==(const RenderPassAttachmentDesc &other) const;
ANGLE_INLINE bool hasImplicitMSTexture() const { return implicitMSTexture.get(); }
TextureRef texture;
// Implicit multisample texture that will be rendered into and discarded at the end of
// a render pass. Its result will be resolved into normal texture above.
TextureRef implicitMSTexture;
uint32_t level;
uint32_t slice;
uint32_t sliceOrDepth;
MTLLoadAction loadAction;
MTLStoreAction storeAction;
MTLStoreActionOptions storeActionOptions;
......@@ -333,6 +346,7 @@ struct RenderPassDesc
inline bool operator!=(const RenderPassDesc &other) const { return !(*this == other); }
uint32_t numColorAttachments = 0;
uint32_t sampleCount = 1;
};
} // namespace mtl
......@@ -365,18 +379,43 @@ namespace rx
{
namespace mtl
{
// render pipeline state cache per shader program
// Abstract factory to create specialized vertex & fragment shaders based on RenderPipelineDesc.
class RenderPipelineCacheSpecializeShaderFactory
{
public:
virtual ~RenderPipelineCacheSpecializeShaderFactory() = default;
// Get specialized shader for the render pipeline cache.
virtual angle::Result getSpecializedShader(Context *context,
gl::ShaderType shaderType,
const RenderPipelineDesc &renderPipelineDesc,
id<MTLFunction> *shaderOut) = 0;
// Check whether specialized shaders is required for the specified RenderPipelineDesc.
// If not, the render pipeline cache will use the supplied non-specialized shaders.
virtual bool hasSpecializedShader(gl::ShaderType shaderType,
const RenderPipelineDesc &renderPipelineDesc) = 0;
};
// Render pipeline state cache per shader program.
class RenderPipelineCache final : angle::NonCopyable
{
public:
RenderPipelineCache();
RenderPipelineCache(RenderPipelineCacheSpecializeShaderFactory *specializedShaderFactory);
~RenderPipelineCache();
// Set non-specialized vertex/fragment shader to be used by render pipeline cache to create
// render pipeline state. If the internal
// RenderPipelineCacheSpecializeShaderFactory.hasSpecializedShader() returns false for a
// particular RenderPipelineDesc, the render pipeline cache will use the non-specialized
// shaders.
void setVertexShader(Context *context, id<MTLFunction> shader);
void setFragmentShader(Context *context, id<MTLFunction> shader);
id<MTLFunction> getVertexShader() { return mVertexShader.get(); }
id<MTLFunction> getFragmentShader() { return mFragmentShader.get(); }
// Get non-specialized shaders supplied via set*Shader().
id<MTLFunction> getVertexShader() { return mVertexShader; }
id<MTLFunction> getFragmentShader() { return mFragmentShader; }
AutoObjCPtr<id<MTLRenderPipelineState>> getRenderPipelineState(ContextMtl *context,
const RenderPipelineDesc &desc);
......@@ -384,8 +423,10 @@ class RenderPipelineCache final : angle::NonCopyable
void clear();
protected:
AutoObjCPtr<id<MTLFunction>> mVertexShader = nil;
AutoObjCPtr<id<MTLFunction>> mFragmentShader = nil;
// Non-specialized vertex shader
AutoObjCPtr<id<MTLFunction>> mVertexShader;
// Non-specialized fragment shader
AutoObjCPtr<id<MTLFunction>> mFragmentShader;
private:
void clearPipelineStates();
......@@ -404,6 +445,8 @@ class RenderPipelineCache final : angle::NonCopyable
// One table with default attrib and one table without.
std::unordered_map<RenderPipelineDesc, AutoObjCPtr<id<MTLRenderPipelineState>>>
mRenderPipelineStates[2];
RenderPipelineCacheSpecializeShaderFactory *mSpecializedShaderFactory;
};
class StateCache final : angle::NonCopyable
......
......@@ -156,11 +156,13 @@ MTLRenderPipelineDescriptor *ToObjC(id<MTLFunction> vertexShader,
}
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc.outputDescriptor, depthAttachmentPixelFormat);
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc.outputDescriptor, stencilAttachmentPixelFormat);
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc.outputDescriptor, sampleCount);
#if ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, inputPrimitiveTopology);
#endif
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, rasterizationEnabled);
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, alphaToCoverageEnabled);
return [objCDesc ANGLE_MTL_AUTORELEASE];
}
......@@ -174,9 +176,44 @@ id<MTLTexture> ToObjC(const TextureRef &texture)
void BaseRenderPassAttachmentDescToObjC(const RenderPassAttachmentDesc &src,
MTLRenderPassAttachmentDescriptor *dst)
{
ANGLE_OBJC_CP_PROPERTY(dst, src, texture);
ANGLE_OBJC_CP_PROPERTY(dst, src, level);
ANGLE_OBJC_CP_PROPERTY(dst, src, slice);
const TextureRef &implicitMsTexture = src.implicitMSTexture;
if (implicitMsTexture)
{
dst.texture = ToObjC(implicitMsTexture);
dst.level = 0;
dst.slice = 0;
dst.resolveTexture = ToObjC(src.texture);
dst.resolveLevel = src.level;
if (dst.resolveTexture.textureType == MTLTextureType3D)
{
dst.resolveDepthPlane = src.sliceOrDepth;
dst.resolveSlice = 0;
}
else
{
dst.resolveSlice = src.sliceOrDepth;
dst.resolveDepthPlane = 0;
}
}
else
{
dst.texture = ToObjC(src.texture);
dst.level = src.level;
if (dst.texture.textureType == MTLTextureType3D)
{
dst.depthPlane = src.sliceOrDepth;
dst.slice = 0;
}
else
{
dst.slice = src.sliceOrDepth;
dst.depthPlane = 0;
}
dst.resolveTexture = nil;
dst.resolveLevel = 0;
dst.resolveSlice = 0;
}
ANGLE_OBJC_CP_PROPERTY(dst, src, loadAction);
ANGLE_OBJC_CP_PROPERTY(dst, src, storeAction);
......@@ -585,7 +622,8 @@ bool RenderPipelineOutputDesc::operator==(const RenderPipelineOutputDesc &rhs) c
RenderPipelineDesc::RenderPipelineDesc()
{
memset(this, 0, sizeof(*this));
rasterizationEnabled = true;
outputDescriptor.sampleCount = 1;
rasterizationEnabled = true;
}
RenderPipelineDesc::RenderPipelineDesc(const RenderPipelineDesc &src)
......@@ -626,8 +664,9 @@ RenderPassAttachmentDesc::RenderPassAttachmentDesc()
void RenderPassAttachmentDesc::reset()
{
texture.reset();
implicitMSTexture.reset();
level = 0;
slice = 0;
sliceOrDepth = 0;
loadAction = MTLLoadActionLoad;
storeAction = MTLStoreActionStore;
storeActionOptions = MTLStoreActionOptionNone;
......@@ -636,7 +675,8 @@ void RenderPassAttachmentDesc::reset()
bool RenderPassAttachmentDesc::equalIgnoreLoadStoreOptions(
const RenderPassAttachmentDesc &other) const
{
return texture == other.texture && level == other.level && slice == other.slice;
return texture == other.texture && implicitMSTexture == other.implicitMSTexture &&
level == other.level && sliceOrDepth == other.sliceOrDepth;
}
bool RenderPassAttachmentDesc::operator==(const RenderPassAttachmentDesc &other) const
......@@ -668,8 +708,9 @@ void RenderPassDesc::populateRenderPipelineOutputDesc(MTLColorWriteMask colorWri
void RenderPassDesc::populateRenderPipelineOutputDesc(const BlendDesc &blendState,
RenderPipelineOutputDesc *outDesc) const
{
auto &outputDescriptor = *outDesc;
outputDescriptor.numColorAttachments = this->numColorAttachments;
RenderPipelineOutputDesc &outputDescriptor = *outDesc;
outputDescriptor.numColorAttachments = this->numColorAttachments;
outputDescriptor.sampleCount = this->sampleCount;
for (uint32_t i = 0; i < this->numColorAttachments; ++i)
{
auto &renderPassColorAttachment = this->colorAttachments[i];
......@@ -694,6 +735,12 @@ void RenderPassDesc::populateRenderPipelineOutputDesc(const BlendDesc &blendStat
}
}
// Reset the unused output slots to ensure consistent hash value
for (uint32_t i = this->numColorAttachments; i < kMaxRenderTargets; ++i)
{
outputDescriptor.colorAttachments[i].reset();
}
auto depthTexture = this->depthAttachment.texture;
outputDescriptor.depthAttachmentPixelFormat =
depthTexture ? depthTexture->pixelFormat() : MTLPixelFormatInvalid;
......@@ -770,7 +817,12 @@ void RenderPassDesc::convertToMetalDesc(MTLRenderPassDescriptor *objCDesc) const
}
// RenderPipelineCache implementation
RenderPipelineCache::RenderPipelineCache() {}
RenderPipelineCache::RenderPipelineCache() : RenderPipelineCache(nullptr) {}
RenderPipelineCache::RenderPipelineCache(
RenderPipelineCacheSpecializeShaderFactory *specializedShaderFactory)
: mSpecializedShaderFactory(specializedShaderFactory)
{}
RenderPipelineCache::~RenderPipelineCache() {}
......@@ -837,6 +889,10 @@ AutoObjCPtr<id<MTLRenderPipelineState>> RenderPipelineCache::insertRenderPipelin
{
AutoObjCPtr<id<MTLRenderPipelineState>> newState =
createRenderPipelineState(context, desc, insertDefaultAttribLayout);
if (!newState)
{
return nil;
}
int tableIdx = insertDefaultAttribLayout ? 1 : 0;
auto re = mRenderPipelineStates[tableIdx].insert(std::make_pair(desc, newState));
......@@ -850,16 +906,66 @@ AutoObjCPtr<id<MTLRenderPipelineState>> RenderPipelineCache::insertRenderPipelin
AutoObjCPtr<id<MTLRenderPipelineState>> RenderPipelineCache::createRenderPipelineState(
Context *context,
const RenderPipelineDesc &desc,
const RenderPipelineDesc &originalDesc,
bool insertDefaultAttribLayout)
{
ANGLE_MTL_OBJC_SCOPE
{
auto metalDevice = context->getMetalDevice();
AutoObjCObj<MTLRenderPipelineDescriptor> objCDesc =
ToObjC(mVertexShader, mFragmentShader, desc);
// Disable coverage if the render pipeline's sample count is only 1.
RenderPipelineDesc desc = originalDesc;
if (desc.outputDescriptor.sampleCount == 1)
{
// Disable sample coverage if the output is not multisample
desc.emulateCoverageMask = false;
desc.alphaToCoverageEnabled = false;
}
// Choose shader variant
id<MTLFunction> vertShader = nil;
id<MTLFunction> fragShader = nil;
if (mSpecializedShaderFactory &&
mSpecializedShaderFactory->hasSpecializedShader(gl::ShaderType::Vertex, desc))
{
if (IsError(mSpecializedShaderFactory->getSpecializedShader(
context, gl::ShaderType::Vertex, desc, &vertShader)))
{
return nil;
}
}
else
{
// Non-specialized version
vertShader = mVertexShader;
}
if (mSpecializedShaderFactory &&
mSpecializedShaderFactory->hasSpecializedShader(gl::ShaderType::Fragment, desc))
{
if (IsError(mSpecializedShaderFactory->getSpecializedShader(
context, gl::ShaderType::Fragment, desc, &fragShader)))
{
return nil;
}
}
else
{
// Non-specialized version
fragShader = mFragmentShader;
}
if (!vertShader)
{
// Render pipeline without vertex shader is invalid.
context->handleError(GL_INVALID_OPERATION, __FILE__, ANGLE_FUNCTION, __LINE__);
return nil;
}
id<MTLDevice> metalDevice = context->getMetalDevice();
// Convert to Objective-C desc:
AutoObjCObj<MTLRenderPipelineDescriptor> objCDesc = ToObjC(vertShader, fragShader, desc);
// special attribute slot for default attribute
// Special attribute slot for default attribute
if (insertDefaultAttribLayout)
{
MTLVertexBufferLayoutDescriptor *defaultAttribLayoutObjCDesc =
......@@ -873,8 +979,9 @@ AutoObjCPtr<id<MTLRenderPipelineState>> RenderPipelineCache::createRenderPipelin
atIndexedSubscript:kDefaultAttribsBindingIndex];
}
// Create pipeline state
NSError *err = nil;
auto newState = [metalDevice newRenderPipelineStateWithDescriptor:objCDesc error:&err];
NSError *err = nil;
id<MTLRenderPipelineState> newState =
[metalDevice newRenderPipelineStateWithDescriptor:objCDesc error:&err];
if (err)
{
context->handleError(err, __FILE__, ANGLE_FUNCTION, __LINE__);
......
......@@ -7,15 +7,28 @@
#include "common.h"
using namespace rx::mtl_shader;
// function_constant(0-3) is already used by gen_indices.metal
constant bool kPremultiplyAlpha [[function_constant(4)]];
constant bool kUnmultiplyAlpha [[function_constant(5)]];
constant int kSourceTextureType [[function_constant(6)]]; // Source texture type.
constant bool kSourceTextureType2D = kSourceTextureType == kTextureType2D;
constant bool kSourceTextureType2DArray = kSourceTextureType == kTextureType2DArray;
constant bool kSourceTextureType2DMS = kSourceTextureType == kTextureType2DMultisample;
constant bool kSourceTextureTypeCube = kSourceTextureType == kTextureTypeCube;
constant bool kSourceTextureType3D = kSourceTextureType == kTextureType3D;
struct BlitParams
{
// 0: lower left, 1: lower right, 2: upper left
float2 srcTexCoords[3];
int srcLevel;
bool srcLuminance; // source texture is luminance texture
int srcLevel; // Source texture level.
int srcLayer; // Source texture layer.
bool dstFlipViewportX;
bool dstFlipViewportY;
bool dstLuminance; // destination texture is luminance;
bool dstLuminance; // destination texture is luminance. Unused by depth & stencil blitting.
};
struct BlitVSOut
......@@ -24,13 +37,16 @@ struct BlitVSOut
float2 texCoords [[user(locn1)]];
};
vertex BlitVSOut blitVS(unsigned int vid [[ vertex_id ]],
constant BlitParams &options [[buffer(0)]])
vertex BlitVSOut blitVS(unsigned int vid [[vertex_id]], constant BlitParams &options [[buffer(0)]])
{
BlitVSOut output;
output.position = float4(gCorners[vid], 0.0, 1.0);
output.texCoords = options.srcTexCoords[vid];
if (options.dstFlipViewportX)
{
output.position.x = -output.position.x;
}
if (!options.dstFlipViewportY)
{
// If viewport is not flipped, we have to flip Y in normalized device coordinates.
......@@ -41,58 +57,124 @@ vertex BlitVSOut blitVS(unsigned int vid [[ vertex_id ]],
return output;
}
float4 blitSampleTexture(texture2d<float> srcTexture,
float2 texCoords,
constant BlitParams &options)
static inline float3 cubeTexcoords(float2 texcoords, int face)
{
constexpr sampler textureSampler(mag_filter::linear,
min_filter::linear);
float4 output = srcTexture.sample(textureSampler, texCoords, level(options.srcLevel));
if (options.srcLuminance)
texcoords = 2.0 * texcoords - 1.0;
switch (face)
{
output.gb = float2(output.r, output.r);
case 0:
return float3(1.0, -texcoords.y, -texcoords.x);
case 1:
return float3(-1.0, -texcoords.y, texcoords.x);
case 2:
return float3(texcoords.x, 1.0, texcoords.y);
case 3:
return float3(texcoords.x, -1.0, -texcoords.y);
case 4:
return float3(texcoords.x, -texcoords.y, 1.0);
case 5:
return float3(-texcoords.x, -texcoords.y, -1.0);
}
return output;
return float3(texcoords, 0);
}
float4 blitOutput(float4 color, constant BlitParams &options)
template <typename T>
static inline vec<T, 4> blitSampleTextureMS(texture2d_ms<T> srcTexture, float2 texCoords)
{
float4 ret = color;
uint2 dimens(srcTexture.get_width(), srcTexture.get_height());
uint2 coords = uint2(texCoords * float2(dimens));
if (options.dstLuminance)
uint samples = srcTexture.get_num_samples();
vec<T, 4> output(0);
for (uint sample = 0; sample < samples; ++sample)
{
ret.r = ret.g = ret.b = color.r;
output += srcTexture.read(coords, sample);
}
return ret;
}
output = output / samples;
fragment float4 blitFS(BlitVSOut input [[stage_in]],
texture2d<float> srcTexture [[texture(0)]],
constant BlitParams &options [[buffer(0)]])
{
return blitOutput(blitSampleTexture(srcTexture, input.texCoords, options), options);
return output;
}
fragment float4 blitPremultiplyAlphaFS(BlitVSOut input [[stage_in]],
texture2d<float> srcTexture [[texture(0)]],
constant BlitParams &options [[buffer(0)]])
template <typename T>
static inline vec<T, 4> blitSampleTexture3D(texture3d<T> srcTexture,
sampler textureSampler,
float2 texCoords,
constant BlitParams &options)
{
float4 output = blitSampleTexture(srcTexture, input.texCoords, options);
output.xyz *= output.a;
return blitOutput(output, options);
uint depth = srcTexture.get_depth(options.srcLevel);
float zCoord = (float(options.srcLayer) + 0.5) / float(depth);
return srcTexture.sample(textureSampler, float3(texCoords, zCoord), level(options.srcLevel));
}
fragment float4 blitUnmultiplyAlphaFS(BlitVSOut input [[stage_in]],
texture2d<float> srcTexture [[texture(0)]],
constant BlitParams &options [[buffer(0)]])
// clang-format off
#define BLIT_COLOR_FS_PARAMS(TYPE) \
BlitVSOut input [[stage_in]], \
texture2d<TYPE> srcTexture2d [[texture(0), function_constant(kSourceTextureType2D)]], \
texture2d_array<TYPE> srcTexture2dArray \
[[texture(0), function_constant(kSourceTextureType2DArray)]], \
texture2d_ms<TYPE> srcTexture2dMS [[texture(0), function_constant(kSourceTextureType2DMS)]], \
texturecube<TYPE> srcTextureCube [[texture(0), function_constant(kSourceTextureTypeCube)]], \
texture3d<TYPE> srcTexture3d [[texture(0), function_constant(kSourceTextureType3D)]], \
sampler textureSampler [[sampler(0)]], \
constant BlitParams &options [[buffer(0)]]
// clang-format on
#define FORWARD_BLIT_COLOR_FS_PARAMS \
input, srcTexture2d, srcTexture2dArray, srcTexture2dMS, srcTextureCube, srcTexture3d, \
textureSampler, options
template <typename T>
static inline vec<T, 4> blitReadTexture(BLIT_COLOR_FS_PARAMS(T))
{
float4 output = blitSampleTexture(srcTexture, input.texCoords, options);
if (output.a != 0.0)
vec<T, 4> output;
switch (kSourceTextureType)
{
output.xyz *= 1.0 / output.a;
case kTextureType2D:
output = srcTexture2d.sample(textureSampler, input.texCoords, level(options.srcLevel));
break;
case kTextureType2DArray:
output = srcTexture2dArray.sample(textureSampler, input.texCoords, options.srcLayer,
level(options.srcLevel));
break;
case kTextureType2DMultisample:
output = blitSampleTextureMS(srcTexture2dMS, input.texCoords);
break;
case kTextureTypeCube:
output = srcTextureCube.sample(textureSampler,
cubeTexcoords(input.texCoords, options.srcLayer),
level(options.srcLevel));
break;
case kTextureType3D:
output = blitSampleTexture3D(srcTexture3d, textureSampler, input.texCoords, options);
break;
}
return blitOutput(output, options);
if (kPremultiplyAlpha)
{
output.xyz *= output.a;
}
else if (kUnmultiplyAlpha)
{
if (output.a != 0.0)
{
output.xyz /= output.a;
}
}
if (options.dstLuminance)
{
output.g = output.b = output.r;
}
return output;
}
fragment float4 blitFS(BLIT_COLOR_FS_PARAMS(float))
{
return blitReadTexture(FORWARD_BLIT_COLOR_FS_PARAMS);
}
......@@ -13,6 +13,8 @@
# include <metal_stdlib>
#endif
#include "constants.h"
#define ANGLE_KERNEL_GUARD(IDX, MAX_COUNT) \
if (IDX >= MAX_COUNT) \
{ \
......
This source diff could not be displayed because it is too large. You can view the blob instead.
//
// Copyright 2020 The ANGLE Project. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// constants.h: Declare some constant values to be used by metal defaultshaders.
#ifndef LIBANGLE_RENDERER_METAL_SHADERS_ENUM_H_
#define LIBANGLE_RENDERER_METAL_SHADERS_ENUM_H_
namespace rx
{
namespace mtl_shader
{
enum
{
kTextureType2D = 0,
kTextureType2DMultisample = 1,
kTextureType2DArray = 2,
kTextureTypeCube = 3,
kTextureType3D = 4,
kTextureTypeCount = 5,
};
} // namespace mtl_shader
} // namespace rx
#endif
......@@ -26,7 +26,8 @@ def main():
# auto_script parameters.
if len(sys.argv) > 1:
inputs = [
'master_source.metal', 'blit.metal', 'clear.metal', 'gen_indices.metal', 'common.h'
'master_source.metal', 'blit.metal', 'clear.metal', 'gen_indices.metal', 'common.h',
'constants.h'
]
outputs = ['compiled/mtl_default_shaders.inc', 'mtl_default_shaders_src_autogen.inc']
......
......@@ -38,7 +38,34 @@ constexpr char default_metallib_src[] = R"(
# 1 "./common.h" 1
# 22 "./common.h"
# 16 "./common.h"
# 1 "./constants.h" 1
# 11 "./constants.h"
namespace rx
{
namespace mtl_shader
{
enum
{
kTextureType2D = 0,
kTextureType2DMultisample = 1,
kTextureType2DArray = 2,
kTextureTypeCube = 3,
kTextureType3D = 4,
kTextureTypeCount = 5,
};
}
}
# 17 "./common.h" 2
using namespace metal;
......@@ -68,13 +95,27 @@ fragment float4 clearFS(constant ClearParams &clearParams [[buffer(0)]])
}
# 10 "master_source.metal" 2
# 1 "./blit.metal" 1
# 11 "./blit.metal"
# 10 "./blit.metal"
using namespace rx::mtl_shader;
constant bool kPremultiplyAlpha [[function_constant(4)]];
constant bool kUnmultiplyAlpha [[function_constant(5)]];
constant int kSourceTextureType [[function_constant(6)]];
constant bool kSourceTextureType2D = kSourceTextureType == kTextureType2D;
constant bool kSourceTextureType2DArray = kSourceTextureType == kTextureType2DArray;
constant bool kSourceTextureType2DMS = kSourceTextureType == kTextureType2DMultisample;
constant bool kSourceTextureTypeCube = kSourceTextureType == kTextureTypeCube;
constant bool kSourceTextureType3D = kSourceTextureType == kTextureType3D;
struct BlitParams
{
float2 srcTexCoords[3];
int srcLevel;
bool srcLuminance;
int srcLayer;
bool dstFlipViewportX;
bool dstFlipViewportY;
bool dstLuminance;
};
......@@ -85,13 +126,16 @@ struct BlitVSOut
float2 texCoords [[user(locn1)]];
};
vertex BlitVSOut blitVS(unsigned int vid [[ vertex_id ]],
constant BlitParams &options [[buffer(0)]])
vertex BlitVSOut blitVS(unsigned int vid [[vertex_id]], constant BlitParams &options [[buffer(0)]])
{
BlitVSOut output;
output.position = float4(gCorners[vid], 0.0, 1.0);
output.texCoords = options.srcTexCoords[vid];
if (options.dstFlipViewportX)
{
output.position.x = -output.position.x;
}
if (!options.dstFlipViewportY)
{
......@@ -102,60 +146,109 @@ vertex BlitVSOut blitVS(unsigned int vid [[ vertex_id ]],
return output;
}
float4 blitSampleTexture(texture2d<float> srcTexture,
float2 texCoords,
constant BlitParams &options)
static inline float3 cubeTexcoords(float2 texcoords, int face)
{
constexpr sampler textureSampler(mag_filter::linear,
min_filter::linear);
float4 output = srcTexture.sample(textureSampler, texCoords, level(options.srcLevel));
if (options.srcLuminance)
texcoords = 2.0 * texcoords - 1.0;
switch (face)
{
output.gb = float2(output.r, output.r);
case 0:
return float3(1.0, -texcoords.y, -texcoords.x);
case 1:
return float3(-1.0, -texcoords.y, texcoords.x);
case 2:
return float3(texcoords.x, 1.0, texcoords.y);
case 3:
return float3(texcoords.x, -1.0, -texcoords.y);
case 4:
return float3(texcoords.x, -texcoords.y, 1.0);
case 5:
return float3(-texcoords.x, -texcoords.y, -1.0);
}
return output;
return float3(texcoords, 0);
}
float4 blitOutput(float4 color, constant BlitParams &options)
template <typename T>
static inline vec<T, 4> blitSampleTextureMS(texture2d_ms<T> srcTexture, float2 texCoords)
{
float4 ret = color;
uint2 dimens(srcTexture.get_width(), srcTexture.get_height());
uint2 coords = uint2(texCoords * float2(dimens));
if (options.dstLuminance)
uint samples = srcTexture.get_num_samples();
vec<T, 4> output(0);
for (uint sample = 0; sample < samples; ++sample)
{
ret.r = ret.g = ret.b = color.r;
output += srcTexture.read(coords, sample);
}
return ret;
}
output = output / samples;
fragment float4 blitFS(BlitVSOut input [[stage_in]],
texture2d<float> srcTexture [[texture(0)]],
constant BlitParams &options [[buffer(0)]])
{
return blitOutput(blitSampleTexture(srcTexture, input.texCoords, options), options);
return output;
}
fragment float4 blitPremultiplyAlphaFS(BlitVSOut input [[stage_in]],
texture2d<float> srcTexture [[texture(0)]],
constant BlitParams &options [[buffer(0)]])
template <typename T>
static inline vec<T, 4> blitSampleTexture3D(texture3d<T> srcTexture,
sampler textureSampler,
float2 texCoords,
constant BlitParams &options)
{
float4 output = blitSampleTexture(srcTexture, input.texCoords, options);
output.xyz *= output.a;
return blitOutput(output, options);
}
uint depth = srcTexture.get_depth(options.srcLevel);
float zCoord = (float(options.srcLayer) + 0.5) / float(depth);
fragment float4 blitUnmultiplyAlphaFS(BlitVSOut input [[stage_in]],
texture2d<float> srcTexture [[texture(0)]],
constant BlitParams &options [[buffer(0)]])
return srcTexture.sample(textureSampler, float3(texCoords, zCoord), level(options.srcLevel));
}
# 130 "./blit.metal"
template <typename T>
static inline vec<T, 4> blitReadTexture(BlitVSOut input [[stage_in]], texture2d<T> srcTexture2d [[texture(0), function_constant(kSourceTextureType2D)]], texture2d_array<T> srcTexture2dArray [[texture(0), function_constant(kSourceTextureType2DArray)]], texture2d_ms<T> srcTexture2dMS [[texture(0), function_constant(kSourceTextureType2DMS)]], texturecube<T> srcTextureCube [[texture(0), function_constant(kSourceTextureTypeCube)]], texture3d<T> srcTexture3d [[texture(0), function_constant(kSourceTextureType3D)]], sampler textureSampler [[sampler(0)]], constant BlitParams &options [[buffer(0)]])
{
float4 output = blitSampleTexture(srcTexture, input.texCoords, options);
if (output.a != 0.0)
vec<T, 4> output;
switch (kSourceTextureType)
{
case kTextureType2D:
output = srcTexture2d.sample(textureSampler, input.texCoords, level(options.srcLevel));
break;
case kTextureType2DArray:
output = srcTexture2dArray.sample(textureSampler, input.texCoords, options.srcLayer,
level(options.srcLevel));
break;
case kTextureType2DMultisample:
output = blitSampleTextureMS(srcTexture2dMS, input.texCoords);
break;
case kTextureTypeCube:
output = srcTextureCube.sample(textureSampler,
cubeTexcoords(input.texCoords, options.srcLayer),
level(options.srcLevel));
break;
case kTextureType3D:
output = blitSampleTexture3D(srcTexture3d, textureSampler, input.texCoords, options);
break;
}
if (kPremultiplyAlpha)
{
output.xyz *= output.a;
}
else if (kUnmultiplyAlpha)
{
if (output.a != 0.0)
{
output.xyz /= output.a;
}
}
if (options.dstLuminance)
{
output.xyz *= 1.0 / output.a;
output.g = output.b = output.r;
}
return blitOutput(output, options);
return output;
}
fragment float4 blitFS(BlitVSOut input [[stage_in]], texture2d<float> srcTexture2d [[texture(0), function_constant(kSourceTextureType2D)]], texture2d_array<float> srcTexture2dArray [[texture(0), function_constant(kSourceTextureType2DArray)]], texture2d_ms<float> srcTexture2dMS [[texture(0), function_constant(kSourceTextureType2DMS)]], texturecube<float> srcTextureCube [[texture(0), function_constant(kSourceTextureTypeCube)]], texture3d<float> srcTexture3d [[texture(0), function_constant(kSourceTextureType3D)]], sampler textureSampler [[sampler(0)]], constant BlitParams &options [[buffer(0)]])
{
return blitReadTexture(input, srcTexture2d, srcTexture2dArray, srcTexture2dMS, srcTextureCube, srcTexture3d, textureSampler, options);
}
# 11 "master_source.metal" 2
# 1 "./gen_indices.metal" 1
......
......@@ -16,15 +16,39 @@ using namespace angle;
namespace
{
class MultisampleTest : public ANGLETest
using MultisampleTestParams = std::tuple<angle::PlatformParameters, bool>;
std::string PrintToStringParamName(const ::testing::TestParamInfo<MultisampleTestParams> &info)
{
::std::stringstream ss;
ss << std::get<0>(info.param);
if (std::get<1>(info.param))
{
ss << "__NoStoreAndResolve";
}
return ss.str();
}
class MultisampleTest : public ANGLETestWithParam<MultisampleTestParams>
{
protected:
void testSetUp() override
{
const angle::PlatformParameters platform = ::testing::get<0>(GetParam());
std::vector<const char *> disabledFeatures;
if (::testing::get<1>(GetParam()))
{
disabledFeatures.push_back("allow_msaa_store_and_resolve");
}
disabledFeatures.push_back(nullptr);
// Get display.
EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
mDisplay = eglGetPlatformDisplayEXT(
EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, platform.getRenderer(),
EGL_FEATURE_OVERRIDES_DISABLED_ANGLE,
reinterpret_cast<EGLAttrib>(disabledFeatures.data()), EGL_NONE};
mDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE,
reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
ASSERT_TRUE(mDisplay != EGL_NO_DISPLAY);
ASSERT_TRUE(eglInitialize(mDisplay, nullptr, nullptr) == EGL_TRUE);
......@@ -57,9 +81,9 @@ class MultisampleTest : public ANGLETest
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR,
GetParam().majorVersion,
platform.majorVersion,
EGL_CONTEXT_MINOR_VERSION_KHR,
GetParam().minorVersion,
platform.minorVersion,
EGL_NONE,
};
......@@ -267,6 +291,90 @@ TEST_P(MultisampleTest, Triangle)
}
}
// Test polygon rendering on a multisampled surface. And rendering is interrupted by a compute pass
// that converts the index buffer. Make sure the rendering's multisample result is preserved after
// interruption.
TEST_P(MultisampleTest, ContentPresevedAfterInterruption)
{
ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_rgb8_rgba8"));
// http://anglebug.com/3470
ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES());
// http://anglebug.com/4609
ANGLE_SKIP_TEST_IF(IsD3D11());
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program);
const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
if (IsGLExtensionEnabled("GL_EXT_discard_framebuffer"))
{
GLenum attachments[] = {GL_COLOR_EXT, GL_DEPTH_EXT, GL_STENCIL_EXT};
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 3, attachments);
}
// Draw triangle
GLBuffer vertexBuffer;
const Vector3 vertices[3] = {{-1.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}};
prepareVertexBuffer(vertexBuffer, vertices, 3, positionLocation);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
ASSERT_GL_NO_ERROR();
// Draw a line
GLBuffer vertexBuffer2;
GLBuffer indexBuffer2;
const Vector3 vertices2[2] = {{-1.0f, -0.3f, 0.0f}, {1.0f, 0.3f, 0.0f}};
const GLubyte indices2[] = {0, 1};
prepareVertexBuffer(vertexBuffer2, vertices2, 2, positionLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer2);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);
glDrawElements(GL_LINES, 2, GL_UNSIGNED_BYTE, 0);
ASSERT_GL_NO_ERROR();
// Top-left pixels should be all red.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kWindowSize / 4, kWindowSize / 4, GLColor::red);
// Triangle edge:
// Diagonal pixels from bottom-left to top-right are between red and black. Pixels above the
// diagonal are red and pixels below it are black.
{
const GLColor kMidRed = {128, 0, 0, 128};
constexpr int kErrorMargin = 16;
for (int i = 1; i + 1 < kWindowSize; ++i)
{
// Exclude the middle pixel where the triangle and line cross each other.
if (abs(kWindowSize / 2 - i) <= 1)
{
continue;
}
int j = kWindowSize - 1 - i;
EXPECT_PIXEL_COLOR_NEAR(i, j, kMidRed, kErrorMargin);
EXPECT_PIXEL_COLOR_EQ(i, j - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(i, j + 1, GLColor::transparentBlack);
}
}
// Line edge:
{
const GLColor kDarkRed = {128, 0, 0, 128};
constexpr int kErrorMargin = 16;
constexpr int kLargeMargin = 80;
static_assert(kWindowSize == 8, "Verification code written for 8x8 window");
// Exclude the triangle region.
EXPECT_PIXEL_COLOR_NEAR(5, 4, GLColor::red, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(6, 4, GLColor::red, kLargeMargin);
EXPECT_PIXEL_COLOR_NEAR(7, 5, kDarkRed, kLargeMargin);
}
}
// Test that resolve from multisample default framebuffer works.
TEST_P(MultisampleTestES3, ResolveToFBO)
{
......@@ -305,26 +413,41 @@ TEST_P(MultisampleTestES3, ResolveToFBO)
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2, kWindowSize / 2, kResult, 1);
}
ANGLE_INSTANTIATE_TEST(MultisampleTest,
WithNoFixture(ES2_D3D11()),
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES31_D3D11()),
WithNoFixture(ES2_OPENGL()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES31_OPENGL()),
WithNoFixture(ES2_OPENGLES()),
WithNoFixture(ES3_OPENGLES()),
WithNoFixture(ES31_OPENGLES()),
WithNoFixture(ES2_VULKAN()),
WithNoFixture(ES3_VULKAN()),
WithNoFixture(ES31_VULKAN()));
ANGLE_INSTANTIATE_TEST(MultisampleTestES3,
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES31_D3D11()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES31_OPENGL()),
WithNoFixture(ES3_OPENGLES()),
WithNoFixture(ES31_OPENGLES()),
WithNoFixture(ES3_VULKAN()),
WithNoFixture(ES31_VULKAN()));
ANGLE_INSTANTIATE_TEST_COMBINE_1(MultisampleTest,
PrintToStringParamName,
testing::Values(false),
WithNoFixture(ES2_D3D11()),
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES31_D3D11()),
WithNoFixture(ES2_METAL()),
WithNoFixture(ES2_OPENGL()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES31_OPENGL()),
WithNoFixture(ES2_OPENGLES()),
WithNoFixture(ES3_OPENGLES()),
WithNoFixture(ES31_OPENGLES()),
WithNoFixture(ES2_VULKAN()),
WithNoFixture(ES3_VULKAN()),
WithNoFixture(ES31_VULKAN()));
namespace store_and_resolve_feature_off
{
// Simulate missing msaa auto resolve feature in Metal.
ANGLE_INSTANTIATE_TEST_COMBINE_1(MultisampleTest,
PrintToStringParamName,
testing::Values(true),
WithNoFixture(ES2_METAL()));
} // namespace store_and_resolve_feature_off
ANGLE_INSTANTIATE_TEST_COMBINE_1(MultisampleTestES3,
PrintToStringParamName,
testing::Values(false),
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES31_D3D11()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES31_OPENGL()),
WithNoFixture(ES3_OPENGLES()),
WithNoFixture(ES31_OPENGLES()),
WithNoFixture(ES3_VULKAN()),
WithNoFixture(ES31_VULKAN()));
} // anonymous namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment