| // |
| // Copyright 2022 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. |
| // |
| |
| // PixelLocalStorage.cpp: Defines the renderer-agnostic container classes |
| // gl::PixelLocalStorage and gl::PixelLocalStoragePlane for |
| // ANGLE_shader_pixel_local_storage. |
| |
| #include "libANGLE/PixelLocalStorage.h" |
| |
| #include <numeric> |
| #include "common/FixedVector.h" |
| #include "libANGLE/Context.h" |
| #include "libANGLE/Framebuffer.h" |
| #include "libANGLE/context_local_call_autogen.h" |
| #include "libANGLE/renderer/ContextImpl.h" |
| #include "libANGLE/renderer/TextureImpl.h" |
| |
| namespace gl |
| { |
| // RAII utilities for working with GL state. |
| namespace |
| { |
| class ScopedBindTexture2D : angle::NonCopyable |
| { |
| public: |
| ScopedBindTexture2D(Context *context, TextureID texture) |
| : mContext(context), |
| mSavedTexBinding2D( |
| mContext->getState().getSamplerTextureId(mContext->getState().getActiveSampler(), |
| TextureType::_2D)) |
| { |
| mContext->bindTexture(TextureType::_2D, texture); |
| } |
| |
| ~ScopedBindTexture2D() { mContext->bindTexture(TextureType::_2D, mSavedTexBinding2D); } |
| |
| private: |
| Context *const mContext; |
| TextureID mSavedTexBinding2D; |
| }; |
| |
| class ScopedRestoreDrawFramebuffer : angle::NonCopyable |
| { |
| public: |
| ScopedRestoreDrawFramebuffer(Context *context) |
| : mContext(context), mSavedFramebuffer(mContext->getState().getDrawFramebuffer()) |
| { |
| ASSERT(mSavedFramebuffer); |
| } |
| |
| ~ScopedRestoreDrawFramebuffer() { mContext->bindDrawFramebuffer(mSavedFramebuffer->id()); } |
| |
| private: |
| Context *const mContext; |
| Framebuffer *const mSavedFramebuffer; |
| }; |
| |
| class ScopedDisableScissor : angle::NonCopyable |
| { |
| public: |
| ScopedDisableScissor(Context *context) |
| : mContext(context), mScissorTestEnabled(mContext->getState().isScissorTestEnabled()) |
| { |
| if (mScissorTestEnabled) |
| { |
| ContextLocalDisable(mContext, GL_SCISSOR_TEST); |
| } |
| } |
| |
| ~ScopedDisableScissor() |
| { |
| if (mScissorTestEnabled) |
| { |
| ContextLocalEnable(mContext, GL_SCISSOR_TEST); |
| } |
| } |
| |
| private: |
| Context *const mContext; |
| const GLint mScissorTestEnabled; |
| }; |
| |
| class ScopedEnableColorMask : angle::NonCopyable |
| { |
| public: |
| ScopedEnableColorMask(Context *context, int numDrawBuffers) |
| : mContext(context), mNumDrawBuffers(numDrawBuffers) |
| { |
| const State &state = mContext->getState(); |
| if (!mContext->getExtensions().drawBuffersIndexedAny()) |
| { |
| std::array<bool, 4> &mask = mSavedColorMasks[0]; |
| state.getBlendStateExt().getColorMaskIndexed(0, &mask[0], &mask[1], &mask[2], &mask[3]); |
| ContextLocalColorMask(mContext, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| } |
| else |
| { |
| for (int i = 0; i < mNumDrawBuffers; ++i) |
| { |
| std::array<bool, 4> &mask = mSavedColorMasks[i]; |
| state.getBlendStateExt().getColorMaskIndexed(i, &mask[0], &mask[1], &mask[2], |
| &mask[3]); |
| ContextLocalColorMaski(mContext, i, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| } |
| } |
| } |
| |
| ~ScopedEnableColorMask() |
| { |
| if (!mContext->getExtensions().drawBuffersIndexedAny()) |
| { |
| const std::array<bool, 4> &mask = mSavedColorMasks[0]; |
| ContextLocalColorMask(mContext, mask[0], mask[1], mask[2], mask[3]); |
| } |
| else |
| { |
| for (int i = 0; i < mNumDrawBuffers; ++i) |
| { |
| const std::array<bool, 4> &mask = mSavedColorMasks[i]; |
| ContextLocalColorMaski(mContext, i, mask[0], mask[1], mask[2], mask[3]); |
| } |
| } |
| } |
| |
| private: |
| Context *const mContext; |
| const int mNumDrawBuffers; |
| DrawBuffersArray<std::array<bool, 4>> mSavedColorMasks; |
| }; |
| } // namespace |
| |
| PixelLocalStoragePlane::PixelLocalStoragePlane() : mTextureObserver(this, 0) {} |
| |
| PixelLocalStoragePlane::~PixelLocalStoragePlane() |
| { |
| // Call deinitialize or onContextObjectsLost first! |
| // (PixelLocalStorage::deleteContextObjects calls deinitialize.) |
| ASSERT(isDeinitialized()); |
| // We can always expect to receive angle::SubjectMessage::TextureIDDeleted, even if our texture |
| // isn't deleted until context teardown. For this reason, we don't need to hold a ref on the |
| // underlying texture that is the subject of mTextureObserver. |
| ASSERT(mTextureObserver.getSubject() == nullptr); |
| } |
| |
| void PixelLocalStoragePlane::onContextObjectsLost() |
| { |
| // We normally call deleteTexture on the memoryless plane texture ID, since we own it, but in |
| // this case we can let it go. |
| mTextureID = TextureID(); |
| deinitialize(nullptr); |
| } |
| |
| void PixelLocalStoragePlane::deinitialize(Context *context) |
| { |
| if (mMemoryless && mTextureID.value != 0) |
| { |
| ASSERT(context); |
| context->deleteTexture(mTextureID); // Will deinitialize the texture via observers. |
| } |
| else |
| { |
| mInternalformat = GL_NONE; |
| mMemoryless = false; |
| mTextureID = TextureID(); |
| mTextureObserver.reset(); |
| } |
| ASSERT(isDeinitialized()); |
| } |
| |
| void PixelLocalStoragePlane::setMemoryless(Context *context, GLenum internalformat) |
| { |
| deinitialize(context); |
| mInternalformat = internalformat; |
| mMemoryless = true; |
| // The backing texture will get allocated lazily, once we know what dimensions it should be. |
| ASSERT(mTextureID.value == 0); |
| mTextureImageIndex = ImageIndex::MakeFromType(TextureType::_2D, 0, 0); |
| } |
| |
| void PixelLocalStoragePlane::setTextureBacked(Context *context, Texture *tex, int level, int layer) |
| { |
| deinitialize(context); |
| ASSERT(tex->getImmutableFormat()); |
| mInternalformat = tex->getState().getBaseLevelDesc().format.info->internalFormat; |
| mMemoryless = false; |
| mTextureID = tex->id(); |
| mTextureObserver.bind(tex); |
| mTextureImageIndex = ImageIndex::MakeFromType(tex->getType(), level, layer); |
| } |
| |
| void PixelLocalStoragePlane::onSubjectStateChange(angle::SubjectIndex index, |
| angle::SubjectMessage message) |
| { |
| ASSERT(index == 0); |
| switch (message) |
| { |
| case angle::SubjectMessage::TextureIDDeleted: |
| // When a texture object is deleted, any pixel local storage plane to which it is bound |
| // is automatically deinitialized. |
| ASSERT(mTextureID.value != 0); |
| mTextureID = TextureID(); |
| deinitialize(nullptr); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool PixelLocalStoragePlane::isDeinitialized() const |
| { |
| if (mInternalformat == GL_NONE) |
| { |
| ASSERT(!isMemoryless()); |
| ASSERT(mTextureID.value == 0); |
| ASSERT(mTextureObserver.getSubject() == nullptr); |
| return true; |
| } |
| return false; |
| } |
| |
| GLint PixelLocalStoragePlane::getIntegeri(GLenum target) const |
| { |
| if (!isDeinitialized()) |
| { |
| switch (target) |
| { |
| case GL_PIXEL_LOCAL_FORMAT_ANGLE: |
| return mInternalformat; |
| case GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE: |
| return isMemoryless() ? 0 : mTextureID.value; |
| case GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: |
| return isMemoryless() ? 0 : mTextureImageIndex.getLevelIndex(); |
| case GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE: |
| return isMemoryless() ? 0 : mTextureImageIndex.getLayerIndex(); |
| } |
| } |
| // Since GL_NONE == 0, PLS queries all return 0 when the plane is deinitialized. |
| static_assert(GL_NONE == 0, "Expecting GL_NONE to be zero."); |
| return 0; |
| } |
| |
| bool PixelLocalStoragePlane::getTextureImageExtents(const Context *context, Extents *extents) const |
| { |
| ASSERT(!isDeinitialized()); |
| if (isMemoryless()) |
| { |
| return false; |
| } |
| Texture *tex = context->getTexture(mTextureID); |
| ASSERT(tex != nullptr); |
| *extents = tex->getExtents(mTextureImageIndex.getTarget(), mTextureImageIndex.getLevelIndex()); |
| extents->depth = 0; |
| return true; |
| } |
| |
| void PixelLocalStoragePlane::ensureBackingTextureIfMemoryless(Context *context, Extents plsExtents) |
| { |
| ASSERT(!isDeinitialized()); |
| if (!isMemoryless()) |
| { |
| ASSERT(mTextureID.value != 0); |
| return; |
| } |
| |
| // Internal textures backing memoryless planes are always 2D and not mipmapped. |
| ASSERT(mTextureImageIndex.getType() == TextureType::_2D); |
| ASSERT(mTextureImageIndex.getLevelIndex() == 0); |
| ASSERT(mTextureImageIndex.getLayerIndex() == 0); |
| |
| Texture *tex = nullptr; |
| if (mTextureID.value != 0) |
| { |
| tex = context->getTexture(mTextureID); |
| ASSERT(tex != nullptr); |
| } |
| |
| // Do we need to allocate a new backing texture? |
| if (tex == nullptr || |
| static_cast<GLsizei>(tex->getWidth(TextureTarget::_2D, 0)) != plsExtents.width || |
| static_cast<GLsizei>(tex->getHeight(TextureTarget::_2D, 0)) != plsExtents.height) |
| { |
| // Call setMemoryless() to release our current data, if any. |
| setMemoryless(context, mInternalformat); |
| ASSERT(mTextureID.value == 0); |
| |
| // Create a new texture that backs the memoryless plane. |
| mTextureID = context->createTexture(); |
| { |
| ScopedBindTexture2D scopedBindTexture2D(context, mTextureID); |
| context->bindTexture(TextureType::_2D, mTextureID); |
| context->texStorage2D(TextureType::_2D, 1, mInternalformat, plsExtents.width, |
| plsExtents.height); |
| } |
| |
| tex = context->getTexture(mTextureID); |
| ASSERT(tex != nullptr); |
| ASSERT(tex->id() == mTextureID); |
| mTextureObserver.bind(tex); |
| } |
| } |
| |
| void PixelLocalStoragePlane::attachToDrawFramebuffer(Context *context, GLenum colorAttachment) const |
| { |
| ASSERT(!isDeinitialized()); |
| // Call ensureBackingTextureIfMemoryless() first! |
| ASSERT(mTextureID.value != 0 && context->getTexture(mTextureID) != nullptr); |
| if (mTextureImageIndex.usesTex3D()) // GL_TEXTURE_3D or GL_TEXTURE_2D_ARRAY. |
| { |
| context->framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, colorAttachment, mTextureID, |
| mTextureImageIndex.getLevelIndex(), |
| mTextureImageIndex.getLayerIndex()); |
| } |
| else |
| { |
| context->framebufferTexture2D(GL_DRAW_FRAMEBUFFER, colorAttachment, |
| mTextureImageIndex.getTarget(), mTextureID, |
| mTextureImageIndex.getLevelIndex()); |
| } |
| } |
| |
| // Clears the draw buffer at 0-based index 'drawBufferIdx' on the current framebuffer. |
| class ClearBufferCommands : public PixelLocalStoragePlane::ClearCommands |
| { |
| public: |
| ClearBufferCommands(Context *context) : mContext(context) {} |
| |
| void clearfv(int drawBufferIdx, const GLfloat value[]) const override |
| { |
| mContext->clearBufferfv(GL_COLOR, drawBufferIdx, value); |
| } |
| |
| void cleariv(int drawBufferIdx, const GLint value[]) const override |
| { |
| mContext->clearBufferiv(GL_COLOR, drawBufferIdx, value); |
| } |
| |
| void clearuiv(int drawBufferIdx, const GLuint value[]) const override |
| { |
| mContext->clearBufferuiv(GL_COLOR, drawBufferIdx, value); |
| } |
| |
| private: |
| Context *const mContext; |
| }; |
| |
| template <typename T, size_t N> |
| void ClampArray(std::array<T, N> &arr, T lo, T hi) |
| { |
| for (T &x : arr) |
| { |
| x = std::clamp(x, lo, hi); |
| } |
| } |
| |
| void PixelLocalStoragePlane::issueClearCommand(ClearCommands *clearCommands, |
| int target, |
| GLenum loadop) const |
| { |
| switch (mInternalformat) |
| { |
| case GL_RGBA8: |
| case GL_R32F: |
| { |
| std::array<GLfloat, 4> clearValue = {0, 0, 0, 0}; |
| if (loadop == GL_LOAD_OP_CLEAR_ANGLE) |
| { |
| clearValue = mClearValuef; |
| if (mInternalformat == GL_RGBA8) |
| { |
| ClampArray(clearValue, 0.f, 1.f); |
| } |
| } |
| clearCommands->clearfv(target, clearValue.data()); |
| break; |
| } |
| case GL_RGBA8I: |
| { |
| std::array<GLint, 4> clearValue = {0, 0, 0, 0}; |
| if (loadop == GL_LOAD_OP_CLEAR_ANGLE) |
| { |
| clearValue = mClearValuei; |
| ClampArray(clearValue, -128, 127); |
| } |
| clearCommands->cleariv(target, clearValue.data()); |
| break; |
| } |
| case GL_RGBA8UI: |
| case GL_R32UI: |
| { |
| std::array<GLuint, 4> clearValue = {0, 0, 0, 0}; |
| if (loadop == GL_LOAD_OP_CLEAR_ANGLE) |
| { |
| clearValue = mClearValueui; |
| if (mInternalformat == GL_RGBA8UI) |
| { |
| ClampArray(clearValue, 0u, 255u); |
| } |
| } |
| clearCommands->clearuiv(target, clearValue.data()); |
| break; |
| } |
| default: |
| // Invalid PLS internalformats should not have made it this far. |
| UNREACHABLE(); |
| } |
| } |
| |
| void PixelLocalStoragePlane::bindToImage(Context *context, GLuint unit, bool needsR32Packing) const |
| { |
| ASSERT(!isDeinitialized()); |
| // Call ensureBackingTextureIfMemoryless() first! |
| ASSERT(mTextureID.value != 0 && context->getTexture(mTextureID) != nullptr); |
| GLenum imageBindingFormat = mInternalformat; |
| if (needsR32Packing) |
| { |
| // D3D and ES require us to pack all PLS formats into r32f, r32i, or r32ui images. |
| switch (imageBindingFormat) |
| { |
| case GL_RGBA8: |
| case GL_RGBA8UI: |
| imageBindingFormat = GL_R32UI; |
| break; |
| case GL_RGBA8I: |
| imageBindingFormat = GL_R32I; |
| break; |
| } |
| } |
| context->bindImageTexture(unit, mTextureID, mTextureImageIndex.getLevelIndex(), GL_FALSE, |
| mTextureImageIndex.getLayerIndex(), GL_READ_WRITE, |
| imageBindingFormat); |
| } |
| |
| const Texture *PixelLocalStoragePlane::getBackingTexture(const Context *context) const |
| { |
| ASSERT(!isDeinitialized()); |
| ASSERT(!isMemoryless()); |
| const Texture *tex = context->getTexture(mTextureID); |
| ASSERT(tex != nullptr); |
| return tex; |
| } |
| |
| PixelLocalStorage::PixelLocalStorage(const ShPixelLocalStorageOptions &plsOptions, const Caps &caps) |
| : mPLSOptions(plsOptions), mPlanes(caps.maxPixelLocalStoragePlanes) |
| {} |
| |
| PixelLocalStorage::~PixelLocalStorage() {} |
| |
| namespace |
| { |
| bool AllPlanesDeinitialized( |
| const angle::FixedVector<PixelLocalStoragePlane, IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES> |
| &planes, |
| const Context *context) |
| { |
| for (const PixelLocalStoragePlane &plane : planes) |
| { |
| if (!plane.isDeinitialized()) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| } // namespace |
| |
| void PixelLocalStorage::onFramebufferDestroyed(const Context *context) |
| { |
| if (context->getRefCount() == 0) |
| { |
| // If the Context's refcount is zero, we know it's in a teardown state and we can just let |
| // go of our GL objects -- they get cleaned up as part of context teardown. Otherwise, the |
| // Context should have called deleteContextObjects before reaching this point. |
| onContextObjectsLost(); |
| for (PixelLocalStoragePlane &plane : mPlanes) |
| { |
| plane.onContextObjectsLost(); |
| } |
| } |
| // Call deleteContextObjects() when a Framebuffer is destroyed outside of context teardown! |
| ASSERT(AllPlanesDeinitialized(mPlanes, context)); |
| } |
| |
| void PixelLocalStorage::deleteContextObjects(Context *context) |
| { |
| onDeleteContextObjects(context); |
| for (PixelLocalStoragePlane &plane : mPlanes) |
| { |
| plane.deinitialize(context); |
| } |
| } |
| |
| void PixelLocalStorage::begin(Context *context, GLsizei n, const GLenum loadops[]) |
| { |
| // Find the pixel local storage rendering dimensions. |
| Extents plsExtents; |
| bool hasPLSExtents = false; |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| PixelLocalStoragePlane &plane = mPlanes[i]; |
| if (plane.getTextureImageExtents(context, &plsExtents)) |
| { |
| hasPLSExtents = true; |
| break; |
| } |
| } |
| if (!hasPLSExtents) |
| { |
| // All PLS planes are memoryless. Use the rendering area of the framebuffer instead. |
| plsExtents = |
| context->getState().getDrawFramebuffer()->getState().getAttachmentExtentsIntersection(); |
| ASSERT(plsExtents.depth == 0); |
| } |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| PixelLocalStoragePlane &plane = mPlanes[i]; |
| if (mPLSOptions.type == ShPixelLocalStorageType::ImageLoadStore || |
| mPLSOptions.type == ShPixelLocalStorageType::FramebufferFetch) |
| { |
| plane.ensureBackingTextureIfMemoryless(context, plsExtents); |
| } |
| plane.markActive(true); |
| } |
| |
| onBegin(context, n, loadops, plsExtents); |
| } |
| |
| void PixelLocalStorage::end(Context *context, const GLenum storeops[]) |
| { |
| onEnd(context, storeops); |
| |
| GLsizei n = context->getState().getPixelLocalStorageActivePlanes(); |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| mPlanes[i].markActive(false); |
| } |
| } |
| |
| void PixelLocalStorage::barrier(Context *context) |
| { |
| ASSERT(!context->getExtensions().shaderPixelLocalStorageCoherentANGLE); |
| onBarrier(context); |
| } |
| |
| void PixelLocalStorage::interrupt(Context *context) |
| { |
| if (mInterruptCount == 0) |
| { |
| mActivePlanesAtInterrupt = context->getState().getPixelLocalStorageActivePlanes(); |
| ASSERT(0 <= mActivePlanesAtInterrupt && |
| mActivePlanesAtInterrupt <= IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES); |
| if (mActivePlanesAtInterrupt >= 1) |
| { |
| context->endPixelLocalStorageWithStoreOpsStore(); |
| } |
| } |
| ++mInterruptCount; |
| ASSERT(mInterruptCount > 0); |
| } |
| |
| void PixelLocalStorage::restore(Context *context) |
| { |
| ASSERT(mInterruptCount > 0); |
| --mInterruptCount; |
| ASSERT(0 <= mActivePlanesAtInterrupt && |
| mActivePlanesAtInterrupt <= IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES); |
| if (mInterruptCount == 0 && mActivePlanesAtInterrupt >= 1) |
| { |
| angle::FixedVector<GLenum, IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES> loadops( |
| mActivePlanesAtInterrupt); |
| for (GLsizei i = 0; i < mActivePlanesAtInterrupt; ++i) |
| { |
| loadops[i] = mPlanes[i].isMemoryless() ? GL_DONT_CARE : GL_LOAD_OP_LOAD_ANGLE; |
| } |
| context->beginPixelLocalStorage(mActivePlanesAtInterrupt, loadops.data()); |
| } |
| } |
| |
| namespace |
| { |
| // Implements pixel local storage with image load/store shader operations. |
| class PixelLocalStorageImageLoadStore : public PixelLocalStorage |
| { |
| public: |
| PixelLocalStorageImageLoadStore(const ShPixelLocalStorageOptions &plsOptions, const Caps &caps) |
| : PixelLocalStorage(plsOptions, caps) |
| { |
| ASSERT(mPLSOptions.type == ShPixelLocalStorageType::ImageLoadStore); |
| } |
| |
| // Call deleteContextObjects or onContextObjectsLost first! |
| ~PixelLocalStorageImageLoadStore() override |
| { |
| ASSERT(mScratchFramebufferForClearing.value == 0); |
| } |
| |
| void onContextObjectsLost() override |
| { |
| mScratchFramebufferForClearing = FramebufferID(); // Let go of GL objects. |
| } |
| |
| void onDeleteContextObjects(Context *context) override |
| { |
| if (mScratchFramebufferForClearing.value != 0) |
| { |
| context->deleteFramebuffer(mScratchFramebufferForClearing); |
| mScratchFramebufferForClearing = FramebufferID(); |
| } |
| } |
| |
| void onBegin(Context *context, GLsizei n, const GLenum loadops[], Extents plsExtents) override |
| { |
| // Save the image bindings so we can restore them during onEnd(). |
| const State &state = context->getState(); |
| ASSERT(static_cast<size_t>(n) <= state.getImageUnits().size()); |
| mSavedImageBindings.clear(); |
| mSavedImageBindings.reserve(n); |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| mSavedImageBindings.emplace_back(state.getImageUnit(i)); |
| } |
| |
| Framebuffer *framebuffer = state.getDrawFramebuffer(); |
| if (mPLSOptions.renderPassNeedsAMDRasterOrderGroupsWorkaround) |
| { |
| // anglebug.com/7792 -- Metal [[raster_order_group()]] does not work for read_write |
| // textures on AMD when the render pass doesn't have a color attachment on slot 0. To |
| // work around this we attach one of the PLS textures to GL_COLOR_ATTACHMENT0, if there |
| // isn't one already. |
| mHadColorAttachment0 = framebuffer->getColorAttachment(0) != nullptr; |
| if (!mHadColorAttachment0) |
| { |
| // Remember the current draw buffer state so we can restore it during onEnd(). |
| const DrawBuffersVector<GLenum> &appDrawBuffers = |
| framebuffer->getDrawBufferStates(); |
| mSavedDrawBuffers.resize(appDrawBuffers.size()); |
| std::copy(appDrawBuffers.begin(), appDrawBuffers.end(), mSavedDrawBuffers.begin()); |
| |
| // Turn off draw buffer 0. |
| if (mSavedDrawBuffers[0] != GL_NONE) |
| { |
| GLenum drawBuffer0 = mSavedDrawBuffers[0]; |
| mSavedDrawBuffers[0] = GL_NONE; |
| context->drawBuffers(static_cast<GLsizei>(mSavedDrawBuffers.size()), |
| mSavedDrawBuffers.data()); |
| mSavedDrawBuffers[0] = drawBuffer0; |
| } |
| |
| // Attach one of the PLS textures to GL_COLOR_ATTACHMENT0. |
| getPlane(0).attachToDrawFramebuffer(context, GL_COLOR_ATTACHMENT0); |
| } |
| } |
| else |
| { |
| // Save the default framebuffer width/height so we can restore it during onEnd(). |
| mSavedFramebufferDefaultWidth = framebuffer->getDefaultWidth(); |
| mSavedFramebufferDefaultHeight = framebuffer->getDefaultHeight(); |
| |
| // Specify the framebuffer width/height explicitly in case we end up rendering |
| // exclusively to shader images. |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, |
| plsExtents.width); |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, |
| plsExtents.height); |
| } |
| |
| // Guard GL state and bind a scratch framebuffer in case we need to reallocate or clear any |
| // PLS planes. |
| const size_t maxDrawBuffers = context->getCaps().maxDrawBuffers; |
| ScopedRestoreDrawFramebuffer ScopedRestoreDrawFramebuffer(context); |
| if (mScratchFramebufferForClearing.value == 0) |
| { |
| context->genFramebuffers(1, &mScratchFramebufferForClearing); |
| context->bindFramebuffer(GL_DRAW_FRAMEBUFFER, mScratchFramebufferForClearing); |
| // Turn on all draw buffers on the scratch framebuffer for clearing. |
| DrawBuffersVector<GLenum> drawBuffers(maxDrawBuffers); |
| std::iota(drawBuffers.begin(), drawBuffers.end(), GL_COLOR_ATTACHMENT0); |
| context->drawBuffers(static_cast<int>(drawBuffers.size()), drawBuffers.data()); |
| } |
| else |
| { |
| context->bindFramebuffer(GL_DRAW_FRAMEBUFFER, mScratchFramebufferForClearing); |
| } |
| ScopedDisableScissor scopedDisableScissor(context); |
| |
| // Bind and clear the PLS planes. |
| size_t maxClearedAttachments = 0; |
| for (GLsizei i = 0; i < n;) |
| { |
| DrawBuffersVector<int> pendingClears; |
| for (; pendingClears.size() < maxDrawBuffers && i < n; ++i) |
| { |
| GLenum loadop = loadops[i]; |
| const PixelLocalStoragePlane &plane = getPlane(i); |
| plane.bindToImage(context, i, !mPLSOptions.supportsNativeRGBA8ImageFormats); |
| if (loadop == GL_LOAD_OP_ZERO_ANGLE || loadop == GL_LOAD_OP_CLEAR_ANGLE) |
| { |
| plane.attachToDrawFramebuffer( |
| context, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(pendingClears.size())); |
| pendingClears.push_back(i); // Defer the clear for later. |
| } |
| } |
| // Clear in batches in order to be more efficient with GL state. |
| ScopedEnableColorMask scopedEnableColorMask(context, |
| static_cast<int>(pendingClears.size())); |
| ClearBufferCommands clearBufferCommands(context); |
| for (size_t drawBufferIdx = 0; drawBufferIdx < pendingClears.size(); ++drawBufferIdx) |
| { |
| int plsIdx = pendingClears[drawBufferIdx]; |
| getPlane(plsIdx).issueClearCommand( |
| &clearBufferCommands, static_cast<int>(drawBufferIdx), loadops[plsIdx]); |
| } |
| maxClearedAttachments = std::max(maxClearedAttachments, pendingClears.size()); |
| } |
| |
| // Detach the cleared PLS textures from the scratch framebuffer. |
| for (size_t i = 0; i < maxClearedAttachments; ++i) |
| { |
| context->framebufferTexture2D(GL_DRAW_FRAMEBUFFER, |
| GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(i), |
| TextureTarget::_2D, TextureID(), 0); |
| } |
| |
| // Unlike other barriers, GL_SHADER_IMAGE_ACCESS_BARRIER_BIT also synchronizes all types of |
| // memory accesses that happened before the barrier: |
| // |
| // SHADER_IMAGE_ACCESS_BARRIER_BIT: Memory accesses using shader built-in image load and |
| // store functions issued after the barrier will reflect data written by shaders prior to |
| // the barrier. Additionally, image stores issued after the barrier will not execute until |
| // all memory accesses (e.g., loads, stores, texture fetches, vertex fetches) initiated |
| // prior to the barrier complete. |
| // |
| // So we don't any barriers other than GL_SHADER_IMAGE_ACCESS_BARRIER_BIT during begin(). |
| context->memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); |
| } |
| |
| void onEnd(Context *context, const GLenum storeops[]) override |
| { |
| GLsizei n = context->getState().getPixelLocalStorageActivePlanes(); |
| |
| // Restore the image bindings. Since glBindImageTexture and any commands that modify |
| // textures are banned while PLS is active, these will all still be alive and valid. |
| ASSERT(mSavedImageBindings.size() == static_cast<size_t>(n)); |
| for (GLuint unit = 0; unit < mSavedImageBindings.size(); ++unit) |
| { |
| ImageUnit &binding = mSavedImageBindings[unit]; |
| context->bindImageTexture(unit, binding.texture.id(), binding.level, binding.layered, |
| binding.layer, binding.access, binding.format); |
| |
| // BindingPointers have to be explicitly cleaned up. |
| binding.texture.set(context, nullptr); |
| } |
| mSavedImageBindings.clear(); |
| |
| if (mPLSOptions.renderPassNeedsAMDRasterOrderGroupsWorkaround) |
| { |
| if (!mHadColorAttachment0) |
| { |
| // Detach the PLS texture we attached to GL_COLOR_ATTACHMENT0. |
| context->framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| TextureTarget::_2D, TextureID(), 0); |
| |
| // Restore the draw buffer state from before PLS was enabled. |
| if (mSavedDrawBuffers[0] != GL_NONE) |
| { |
| context->drawBuffers(static_cast<GLsizei>(mSavedDrawBuffers.size()), |
| mSavedDrawBuffers.data()); |
| } |
| mSavedDrawBuffers.clear(); |
| } |
| } |
| else |
| { |
| // Restore the default framebuffer width/height. |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, |
| mSavedFramebufferDefaultWidth); |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, |
| mSavedFramebufferDefaultHeight); |
| } |
| |
| // We need ALL_BARRIER_BITS during end() because GL_SHADER_IMAGE_ACCESS_BARRIER_BIT doesn't |
| // synchronize all types of memory accesses that can happen after the barrier. |
| context->memoryBarrier(GL_ALL_BARRIER_BITS); |
| } |
| |
| void onBarrier(Context *context) override |
| { |
| context->memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); |
| } |
| |
| private: |
| // D3D and ES require us to pack all PLS formats into r32f, r32i, or r32ui images. |
| FramebufferID mScratchFramebufferForClearing{}; |
| |
| // Saved values to restore during onEnd(). |
| std::vector<ImageUnit> mSavedImageBindings; |
| // If mPLSOptions.plsRenderPassNeedsColorAttachmentWorkaround. |
| bool mHadColorAttachment0; |
| DrawBuffersVector<GLenum> mSavedDrawBuffers; |
| // If !mPLSOptions.plsRenderPassNeedsColorAttachmentWorkaround. |
| GLint mSavedFramebufferDefaultWidth; |
| GLint mSavedFramebufferDefaultHeight; |
| }; |
| |
| // Implements pixel local storage via framebuffer fetch. |
| class PixelLocalStorageFramebufferFetch : public PixelLocalStorage |
| { |
| public: |
| PixelLocalStorageFramebufferFetch(const ShPixelLocalStorageOptions &plsOptions, |
| const Caps &caps) |
| : PixelLocalStorage(plsOptions, caps) |
| { |
| ASSERT(mPLSOptions.type == ShPixelLocalStorageType::FramebufferFetch); |
| } |
| |
| void onContextObjectsLost() override {} |
| |
| void onDeleteContextObjects(Context *) override {} |
| |
| void onBegin(Context *context, GLsizei n, const GLenum loadops[], Extents plsExtents) override |
| { |
| const State &state = context->getState(); |
| const Caps &caps = context->getCaps(); |
| Framebuffer *framebuffer = context->getState().getDrawFramebuffer(); |
| const DrawBuffersVector<GLenum> &appDrawBuffers = framebuffer->getDrawBufferStates(); |
| |
| // Remember the current draw buffer state so we can restore it during onEnd(). |
| mSavedDrawBuffers.resize(appDrawBuffers.size()); |
| std::copy(appDrawBuffers.begin(), appDrawBuffers.end(), mSavedDrawBuffers.begin()); |
| |
| // Set up new draw buffers for PLS. |
| int firstPLSDrawBuffer = caps.maxCombinedDrawBuffersAndPixelLocalStoragePlanes - n; |
| int numAppDrawBuffers = |
| std::min(static_cast<int>(appDrawBuffers.size()), firstPLSDrawBuffer); |
| DrawBuffersArray<GLenum> plsDrawBuffers; |
| std::copy(appDrawBuffers.begin(), appDrawBuffers.begin() + numAppDrawBuffers, |
| plsDrawBuffers.begin()); |
| std::fill(plsDrawBuffers.begin() + numAppDrawBuffers, |
| plsDrawBuffers.begin() + firstPLSDrawBuffer, GL_NONE); |
| |
| mBlendsToReEnable.reset(); |
| mColorMasksToRestore.reset(); |
| bool needsClear = false; |
| |
| bool hasIndexedBlendAndColorMask = context->getExtensions().drawBuffersIndexedAny(); |
| if (!hasIndexedBlendAndColorMask) |
| { |
| // We don't have indexed blend and color mask control. Disable them globally. (This also |
| // means the app can't have its own draw buffers while PLS is active.) |
| ASSERT(caps.maxColorAttachmentsWithActivePixelLocalStorage == 0); |
| if (state.isBlendEnabled()) |
| { |
| ContextLocalDisable(context, GL_BLEND); |
| mBlendsToReEnable.set(0); |
| } |
| std::array<bool, 4> &mask = mSavedColorMasks[0]; |
| state.getBlendStateExt().getColorMaskIndexed(0, &mask[0], &mask[1], &mask[2], &mask[3]); |
| if (!(mask[0] && mask[1] && mask[2] && mask[3])) |
| { |
| ContextLocalColorMask(context, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| mColorMasksToRestore.set(0); |
| } |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| GLuint drawBufferIdx = GetDrawBufferIdx(caps, i); |
| GLenum loadop = loadops[i]; |
| const PixelLocalStoragePlane &plane = getPlane(i); |
| ASSERT(!plane.isDeinitialized()); |
| |
| // Attach our PLS texture to the framebuffer. Validation should have already ensured |
| // nothing else was attached at this point. |
| GLenum colorAttachment = GL_COLOR_ATTACHMENT0 + drawBufferIdx; |
| ASSERT(!framebuffer->getAttachment(context, colorAttachment)); |
| plane.attachToDrawFramebuffer(context, colorAttachment); |
| plsDrawBuffers[drawBufferIdx] = colorAttachment; |
| |
| if (hasIndexedBlendAndColorMask) |
| { |
| // Ensure blend and color mask are disabled for this draw buffer. |
| if (state.isBlendEnabledIndexed(drawBufferIdx)) |
| { |
| ContextLocalDisablei(context, GL_BLEND, drawBufferIdx); |
| mBlendsToReEnable.set(drawBufferIdx); |
| } |
| std::array<bool, 4> &mask = mSavedColorMasks[drawBufferIdx]; |
| state.getBlendStateExt().getColorMaskIndexed(drawBufferIdx, &mask[0], &mask[1], |
| &mask[2], &mask[3]); |
| if (!(mask[0] && mask[1] && mask[2] && mask[3])) |
| { |
| ContextLocalColorMaski(context, drawBufferIdx, GL_TRUE, GL_TRUE, GL_TRUE, |
| GL_TRUE); |
| mColorMasksToRestore.set(drawBufferIdx); |
| } |
| } |
| |
| needsClear = needsClear || (loadop != GL_LOAD_OP_LOAD_ANGLE); |
| } |
| |
| // Turn on the PLS draw buffers. |
| context->drawBuffers(caps.maxCombinedDrawBuffersAndPixelLocalStoragePlanes, |
| plsDrawBuffers.data()); |
| |
| // Clear the non-LOAD_OP_LOAD PLS planes now that their draw buffers are turned on. |
| if (needsClear) |
| { |
| ScopedDisableScissor scopedDisableScissor(context); |
| ClearBufferCommands clearBufferCommands(context); |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| GLenum loadop = loadops[i]; |
| if (loadop != GL_LOAD_OP_LOAD_ANGLE) |
| { |
| GLuint drawBufferIdx = GetDrawBufferIdx(caps, i); |
| getPlane(i).issueClearCommand(&clearBufferCommands, drawBufferIdx, loadop); |
| } |
| } |
| } |
| |
| if (!context->getExtensions().shaderPixelLocalStorageCoherentANGLE) |
| { |
| // Insert a barrier if we aren't coherent, since the textures may have been rendered to |
| // previously. |
| barrier(context); |
| } |
| } |
| |
| void onEnd(Context *context, const GLenum storeops[]) override |
| { |
| GLsizei n = context->getState().getPixelLocalStorageActivePlanes(); |
| const Caps &caps = context->getCaps(); |
| |
| // Invalidate the non-preserved PLS attachments. |
| DrawBuffersVector<GLenum> invalidateList; |
| for (GLsizei i = n - 1; i >= 0; --i) |
| { |
| if (!getPlane(i).isActive()) |
| { |
| continue; |
| } |
| if (storeops[i] != GL_STORE_OP_STORE_ANGLE || getPlane(i).isMemoryless()) |
| { |
| int drawBufferIdx = GetDrawBufferIdx(caps, i); |
| invalidateList.push_back(GL_COLOR_ATTACHMENT0 + drawBufferIdx); |
| } |
| } |
| if (!invalidateList.empty()) |
| { |
| context->invalidateFramebuffer(GL_DRAW_FRAMEBUFFER, |
| static_cast<GLsizei>(invalidateList.size()), |
| invalidateList.data()); |
| } |
| |
| bool hasIndexedBlendAndColorMask = context->getExtensions().drawBuffersIndexedAny(); |
| if (!hasIndexedBlendAndColorMask) |
| { |
| // Restore global blend and color mask. Validation should have ensured these didn't |
| // change while pixel local storage was active. |
| if (mBlendsToReEnable[0]) |
| { |
| ContextLocalEnable(context, GL_BLEND); |
| } |
| if (mColorMasksToRestore[0]) |
| { |
| const std::array<bool, 4> &mask = mSavedColorMasks[0]; |
| ContextLocalColorMask(context, mask[0], mask[1], mask[2], mask[3]); |
| } |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) |
| { |
| // Reset color attachments where PLS was attached. Validation should have already |
| // ensured nothing was attached at these points when we activated pixel local storage, |
| // and that nothing got attached during. |
| GLuint drawBufferIdx = GetDrawBufferIdx(caps, i); |
| GLenum colorAttachment = GL_COLOR_ATTACHMENT0 + drawBufferIdx; |
| context->framebufferTexture2D(GL_DRAW_FRAMEBUFFER, colorAttachment, TextureTarget::_2D, |
| TextureID(), 0); |
| |
| if (hasIndexedBlendAndColorMask) |
| { |
| // Restore this draw buffer's blend and color mask. Validation should have ensured |
| // these did not change while pixel local storage was active. |
| if (mBlendsToReEnable[drawBufferIdx]) |
| { |
| ContextLocalEnablei(context, GL_BLEND, drawBufferIdx); |
| } |
| if (mColorMasksToRestore[drawBufferIdx]) |
| { |
| const std::array<bool, 4> &mask = mSavedColorMasks[drawBufferIdx]; |
| ContextLocalColorMaski(context, drawBufferIdx, mask[0], mask[1], mask[2], |
| mask[3]); |
| } |
| } |
| } |
| |
| // Restore the draw buffer state from before PLS was enabled. |
| context->drawBuffers(static_cast<GLsizei>(mSavedDrawBuffers.size()), |
| mSavedDrawBuffers.data()); |
| mSavedDrawBuffers.clear(); |
| } |
| |
| void onBarrier(Context *context) override { context->framebufferFetchBarrier(); } |
| |
| private: |
| static GLuint GetDrawBufferIdx(const Caps &caps, GLuint plsPlaneIdx) |
| { |
| // Bind the PLS attachments in reverse order from the rear. This way, the shader translator |
| // doesn't need to know how many planes are going to be active in order to figure out plane |
| // indices. |
| return caps.maxCombinedDrawBuffersAndPixelLocalStoragePlanes - plsPlaneIdx - 1; |
| } |
| |
| DrawBuffersVector<GLenum> mSavedDrawBuffers; |
| DrawBufferMask mBlendsToReEnable; |
| DrawBufferMask mColorMasksToRestore; |
| DrawBuffersArray<std::array<bool, 4>> mSavedColorMasks; |
| }; |
| |
| // Implements ANGLE_shader_pixel_local_storage directly via EXT_shader_pixel_local_storage. |
| class PixelLocalStorageEXT : public PixelLocalStorage |
| { |
| public: |
| PixelLocalStorageEXT(const ShPixelLocalStorageOptions &plsOptions, const Caps &caps) |
| : PixelLocalStorage(plsOptions, caps) |
| { |
| ASSERT(mPLSOptions.type == ShPixelLocalStorageType::PixelLocalStorageEXT); |
| } |
| |
| private: |
| void onContextObjectsLost() override {} |
| |
| void onDeleteContextObjects(Context *) override {} |
| |
| void onBegin(Context *context, GLsizei n, const GLenum loadops[], Extents plsExtents) override |
| { |
| const State &state = context->getState(); |
| Framebuffer *framebuffer = state.getDrawFramebuffer(); |
| |
| // Remember the current draw buffer state so we can restore it during onEnd(). |
| const DrawBuffersVector<GLenum> &appDrawBuffers = framebuffer->getDrawBufferStates(); |
| mSavedDrawBuffers.resize(appDrawBuffers.size()); |
| std::copy(appDrawBuffers.begin(), appDrawBuffers.end(), mSavedDrawBuffers.begin()); |
| |
| // Turn off draw buffers. |
| context->drawBuffers(0, nullptr); |
| |
| // Save the default framebuffer width/height so we can restore it during onEnd(). |
| mSavedFramebufferDefaultWidth = framebuffer->getDefaultWidth(); |
| mSavedFramebufferDefaultHeight = framebuffer->getDefaultHeight(); |
| |
| // Specify the framebuffer width/height explicitly since we don't use color attachments in |
| // this mode. |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, |
| plsExtents.width); |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, |
| plsExtents.height); |
| |
| context->drawPixelLocalStorageEXTEnable(n, getPlanes(), loadops); |
| |
| memcpy(mActiveLoadOps.data(), loadops, sizeof(GLenum) * n); |
| } |
| |
| void onEnd(Context *context, const GLenum storeops[]) override |
| { |
| context->drawPixelLocalStorageEXTDisable(getPlanes(), storeops); |
| |
| // Restore the default framebuffer width/height. |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, |
| mSavedFramebufferDefaultWidth); |
| context->framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, |
| mSavedFramebufferDefaultHeight); |
| |
| // Restore the draw buffer state from before PLS was enabled. |
| context->drawBuffers(static_cast<GLsizei>(mSavedDrawBuffers.size()), |
| mSavedDrawBuffers.data()); |
| mSavedDrawBuffers.clear(); |
| } |
| |
| void onBarrier(Context *context) override { UNREACHABLE(); } |
| |
| // Saved values to restore during onEnd(). |
| GLint mSavedFramebufferDefaultWidth; |
| GLint mSavedFramebufferDefaultHeight; |
| DrawBuffersVector<GLenum> mSavedDrawBuffers; |
| |
| std::array<GLenum, IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES> mActiveLoadOps; |
| }; |
| } // namespace |
| |
| std::unique_ptr<PixelLocalStorage> PixelLocalStorage::Make(const Context *context) |
| { |
| const ShPixelLocalStorageOptions &plsOptions = |
| context->getImplementation()->getNativePixelLocalStorageOptions(); |
| const Caps &caps = context->getState().getCaps(); |
| switch (plsOptions.type) |
| { |
| case ShPixelLocalStorageType::ImageLoadStore: |
| return std::make_unique<PixelLocalStorageImageLoadStore>(plsOptions, caps); |
| case ShPixelLocalStorageType::FramebufferFetch: |
| return std::make_unique<PixelLocalStorageFramebufferFetch>(plsOptions, caps); |
| case ShPixelLocalStorageType::PixelLocalStorageEXT: |
| return std::make_unique<PixelLocalStorageEXT>(plsOptions, caps); |
| default: |
| UNREACHABLE(); |
| return nullptr; |
| } |
| } |
| } // namespace gl |