| // |
| // 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. |
| // |
| // FramebufferMtl.mm: |
| // Implements the class methods for FramebufferMtl. |
| // |
| |
| #include "libANGLE/angletypes.h" |
| #include "libANGLE/renderer/metal/ContextMtl.h" |
| |
| #include <TargetConditionals.h> |
| |
| #include "common/MemoryBuffer.h" |
| #include "common/angleutils.h" |
| #include "common/debug.h" |
| #include "libANGLE/ErrorStrings.h" |
| #include "libANGLE/renderer/metal/BufferMtl.h" |
| #include "libANGLE/renderer/metal/DisplayMtl.h" |
| #include "libANGLE/renderer/metal/FrameBufferMtl.h" |
| #include "libANGLE/renderer/metal/SurfaceMtl.h" |
| #include "libANGLE/renderer/metal/mtl_utils.h" |
| #include "libANGLE/renderer/renderer_utils.h" |
| |
| namespace rx |
| { |
| namespace |
| { |
| // Override clear color based on texture's write mask |
| void OverrideMTLClearColor(const mtl::TextureRef &texture, |
| const mtl::ClearColorValue &clearColor, |
| MTLClearColor *colorOut) |
| { |
| *colorOut = |
| mtl::EmulatedAlphaClearColor(clearColor.toMTLClearColor(), texture->getColorWritableMask()); |
| } |
| |
| const gl::InternalFormat &GetReadAttachmentInfo(const gl::Context *context, |
| RenderTargetMtl *renderTarget) |
| { |
| GLenum implFormat; |
| |
| if (renderTarget && renderTarget->getFormat()) |
| { |
| implFormat = renderTarget->getFormat()->actualAngleFormat().fboImplementationInternalFormat; |
| } |
| else |
| { |
| implFormat = GL_NONE; |
| } |
| |
| return gl::GetSizedInternalFormatInfo(implFormat); |
| } |
| |
| } |
| |
| // FramebufferMtl implementation |
| FramebufferMtl::FramebufferMtl(const gl::FramebufferState &state, |
| bool flipY, |
| WindowSurfaceMtl *backbuffer) |
| : FramebufferImpl(state), mBackbuffer(backbuffer), mFlipY(flipY) |
| { |
| reset(); |
| } |
| |
| FramebufferMtl::~FramebufferMtl() {} |
| |
| void FramebufferMtl::reset() |
| { |
| for (auto &rt : mColorRenderTargets) |
| { |
| rt = nullptr; |
| } |
| mDepthRenderTarget = mStencilRenderTarget = nullptr; |
| |
| mRenderPassFirstColorAttachmentFormat = nullptr; |
| |
| mReadPixelBuffer = nullptr; |
| } |
| |
| void FramebufferMtl::destroy(const gl::Context *context) |
| { |
| reset(); |
| } |
| |
| angle::Result FramebufferMtl::discard(const gl::Context *context, |
| size_t count, |
| const GLenum *attachments) |
| { |
| return invalidate(context, count, attachments); |
| } |
| |
| angle::Result FramebufferMtl::invalidate(const gl::Context *context, |
| size_t count, |
| const GLenum *attachments) |
| { |
| return invalidateImpl(mtl::GetImpl(context), count, attachments); |
| } |
| |
| angle::Result FramebufferMtl::invalidateSub(const gl::Context *context, |
| size_t count, |
| const GLenum *attachments, |
| const gl::Rectangle &area) |
| { |
| if (area.encloses(getCompleteRenderArea())) |
| { |
| return invalidateImpl(mtl::GetImpl(context), count, attachments); |
| } |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::clear(const gl::Context *context, GLbitfield mask) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| mtl::ClearRectParams clearOpts; |
| |
| bool clearColor = IsMaskFlagSet(mask, static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT)); |
| bool clearDepth = IsMaskFlagSet(mask, static_cast<GLbitfield>(GL_DEPTH_BUFFER_BIT)); |
| bool clearStencil = IsMaskFlagSet(mask, static_cast<GLbitfield>(GL_STENCIL_BUFFER_BIT)); |
| |
| gl::DrawBufferMask clearColorBuffers; |
| if (clearColor) |
| { |
| clearColorBuffers = mState.getEnabledDrawBuffers(); |
| clearOpts.clearColor = contextMtl->getClearColorValue(); |
| } |
| if (clearDepth) |
| { |
| clearOpts.clearDepth = contextMtl->getClearDepthValue(); |
| } |
| if (clearStencil) |
| { |
| clearOpts.clearStencil = contextMtl->getClearStencilValue(); |
| } |
| |
| return clearImpl(context, clearColorBuffers, &clearOpts); |
| } |
| |
| angle::Result FramebufferMtl::clearBufferfv(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| const GLfloat *values) |
| { |
| mtl::ClearRectParams clearOpts; |
| |
| gl::DrawBufferMask clearColorBuffers; |
| if (buffer == GL_DEPTH) |
| { |
| clearOpts.clearDepth = values[0]; |
| } |
| else |
| { |
| clearColorBuffers.set(drawbuffer); |
| clearOpts.clearColor = mtl::ClearColorValue(values[0], values[1], values[2], values[3]); |
| } |
| |
| return clearImpl(context, clearColorBuffers, &clearOpts); |
| } |
| angle::Result FramebufferMtl::clearBufferuiv(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| const GLuint *values) |
| { |
| gl::DrawBufferMask clearColorBuffers; |
| clearColorBuffers.set(drawbuffer); |
| |
| mtl::ClearRectParams clearOpts; |
| clearOpts.clearColor = mtl::ClearColorValue(values[0], values[1], values[2], values[3]); |
| |
| return clearImpl(context, clearColorBuffers, &clearOpts); |
| } |
| angle::Result FramebufferMtl::clearBufferiv(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| const GLint *values) |
| { |
| mtl::ClearRectParams clearOpts; |
| |
| gl::DrawBufferMask clearColorBuffers; |
| if (buffer == GL_STENCIL) |
| { |
| clearOpts.clearStencil = values[0] & mtl::kStencilMaskAll; |
| } |
| else |
| { |
| clearColorBuffers.set(drawbuffer); |
| clearOpts.clearColor = mtl::ClearColorValue(values[0], values[1], values[2], values[3]); |
| } |
| |
| return clearImpl(context, clearColorBuffers, &clearOpts); |
| } |
| angle::Result FramebufferMtl::clearBufferfi(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| GLfloat depth, |
| GLint stencil) |
| { |
| mtl::ClearRectParams clearOpts; |
| clearOpts.clearDepth = depth; |
| clearOpts.clearStencil = stencil & mtl::kStencilMaskAll; |
| |
| return clearImpl(context, gl::DrawBufferMask(), &clearOpts); |
| } |
| |
| const gl::InternalFormat &FramebufferMtl::getImplementationColorReadFormat( |
| const gl::Context *context) const |
| { |
| return GetReadAttachmentInfo(context, getColorReadRenderTargetNoCache(context)); |
| } |
| |
| angle::Result FramebufferMtl::readPixels(const gl::Context *context, |
| const gl::Rectangle &area, |
| GLenum format, |
| GLenum type, |
| const gl::PixelPackState &pack, |
| gl::Buffer *packBuffer, |
| void *pixels) |
| { |
| // Clip read area to framebuffer. |
| const gl::Extents &fbSize = getState().getReadAttachment()->getSize(); |
| const gl::Rectangle fbRect(0, 0, fbSize.width, fbSize.height); |
| |
| gl::Rectangle clippedArea; |
| if (!ClipRectangle(area, fbRect, &clippedArea)) |
| { |
| // nothing to read |
| return angle::Result::Continue; |
| } |
| gl::Rectangle flippedArea = getCorrectFlippedReadArea(context, clippedArea); |
| |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| const gl::InternalFormat &sizedFormatInfo = gl::GetInternalFormatInfo(format, type); |
| |
| GLuint outputPitch = 0; |
| ANGLE_CHECK_GL_MATH(contextMtl, |
| sizedFormatInfo.computeRowPitch(type, area.width, pack.alignment, |
| pack.rowLength, &outputPitch)); |
| GLuint outputSkipBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextMtl, sizedFormatInfo.computeSkipBytes(type, outputPitch, 0, pack, |
| false, &outputSkipBytes)); |
| |
| outputSkipBytes += (clippedArea.x - area.x) * sizedFormatInfo.pixelBytes + |
| (clippedArea.y - area.y) * outputPitch; |
| |
| const angle::Format &angleFormat = GetFormatFromFormatType(format, type); |
| |
| PackPixelsParams params(flippedArea, angleFormat, outputPitch, pack.reverseRowOrder, packBuffer, |
| 0); |
| |
| if (params.packBuffer) |
| { |
| // If PBO is active, pixels is treated as offset. |
| params.offset = reinterpret_cast<ptrdiff_t>(pixels) + outputSkipBytes; |
| } |
| |
| if (mFlipY) |
| { |
| params.reverseRowOrder = !params.reverseRowOrder; |
| } |
| |
| ANGLE_TRY(readPixelsImpl(context, flippedArea, params, getColorReadRenderTarget(context), |
| static_cast<uint8_t *>(pixels) + outputSkipBytes)); |
| |
| return angle::Result::Continue; |
| } |
| |
| namespace |
| { |
| |
| using FloatRectangle = gl::RectangleImpl<float>; |
| |
| float clamp0Max(float v, float max) |
| { |
| return std::max(0.0f, std::min(max, v)); |
| } |
| |
| void ClampToBoundsAndAdjustCorrespondingValue(float a, |
| float originalASize, |
| float maxSize, |
| float b, |
| float originalBSize, |
| float *newA, |
| float *newB) |
| { |
| float clippedA = clamp0Max(a, maxSize); |
| float delta = clippedA - a; |
| *newA = clippedA; |
| *newB = b + delta * originalBSize / originalASize; |
| } |
| |
| void ClipRectToBoundsAndAdjustCorrespondingRect(const FloatRectangle &a, |
| const gl::Rectangle &originalA, |
| const gl::Rectangle &clipDimensions, |
| const FloatRectangle &b, |
| const gl::Rectangle &originalB, |
| FloatRectangle *newA, |
| FloatRectangle *newB) |
| { |
| float newAValues[4]; |
| float newBValues[4]; |
| ClampToBoundsAndAdjustCorrespondingValue(a.x0(), originalA.width, clipDimensions.width, b.x0(), |
| originalB.width, &newAValues[0], &newBValues[0]); |
| ClampToBoundsAndAdjustCorrespondingValue(a.y0(), originalA.height, clipDimensions.height, |
| b.y0(), originalB.height, &newAValues[1], |
| &newBValues[1]); |
| ClampToBoundsAndAdjustCorrespondingValue(a.x1(), originalA.width, clipDimensions.width, b.x1(), |
| originalB.width, &newAValues[2], &newBValues[2]); |
| ClampToBoundsAndAdjustCorrespondingValue(a.y1(), originalA.height, clipDimensions.height, |
| b.y1(), originalB.height, &newAValues[3], |
| &newBValues[3]); |
| |
| *newA = FloatRectangle(newAValues); |
| *newB = FloatRectangle(newBValues); |
| } |
| |
| void ClipRectsToBoundsAndAdjustCorrespondingRect(const FloatRectangle &a, |
| const gl::Rectangle &originalA, |
| const gl::Rectangle &aClipDimensions, |
| const FloatRectangle &b, |
| const gl::Rectangle &originalB, |
| const gl::Rectangle &bClipDimensions, |
| FloatRectangle *newA, |
| FloatRectangle *newB) |
| { |
| FloatRectangle tempA; |
| FloatRectangle tempB; |
| ClipRectToBoundsAndAdjustCorrespondingRect(a, originalA, aClipDimensions, b, originalB, &tempA, |
| &tempB); |
| ClipRectToBoundsAndAdjustCorrespondingRect(tempB, originalB, bClipDimensions, tempA, originalA, |
| newB, newA); |
| } |
| |
| void RoundValueAndAdjustCorrespondingValue(float a, |
| float originalASize, |
| float b, |
| float originalBSize, |
| int *newA, |
| float *newB) |
| { |
| float roundedA = std::round(a); |
| float delta = roundedA - a; |
| *newA = static_cast<int>(roundedA); |
| *newB = b + delta * originalBSize / originalASize; |
| } |
| |
| gl::Rectangle RoundRectToPixelsAndAdjustCorrespondingRectToMatch(const FloatRectangle &a, |
| const gl::Rectangle &originalA, |
| const FloatRectangle &b, |
| const gl::Rectangle &originalB, |
| FloatRectangle *newB) |
| { |
| int newAValues[4]; |
| float newBValues[4]; |
| RoundValueAndAdjustCorrespondingValue(a.x0(), originalA.width, b.x0(), originalB.width, |
| &newAValues[0], &newBValues[0]); |
| RoundValueAndAdjustCorrespondingValue(a.y0(), originalA.height, b.y0(), originalB.height, |
| &newAValues[1], &newBValues[1]); |
| RoundValueAndAdjustCorrespondingValue(a.x1(), originalA.width, b.x1(), originalB.width, |
| &newAValues[2], &newBValues[2]); |
| RoundValueAndAdjustCorrespondingValue(a.y1(), originalA.height, b.y1(), originalB.height, |
| &newAValues[3], &newBValues[3]); |
| |
| *newB = FloatRectangle(newBValues); |
| return gl::Rectangle(newAValues[0], newAValues[1], newAValues[2] - newAValues[0], |
| newAValues[3] - newAValues[1]); |
| } |
| |
| } // namespace |
| |
| angle::Result FramebufferMtl::blit(const gl::Context *context, |
| const gl::Rectangle &sourceAreaIn, |
| const gl::Rectangle &destAreaIn, |
| GLbitfield mask, |
| GLenum filter) |
| { |
| bool blitColorBuffer = (mask & GL_COLOR_BUFFER_BIT) != 0; |
| bool blitDepthBuffer = (mask & GL_DEPTH_BUFFER_BIT) != 0; |
| bool blitStencilBuffer = (mask & GL_STENCIL_BUFFER_BIT) != 0; |
| |
| const gl::State &glState = context->getState(); |
| const gl::Framebuffer *glSrcFramebuffer = glState.getReadFramebuffer(); |
| |
| FramebufferMtl *srcFrameBuffer = mtl::GetImpl(glSrcFramebuffer); |
| |
| blitColorBuffer = |
| blitColorBuffer && srcFrameBuffer->getColorReadRenderTarget(context) != nullptr; |
| blitDepthBuffer = blitDepthBuffer && srcFrameBuffer->getDepthRenderTarget() != nullptr; |
| blitStencilBuffer = blitStencilBuffer && srcFrameBuffer->getStencilRenderTarget() != nullptr; |
| |
| if (!blitColorBuffer && !blitDepthBuffer && !blitStencilBuffer) |
| { |
| // No-op |
| return angle::Result::Continue; |
| } |
| |
| const gl::Rectangle srcFramebufferDimensions = srcFrameBuffer->getCompleteRenderArea(); |
| const gl::Rectangle dstFramebufferDimensions = this->getCompleteRenderArea(); |
| |
| FloatRectangle srcRect(sourceAreaIn); |
| FloatRectangle dstRect(destAreaIn); |
| |
| FloatRectangle clippedSrcRect; |
| FloatRectangle clippedDstRect; |
| ClipRectsToBoundsAndAdjustCorrespondingRect(srcRect, sourceAreaIn, srcFramebufferDimensions, |
| dstRect, destAreaIn, dstFramebufferDimensions, |
| &clippedSrcRect, &clippedDstRect); |
| |
| FloatRectangle adjustedSrcRect; |
| gl::Rectangle srcClippedDestArea = RoundRectToPixelsAndAdjustCorrespondingRectToMatch( |
| clippedDstRect, destAreaIn, clippedSrcRect, sourceAreaIn, &adjustedSrcRect); |
| |
| if (srcFrameBuffer->flipY()) |
| { |
| adjustedSrcRect.y = |
| srcFramebufferDimensions.height - adjustedSrcRect.y - adjustedSrcRect.height; |
| adjustedSrcRect = adjustedSrcRect.flip(false, true); |
| } |
| |
| // If the destination is flipped in either direction, we will flip the source instead so that |
| // the destination area is always unflipped. |
| adjustedSrcRect = |
| adjustedSrcRect.flip(srcClippedDestArea.isReversedX(), srcClippedDestArea.isReversedY()); |
| srcClippedDestArea = srcClippedDestArea.removeReversal(); |
| |
| // Clip the destination area to the framebuffer size and scissor. |
| gl::Rectangle scissoredDestArea; |
| if (!gl::ClipRectangle(ClipRectToScissor(glState, dstFramebufferDimensions, false), |
| srcClippedDestArea, &scissoredDestArea)) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // Use blit with draw |
| mtl::BlitParams baseParams; |
| baseParams.dstTextureSize = |
| gl::Extents(dstFramebufferDimensions.width, dstFramebufferDimensions.height, 1); |
| baseParams.dstRect = srcClippedDestArea; |
| baseParams.dstScissorRect = scissoredDestArea; |
| baseParams.dstFlipY = this->flipY(); |
| |
| baseParams.srcNormalizedCoords = |
| mtl::NormalizedCoords(adjustedSrcRect.x, adjustedSrcRect.y, adjustedSrcRect.width, |
| adjustedSrcRect.height, srcFramebufferDimensions); |
| // This flag is for auto flipping the rect inside RenderUtils. Since we already flip it using |
| // getCorrectFlippedReadArea(). This flag is not needed. |
| baseParams.srcYFlipped = false; |
| baseParams.unpackFlipX = false; |
| baseParams.unpackFlipY = false; |
| |
| return blitWithDraw(context, srcFrameBuffer, blitColorBuffer, blitDepthBuffer, |
| blitStencilBuffer, filter, baseParams); |
| } |
| |
| angle::Result FramebufferMtl::blitWithDraw(const gl::Context *context, |
| FramebufferMtl *srcFrameBuffer, |
| bool blitColorBuffer, |
| bool blitDepthBuffer, |
| bool blitStencilBuffer, |
| GLenum filter, |
| const mtl::BlitParams &baseParams) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| // Use blit with draw |
| mtl::RenderCommandEncoder *renderEncoder = nullptr; |
| |
| // Blit Depth & stencil |
| if (blitDepthBuffer || blitStencilBuffer) |
| { |
| mtl::DepthStencilBlitParams dsBlitParams; |
| memcpy(&dsBlitParams, &baseParams, sizeof(baseParams)); |
| RenderTargetMtl *srcDepthRt = srcFrameBuffer->getDepthRenderTarget(); |
| RenderTargetMtl *srcStencilRt = srcFrameBuffer->getStencilRenderTarget(); |
| |
| if (blitDepthBuffer) |
| { |
| dsBlitParams.src = srcDepthRt->getTexture(); |
| dsBlitParams.srcLevel = srcDepthRt->getLevelIndex(); |
| dsBlitParams.srcLayer = srcDepthRt->getLayerIndex(); |
| } |
| |
| if (blitStencilBuffer && srcStencilRt->getTexture()) |
| { |
| dsBlitParams.srcStencil = srcStencilRt->getTexture()->getStencilView(); |
| dsBlitParams.srcLevel = srcStencilRt->getLevelIndex(); |
| dsBlitParams.srcLayer = srcStencilRt->getLayerIndex(); |
| |
| if (!contextMtl->getDisplay()->getFeatures().hasStencilOutput.enabled && |
| mStencilRenderTarget) |
| { |
| // Directly writing to stencil in shader is not supported, use temporary copy buffer |
| // work around. This is a compute pass. |
| mtl::StencilBlitViaBufferParams stencilOnlyBlitParams = dsBlitParams; |
| stencilOnlyBlitParams.dstStencil = mStencilRenderTarget->getTexture(); |
| stencilOnlyBlitParams.dstStencilLayer = mStencilRenderTarget->getLayerIndex(); |
| stencilOnlyBlitParams.dstStencilLevel = mStencilRenderTarget->getLevelIndex(); |
| stencilOnlyBlitParams.dstPackedDepthStencilFormat = |
| mStencilRenderTarget->getFormat()->hasDepthAndStencilBits(); |
| |
| ANGLE_TRY(contextMtl->getDisplay()->getUtils().blitStencilViaCopyBuffer( |
| context, stencilOnlyBlitParams)); |
| |
| // Prevent the stencil to be blitted with draw again |
| dsBlitParams.srcStencil = nullptr; |
| } |
| } |
| |
| // The actual blitting of depth and/or stencil |
| renderEncoder = ensureRenderPassStarted(context); |
| ANGLE_TRY(contextMtl->getDisplay()->getUtils().blitDepthStencilWithDraw( |
| context, renderEncoder, dsBlitParams)); |
| } // if (blitDepthBuffer || blitStencilBuffer) |
| else |
| { |
| renderEncoder = ensureRenderPassStarted(context); |
| } |
| |
| // Blit color |
| if (blitColorBuffer) |
| { |
| mtl::ColorBlitParams colorBlitParams; |
| memcpy(&colorBlitParams, &baseParams, sizeof(baseParams)); |
| |
| RenderTargetMtl *srcColorRt = srcFrameBuffer->getColorReadRenderTarget(context); |
| ASSERT(srcColorRt); |
| |
| colorBlitParams.src = srcColorRt->getTexture(); |
| colorBlitParams.srcLevel = srcColorRt->getLevelIndex(); |
| colorBlitParams.srcLayer = srcColorRt->getLayerIndex(); |
| |
| colorBlitParams.enabledBuffers = getState().getEnabledDrawBuffers(); |
| colorBlitParams.filter = filter; |
| colorBlitParams.dstLuminance = srcColorRt->getFormat()->actualAngleFormat().isLUMA(); |
| |
| ANGLE_TRY(contextMtl->getDisplay()->getUtils().blitColorWithDraw( |
| context, renderEncoder, srcColorRt->getFormat()->actualAngleFormat(), colorBlitParams)); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| gl::FramebufferStatus FramebufferMtl::checkStatus(const gl::Context *context) const |
| { |
| if (!mState.attachmentsHaveSameDimensions()) |
| { |
| return gl::FramebufferStatus::Incomplete( |
| GL_FRAMEBUFFER_UNSUPPORTED, |
| gl::err::kFramebufferIncompleteUnsupportedMissmatchedDimensions); |
| } |
| |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| if (!contextMtl->getDisplay()->getFeatures().allowSeparatedDepthStencilBuffers.enabled && |
| mState.hasSeparateDepthAndStencilAttachments()) |
| { |
| return gl::FramebufferStatus::Incomplete( |
| GL_FRAMEBUFFER_UNSUPPORTED, |
| gl::err::kFramebufferIncompleteUnsupportedSeparateDepthStencilBuffers); |
| } |
| |
| if (mState.getDepthAttachment() && mState.getDepthAttachment()->getFormat().info->depthBits && |
| mState.getDepthAttachment()->getFormat().info->stencilBits) |
| { |
| return checkPackedDepthStencilAttachment(); |
| } |
| |
| if (mState.getStencilAttachment() && |
| mState.getStencilAttachment()->getFormat().info->depthBits && |
| mState.getStencilAttachment()->getFormat().info->stencilBits) |
| { |
| return checkPackedDepthStencilAttachment(); |
| } |
| |
| return gl::FramebufferStatus::Complete(); |
| } |
| |
| gl::FramebufferStatus FramebufferMtl::checkPackedDepthStencilAttachment() const |
| { |
| if (ANGLE_APPLE_AVAILABLE_XCI(10.14, 13.0, 12.0)) |
| { |
| // If depth/stencil attachment has depth & stencil bits, then depth & stencil must not have |
| // separate attachment. i.e. They must be the same texture or one of them has no |
| // attachment. |
| if (mState.hasSeparateDepthAndStencilAttachments()) |
| { |
| WARN() << "Packed depth stencil texture/buffer must not be mixed with other " |
| "texture/buffer."; |
| return gl::FramebufferStatus::Incomplete( |
| GL_FRAMEBUFFER_UNSUPPORTED, |
| gl::err::kFramebufferIncompleteUnsupportedSeparateDepthStencilBuffers); |
| } |
| } |
| else |
| { |
| // Metal 2.0 and below doesn't allow packed depth stencil texture to be attached only as |
| // depth or stencil buffer. i.e. None of the depth & stencil attachment can be null. |
| if (!mState.getDepthStencilAttachment()) |
| { |
| WARN() << "Packed depth stencil texture/buffer must be bound to both depth & stencil " |
| "attachment point."; |
| return gl::FramebufferStatus::Incomplete( |
| GL_FRAMEBUFFER_UNSUPPORTED, |
| gl::err::kFramebufferIncompleteUnsupportedSeparateDepthStencilBuffers); |
| } |
| } |
| return gl::FramebufferStatus::Complete(); |
| } |
| |
| angle::Result FramebufferMtl::syncState(const gl::Context *context, |
| GLenum binding, |
| const gl::Framebuffer::DirtyBits &dirtyBits, |
| gl::Command command) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| bool mustNotifyContext = false; |
| // Cache old mRenderPassDesc before update*RenderTarget() invalidate it. |
| mtl::RenderPassDesc oldRenderPassDesc = mRenderPassDesc; |
| |
| for (size_t dirtyBit : dirtyBits) |
| { |
| switch (dirtyBit) |
| { |
| case gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT: |
| ANGLE_TRY(updateDepthRenderTarget(context)); |
| break; |
| case gl::Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT: |
| ANGLE_TRY(updateStencilRenderTarget(context)); |
| break; |
| case gl::Framebuffer::DIRTY_BIT_DEPTH_BUFFER_CONTENTS: |
| case gl::Framebuffer::DIRTY_BIT_STENCIL_BUFFER_CONTENTS: |
| // NOTE(hqle): What are we supposed to do? |
| break; |
| case gl::Framebuffer::DIRTY_BIT_DRAW_BUFFERS: |
| mustNotifyContext = true; |
| break; |
| case gl::Framebuffer::DIRTY_BIT_READ_BUFFER: |
| case gl::Framebuffer::DIRTY_BIT_DEFAULT_WIDTH: |
| case gl::Framebuffer::DIRTY_BIT_DEFAULT_HEIGHT: |
| case gl::Framebuffer::DIRTY_BIT_DEFAULT_SAMPLES: |
| case gl::Framebuffer::DIRTY_BIT_DEFAULT_FIXED_SAMPLE_LOCATIONS: |
| break; |
| default: |
| { |
| static_assert(gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0 == 0, "FB dirty bits"); |
| if (dirtyBit < gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX) |
| { |
| size_t colorIndexGL = static_cast<size_t>( |
| dirtyBit - gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0); |
| ANGLE_TRY(updateColorRenderTarget(context, colorIndexGL)); |
| } |
| else |
| { |
| ASSERT(dirtyBit >= gl::Framebuffer::DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 && |
| dirtyBit < gl::Framebuffer::DIRTY_BIT_COLOR_BUFFER_CONTENTS_MAX); |
| // NOTE: might need to notify context. |
| } |
| break; |
| } |
| } |
| } |
| |
| ANGLE_TRY(prepareRenderPass(context, &mRenderPassDesc)); |
| bool renderPassChanged = !oldRenderPassDesc.equalIgnoreLoadStoreOptions(mRenderPassDesc); |
| |
| if (mustNotifyContext || renderPassChanged) |
| { |
| FramebufferMtl *currentDrawFramebuffer = |
| mtl::GetImpl(context->getState().getDrawFramebuffer()); |
| if (currentDrawFramebuffer == this) |
| { |
| contextMtl->onDrawFrameBufferChangedState(context, this, renderPassChanged); |
| } |
| |
| // Recreate pixel reading buffer if needed in future. |
| mReadPixelBuffer = nullptr; |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::getSamplePosition(const gl::Context *context, |
| size_t index, |
| GLfloat *xy) const |
| { |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| |
| RenderTargetMtl *FramebufferMtl::getColorReadRenderTarget(const gl::Context *context) const |
| { |
| if (mState.getReadIndex() >= mColorRenderTargets.size()) |
| { |
| return nullptr; |
| } |
| |
| if (mBackbuffer) |
| { |
| bool isNewDrawable = false; |
| if (IsError(mBackbuffer->ensureCurrentDrawableObtained(context, &isNewDrawable))) |
| { |
| return nullptr; |
| } |
| |
| if (isNewDrawable && mBackbuffer->hasRobustResourceInit()) |
| { |
| (void)mBackbuffer->initializeContents(context, gl::ImageIndex::Make2D(0)); |
| } |
| } |
| |
| return mColorRenderTargets[mState.getReadIndex()]; |
| } |
| |
| RenderTargetMtl *FramebufferMtl::getColorReadRenderTargetNoCache(const gl::Context *context) const |
| { |
| if (mState.getReadIndex() >= mColorRenderTargets.size()) |
| { |
| return nullptr; |
| } |
| |
| if (mBackbuffer) |
| { |
| // If we have a backbuffer/window surface, we can take the old path here and return |
| // the cached color render target. |
| return getColorReadRenderTarget(context); |
| } |
| // If we have no backbuffer, get the attachment from state color attachments, as it may have |
| // changed before syncing. |
| const gl::FramebufferAttachment *attachment = mState.getColorAttachment(mState.getReadIndex()); |
| RenderTargetMtl *currentTarget = nullptr; |
| if (attachment->getRenderTarget(context, attachment->getRenderToTextureSamples(), |
| ¤tTarget) == angle::Result::Stop) |
| { |
| return nullptr; |
| } |
| return currentTarget; |
| } |
| |
| int FramebufferMtl::getSamples() const |
| { |
| return mRenderPassDesc.sampleCount; |
| } |
| |
| gl::Rectangle FramebufferMtl::getCompleteRenderArea() const |
| { |
| return gl::Rectangle(0, 0, mState.getDimensions().width, mState.getDimensions().height); |
| } |
| |
| bool FramebufferMtl::renderPassHasStarted(ContextMtl *contextMtl) const |
| { |
| return contextMtl->hasStartedRenderPass(mRenderPassDesc); |
| } |
| |
| mtl::RenderCommandEncoder *FramebufferMtl::ensureRenderPassStarted(const gl::Context *context) |
| { |
| return ensureRenderPassStarted(context, mRenderPassDesc); |
| } |
| |
| mtl::RenderCommandEncoder *FramebufferMtl::ensureRenderPassStarted(const gl::Context *context, |
| const mtl::RenderPassDesc &desc) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| if (mBackbuffer) |
| { |
| // Backbuffer might obtain new drawable, which means it might change the |
| // the native texture used as the target of the render pass. |
| // We need to call this before creating render encoder. |
| bool isNewDrawable; |
| if (IsError(mBackbuffer->ensureCurrentDrawableObtained(context, &isNewDrawable))) |
| { |
| return nullptr; |
| } |
| |
| if (isNewDrawable && mBackbuffer->hasRobustResourceInit()) |
| { |
| // Apply robust resource initialization on newly obtained drawable. |
| (void)mBackbuffer->initializeContents(context, gl::ImageIndex::Make2D(0)); |
| } |
| } |
| |
| // Only support ensureRenderPassStarted() with different load & store options only. The |
| // texture, level, slice must be the same. |
| ASSERT(desc.equalIgnoreLoadStoreOptions(mRenderPassDesc)); |
| |
| mtl::RenderCommandEncoder *encoder = contextMtl->getRenderPassCommandEncoder(desc); |
| |
| if (mRenderPassCleanStart) |
| { |
| // After a clean start we should reset the loadOp to MTLLoadActionLoad in case this render |
| // pass could be interrupted by a conversion compute shader pass then being resumed later. |
| mRenderPassCleanStart = false; |
| for (mtl::RenderPassColorAttachmentDesc &colorAttachment : mRenderPassDesc.colorAttachments) |
| { |
| colorAttachment.loadAction = MTLLoadActionLoad; |
| } |
| mRenderPassDesc.depthAttachment.loadAction = MTLLoadActionLoad; |
| mRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionLoad; |
| } |
| |
| return encoder; |
| } |
| |
| void FramebufferMtl::setLoadStoreActionOnRenderPassFirstStart( |
| mtl::RenderPassAttachmentDesc *attachmentOut, |
| const bool forceDepthStencilMultisampleLoad) |
| { |
| ASSERT(mRenderPassCleanStart); |
| |
| mtl::RenderPassAttachmentDesc &attachment = *attachmentOut; |
| |
| if (!forceDepthStencilMultisampleLoad && attachment.storeAction == MTLStoreActionDontCare) |
| { |
| // If we previously discarded attachment's content, then don't need to load it. |
| attachment.loadAction = MTLLoadActionDontCare; |
| } |
| else |
| { |
| attachment.loadAction = MTLLoadActionLoad; |
| } |
| |
| if (attachment.hasImplicitMSTexture()) |
| { |
| if (mBackbuffer) |
| { |
| // Default action for default framebuffer is resolve and keep MS texture's content. |
| // We only discard MS texture's content at the end of the frame. See onFrameEnd(). |
| attachment.storeAction = MTLStoreActionStoreAndMultisampleResolve; |
| } |
| else |
| { |
| // Default action is resolve but don't keep MS texture's content. |
| attachment.storeAction = MTLStoreActionMultisampleResolve; |
| } |
| } |
| else |
| { |
| attachment.storeAction = MTLStoreActionStore; // Default action is store |
| } |
| } |
| |
| void FramebufferMtl::onStartedDrawingToFrameBuffer(const gl::Context *context) |
| { |
| mRenderPassCleanStart = true; |
| |
| // If any of the render targets need to load their multisample textures, we should do the same |
| // for depth/stencil. |
| bool forceDepthStencilMultisampleLoad = false; |
| |
| // Compute loadOp based on previous storeOp and reset storeOp flags: |
| for (mtl::RenderPassColorAttachmentDesc &colorAttachment : mRenderPassDesc.colorAttachments) |
| { |
| forceDepthStencilMultisampleLoad |= |
| colorAttachment.storeAction == MTLStoreActionStoreAndMultisampleResolve; |
| setLoadStoreActionOnRenderPassFirstStart(&colorAttachment, false); |
| } |
| // Depth load/store |
| setLoadStoreActionOnRenderPassFirstStart(&mRenderPassDesc.depthAttachment, |
| forceDepthStencilMultisampleLoad); |
| |
| // Stencil load/store |
| setLoadStoreActionOnRenderPassFirstStart(&mRenderPassDesc.stencilAttachment, |
| forceDepthStencilMultisampleLoad); |
| } |
| |
| void FramebufferMtl::onFrameEnd(const gl::Context *context) |
| { |
| if (!mBackbuffer || mBackbuffer->preserveBuffer()) |
| { |
| return; |
| } |
| |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| // Always discard default FBO's depth stencil & multisample buffers at the end of the frame: |
| if (this->renderPassHasStarted(contextMtl)) |
| { |
| mtl::RenderCommandEncoder *encoder = contextMtl->getRenderCommandEncoder(); |
| |
| constexpr GLenum dsAttachments[] = {GL_DEPTH, GL_STENCIL}; |
| (void)invalidateImpl(contextMtl, 2, dsAttachments); |
| if (mBackbuffer->getSamples() > 1) |
| { |
| encoder->setColorStoreAction(MTLStoreActionMultisampleResolve, 0); |
| } |
| |
| contextMtl->endEncoding(false); |
| |
| // Reset discard flag. |
| onStartedDrawingToFrameBuffer(context); |
| } |
| } |
| |
| angle::Result FramebufferMtl::updateColorRenderTarget(const gl::Context *context, |
| size_t colorIndexGL) |
| { |
| ASSERT(colorIndexGL < mtl::kMaxRenderTargets); |
| // Reset load store action |
| mRenderPassDesc.colorAttachments[colorIndexGL].reset(); |
| return updateCachedRenderTarget(context, mState.getColorAttachment(colorIndexGL), |
| &mColorRenderTargets[colorIndexGL]); |
| } |
| |
| angle::Result FramebufferMtl::updateDepthRenderTarget(const gl::Context *context) |
| { |
| // Reset load store action |
| mRenderPassDesc.depthAttachment.reset(); |
| return updateCachedRenderTarget(context, mState.getDepthAttachment(), &mDepthRenderTarget); |
| } |
| |
| angle::Result FramebufferMtl::updateStencilRenderTarget(const gl::Context *context) |
| { |
| // Reset load store action |
| mRenderPassDesc.stencilAttachment.reset(); |
| return updateCachedRenderTarget(context, mState.getStencilAttachment(), &mStencilRenderTarget); |
| } |
| |
| angle::Result FramebufferMtl::updateCachedRenderTarget(const gl::Context *context, |
| const gl::FramebufferAttachment *attachment, |
| RenderTargetMtl **cachedRenderTarget) |
| { |
| RenderTargetMtl *newRenderTarget = nullptr; |
| if (attachment) |
| { |
| ASSERT(attachment->isAttached()); |
| ANGLE_TRY(attachment->getRenderTarget(context, attachment->getRenderToTextureSamples(), |
| &newRenderTarget)); |
| } |
| *cachedRenderTarget = newRenderTarget; |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::getReadableViewForRenderTarget( |
| const gl::Context *context, |
| const RenderTargetMtl &rtt, |
| const gl::Rectangle &readArea, |
| mtl::TextureRef *readableDepthViewOut, |
| mtl::TextureRef *readableStencilViewOut, |
| uint32_t *readableViewLevel, |
| uint32_t *readableViewLayer, |
| gl::Rectangle *readableViewArea) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| mtl::TextureRef srcTexture = rtt.getTexture(); |
| uint32_t level = rtt.getLevelIndex().get(); |
| uint32_t slice = rtt.getLayerIndex(); |
| |
| // NOTE(hqle): slice is not used atm. |
| ASSERT(slice == 0); |
| |
| bool readStencil = readableStencilViewOut; |
| |
| if (!srcTexture) |
| { |
| if (readableDepthViewOut) |
| { |
| *readableDepthViewOut = nullptr; |
| } |
| if (readableStencilViewOut) |
| { |
| *readableStencilViewOut = nullptr; |
| } |
| *readableViewArea = readArea; |
| return angle::Result::Continue; |
| } |
| |
| bool skipCopy = srcTexture->isShaderReadable(); |
| if (rtt.getFormat()->hasDepthAndStencilBits() && readStencil) |
| { |
| // If the texture is packed depth stencil, and we need stencil view, |
| // then it must support creating different format view. |
| skipCopy = skipCopy && srcTexture->supportFormatView(); |
| } |
| |
| if (skipCopy) |
| { |
| // Texture supports stencil view, just use it directly |
| if (readableDepthViewOut) |
| { |
| *readableDepthViewOut = srcTexture; |
| } |
| if (readableStencilViewOut) |
| { |
| *readableStencilViewOut = srcTexture; |
| } |
| *readableViewLevel = level; |
| *readableViewLayer = slice; |
| *readableViewArea = readArea; |
| } |
| else |
| { |
| ASSERT(srcTexture->textureType() != MTLTextureType3D); |
| |
| // Texture doesn't support stencil view or not shader readable, copy to an interminate |
| // texture that supports stencil view and shader read. |
| mtl::TextureRef formatableView = srcTexture->getReadableCopy( |
| contextMtl, contextMtl->getBlitCommandEncoder(), level, slice, |
| MTLRegionMake2D(readArea.x, readArea.y, readArea.width, readArea.height)); |
| |
| ANGLE_CHECK_GL_ALLOC(contextMtl, formatableView); |
| |
| if (readableDepthViewOut) |
| { |
| *readableDepthViewOut = formatableView; |
| } |
| if (readableStencilViewOut) |
| { |
| *readableStencilViewOut = formatableView->getStencilView(); |
| } |
| |
| *readableViewLevel = 0; |
| *readableViewLayer = 0; |
| *readableViewArea = gl::Rectangle(0, 0, readArea.width, readArea.height); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::prepareRenderPass(const gl::Context *context, |
| mtl::RenderPassDesc *pDescOut) |
| { |
| mtl::RenderPassDesc &desc = *pDescOut; |
| |
| mRenderPassFirstColorAttachmentFormat = nullptr; |
| mRenderPassAttachmentsSameColorType = true; |
| uint32_t maxColorAttachments = static_cast<uint32_t>(mState.getColorAttachments().size()); |
| desc.numColorAttachments = 0; |
| desc.sampleCount = 1; |
| for (uint32_t colorIndexGL = 0; colorIndexGL < maxColorAttachments; ++colorIndexGL) |
| { |
| ASSERT(colorIndexGL < mtl::kMaxRenderTargets); |
| |
| mtl::RenderPassColorAttachmentDesc &colorAttachment = desc.colorAttachments[colorIndexGL]; |
| const RenderTargetMtl *colorRenderTarget = mColorRenderTargets[colorIndexGL]; |
| |
| if (colorRenderTarget) |
| { |
| colorRenderTarget->toRenderPassAttachmentDesc(&colorAttachment); |
| |
| desc.numColorAttachments = std::max(desc.numColorAttachments, colorIndexGL + 1); |
| desc.sampleCount = std::max(desc.sampleCount, colorRenderTarget->getRenderSamples()); |
| |
| if (!mRenderPassFirstColorAttachmentFormat) |
| { |
| mRenderPassFirstColorAttachmentFormat = colorRenderTarget->getFormat(); |
| } |
| else if (colorRenderTarget->getFormat()) |
| { |
| if (mRenderPassFirstColorAttachmentFormat->actualAngleFormat().isSint() != |
| colorRenderTarget->getFormat()->actualAngleFormat().isSint() || |
| mRenderPassFirstColorAttachmentFormat->actualAngleFormat().isUint() != |
| colorRenderTarget->getFormat()->actualAngleFormat().isUint()) |
| { |
| mRenderPassAttachmentsSameColorType = false; |
| } |
| } |
| } |
| else |
| { |
| colorAttachment.reset(); |
| } |
| } |
| |
| if (mDepthRenderTarget) |
| { |
| mDepthRenderTarget->toRenderPassAttachmentDesc(&desc.depthAttachment); |
| desc.sampleCount = std::max(desc.sampleCount, mDepthRenderTarget->getRenderSamples()); |
| } |
| else |
| { |
| desc.depthAttachment.reset(); |
| } |
| |
| if (mStencilRenderTarget) |
| { |
| mStencilRenderTarget->toRenderPassAttachmentDesc(&desc.stencilAttachment); |
| desc.sampleCount = std::max(desc.sampleCount, mStencilRenderTarget->getRenderSamples()); |
| } |
| else |
| { |
| desc.stencilAttachment.reset(); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::clearWithLoadOp(const gl::Context *context, |
| gl::DrawBufferMask clearColorBuffers, |
| const mtl::ClearRectParams &clearOpts) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| bool startedRenderPass = contextMtl->hasStartedRenderPass(mRenderPassDesc); |
| mtl::RenderCommandEncoder *encoder = nullptr; |
| |
| if (startedRenderPass) |
| { |
| encoder = ensureRenderPassStarted(context); |
| if (encoder->hasDrawCalls()) |
| { |
| // Render pass already has draw calls recorded, it is better to use clear with draw |
| // operation. |
| return clearWithDraw(context, clearColorBuffers, clearOpts); |
| } |
| else |
| { |
| // If render pass has started but there is no draw call yet. It is OK to change the |
| // loadOp. |
| return clearWithLoadOpRenderPassStarted(context, clearColorBuffers, clearOpts, encoder); |
| } |
| } |
| else |
| { |
| return clearWithLoadOpRenderPassNotStarted(context, clearColorBuffers, clearOpts); |
| } |
| } |
| |
| angle::Result FramebufferMtl::clearWithLoadOpRenderPassNotStarted( |
| const gl::Context *context, |
| gl::DrawBufferMask clearColorBuffers, |
| const mtl::ClearRectParams &clearOpts) |
| { |
| mtl::RenderPassDesc tempDesc = mRenderPassDesc; |
| |
| for (uint32_t colorIndexGL = 0; colorIndexGL < tempDesc.numColorAttachments; ++colorIndexGL) |
| { |
| ASSERT(colorIndexGL < mtl::kMaxRenderTargets); |
| |
| mtl::RenderPassColorAttachmentDesc &colorAttachment = |
| tempDesc.colorAttachments[colorIndexGL]; |
| const mtl::TextureRef &texture = colorAttachment.texture; |
| |
| if (clearColorBuffers.test(colorIndexGL)) |
| { |
| colorAttachment.loadAction = MTLLoadActionClear; |
| OverrideMTLClearColor(texture, clearOpts.clearColor.value(), |
| &colorAttachment.clearColor); |
| } |
| } |
| |
| if (clearOpts.clearDepth.valid()) |
| { |
| tempDesc.depthAttachment.loadAction = MTLLoadActionClear; |
| tempDesc.depthAttachment.clearDepth = clearOpts.clearDepth.value(); |
| } |
| |
| if (clearOpts.clearStencil.valid()) |
| { |
| tempDesc.stencilAttachment.loadAction = MTLLoadActionClear; |
| tempDesc.stencilAttachment.clearStencil = clearOpts.clearStencil.value(); |
| } |
| |
| // Start new render encoder with loadOp=Clear |
| ensureRenderPassStarted(context, tempDesc); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::clearWithLoadOpRenderPassStarted( |
| const gl::Context *context, |
| gl::DrawBufferMask clearColorBuffers, |
| const mtl::ClearRectParams &clearOpts, |
| mtl::RenderCommandEncoder *encoder) |
| { |
| ASSERT(!encoder->hasDrawCalls()); |
| |
| for (uint32_t colorIndexGL = 0; colorIndexGL < mRenderPassDesc.numColorAttachments; |
| ++colorIndexGL) |
| { |
| ASSERT(colorIndexGL < mtl::kMaxRenderTargets); |
| |
| mtl::RenderPassColorAttachmentDesc &colorAttachment = |
| mRenderPassDesc.colorAttachments[colorIndexGL]; |
| const mtl::TextureRef &texture = colorAttachment.texture; |
| |
| if (clearColorBuffers.test(colorIndexGL)) |
| { |
| MTLClearColor clearVal; |
| OverrideMTLClearColor(texture, clearOpts.clearColor.value(), &clearVal); |
| |
| encoder->setColorLoadAction(MTLLoadActionClear, clearVal, colorIndexGL); |
| } |
| } |
| |
| if (clearOpts.clearDepth.valid()) |
| { |
| encoder->setDepthLoadAction(MTLLoadActionClear, clearOpts.clearDepth.value()); |
| } |
| |
| if (clearOpts.clearStencil.valid()) |
| { |
| encoder->setStencilLoadAction(MTLLoadActionClear, clearOpts.clearStencil.value()); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::clearWithDraw(const gl::Context *context, |
| gl::DrawBufferMask clearColorBuffers, |
| const mtl::ClearRectParams &clearOpts) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| DisplayMtl *display = contextMtl->getDisplay(); |
| |
| if (mRenderPassAttachmentsSameColorType) |
| { |
| // Start new render encoder if not already. |
| mtl::RenderCommandEncoder *encoder = ensureRenderPassStarted(context, mRenderPassDesc); |
| |
| return display->getUtils().clearWithDraw(context, encoder, clearOpts); |
| } |
| |
| // Not all attachments have the same color type. |
| mtl::ClearRectParams overrideClearOps = clearOpts; |
| overrideClearOps.enabledBuffers.reset(); |
| |
| // First clear depth/stencil without color attachment |
| if (clearOpts.clearDepth.valid() || clearOpts.clearStencil.valid()) |
| { |
| mtl::RenderPassDesc dsOnlyDesc = mRenderPassDesc; |
| dsOnlyDesc.numColorAttachments = 0; |
| mtl::RenderCommandEncoder *encoder = contextMtl->getRenderPassCommandEncoder(dsOnlyDesc); |
| |
| ANGLE_TRY(display->getUtils().clearWithDraw(context, encoder, overrideClearOps)); |
| } |
| |
| // Clear the color attachment one by one. |
| overrideClearOps.enabledBuffers.set(0); |
| for (size_t drawbuffer : clearColorBuffers) |
| { |
| if (drawbuffer >= mRenderPassDesc.numColorAttachments) |
| { |
| // Iteration over drawbuffer indices always goes in ascending order |
| break; |
| } |
| RenderTargetMtl *renderTarget = mColorRenderTargets[drawbuffer]; |
| if (!renderTarget || !renderTarget->getTexture()) |
| { |
| continue; |
| } |
| const mtl::Format &format = *renderTarget->getFormat(); |
| mtl::PixelType clearColorType = overrideClearOps.clearColor.value().getType(); |
| if ((clearColorType == mtl::PixelType::Int && !format.actualAngleFormat().isSint()) || |
| (clearColorType == mtl::PixelType::UInt && !format.actualAngleFormat().isUint()) || |
| (clearColorType == mtl::PixelType::Float && format.actualAngleFormat().isInt())) |
| { |
| continue; |
| } |
| |
| overrideClearOps.clearWriteMaskArray[0] = overrideClearOps.clearWriteMaskArray[drawbuffer]; |
| |
| mtl::RenderCommandEncoder *encoder = |
| contextMtl->getRenderTargetCommandEncoder(*renderTarget); |
| ANGLE_TRY(display->getUtils().clearWithDraw(context, encoder, overrideClearOps)); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::clearImpl(const gl::Context *context, |
| gl::DrawBufferMask clearColorBuffers, |
| mtl::ClearRectParams *pClearOpts) |
| { |
| auto &clearOpts = *pClearOpts; |
| |
| if (!clearOpts.clearColor.valid() && !clearOpts.clearDepth.valid() && |
| !clearOpts.clearStencil.valid()) |
| { |
| // No Op. |
| return angle::Result::Continue; |
| } |
| |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| const gl::Rectangle renderArea(0, 0, mState.getDimensions().width, |
| mState.getDimensions().height); |
| |
| clearOpts.colorFormat = mRenderPassFirstColorAttachmentFormat; |
| clearOpts.dstTextureSize = mState.getExtents(); |
| clearOpts.clearArea = ClipRectToScissor(contextMtl->getState(), renderArea, false); |
| clearOpts.flipY = mFlipY; |
| |
| // Discard clear altogether if scissor has 0 width or height. |
| if (clearOpts.clearArea.width == 0 || clearOpts.clearArea.height == 0) |
| { |
| return angle::Result::Continue; |
| } |
| |
| clearOpts.clearWriteMaskArray = contextMtl->getWriteMaskArray(); |
| uint32_t stencilMask = contextMtl->getStencilMask(); |
| if (!contextMtl->getDepthMask()) |
| { |
| // Disable depth clearing, since depth write is disable |
| clearOpts.clearDepth.reset(); |
| } |
| |
| // Only clear enabled buffers |
| clearOpts.enabledBuffers = clearColorBuffers; |
| |
| bool allBuffersUnmasked = true; |
| for (size_t enabledBuffer : clearColorBuffers) |
| { |
| if (clearOpts.clearWriteMaskArray[enabledBuffer] != MTLColorWriteMaskAll) |
| { |
| allBuffersUnmasked = false; |
| break; |
| } |
| } |
| |
| if (clearOpts.clearArea == renderArea && |
| (!clearOpts.clearColor.valid() || allBuffersUnmasked) && |
| (!clearOpts.clearStencil.valid() || |
| (stencilMask & mtl::kStencilMaskAll) == mtl::kStencilMaskAll)) |
| { |
| return clearWithLoadOp(context, clearColorBuffers, clearOpts); |
| } |
| |
| return clearWithDraw(context, clearColorBuffers, clearOpts); |
| } |
| |
| angle::Result FramebufferMtl::invalidateImpl(ContextMtl *contextMtl, |
| size_t count, |
| const GLenum *attachments) |
| { |
| gl::DrawBufferMask invalidateColorBuffers; |
| bool invalidateDepthBuffer = false; |
| bool invalidateStencilBuffer = false; |
| |
| for (size_t i = 0; i < count; ++i) |
| { |
| const GLenum attachment = attachments[i]; |
| |
| switch (attachment) |
| { |
| case GL_DEPTH: |
| case GL_DEPTH_ATTACHMENT: |
| invalidateDepthBuffer = true; |
| break; |
| case GL_STENCIL: |
| case GL_STENCIL_ATTACHMENT: |
| invalidateStencilBuffer = true; |
| break; |
| case GL_DEPTH_STENCIL_ATTACHMENT: |
| invalidateDepthBuffer = true; |
| invalidateStencilBuffer = true; |
| break; |
| default: |
| ASSERT( |
| (attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15) || |
| (attachment == GL_COLOR)); |
| |
| invalidateColorBuffers.set( |
| attachment == GL_COLOR ? 0u : (attachment - GL_COLOR_ATTACHMENT0)); |
| } |
| } |
| |
| // Set the appropriate storeOp for attachments. |
| // If we already start the render pass, then need to set the store action now. |
| bool renderPassStarted = contextMtl->hasStartedRenderPass(mRenderPassDesc); |
| mtl::RenderCommandEncoder *encoder = |
| renderPassStarted ? contextMtl->getRenderCommandEncoder() : nullptr; |
| |
| for (uint32_t i = 0; i < mRenderPassDesc.numColorAttachments; ++i) |
| { |
| if (invalidateColorBuffers.test(i)) |
| { |
| mtl::RenderPassColorAttachmentDesc &colorAttachment = |
| mRenderPassDesc.colorAttachments[i]; |
| colorAttachment.storeAction = MTLStoreActionDontCare; |
| if (renderPassStarted) |
| { |
| encoder->setColorStoreAction(MTLStoreActionDontCare, i); |
| } |
| } |
| } |
| |
| if (invalidateDepthBuffer && mDepthRenderTarget) |
| { |
| mRenderPassDesc.depthAttachment.storeAction = MTLStoreActionDontCare; |
| if (renderPassStarted) |
| { |
| encoder->setDepthStoreAction(MTLStoreActionDontCare); |
| } |
| } |
| |
| if (invalidateStencilBuffer && mStencilRenderTarget) |
| { |
| mRenderPassDesc.stencilAttachment.storeAction = MTLStoreActionDontCare; |
| if (renderPassStarted) |
| { |
| encoder->setStencilStoreAction(MTLStoreActionDontCare); |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| gl::Rectangle FramebufferMtl::getCorrectFlippedReadArea(const gl::Context *context, |
| const gl::Rectangle &glArea) const |
| { |
| RenderTargetMtl *readRT = getColorReadRenderTarget(context); |
| if (!readRT) |
| { |
| readRT = mDepthRenderTarget; |
| } |
| if (!readRT) |
| { |
| readRT = mStencilRenderTarget; |
| } |
| ASSERT(readRT); |
| gl::Rectangle flippedArea = glArea; |
| if (mFlipY) |
| { |
| flippedArea.y = readRT->getTexture()->height(readRT->getLevelIndex()) - flippedArea.y - |
| flippedArea.height; |
| } |
| |
| return flippedArea; |
| } |
| |
| angle::Result FramebufferMtl::readPixelsImpl(const gl::Context *context, |
| const gl::Rectangle &area, |
| const PackPixelsParams &packPixelsParams, |
| const RenderTargetMtl *renderTarget, |
| uint8_t *pixels) const |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| if (!renderTarget) |
| { |
| return angle::Result::Continue; |
| } |
| |
| if (packPixelsParams.packBuffer) |
| { |
| return readPixelsToPBO(context, area, packPixelsParams, renderTarget); |
| } |
| |
| mtl::TextureRef texture; |
| if (mBackbuffer) |
| { |
| // Backbuffer might have MSAA texture as render target, needs to obtain the |
| // resolved texture to be able to read pixels. |
| ANGLE_TRY(mBackbuffer->ensureColorTextureReadyForReadPixels(context)); |
| texture = mBackbuffer->getColorTexture(); |
| } |
| else |
| { |
| texture = renderTarget->getTexture(); |
| // For non-default framebuffer, MSAA read pixels is disallowed. |
| if (!texture) |
| { |
| return angle::Result::Stop; |
| } |
| ANGLE_MTL_CHECK(contextMtl, texture->samples() == 1, GL_INVALID_OPERATION); |
| } |
| if (texture->isBeingUsedByGPU(contextMtl)) |
| { |
| contextMtl->flushCommandBuffer(mtl::WaitUntilFinished); |
| } |
| |
| const mtl::Format &readFormat = *renderTarget->getFormat(); |
| const angle::Format &readAngleFormat = readFormat.actualAngleFormat(); |
| |
| int bufferRowPitch = area.width * readAngleFormat.pixelBytes; |
| angle::MemoryBuffer readPixelRowBuffer; |
| ANGLE_CHECK_GL_ALLOC(contextMtl, readPixelRowBuffer.resize(bufferRowPitch)); |
| |
| auto packPixelsRowParams = packPixelsParams; |
| gl::Rectangle srcRowRegion(area.x, area.y, area.width, 1); |
| |
| int rowOffset = packPixelsParams.reverseRowOrder ? -1 : 1; |
| int startRow = packPixelsParams.reverseRowOrder ? (area.y1() - 1) : area.y; |
| |
| // Copy pixels row by row |
| packPixelsRowParams.area.height = 1; |
| packPixelsRowParams.reverseRowOrder = false; |
| for (int r = startRow, i = 0; i < area.height; |
| ++i, r += rowOffset, pixels += packPixelsRowParams.outputPitch) |
| { |
| srcRowRegion.y = r; |
| packPixelsRowParams.area.y = packPixelsParams.area.y + i; |
| |
| // Read the pixels data to the row buffer |
| ANGLE_TRY(mtl::ReadTexturePerSliceBytes( |
| context, texture, bufferRowPitch, srcRowRegion, renderTarget->getLevelIndex(), |
| renderTarget->getLayerIndex(), readPixelRowBuffer.data())); |
| |
| // Convert to destination format |
| PackPixels(packPixelsRowParams, readAngleFormat, bufferRowPitch, readPixelRowBuffer.data(), |
| pixels); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferMtl::readPixelsToPBO(const gl::Context *context, |
| const gl::Rectangle &area, |
| const PackPixelsParams &packPixelsParams, |
| const RenderTargetMtl *renderTarget) const |
| { |
| ASSERT(packPixelsParams.packBuffer); |
| ASSERT(renderTarget); |
| |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| ANGLE_MTL_CHECK(contextMtl, packPixelsParams.offset <= std::numeric_limits<uint32_t>::max(), |
| GL_INVALID_OPERATION); |
| uint32_t offset = static_cast<uint32_t>(packPixelsParams.offset); |
| |
| BufferMtl *packBufferMtl = mtl::GetImpl(packPixelsParams.packBuffer); |
| mtl::BufferRef dstBuffer = packBufferMtl->getCurrentBuffer(); |
| |
| return readPixelsToBuffer(context, area, renderTarget, packPixelsParams.reverseRowOrder, |
| *packPixelsParams.destFormat, offset, packPixelsParams.outputPitch, |
| &dstBuffer); |
| } |
| |
| angle::Result FramebufferMtl::readPixelsToBuffer(const gl::Context *context, |
| const gl::Rectangle &area, |
| const RenderTargetMtl *renderTarget, |
| bool reverseRowOrder, |
| const angle::Format &dstAngleFormat, |
| uint32_t dstBufferOffset, |
| uint32_t dstBufferRowPitch, |
| const mtl::BufferRef *pDstBuffer) const |
| { |
| ASSERT(renderTarget); |
| |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| const mtl::Format &readFormat = *renderTarget->getFormat(); |
| const angle::Format &readAngleFormat = readFormat.actualAngleFormat(); |
| |
| mtl::TextureRef texture = renderTarget->getTexture(); |
| |
| const mtl::BufferRef &dstBuffer = *pDstBuffer; |
| |
| if (dstAngleFormat.id != readAngleFormat.id || texture->samples() > 1 || |
| (dstBufferOffset % dstAngleFormat.pixelBytes) || |
| (dstBufferOffset % mtl::kTextureToBufferBlittingAlignment)) |
| { |
| const angle::Format *actualDstAngleFormat; |
| |
| // SRGB is special case: We need to write sRGB values to buffer, not linear values. |
| switch (readAngleFormat.id) |
| { |
| case angle::FormatID::B8G8R8A8_UNORM_SRGB: |
| case angle::FormatID::R8G8B8_UNORM_SRGB: |
| case angle::FormatID::R8G8B8A8_UNORM_SRGB: |
| if (dstAngleFormat.id != readAngleFormat.id) |
| { |
| switch (dstAngleFormat.id) |
| { |
| case angle::FormatID::B8G8R8A8_UNORM: |
| actualDstAngleFormat = |
| &angle::Format::Get(angle::FormatID::B8G8R8A8_UNORM_SRGB); |
| break; |
| case angle::FormatID::R8G8B8A8_UNORM: |
| actualDstAngleFormat = |
| &angle::Format::Get(angle::FormatID::R8G8B8A8_UNORM_SRGB); |
| break; |
| default: |
| // Unsupported format. |
| ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_ENUM); |
| } |
| break; |
| } |
| OS_FALLTHROUGH; |
| default: |
| actualDstAngleFormat = &dstAngleFormat; |
| } |
| |
| // Use compute shader |
| mtl::CopyPixelsToBufferParams params; |
| params.buffer = dstBuffer; |
| params.bufferStartOffset = dstBufferOffset; |
| params.bufferRowPitch = dstBufferRowPitch; |
| |
| params.texture = texture; |
| params.textureArea = area; |
| params.textureLevel = renderTarget->getLevelIndex(); |
| params.textureSliceOrDeph = renderTarget->getLayerIndex(); |
| params.reverseTextureRowOrder = reverseRowOrder; |
| |
| ANGLE_TRY(contextMtl->getDisplay()->getUtils().packPixelsFromTextureToBuffer( |
| contextMtl, *actualDstAngleFormat, params)); |
| } |
| else |
| { |
| // Use blit command encoder |
| if (!reverseRowOrder) |
| { |
| ANGLE_TRY(mtl::ReadTexturePerSliceBytesToBuffer( |
| context, texture, dstBufferRowPitch, area, renderTarget->getLevelIndex(), |
| renderTarget->getLayerIndex(), dstBufferOffset, dstBuffer)); |
| } |
| else |
| { |
| gl::Rectangle srcRowRegion(area.x, area.y, area.width, 1); |
| |
| int startRow = area.y1() - 1; |
| |
| uint32_t bufferRowOffset = dstBufferOffset; |
| // Copy pixels row by row |
| for (int r = startRow, copiedRows = 0; copiedRows < area.height; |
| ++copiedRows, --r, bufferRowOffset += dstBufferRowPitch) |
| { |
| srcRowRegion.y = r; |
| |
| // Read the pixels data to the buffer's row |
| ANGLE_TRY(mtl::ReadTexturePerSliceBytesToBuffer( |
| context, texture, dstBufferRowPitch, srcRowRegion, |
| renderTarget->getLevelIndex(), renderTarget->getLayerIndex(), bufferRowOffset, |
| dstBuffer)); |
| } |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| } |