| // |
| // Copyright 2015 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. |
| // |
| |
| // FramebufferGL.cpp: Implements the class methods for FramebufferGL. |
| |
| #include "libANGLE/renderer/gl/FramebufferGL.h" |
| |
| #include "common/bitset_utils.h" |
| #include "common/debug.h" |
| #include "libANGLE/ErrorStrings.h" |
| #include "libANGLE/FramebufferAttachment.h" |
| #include "libANGLE/State.h" |
| #include "libANGLE/angletypes.h" |
| #include "libANGLE/formatutils.h" |
| #include "libANGLE/queryconversions.h" |
| #include "libANGLE/renderer/ContextImpl.h" |
| #include "libANGLE/renderer/gl/BlitGL.h" |
| #include "libANGLE/renderer/gl/ClearMultiviewGL.h" |
| #include "libANGLE/renderer/gl/ContextGL.h" |
| #include "libANGLE/renderer/gl/FunctionsGL.h" |
| #include "libANGLE/renderer/gl/RenderbufferGL.h" |
| #include "libANGLE/renderer/gl/StateManagerGL.h" |
| #include "libANGLE/renderer/gl/TextureGL.h" |
| #include "libANGLE/renderer/gl/formatutilsgl.h" |
| #include "libANGLE/renderer/gl/renderergl_utils.h" |
| #include "platform/FeaturesGL.h" |
| #include "platform/PlatformMethods.h" |
| |
| using namespace gl; |
| using angle::CheckedNumeric; |
| |
| namespace rx |
| { |
| |
| namespace |
| { |
| |
| struct BlitFramebufferBounds |
| { |
| gl::Rectangle sourceBounds; |
| gl::Rectangle sourceRegion; |
| |
| gl::Rectangle destBounds; |
| gl::Rectangle destRegion; |
| |
| bool xFlipped; |
| bool yFlipped; |
| }; |
| |
| static BlitFramebufferBounds GetBlitFramebufferBounds(const gl::Context *context, |
| const gl::Rectangle &sourceArea, |
| const gl::Rectangle &destArea) |
| { |
| BlitFramebufferBounds bounds; |
| |
| const Framebuffer *sourceFramebuffer = context->getState().getReadFramebuffer(); |
| const Framebuffer *destFramebuffer = context->getState().getDrawFramebuffer(); |
| |
| gl::Extents readSize = sourceFramebuffer->getExtents(); |
| gl::Extents drawSize = destFramebuffer->getExtents(); |
| |
| bounds.sourceBounds = gl::Rectangle(0, 0, readSize.width, readSize.height); |
| bounds.sourceRegion = sourceArea.removeReversal(); |
| |
| bounds.destBounds = gl::Rectangle(0, 0, drawSize.width, drawSize.height); |
| bounds.destRegion = destArea.removeReversal(); |
| |
| bounds.xFlipped = sourceArea.isReversedX() != destArea.isReversedX(); |
| bounds.yFlipped = sourceArea.isReversedY() != destArea.isReversedY(); |
| |
| return bounds; |
| } |
| |
| void BindFramebufferAttachment(const FunctionsGL *functions, |
| GLenum attachmentPoint, |
| const FramebufferAttachment *attachment, |
| const angle::FeaturesGL &features) |
| { |
| if (attachment) |
| { |
| if (attachment->type() == GL_TEXTURE) |
| { |
| const Texture *texture = attachment->getTexture(); |
| const TextureGL *textureGL = GetImplAs<TextureGL>(texture); |
| |
| if (texture->getType() == TextureType::_2D || |
| texture->getType() == TextureType::_2DMultisample || |
| texture->getType() == TextureType::Rectangle || |
| texture->getType() == TextureType::External) |
| { |
| if (attachment->isRenderToTexture()) |
| { |
| if (functions->framebufferTexture2DMultisampleEXT) |
| { |
| functions->framebufferTexture2DMultisampleEXT( |
| GL_FRAMEBUFFER, attachmentPoint, ToGLenum(texture->getType()), |
| textureGL->getTextureID(), attachment->mipLevel(), |
| attachment->getSamples()); |
| } |
| else |
| { |
| ASSERT(functions->framebufferTexture2DMultisampleIMG); |
| functions->framebufferTexture2DMultisampleIMG( |
| GL_FRAMEBUFFER, attachmentPoint, ToGLenum(texture->getType()), |
| textureGL->getTextureID(), attachment->mipLevel(), |
| attachment->getSamples()); |
| } |
| } |
| else |
| { |
| functions->framebufferTexture2D( |
| GL_FRAMEBUFFER, attachmentPoint, ToGLenum(texture->getType()), |
| textureGL->getTextureID(), attachment->mipLevel()); |
| } |
| } |
| else if (attachment->isLayered()) |
| { |
| TextureType textureType = texture->getType(); |
| ASSERT(textureType == TextureType::_2DArray || textureType == TextureType::_3D || |
| textureType == TextureType::CubeMap || |
| textureType == TextureType::_2DMultisampleArray || |
| textureType == TextureType::CubeMapArray); |
| functions->framebufferTexture(GL_FRAMEBUFFER, attachmentPoint, |
| textureGL->getTextureID(), attachment->mipLevel()); |
| } |
| else if (texture->getType() == TextureType::CubeMap) |
| { |
| functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, |
| ToGLenum(attachment->cubeMapFace()), |
| textureGL->getTextureID(), attachment->mipLevel()); |
| } |
| else if (texture->getType() == TextureType::_2DArray || |
| texture->getType() == TextureType::_3D || |
| texture->getType() == TextureType::_2DMultisampleArray || |
| texture->getType() == TextureType::CubeMapArray) |
| { |
| if (attachment->isMultiview()) |
| { |
| ASSERT(functions->framebufferTexture); |
| functions->framebufferTexture(GL_FRAMEBUFFER, attachmentPoint, |
| textureGL->getTextureID(), |
| attachment->mipLevel()); |
| } |
| else |
| { |
| functions->framebufferTextureLayer(GL_FRAMEBUFFER, attachmentPoint, |
| textureGL->getTextureID(), |
| attachment->mipLevel(), attachment->layer()); |
| } |
| } |
| else |
| { |
| UNREACHABLE(); |
| } |
| } |
| else if (attachment->type() == GL_RENDERBUFFER) |
| { |
| const Renderbuffer *renderbuffer = attachment->getRenderbuffer(); |
| const RenderbufferGL *renderbufferGL = GetImplAs<RenderbufferGL>(renderbuffer); |
| |
| if (features.alwaysUnbindFramebufferTexture2D.enabled) |
| { |
| functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, 0, |
| 0); |
| } |
| |
| functions->framebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoint, GL_RENDERBUFFER, |
| renderbufferGL->getRenderbufferID()); |
| } |
| else |
| { |
| UNREACHABLE(); |
| } |
| } |
| else |
| { |
| // Unbind this attachment |
| functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, 0, 0); |
| } |
| } |
| |
| bool AreAllLayersActive(const FramebufferAttachment &attachment) |
| { |
| int baseViewIndex = attachment.getBaseViewIndex(); |
| if (baseViewIndex != 0) |
| { |
| return false; |
| } |
| const ImageIndex &imageIndex = attachment.getTextureImageIndex(); |
| int numLayers = static_cast<int>( |
| attachment.getTexture()->getDepth(imageIndex.getTarget(), imageIndex.getLevelIndex())); |
| return (attachment.getNumViews() == numLayers); |
| } |
| |
| bool RequiresMultiviewClear(const FramebufferState &state, bool scissorTestEnabled) |
| { |
| // Get one attachment and check whether all layers are attached. |
| const FramebufferAttachment *attachment = nullptr; |
| bool allTextureArraysAreFullyAttached = true; |
| for (const FramebufferAttachment &colorAttachment : state.getColorAttachments()) |
| { |
| if (colorAttachment.isAttached()) |
| { |
| if (!colorAttachment.isMultiview()) |
| { |
| return false; |
| } |
| attachment = &colorAttachment; |
| allTextureArraysAreFullyAttached = |
| allTextureArraysAreFullyAttached && AreAllLayersActive(*attachment); |
| } |
| } |
| |
| const FramebufferAttachment *depthAttachment = state.getDepthAttachment(); |
| if (depthAttachment) |
| { |
| if (!depthAttachment->isMultiview()) |
| { |
| return false; |
| } |
| attachment = depthAttachment; |
| allTextureArraysAreFullyAttached = |
| allTextureArraysAreFullyAttached && AreAllLayersActive(*attachment); |
| } |
| const FramebufferAttachment *stencilAttachment = state.getStencilAttachment(); |
| if (stencilAttachment) |
| { |
| if (!stencilAttachment->isMultiview()) |
| { |
| return false; |
| } |
| attachment = stencilAttachment; |
| allTextureArraysAreFullyAttached = |
| allTextureArraysAreFullyAttached && AreAllLayersActive(*attachment); |
| } |
| |
| if (attachment == nullptr) |
| { |
| return false; |
| } |
| if (attachment->isMultiview()) |
| { |
| // If all layers of each texture array are active, then there is no need to issue a |
| // special multiview clear. |
| return !allTextureArraysAreFullyAttached; |
| } |
| return false; |
| } |
| |
| bool IsEmulatedAlphaChannelTextureAttachment(const FramebufferAttachment *attachment) |
| { |
| if (!attachment || attachment->type() != GL_TEXTURE) |
| { |
| return false; |
| } |
| |
| const Texture *texture = attachment->getTexture(); |
| const TextureGL *textureGL = GetImplAs<TextureGL>(texture); |
| return textureGL->hasEmulatedAlphaChannel(attachment->getTextureImageIndex()); |
| } |
| |
| class ANGLE_NO_DISCARD ScopedEXTTextureNorm16ReadbackWorkaround |
| { |
| public: |
| ScopedEXTTextureNorm16ReadbackWorkaround() |
| : tmpPixels(nullptr), clientPixels(nullptr), enabled(false) |
| {} |
| |
| ~ScopedEXTTextureNorm16ReadbackWorkaround() |
| { |
| if (tmpPixels) |
| { |
| delete[] tmpPixels; |
| } |
| } |
| |
| angle::Result Initialize(const gl::Context *context, |
| const gl::Rectangle &area, |
| GLenum originalReadFormat, |
| GLenum format, |
| GLenum type, |
| GLuint skipBytes, |
| GLuint rowBytes, |
| GLuint pixelBytes, |
| GLubyte *pixels) |
| { |
| // Separate from constructor as there may be checked math result exception that needs to |
| // early return |
| ASSERT(tmpPixels == nullptr); |
| ASSERT(clientPixels == nullptr); |
| |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const angle::FeaturesGL &features = GetFeaturesGL(context); |
| |
| enabled = features.readPixelsUsingImplementationColorReadFormatForNorm16.enabled && |
| type == GL_UNSIGNED_SHORT && originalReadFormat == GL_RGBA && |
| (format == GL_RED || format == GL_RG); |
| |
| clientPixels = pixels; |
| |
| if (enabled) |
| { |
| CheckedNumeric<GLuint> checkedRowBytes(rowBytes); |
| CheckedNumeric<GLuint> checkedRows(area.height); |
| CheckedNumeric<GLuint> checkedSkipBytes(skipBytes); |
| auto checkedAllocatedBytes = checkedSkipBytes + checkedRowBytes * checkedRows; |
| if (rowBytes < area.width * pixelBytes) |
| { |
| checkedAllocatedBytes += area.width * pixelBytes - rowBytes; |
| } |
| ANGLE_CHECK_GL_MATH(contextGL, checkedAllocatedBytes.IsValid()); |
| const GLuint allocatedBytes = checkedAllocatedBytes.ValueOrDie(); |
| tmpPixels = new GLubyte[allocatedBytes]; |
| memset(tmpPixels, 0, allocatedBytes); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| GLubyte *Pixels() const { return tmpPixels ? tmpPixels : clientPixels; } |
| |
| bool IsEnabled() const { return enabled; } |
| |
| private: |
| // Temporarily allocated pixel readback buffer |
| GLubyte *tmpPixels; |
| // Client pixel array pointer passed to readPixels |
| GLubyte *clientPixels; |
| |
| bool enabled; |
| }; |
| |
| // Workaround to rearrange pixels read by RED/RG to RGBA for RGBA/UNSIGNED_SHORT pixel type |
| // combination |
| angle::Result RearrangeEXTTextureNorm16Pixels(const gl::Context *context, |
| const gl::Rectangle &area, |
| GLenum originalReadFormat, |
| GLenum format, |
| GLenum type, |
| GLuint skipBytes, |
| GLuint rowBytes, |
| GLuint pixelBytes, |
| const gl::PixelPackState &pack, |
| GLubyte *clientPixels, |
| GLubyte *tmpPixels) |
| { |
| ASSERT(tmpPixels != nullptr); |
| ASSERT(originalReadFormat == GL_RGBA); |
| ASSERT(format == GL_RED_EXT || format == GL_RG_EXT); |
| ASSERT(type == GL_UNSIGNED_SHORT); |
| |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| |
| const gl::InternalFormat &glFormatOriginal = |
| gl::GetInternalFormatInfo(originalReadFormat, type); |
| |
| GLuint originalReadFormatRowBytes = 0; |
| ANGLE_CHECK_GL_MATH( |
| contextGL, glFormatOriginal.computeRowPitch(type, area.width, pack.alignment, |
| pack.rowLength, &originalReadFormatRowBytes)); |
| GLuint originalReadFormatSkipBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextGL, |
| glFormatOriginal.computeSkipBytes(type, originalReadFormatRowBytes, 0, pack, |
| false, &originalReadFormatSkipBytes)); |
| |
| GLuint originalReadFormatPixelBytes = glFormatOriginal.computePixelBytes(type); |
| GLuint alphaChannelBytes = glFormatOriginal.alphaBits / 8; |
| |
| ASSERT(originalReadFormatPixelBytes > pixelBytes); |
| ASSERT(originalReadFormatPixelBytes > alphaChannelBytes); |
| ASSERT(alphaChannelBytes != 0); |
| ASSERT(glFormatOriginal.alphaBits % 8 == 0); |
| |
| // Populating rearrangedPixels values from pixels |
| GLubyte *srcRowStart = tmpPixels; |
| GLubyte *dstRowStart = clientPixels; |
| |
| srcRowStart += skipBytes; |
| dstRowStart += originalReadFormatSkipBytes; |
| |
| for (GLint y = 0; y < area.height; ++y) |
| { |
| GLubyte *src = srcRowStart; |
| GLubyte *dst = dstRowStart; |
| for (GLint x = 0; x < area.width; ++x) |
| { |
| GLushort *srcPixel = reinterpret_cast<GLushort *>(src); |
| GLushort *dstPixel = reinterpret_cast<GLushort *>(dst); |
| dstPixel[0] = srcPixel[0]; |
| dstPixel[1] = format == GL_RG ? srcPixel[1] : 0; |
| // Set other channel of RGBA to 0 (GB when format == GL_RED, B when format == GL_RG) |
| dstPixel[2] = 0; |
| // Set alpha channel to 1 |
| dstPixel[3] = 0xFFFF; |
| |
| src += pixelBytes; |
| dst += originalReadFormatPixelBytes; |
| } |
| |
| srcRowStart += rowBytes; |
| dstRowStart += originalReadFormatRowBytes; |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| bool IsValidUnsignedShortReadPixelsFormat(GLenum readFormat, const gl::Context *context) |
| { |
| return (readFormat == GL_RED) || (readFormat == GL_RG) || (readFormat == GL_RGBA) || |
| ((readFormat == GL_DEPTH_COMPONENT) && (context->getExtensions().readDepthNV)); |
| } |
| |
| } // namespace |
| |
| FramebufferGL::FramebufferGL(const gl::FramebufferState &data, |
| GLuint id, |
| bool isDefault, |
| bool emulatedAlpha) |
| : FramebufferImpl(data), |
| mFramebufferID(id), |
| mIsDefault(isDefault), |
| mHasEmulatedAlphaAttachment(emulatedAlpha), |
| mAppliedEnabledDrawBuffers(1) |
| {} |
| |
| FramebufferGL::~FramebufferGL() |
| { |
| ASSERT(mFramebufferID == 0); |
| } |
| |
| void FramebufferGL::destroy(const gl::Context *context) |
| { |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| stateManager->deleteFramebuffer(mFramebufferID); |
| mFramebufferID = 0; |
| } |
| |
| angle::Result FramebufferGL::discard(const gl::Context *context, |
| size_t count, |
| const GLenum *attachments) |
| { |
| // glInvalidateFramebuffer accepts the same enums as glDiscardFramebufferEXT |
| return invalidate(context, count, attachments); |
| } |
| |
| angle::Result FramebufferGL::invalidate(const gl::Context *context, |
| size_t count, |
| const GLenum *attachments) |
| { |
| const GLenum *finalAttachmentsPtr = attachments; |
| |
| std::vector<GLenum> modifiedAttachments; |
| if (modifyInvalidateAttachmentsForEmulatedDefaultFBO(count, attachments, &modifiedAttachments)) |
| { |
| finalAttachmentsPtr = modifiedAttachments.data(); |
| } |
| |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| // Since this function is just a hint, only call a native function if it exists. |
| if (functions->invalidateFramebuffer) |
| { |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| functions->invalidateFramebuffer(GL_FRAMEBUFFER, static_cast<GLsizei>(count), |
| finalAttachmentsPtr); |
| } |
| else if (functions->discardFramebufferEXT) |
| { |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| functions->discardFramebufferEXT(GL_FRAMEBUFFER, static_cast<GLsizei>(count), |
| finalAttachmentsPtr); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::invalidateSub(const gl::Context *context, |
| size_t count, |
| const GLenum *attachments, |
| const gl::Rectangle &area) |
| { |
| |
| const GLenum *finalAttachmentsPtr = attachments; |
| |
| std::vector<GLenum> modifiedAttachments; |
| if (modifyInvalidateAttachmentsForEmulatedDefaultFBO(count, attachments, &modifiedAttachments)) |
| { |
| finalAttachmentsPtr = modifiedAttachments.data(); |
| } |
| |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| // Since this function is just a hint and not available until OpenGL 4.3, only call it if it is |
| // available. |
| if (functions->invalidateSubFramebuffer) |
| { |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| functions->invalidateSubFramebuffer(GL_FRAMEBUFFER, static_cast<GLsizei>(count), |
| finalAttachmentsPtr, area.x, area.y, area.width, |
| area.height); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::clear(const gl::Context *context, GLbitfield mask) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| syncClearState(context, mask); |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| |
| if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled())) |
| { |
| functions->clear(mask); |
| } |
| else |
| { |
| ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context); |
| multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(), |
| ClearMultiviewGL::ClearCommandType::Clear, mask, |
| GL_NONE, 0, nullptr, 0.0f, 0); |
| } |
| |
| contextGL->markWorkSubmitted(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::clearBufferfv(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| const GLfloat *values) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| syncClearBufferState(context, buffer, drawbuffer); |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| |
| if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled())) |
| { |
| functions->clearBufferfv(buffer, drawbuffer, values); |
| } |
| else |
| { |
| ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context); |
| multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(), |
| ClearMultiviewGL::ClearCommandType::ClearBufferfv, |
| static_cast<GLbitfield>(0u), buffer, drawbuffer, |
| reinterpret_cast<const uint8_t *>(values), 0.0f, 0); |
| } |
| |
| contextGL->markWorkSubmitted(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::clearBufferuiv(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| const GLuint *values) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| syncClearBufferState(context, buffer, drawbuffer); |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| |
| if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled())) |
| { |
| functions->clearBufferuiv(buffer, drawbuffer, values); |
| } |
| else |
| { |
| ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context); |
| multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(), |
| ClearMultiviewGL::ClearCommandType::ClearBufferuiv, |
| static_cast<GLbitfield>(0u), buffer, drawbuffer, |
| reinterpret_cast<const uint8_t *>(values), 0.0f, 0); |
| } |
| |
| contextGL->markWorkSubmitted(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::clearBufferiv(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| const GLint *values) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| syncClearBufferState(context, buffer, drawbuffer); |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| |
| if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled())) |
| { |
| functions->clearBufferiv(buffer, drawbuffer, values); |
| } |
| else |
| { |
| ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context); |
| multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(), |
| ClearMultiviewGL::ClearCommandType::ClearBufferiv, |
| static_cast<GLbitfield>(0u), buffer, drawbuffer, |
| reinterpret_cast<const uint8_t *>(values), 0.0f, 0); |
| } |
| |
| contextGL->markWorkSubmitted(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::clearBufferfi(const gl::Context *context, |
| GLenum buffer, |
| GLint drawbuffer, |
| GLfloat depth, |
| GLint stencil) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| syncClearBufferState(context, buffer, drawbuffer); |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| |
| if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled())) |
| { |
| functions->clearBufferfi(buffer, drawbuffer, depth, stencil); |
| } |
| else |
| { |
| ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context); |
| multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(), |
| ClearMultiviewGL::ClearCommandType::ClearBufferfi, |
| static_cast<GLbitfield>(0u), buffer, drawbuffer, |
| nullptr, depth, stencil); |
| } |
| |
| contextGL->markWorkSubmitted(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::readPixels(const gl::Context *context, |
| const gl::Rectangle &area, |
| GLenum format, |
| GLenum type, |
| const gl::PixelPackState &pack, |
| gl::Buffer *packBuffer, |
| void *pixels) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| const angle::FeaturesGL &features = GetFeaturesGL(context); |
| gl::PixelPackState packState = pack; |
| |
| // Clip read area to framebuffer. |
| const auto *readAttachment = mState.getReadPixelsAttachment(format); |
| const gl::Extents fbSize = readAttachment->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; |
| } |
| |
| GLenum attachmentReadFormat = |
| readAttachment->getFormat().info->getReadPixelsFormat(context->getExtensions()); |
| nativegl::ReadPixelsFormat readPixelsFormat = |
| nativegl::GetReadPixelsFormat(functions, features, attachmentReadFormat, format, type); |
| GLenum readFormat = readPixelsFormat.format; |
| GLenum readType = readPixelsFormat.type; |
| if (features.readPixelsUsingImplementationColorReadFormatForNorm16.enabled && |
| readType == GL_UNSIGNED_SHORT) |
| { |
| ANGLE_CHECK(contextGL, IsValidUnsignedShortReadPixelsFormat(readFormat, context), |
| "glReadPixels: GL_IMPLEMENTATION_COLOR_READ_FORMAT advertised by the driver is " |
| "not handled by RGBA16 readPixels workaround.", |
| GL_INVALID_OPERATION); |
| } |
| |
| GLenum framebufferTarget = |
| stateManager->getHasSeparateFramebufferBindings() ? GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER; |
| stateManager->bindFramebuffer(framebufferTarget, mFramebufferID); |
| |
| bool useOverlappingRowsWorkaround = features.packOverlappingRowsSeparatelyPackBuffer.enabled && |
| packBuffer && packState.rowLength != 0 && |
| packState.rowLength < clippedArea.width; |
| |
| GLubyte *outPtr = static_cast<GLubyte *>(pixels); |
| int leftClip = clippedArea.x - area.x; |
| int topClip = clippedArea.y - area.y; |
| if (leftClip || topClip) |
| { |
| // Adjust destination to match portion clipped off left and/or top. |
| const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(readFormat, readType); |
| |
| GLuint rowBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextGL, |
| glFormat.computeRowPitch(readType, area.width, packState.alignment, |
| packState.rowLength, &rowBytes)); |
| outPtr += leftClip * glFormat.pixelBytes + topClip * rowBytes; |
| } |
| |
| if (packState.rowLength == 0 && clippedArea.width != area.width) |
| { |
| // No rowLength was specified so it will derive from read width, but clipping changed the |
| // read width. Use the original width so we fill the user's buffer as they intended. |
| packState.rowLength = area.width; |
| } |
| |
| // We want to use rowLength, but that might not be supported. |
| bool cannotSetDesiredRowLength = |
| packState.rowLength && !GetImplAs<ContextGL>(context)->getNativeExtensions().packSubimageNV; |
| |
| bool usePackSkipWorkaround = features.emulatePackSkipRowsAndPackSkipPixels.enabled && |
| (packState.skipRows != 0 || packState.skipPixels != 0); |
| |
| if (cannotSetDesiredRowLength || useOverlappingRowsWorkaround || usePackSkipWorkaround) |
| { |
| return readPixelsRowByRow(context, clippedArea, format, readFormat, readType, packState, |
| outPtr); |
| } |
| |
| bool useLastRowPaddingWorkaround = false; |
| if (features.packLastRowSeparatelyForPaddingInclusion.enabled) |
| { |
| ANGLE_TRY(ShouldApplyLastRowPaddingWorkaround( |
| contextGL, gl::Extents(clippedArea.width, clippedArea.height, 1), packState, packBuffer, |
| readFormat, readType, false, outPtr, &useLastRowPaddingWorkaround)); |
| } |
| |
| return readPixelsAllAtOnce(context, clippedArea, format, readFormat, readType, packState, |
| outPtr, useLastRowPaddingWorkaround); |
| } |
| |
| angle::Result FramebufferGL::blit(const gl::Context *context, |
| const gl::Rectangle &sourceArea, |
| const gl::Rectangle &destArea, |
| GLbitfield mask, |
| GLenum filter) |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| const angle::FeaturesGL &features = GetFeaturesGL(context); |
| |
| const Framebuffer *sourceFramebuffer = context->getState().getReadFramebuffer(); |
| const Framebuffer *destFramebuffer = context->getState().getDrawFramebuffer(); |
| |
| const FramebufferAttachment *colorReadAttachment = sourceFramebuffer->getReadColorAttachment(); |
| |
| GLsizei readAttachmentSamples = 0; |
| if (colorReadAttachment != nullptr) |
| { |
| // Blitting requires that the textures be single sampled. getSamples will return |
| // emulated sample number, but the EXT_multisampled_render_to_texture extension will |
| // take care of resolving the texture, so even if emulated samples > 0, we should still |
| // be able to blit as long as the underlying resource samples is single sampled. |
| readAttachmentSamples = colorReadAttachment->getResourceSamples(); |
| } |
| |
| bool needManualColorBlit = false; |
| |
| // TODO(cwallez) when the filter is LINEAR and both source and destination are SRGB, we |
| // could avoid doing a manual blit. |
| |
| // Prior to OpenGL 4.4 BlitFramebuffer (section 18.3.1 of GL 4.3 core profile) reads: |
| // When values are taken from the read buffer, no linearization is performed, even |
| // if the format of the buffer is SRGB. |
| // Starting from OpenGL 4.4 (section 18.3.1) it reads: |
| // When values are taken from the read buffer, if FRAMEBUFFER_SRGB is enabled and the |
| // value of FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING for the framebuffer attachment |
| // corresponding to the read buffer is SRGB, the red, green, and blue components are |
| // converted from the non-linear sRGB color space according [...]. |
| { |
| bool sourceSRGB = |
| colorReadAttachment != nullptr && colorReadAttachment->getColorEncoding() == GL_SRGB; |
| needManualColorBlit = |
| needManualColorBlit || (sourceSRGB && functions->isAtMostGL(gl::Version(4, 3))); |
| } |
| |
| // Prior to OpenGL 4.2 BlitFramebuffer (section 4.3.2 of GL 4.1 core profile) reads: |
| // Blit operations bypass the fragment pipeline. The only fragment operations which |
| // affect a blit are the pixel ownership test and scissor test. |
| // Starting from OpenGL 4.2 (section 4.3.2) it reads: |
| // When values are written to the draw buffers, blit operations bypass the fragment |
| // pipeline. The only fragment operations which affect a blit are the pixel ownership |
| // test, the scissor test and sRGB conversion. |
| if (!needManualColorBlit) |
| { |
| bool destSRGB = false; |
| for (size_t i = 0; i < destFramebuffer->getDrawbufferStateCount(); ++i) |
| { |
| const FramebufferAttachment *attachment = destFramebuffer->getDrawBuffer(i); |
| if (attachment && attachment->getColorEncoding() == GL_SRGB) |
| { |
| destSRGB = true; |
| break; |
| } |
| } |
| |
| needManualColorBlit = |
| needManualColorBlit || (destSRGB && functions->isAtMostGL(gl::Version(4, 1))); |
| } |
| |
| // If the destination has an emulated alpha channel, we need to blit with a shader with alpha |
| // writes disabled. |
| if (mHasEmulatedAlphaAttachment) |
| { |
| needManualColorBlit = true; |
| } |
| |
| // Enable FRAMEBUFFER_SRGB if needed |
| stateManager->setFramebufferSRGBEnabledForFramebuffer(context, true, this); |
| |
| GLenum blitMask = mask; |
| if (needManualColorBlit && (mask & GL_COLOR_BUFFER_BIT) && readAttachmentSamples <= 1) |
| { |
| BlitGL *blitter = GetBlitGL(context); |
| ANGLE_TRY(blitter->blitColorBufferWithShader(context, sourceFramebuffer, destFramebuffer, |
| sourceArea, destArea, filter, |
| !mHasEmulatedAlphaAttachment)); |
| blitMask &= ~GL_COLOR_BUFFER_BIT; |
| } |
| |
| if (blitMask == 0) |
| { |
| return angle::Result::Continue; |
| } |
| |
| const FramebufferGL *sourceFramebufferGL = GetImplAs<FramebufferGL>(sourceFramebuffer); |
| stateManager->bindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebufferGL->getFramebufferID()); |
| stateManager->bindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebufferID); |
| |
| gl::Rectangle finalSourceArea(sourceArea); |
| gl::Rectangle finalDestArea(destArea); |
| |
| if (features.adjustSrcDstRegionBlitFramebuffer.enabled) |
| { |
| angle::Result result = adjustSrcDstRegion(context, finalSourceArea, finalDestArea, |
| &finalSourceArea, &finalDestArea); |
| if (result != angle::Result::Continue) |
| { |
| return result; |
| } |
| } |
| if (features.clipSrcRegionBlitFramebuffer.enabled) |
| { |
| angle::Result result = clipSrcRegion(context, finalSourceArea, finalDestArea, |
| &finalSourceArea, &finalDestArea); |
| if (result != angle::Result::Continue) |
| { |
| return result; |
| } |
| } |
| |
| functions->blitFramebuffer(finalSourceArea.x, finalSourceArea.y, finalSourceArea.x1(), |
| finalSourceArea.y1(), finalDestArea.x, finalDestArea.y, |
| finalDestArea.x1(), finalDestArea.y1(), blitMask, filter); |
| |
| contextGL->markWorkSubmitted(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::adjustSrcDstRegion(const gl::Context *context, |
| const gl::Rectangle &sourceArea, |
| const gl::Rectangle &destArea, |
| gl::Rectangle *newSourceArea, |
| gl::Rectangle *newDestArea) |
| { |
| BlitFramebufferBounds bounds = GetBlitFramebufferBounds(context, sourceArea, destArea); |
| |
| if (bounds.destRegion.width == 0 || bounds.sourceRegion.width == 0 || |
| bounds.destRegion.height == 0 || bounds.sourceRegion.height == 0) |
| { |
| return angle::Result::Stop; |
| } |
| if (!ClipRectangle(bounds.destBounds, bounds.destRegion, nullptr)) |
| { |
| return angle::Result::Stop; |
| } |
| |
| if (!bounds.destBounds.encloses(bounds.destRegion)) |
| { |
| // destRegion is not within destBounds. We want to adjust it to a |
| // reasonable size. This is done by halving the destRegion until it is at |
| // most twice the size of the framebuffer. We cut it in half instead |
| // of arbitrarily shrinking it to fit so that we don't end up with |
| // non-power-of-two scale factors which could mess up pixel interpolation. |
| // Naively clipping the dst rect and then proportionally sizing the |
| // src rect yields incorrect results. |
| |
| GLuint destXHalvings = 0; |
| GLuint destYHalvings = 0; |
| GLint destOriginX = bounds.destRegion.x; |
| GLint destOriginY = bounds.destRegion.y; |
| |
| GLint destClippedWidth = bounds.destRegion.width; |
| while (destClippedWidth > 2 * bounds.destBounds.width) |
| { |
| destClippedWidth = destClippedWidth / 2; |
| destXHalvings++; |
| } |
| |
| GLint destClippedHeight = bounds.destRegion.height; |
| while (destClippedHeight > 2 * bounds.destBounds.height) |
| { |
| destClippedHeight = destClippedHeight / 2; |
| destYHalvings++; |
| } |
| |
| // Before this block, we check that the two rectangles intersect. |
| // Now, compute the location of a new region origin such that we use the |
| // scaled dimensions but the new region has the same intersection as the |
| // original region. |
| |
| GLint left = bounds.destRegion.x0(); |
| GLint right = bounds.destRegion.x1(); |
| GLint top = bounds.destRegion.y0(); |
| GLint bottom = bounds.destRegion.y1(); |
| |
| GLint extraXOffset = 0; |
| if (left >= 0 && left < bounds.destBounds.width) |
| { |
| // Left edge is in-bounds |
| destOriginX = bounds.destRegion.x; |
| } |
| else if (right > 0 && right <= bounds.destBounds.width) |
| { |
| // Right edge is in-bounds |
| destOriginX = right - destClippedWidth; |
| } |
| else |
| { |
| // Region completely spans bounds |
| extraXOffset = (bounds.destRegion.width - destClippedWidth) / 2; |
| destOriginX = bounds.destRegion.x + extraXOffset; |
| } |
| |
| GLint extraYOffset = 0; |
| if (top >= 0 && top < bounds.destBounds.height) |
| { |
| // Top edge is in-bounds |
| destOriginY = bounds.destRegion.y; |
| } |
| else if (bottom > 0 && bottom <= bounds.destBounds.height) |
| { |
| // Bottom edge is in-bounds |
| destOriginY = bottom - destClippedHeight; |
| } |
| else |
| { |
| // Region completely spans bounds |
| extraYOffset = (bounds.destRegion.height - destClippedHeight) / 2; |
| destOriginY = bounds.destRegion.y + extraYOffset; |
| } |
| |
| // Offsets from the bottom left corner of the original region to |
| // the bottom left corner of the clipped region. |
| // This value (after it is scaled) is the respective offset we will apply |
| // to the src origin. |
| |
| CheckedNumeric<GLuint> checkedXOffset(destOriginX - bounds.destRegion.x - extraXOffset / 2); |
| CheckedNumeric<GLuint> checkedYOffset(destOriginY - bounds.destRegion.y - extraYOffset / 2); |
| |
| // if X/Y is reversed, use the top/right out-of-bounds region to compute |
| // the origin offset instead of the left/bottom out-of-bounds region |
| if (bounds.xFlipped) |
| { |
| checkedXOffset = |
| (bounds.destRegion.x1() - (destOriginX + destClippedWidth) + extraXOffset / 2); |
| } |
| if (bounds.yFlipped) |
| { |
| checkedYOffset = |
| (bounds.destRegion.y1() - (destOriginY + destClippedHeight) + extraYOffset / 2); |
| } |
| |
| // These offsets should never overflow |
| GLuint xOffset, yOffset; |
| if (!checkedXOffset.AssignIfValid(&xOffset) || !checkedYOffset.AssignIfValid(&yOffset)) |
| { |
| UNREACHABLE(); |
| return angle::Result::Stop; |
| } |
| |
| bounds.destRegion = |
| gl::Rectangle(destOriginX, destOriginY, destClippedWidth, destClippedHeight); |
| |
| // Adjust the src region by the same factor |
| bounds.sourceRegion = gl::Rectangle(bounds.sourceRegion.x + (xOffset >> destXHalvings), |
| bounds.sourceRegion.y + (yOffset >> destYHalvings), |
| bounds.sourceRegion.width >> destXHalvings, |
| bounds.sourceRegion.height >> destYHalvings); |
| |
| // if the src was scaled to 0, set it to 1 so the src is non-empty |
| if (bounds.sourceRegion.width == 0) |
| { |
| bounds.sourceRegion.width = 1; |
| } |
| if (bounds.sourceRegion.height == 0) |
| { |
| bounds.sourceRegion.height = 1; |
| } |
| } |
| |
| if (!bounds.sourceBounds.encloses(bounds.sourceRegion)) |
| { |
| // sourceRegion is not within sourceBounds. We want to adjust it to a |
| // reasonable size. This is done by halving the sourceRegion until it is at |
| // most twice the size of the framebuffer. We cut it in half instead |
| // of arbitrarily shrinking it to fit so that we don't end up with |
| // non-power-of-two scale factors which could mess up pixel interpolation. |
| // Naively clipping the source rect and then proportionally sizing the |
| // dest rect yields incorrect results. |
| |
| GLuint sourceXHalvings = 0; |
| GLuint sourceYHalvings = 0; |
| GLint sourceOriginX = bounds.sourceRegion.x; |
| GLint sourceOriginY = bounds.sourceRegion.y; |
| |
| GLint sourceClippedWidth = bounds.sourceRegion.width; |
| while (sourceClippedWidth > 2 * bounds.sourceBounds.width) |
| { |
| sourceClippedWidth = sourceClippedWidth / 2; |
| sourceXHalvings++; |
| } |
| |
| GLint sourceClippedHeight = bounds.sourceRegion.height; |
| while (sourceClippedHeight > 2 * bounds.sourceBounds.height) |
| { |
| sourceClippedHeight = sourceClippedHeight / 2; |
| sourceYHalvings++; |
| } |
| |
| // Before this block, we check that the two rectangles intersect. |
| // Now, compute the location of a new region origin such that we use the |
| // scaled dimensions but the new region has the same intersection as the |
| // original region. |
| |
| GLint left = bounds.sourceRegion.x0(); |
| GLint right = bounds.sourceRegion.x1(); |
| GLint top = bounds.sourceRegion.y0(); |
| GLint bottom = bounds.sourceRegion.y1(); |
| |
| GLint extraXOffset = 0; |
| if (left >= 0 && left < bounds.sourceBounds.width) |
| { |
| // Left edge is in-bounds |
| sourceOriginX = bounds.sourceRegion.x; |
| } |
| else if (right > 0 && right <= bounds.sourceBounds.width) |
| { |
| // Right edge is in-bounds |
| sourceOriginX = right - sourceClippedWidth; |
| } |
| else |
| { |
| // Region completely spans bounds |
| extraXOffset = (bounds.sourceRegion.width - sourceClippedWidth) / 2; |
| sourceOriginX = bounds.sourceRegion.x + extraXOffset; |
| } |
| |
| GLint extraYOffset = 0; |
| if (top >= 0 && top < bounds.sourceBounds.height) |
| { |
| // Top edge is in-bounds |
| sourceOriginY = bounds.sourceRegion.y; |
| } |
| else if (bottom > 0 && bottom <= bounds.sourceBounds.height) |
| { |
| // Bottom edge is in-bounds |
| sourceOriginY = bottom - sourceClippedHeight; |
| } |
| else |
| { |
| // Region completely spans bounds |
| extraYOffset = (bounds.sourceRegion.height - sourceClippedHeight) / 2; |
| sourceOriginY = bounds.sourceRegion.y + extraYOffset; |
| } |
| |
| // Offsets from the bottom left corner of the original region to |
| // the bottom left corner of the clipped region. |
| // This value (after it is scaled) is the respective offset we will apply |
| // to the dest origin. |
| |
| CheckedNumeric<GLuint> checkedXOffset(sourceOriginX - bounds.sourceRegion.x - |
| extraXOffset / 2); |
| CheckedNumeric<GLuint> checkedYOffset(sourceOriginY - bounds.sourceRegion.y - |
| extraYOffset / 2); |
| |
| // if X/Y is reversed, use the top/right out-of-bounds region to compute |
| // the origin offset instead of the left/bottom out-of-bounds region |
| if (bounds.xFlipped) |
| { |
| checkedXOffset = (bounds.sourceRegion.x1() - (sourceOriginX + sourceClippedWidth) + |
| extraXOffset / 2); |
| } |
| if (bounds.yFlipped) |
| { |
| checkedYOffset = (bounds.sourceRegion.y1() - (sourceOriginY + sourceClippedHeight) + |
| extraYOffset / 2); |
| } |
| |
| // These offsets should never overflow |
| GLuint xOffset, yOffset; |
| if (!checkedXOffset.AssignIfValid(&xOffset) || !checkedYOffset.AssignIfValid(&yOffset)) |
| { |
| UNREACHABLE(); |
| return angle::Result::Stop; |
| } |
| |
| bounds.sourceRegion = |
| gl::Rectangle(sourceOriginX, sourceOriginY, sourceClippedWidth, sourceClippedHeight); |
| |
| // Adjust the dest region by the same factor |
| bounds.destRegion = gl::Rectangle(bounds.destRegion.x + (xOffset >> sourceXHalvings), |
| bounds.destRegion.y + (yOffset >> sourceYHalvings), |
| bounds.destRegion.width >> sourceXHalvings, |
| bounds.destRegion.height >> sourceYHalvings); |
| } |
| // Set the src and dst endpoints. If they were previously flipped, |
| // set them as flipped. |
| *newSourceArea = bounds.sourceRegion.flip(sourceArea.isReversedX(), sourceArea.isReversedY()); |
| *newDestArea = bounds.destRegion.flip(destArea.isReversedX(), destArea.isReversedY()); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::clipSrcRegion(const gl::Context *context, |
| const gl::Rectangle &sourceArea, |
| const gl::Rectangle &destArea, |
| gl::Rectangle *newSourceArea, |
| gl::Rectangle *newDestArea) |
| { |
| BlitFramebufferBounds bounds = GetBlitFramebufferBounds(context, sourceArea, destArea); |
| |
| if (bounds.destRegion.width == 0 || bounds.sourceRegion.width == 0 || |
| bounds.destRegion.height == 0 || bounds.sourceRegion.height == 0) |
| { |
| return angle::Result::Stop; |
| } |
| if (!ClipRectangle(bounds.destBounds, bounds.destRegion, nullptr)) |
| { |
| return angle::Result::Stop; |
| } |
| |
| if (!bounds.sourceBounds.encloses(bounds.sourceRegion)) |
| { |
| // If pixels lying outside the read framebuffer, adjust src region |
| // and dst region to appropriate in-bounds regions respectively. |
| gl::Rectangle realSourceRegion; |
| if (!ClipRectangle(bounds.sourceRegion, bounds.sourceBounds, &realSourceRegion)) |
| { |
| return angle::Result::Stop; |
| } |
| GLuint xOffset = realSourceRegion.x - bounds.sourceRegion.x; |
| GLuint yOffset = realSourceRegion.y - bounds.sourceRegion.y; |
| |
| // if X/Y is reversed, use the top/right out-of-bounds region for mapping |
| // to dst region, instead of left/bottom out-of-bounds region for mapping. |
| if (bounds.xFlipped) |
| { |
| xOffset = bounds.sourceRegion.x1() - realSourceRegion.x1(); |
| } |
| if (bounds.yFlipped) |
| { |
| yOffset = bounds.sourceRegion.y1() - realSourceRegion.y1(); |
| } |
| |
| GLfloat destMappingWidth = static_cast<GLfloat>(realSourceRegion.width) * |
| bounds.destRegion.width / bounds.sourceRegion.width; |
| GLfloat destMappingHeight = static_cast<GLfloat>(realSourceRegion.height) * |
| bounds.destRegion.height / bounds.sourceRegion.height; |
| GLfloat destMappingXOffset = |
| static_cast<GLfloat>(xOffset) * bounds.destRegion.width / bounds.sourceRegion.width; |
| GLfloat destMappingYOffset = |
| static_cast<GLfloat>(yOffset) * bounds.destRegion.height / bounds.sourceRegion.height; |
| |
| GLuint destMappingX0 = |
| static_cast<GLuint>(std::round(bounds.destRegion.x + destMappingXOffset)); |
| GLuint destMappingY0 = |
| static_cast<GLuint>(std::round(bounds.destRegion.y + destMappingYOffset)); |
| |
| GLuint destMappingX1 = static_cast<GLuint>( |
| std::round(bounds.destRegion.x + destMappingXOffset + destMappingWidth)); |
| GLuint destMappingY1 = static_cast<GLuint>( |
| std::round(bounds.destRegion.y + destMappingYOffset + destMappingHeight)); |
| |
| bounds.destRegion = |
| gl::Rectangle(destMappingX0, destMappingY0, destMappingX1 - destMappingX0, |
| destMappingY1 - destMappingY0); |
| |
| bounds.sourceRegion = realSourceRegion; |
| } |
| // Set the src and dst endpoints. If they were previously flipped, |
| // set them as flipped. |
| *newSourceArea = bounds.sourceRegion.flip(sourceArea.isReversedX(), sourceArea.isReversedY()); |
| *newDestArea = bounds.destRegion.flip(destArea.isReversedX(), destArea.isReversedY()); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::getSamplePosition(const gl::Context *context, |
| size_t index, |
| GLfloat *xy) const |
| { |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| functions->getMultisamplefv(GL_SAMPLE_POSITION, static_cast<GLuint>(index), xy); |
| return angle::Result::Continue; |
| } |
| |
| bool FramebufferGL::shouldSyncStateBeforeCheckStatus() const |
| { |
| return true; |
| } |
| |
| gl::FramebufferStatus FramebufferGL::checkStatus(const gl::Context *context) const |
| { |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| GLenum status = functions->checkFramebufferStatus(GL_FRAMEBUFFER); |
| if (status != GL_FRAMEBUFFER_COMPLETE) |
| { |
| WARN() << "GL framebuffer returned incomplete: " << gl::FmtHex(status); |
| return gl::FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNSUPPORTED, |
| gl::err::kFramebufferIncompleteDriverUnsupported); |
| } |
| |
| return gl::FramebufferStatus::Complete(); |
| } |
| |
| angle::Result FramebufferGL::syncState(const gl::Context *context, |
| GLenum binding, |
| const gl::Framebuffer::DirtyBits &dirtyBits, |
| gl::Command command) |
| { |
| // Don't need to sync state for the default FBO. |
| if (mIsDefault) |
| { |
| return angle::Result::Continue; |
| } |
| |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| |
| stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); |
| |
| // A pointer to one of the attachments for which the texture or the render buffer is not zero. |
| const FramebufferAttachment *attachment = nullptr; |
| |
| for (auto dirtyBit : dirtyBits) |
| { |
| switch (dirtyBit) |
| { |
| case Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT: |
| { |
| const FramebufferAttachment *newAttachment = mState.getDepthAttachment(); |
| BindFramebufferAttachment(functions, GL_DEPTH_ATTACHMENT, newAttachment, |
| GetFeaturesGL(context)); |
| if (newAttachment) |
| { |
| attachment = newAttachment; |
| } |
| break; |
| } |
| case Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT: |
| { |
| const FramebufferAttachment *newAttachment = mState.getStencilAttachment(); |
| BindFramebufferAttachment(functions, GL_STENCIL_ATTACHMENT, newAttachment, |
| GetFeaturesGL(context)); |
| if (newAttachment) |
| { |
| attachment = newAttachment; |
| } |
| break; |
| } |
| case Framebuffer::DIRTY_BIT_DRAW_BUFFERS: |
| { |
| const auto &drawBuffers = mState.getDrawBufferStates(); |
| functions->drawBuffers(static_cast<GLsizei>(drawBuffers.size()), |
| drawBuffers.data()); |
| mAppliedEnabledDrawBuffers = mState.getEnabledDrawBuffers(); |
| break; |
| } |
| case Framebuffer::DIRTY_BIT_READ_BUFFER: |
| functions->readBuffer(mState.getReadBufferState()); |
| break; |
| case Framebuffer::DIRTY_BIT_DEFAULT_WIDTH: |
| functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, |
| mState.getDefaultWidth()); |
| break; |
| case Framebuffer::DIRTY_BIT_DEFAULT_HEIGHT: |
| functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, |
| mState.getDefaultHeight()); |
| break; |
| case Framebuffer::DIRTY_BIT_DEFAULT_SAMPLES: |
| functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES, |
| mState.getDefaultSamples()); |
| break; |
| case Framebuffer::DIRTY_BIT_DEFAULT_FIXED_SAMPLE_LOCATIONS: |
| functions->framebufferParameteri( |
| GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS, |
| gl::ConvertToGLBoolean(mState.getDefaultFixedSampleLocations())); |
| break; |
| case Framebuffer::DIRTY_BIT_DEFAULT_LAYERS: |
| functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_LAYERS_EXT, |
| mState.getDefaultLayers()); |
| break; |
| case Framebuffer::DIRTY_BIT_FLIP_Y: |
| functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, |
| gl::ConvertToGLBoolean(mState.getFlipY())); |
| break; |
| default: |
| { |
| static_assert(Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0 == 0, "FB dirty bits"); |
| if (dirtyBit < Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX) |
| { |
| size_t index = |
| static_cast<size_t>(dirtyBit - Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0); |
| const FramebufferAttachment *newAttachment = mState.getColorAttachment(index); |
| BindFramebufferAttachment(functions, |
| static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index), |
| newAttachment, GetFeaturesGL(context)); |
| if (newAttachment) |
| { |
| attachment = newAttachment; |
| } |
| |
| // Hiding an alpha channel is only supported when it's the first attachment |
| // currently. Assert that these emulated textures are not bound to a framebuffer |
| // using MRT. |
| if (index == 0) |
| { |
| mHasEmulatedAlphaAttachment = |
| IsEmulatedAlphaChannelTextureAttachment(attachment); |
| } |
| ASSERT(index == 0 || !IsEmulatedAlphaChannelTextureAttachment(attachment)); |
| } |
| break; |
| } |
| } |
| } |
| |
| if (attachment && mState.id() == context->getState().getDrawFramebuffer()->id()) |
| { |
| stateManager->updateMultiviewBaseViewLayerIndexUniform(context->getState().getProgram(), |
| getState()); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| GLuint FramebufferGL::getFramebufferID() const |
| { |
| return mFramebufferID; |
| } |
| |
| void FramebufferGL::updateDefaultFramebufferID(GLuint framebufferID) |
| { |
| // We only update framebufferID for a default frambuffer, and the framebufferID is created |
| // externally. ANGLE doesn't owne it. |
| ASSERT(isDefault()); |
| mFramebufferID = framebufferID; |
| } |
| |
| bool FramebufferGL::isDefault() const |
| { |
| return mIsDefault; |
| } |
| |
| bool FramebufferGL::hasEmulatedAlphaChannelTextureAttachment() const |
| { |
| return mHasEmulatedAlphaAttachment; |
| } |
| |
| void FramebufferGL::syncClearState(const gl::Context *context, GLbitfield mask) |
| { |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| const angle::FeaturesGL &features = GetFeaturesGL(context); |
| |
| if (features.doesSRGBClearsOnLinearFramebufferAttachments.enabled && |
| (mask & GL_COLOR_BUFFER_BIT) != 0 && !mIsDefault) |
| { |
| bool hasSRGBAttachment = false; |
| for (const auto &attachment : mState.getColorAttachments()) |
| { |
| if (attachment.isAttached() && attachment.getColorEncoding() == GL_SRGB) |
| { |
| hasSRGBAttachment = true; |
| break; |
| } |
| } |
| |
| stateManager->setFramebufferSRGBEnabled(context, hasSRGBAttachment); |
| } |
| else |
| { |
| stateManager->setFramebufferSRGBEnabled(context, !mIsDefault); |
| } |
| } |
| |
| void FramebufferGL::syncClearBufferState(const gl::Context *context, |
| GLenum buffer, |
| GLint drawBuffer) |
| { |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| const angle::FeaturesGL &features = GetFeaturesGL(context); |
| |
| if (features.doesSRGBClearsOnLinearFramebufferAttachments.enabled && buffer == GL_COLOR && |
| !mIsDefault) |
| { |
| // If doing a clear on a color buffer, set SRGB blend enabled only if the color buffer |
| // is an SRGB format. |
| const auto &drawbufferState = mState.getDrawBufferStates(); |
| const auto &colorAttachments = mState.getColorAttachments(); |
| |
| const FramebufferAttachment *attachment = nullptr; |
| if (drawbufferState[drawBuffer] >= GL_COLOR_ATTACHMENT0 && |
| drawbufferState[drawBuffer] < GL_COLOR_ATTACHMENT0 + colorAttachments.size()) |
| { |
| size_t attachmentIdx = |
| static_cast<size_t>(drawbufferState[drawBuffer] - GL_COLOR_ATTACHMENT0); |
| attachment = &colorAttachments[attachmentIdx]; |
| } |
| |
| if (attachment != nullptr) |
| { |
| stateManager->setFramebufferSRGBEnabled(context, |
| attachment->getColorEncoding() == GL_SRGB); |
| } |
| } |
| else |
| { |
| stateManager->setFramebufferSRGBEnabled(context, !mIsDefault); |
| } |
| } |
| |
| bool FramebufferGL::modifyInvalidateAttachmentsForEmulatedDefaultFBO( |
| size_t count, |
| const GLenum *attachments, |
| std::vector<GLenum> *modifiedAttachments) const |
| { |
| bool needsModification = mIsDefault && mFramebufferID != 0; |
| if (!needsModification) |
| { |
| return false; |
| } |
| |
| modifiedAttachments->resize(count); |
| for (size_t i = 0; i < count; i++) |
| { |
| switch (attachments[i]) |
| { |
| case GL_COLOR: |
| (*modifiedAttachments)[i] = GL_COLOR_ATTACHMENT0; |
| break; |
| |
| case GL_DEPTH: |
| (*modifiedAttachments)[i] = GL_DEPTH_ATTACHMENT; |
| break; |
| |
| case GL_STENCIL: |
| (*modifiedAttachments)[i] = GL_STENCIL_ATTACHMENT; |
| break; |
| |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| angle::Result FramebufferGL::readPixelsRowByRow(const gl::Context *context, |
| const gl::Rectangle &area, |
| GLenum originalReadFormat, |
| GLenum format, |
| GLenum type, |
| const gl::PixelPackState &pack, |
| GLubyte *pixels) const |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| GLubyte *originalReadFormatPixels = pixels; |
| |
| const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(format, type); |
| |
| GLuint rowBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeRowPitch(type, area.width, pack.alignment, |
| pack.rowLength, &rowBytes)); |
| GLuint skipBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextGL, |
| glFormat.computeSkipBytes(type, rowBytes, 0, pack, false, &skipBytes)); |
| |
| ScopedEXTTextureNorm16ReadbackWorkaround workaround; |
| angle::Result result = |
| workaround.Initialize(context, area, originalReadFormat, format, type, skipBytes, rowBytes, |
| glFormat.computePixelBytes(type), pixels); |
| if (result != angle::Result::Continue) |
| { |
| return result; |
| } |
| |
| gl::PixelPackState directPack; |
| directPack.alignment = 1; |
| ANGLE_TRY(stateManager->setPixelPackState(context, directPack)); |
| |
| GLubyte *readbackPixels = workaround.Pixels(); |
| readbackPixels += skipBytes; |
| for (GLint y = area.y; y < area.y + area.height; ++y) |
| { |
| ANGLE_GL_TRY(context, |
| functions->readPixels(area.x, y, area.width, 1, format, type, readbackPixels)); |
| readbackPixels += rowBytes; |
| } |
| |
| if (workaround.IsEnabled()) |
| { |
| return RearrangeEXTTextureNorm16Pixels( |
| context, area, originalReadFormat, format, type, skipBytes, rowBytes, |
| glFormat.computePixelBytes(type), pack, originalReadFormatPixels, workaround.Pixels()); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result FramebufferGL::readPixelsAllAtOnce(const gl::Context *context, |
| const gl::Rectangle &area, |
| GLenum originalReadFormat, |
| GLenum format, |
| GLenum type, |
| const gl::PixelPackState &pack, |
| GLubyte *pixels, |
| bool readLastRowSeparately) const |
| { |
| ContextGL *contextGL = GetImplAs<ContextGL>(context); |
| const FunctionsGL *functions = GetFunctionsGL(context); |
| StateManagerGL *stateManager = GetStateManagerGL(context); |
| GLubyte *originalReadFormatPixels = pixels; |
| |
| const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(format, type); |
| |
| GLuint rowBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeRowPitch(type, area.width, pack.alignment, |
| pack.rowLength, &rowBytes)); |
| GLuint skipBytes = 0; |
| ANGLE_CHECK_GL_MATH(contextGL, |
| glFormat.computeSkipBytes(type, rowBytes, 0, pack, false, &skipBytes)); |
| |
| ScopedEXTTextureNorm16ReadbackWorkaround workaround; |
| angle::Result result = |
| workaround.Initialize(context, area, originalReadFormat, format, type, skipBytes, rowBytes, |
| glFormat.computePixelBytes(type), pixels); |
| if (result != angle::Result::Continue) |
| { |
| return result; |
| } |
| |
| GLint height = area.height - readLastRowSeparately; |
| if (height > 0) |
| { |
| ANGLE_TRY(stateManager->setPixelPackState(context, pack)); |
| ANGLE_GL_TRY(context, functions->readPixels(area.x, area.y, area.width, height, format, |
| type, workaround.Pixels())); |
| } |
| |
| if (readLastRowSeparately) |
| { |
| gl::PixelPackState directPack; |
| directPack.alignment = 1; |
| ANGLE_TRY(stateManager->setPixelPackState(context, directPack)); |
| |
| GLubyte *readbackPixels = workaround.Pixels(); |
| readbackPixels += skipBytes + (area.height - 1) * rowBytes; |
| ANGLE_GL_TRY(context, functions->readPixels(area.x, area.y + area.height - 1, area.width, 1, |
| format, type, readbackPixels)); |
| } |
| |
| if (workaround.IsEnabled()) |
| { |
| return RearrangeEXTTextureNorm16Pixels( |
| context, area, originalReadFormat, format, type, skipBytes, rowBytes, |
| glFormat.computePixelBytes(type), pack, originalReadFormatPixels, workaround.Pixels()); |
| } |
| |
| return angle::Result::Continue; |
| } |
| } // namespace rx |