summaryrefslogtreecommitdiffstats
path: root/gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp')
-rw-r--r--gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp826
1 files changed, 826 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp b/gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp
new file mode 100644
index 0000000000..e5e560af5f
--- /dev/null
+++ b/gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp
@@ -0,0 +1,826 @@
+//
+// 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 "libANGLE/Context.h"
+#include "libANGLE/Framebuffer.h"
+#include "libANGLE/Texture.h"
+#include "libANGLE/renderer/ContextImpl.h"
+
+namespace gl
+{
+// RAII utilities for working with GL state.
+namespace
+{
+class ScopedBindTexture2D
+{
+ 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
+{
+ 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
+{
+ public:
+ ScopedDisableScissor(Context *context)
+ : mContext(context), mScissorTestEnabled(mContext->getState().isScissorTestEnabled())
+ {
+ if (mScissorTestEnabled)
+ {
+ mContext->disable(GL_SCISSOR_TEST);
+ }
+ }
+
+ ~ScopedDisableScissor()
+ {
+ if (mScissorTestEnabled)
+ {
+ mContext->enable(GL_SCISSOR_TEST);
+ }
+ }
+
+ private:
+ Context *const mContext;
+ const GLint mScissorTestEnabled;
+};
+} // namespace
+
+PixelLocalStoragePlane::~PixelLocalStoragePlane()
+{
+ // Call deinitialize or onContextObjectsLost first!
+ ASSERT(mMemorylessTextureID.value == 0);
+ // Call deinitialize or onFramebufferDestroyed first!
+ ASSERT(mTextureRef == 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.
+ mMemorylessTextureID = TextureID();
+}
+
+void PixelLocalStoragePlane::onFramebufferDestroyed(const Context *context)
+{
+ if (mTextureRef != nullptr)
+ {
+ mTextureRef->release(context);
+ mTextureRef = nullptr;
+ }
+}
+
+void PixelLocalStoragePlane::deinitialize(Context *context)
+{
+ mInternalformat = GL_NONE;
+ mMemoryless = false;
+ if (mMemorylessTextureID.value != 0)
+ {
+ // The app could have technically deleted mMemorylessTextureID by guessing its value and
+ // calling glDeleteTextures, but it seems unnecessary to worry about that here. (Worst case
+ // we delete one of their textures.) This also isn't a problem in WebGL.
+ context->deleteTexture(mMemorylessTextureID);
+ mMemorylessTextureID = TextureID();
+ }
+ if (mTextureRef != nullptr)
+ {
+ mTextureRef->release(context);
+ mTextureRef = nullptr;
+ }
+}
+
+void PixelLocalStoragePlane::setMemoryless(Context *context, GLenum internalformat)
+{
+ deinitialize(context);
+ mInternalformat = internalformat;
+ mMemoryless = true;
+ mTextureImageIndex = ImageIndex::MakeFromType(TextureType::_2D, 0, 0);
+ // The backing texture will get allocated lazily, once we know what dimensions it should be.
+ ASSERT(mMemorylessTextureID.value == 0);
+ ASSERT(mTextureRef == nullptr);
+}
+
+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;
+ mTextureImageIndex = ImageIndex::MakeFromType(tex->getType(), level, layer);
+ mTextureRef = tex;
+ mTextureRef->addRef();
+}
+
+bool PixelLocalStoragePlane::isTextureIDDeleted(const Context *context) const
+{
+ // We can tell if the texture has been deleted by looking up mTextureRef's ID on the Context. If
+ // they don't match, it's been deleted.
+ ASSERT(!isDeinitialized() || mTextureRef == nullptr);
+ return mTextureRef != nullptr && context->getTexture(mTextureRef->id()) != mTextureRef;
+}
+
+GLint PixelLocalStoragePlane::getIntegeri(const Context *context, GLenum target, GLuint index) const
+{
+ if (!isDeinitialized())
+ {
+ bool memoryless = isMemoryless() || isTextureIDDeleted(context);
+ switch (target)
+ {
+ case GL_PIXEL_LOCAL_FORMAT_ANGLE:
+ return mInternalformat;
+ case GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE:
+ return memoryless ? 0 : mTextureRef->id().value;
+ case GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE:
+ return memoryless ? 0 : mTextureImageIndex.getLevelIndex();
+ case GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE:
+ return memoryless ? 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
+{
+ if (isDeinitialized() || isMemoryless() || isTextureIDDeleted(context))
+ {
+ return false;
+ }
+ ASSERT(mTextureRef != nullptr);
+ *extents =
+ mTextureRef->getExtents(mTextureImageIndex.getTarget(), mTextureImageIndex.getLevelIndex());
+ extents->depth = 0;
+ return true;
+}
+
+void PixelLocalStoragePlane::ensureBackingIfMemoryless(Context *context, Extents plsExtents)
+{
+ ASSERT(!isDeinitialized());
+ ASSERT(!isTextureIDDeleted(context)); // Convert to memoryless first in this case.
+ if (!isMemoryless())
+ {
+ ASSERT(mTextureRef != nullptr);
+ 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);
+ const bool hasMemorylessTextureId = mMemorylessTextureID.value != 0;
+ const bool hasTextureRef = mTextureRef != nullptr;
+ ASSERT(hasMemorylessTextureId == hasTextureRef);
+
+ // Do we need to allocate a new backing texture?
+ if (mTextureRef == nullptr ||
+ static_cast<GLsizei>(mTextureRef->getWidth(TextureTarget::_2D, 0)) != plsExtents.width ||
+ static_cast<GLsizei>(mTextureRef->getHeight(TextureTarget::_2D, 0)) != plsExtents.height)
+ {
+ // Call setMemoryless() to release our current data.
+ setMemoryless(context, mInternalformat);
+ ASSERT(mTextureRef == nullptr);
+ ASSERT(mMemorylessTextureID.value == 0);
+
+ // Create a new texture that backs the memoryless plane.
+ context->genTextures(1, &mMemorylessTextureID);
+ {
+ ScopedBindTexture2D scopedBindTexture2D(context, mMemorylessTextureID);
+ context->bindTexture(TextureType::_2D, mMemorylessTextureID);
+ context->texStorage2D(TextureType::_2D, 1, mInternalformat, plsExtents.width,
+ plsExtents.height);
+ }
+
+ mTextureRef = context->getTexture(mMemorylessTextureID);
+ ASSERT(mTextureRef != nullptr);
+ ASSERT(mTextureRef->id() == mMemorylessTextureID);
+ mTextureRef->addRef();
+ }
+}
+
+void PixelLocalStoragePlane::attachToDrawFramebuffer(Context *context,
+ Extents plsExtents,
+ GLenum colorAttachment)
+{
+ ASSERT(!isDeinitialized());
+ ensureBackingIfMemoryless(context, plsExtents);
+ ASSERT(mTextureRef != nullptr);
+ if (mTextureImageIndex.usesTex3D()) // GL_TEXTURE_3D or GL_TEXTURE_2D_ARRAY.
+ {
+ context->framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, colorAttachment, mTextureRef->id(),
+ mTextureImageIndex.getLevelIndex(),
+ mTextureImageIndex.getLayerIndex());
+ }
+ else
+ {
+ context->framebufferTexture2D(GL_DRAW_FRAMEBUFFER, colorAttachment,
+ mTextureImageIndex.getTarget(), mTextureRef->id(),
+ mTextureImageIndex.getLevelIndex());
+ }
+}
+
+void PixelLocalStoragePlane::performLoadOperationClear(Context *context,
+ GLint drawBuffer,
+ GLenum loadop,
+ const void *data)
+{
+ // The GL scissor test must be disabled, since the intention is to clear the entire surface.
+ ASSERT(!context->getState().isScissorTestEnabled());
+ switch (mInternalformat)
+ {
+ case GL_RGBA8:
+ case GL_R32F:
+ {
+ GLfloat clearValue[4]{};
+ if (loadop == GL_CLEAR_ANGLE)
+ {
+ memcpy(clearValue, data, sizeof(clearValue));
+ }
+ context->clearBufferfv(GL_COLOR, drawBuffer, clearValue);
+ break;
+ }
+ case GL_RGBA8I:
+ {
+ GLint clearValue[4]{};
+ if (loadop == GL_CLEAR_ANGLE)
+ {
+ memcpy(clearValue, data, sizeof(clearValue));
+ }
+ context->clearBufferiv(GL_COLOR, drawBuffer, clearValue);
+ break;
+ }
+ case GL_RGBA8UI:
+ case GL_R32UI:
+ {
+ GLuint clearValue[4]{};
+ if (loadop == GL_CLEAR_ANGLE)
+ {
+ memcpy(clearValue, data, sizeof(clearValue));
+ }
+ context->clearBufferuiv(GL_COLOR, drawBuffer, clearValue);
+ break;
+ }
+ default:
+ // Invalid PLS internalformats should not have made it this far.
+ UNREACHABLE();
+ }
+}
+
+void PixelLocalStoragePlane::bindToImage(Context *context,
+ Extents plsExtents,
+ GLuint unit,
+ bool needsR32Packing)
+{
+ ASSERT(!isDeinitialized());
+ ensureBackingIfMemoryless(context, plsExtents);
+ ASSERT(mTextureRef != 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;
+ }
+ }
+ if (mTextureRef->getType() != TextureType::_2D)
+ {
+ // TODO(anglebug.com/7279): Texture types other than GL_TEXTURE_2D will take a lot of
+ // consideration to support on all backends. Hold of on fully implementing them until the
+ // other backends are in place.
+ UNIMPLEMENTED();
+ }
+ context->bindImageTexture(unit, mTextureRef->id(), mTextureImageIndex.getLevelIndex(), GL_FALSE,
+ mTextureImageIndex.getLayerIndex(), GL_READ_WRITE,
+ imageBindingFormat);
+}
+
+PixelLocalStorage::PixelLocalStorage() {}
+PixelLocalStorage::~PixelLocalStorage() {}
+
+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();
+ }
+ }
+ for (PixelLocalStoragePlane &plane : mPlanes)
+ {
+ plane.onFramebufferDestroyed(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[],
+ const void *cleardata)
+{
+ // Convert planes whose backing texture has been deleted to memoryless, and find the pixel local
+ // storage rendering dimensions.
+ Extents plsExtents;
+ bool hasPLSExtents = false;
+ for (int i = 0; i < n; ++i)
+ {
+ if (loadops[i] == GL_DISABLE_ANGLE)
+ {
+ continue;
+ }
+ PixelLocalStoragePlane &plane = mPlanes[i];
+ if (plane.isTextureIDDeleted(context))
+ {
+ // [ANGLE_shader_pixel_local_storage] Section 4.4.2.X "Configuring Pixel Local Storage
+ // on a Framebuffer": When a texture object is deleted, any pixel local storage plane to
+ // which it was bound is automatically converted to a memoryless plane of matching
+ // internalformat.
+ plane.setMemoryless(context, plane.getInternalformat());
+ }
+ if (!hasPLSExtents && plane.getTextureImageExtents(context, &plsExtents))
+ {
+ hasPLSExtents = true;
+ }
+ }
+ 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);
+ }
+
+ onBegin(context, n, loadops, reinterpret_cast<const char *>(cleardata), plsExtents);
+ mNumActivePLSPlanes = n;
+}
+
+void PixelLocalStorage::end(Context *context)
+{
+ onEnd(context, mNumActivePLSPlanes);
+ mNumActivePLSPlanes = 0;
+}
+
+void PixelLocalStorage::barrier(Context *context)
+{
+ ASSERT(!context->getExtensions().shaderPixelLocalStorageCoherentANGLE);
+ onBarrier(context);
+}
+
+namespace
+{
+// Implements pixel local storage with image load/store shader operations.
+class PixelLocalStorageImageLoadStore : public PixelLocalStorage
+{
+ public:
+ PixelLocalStorageImageLoadStore(bool needsR32Packing) : mNeedsR32Packing(needsR32Packing) {}
+
+ // 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[],
+ const char *cleardata,
+ 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 (int i = 0; i < n; ++i)
+ {
+ mSavedImageBindings.emplace_back(state.getImageUnit(i));
+ }
+
+ // Save the default framebuffer width/height so we can restore it during onEnd().
+ Framebuffer *framebuffer = state.getDrawFramebuffer();
+ 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 (int i = 0; i < n;)
+ {
+ angle::FixedVector<int, IMPLEMENTATION_MAX_DRAW_BUFFERS> pendingClears;
+ for (; pendingClears.size() < maxDrawBuffers && i < n; ++i)
+ {
+ GLenum loadop = loadops[i];
+ if (loadop == GL_DISABLE_ANGLE)
+ {
+ continue;
+ }
+ PixelLocalStoragePlane &plane = getPlane(i);
+ ASSERT(!plane.isDeinitialized());
+ plane.bindToImage(context, plsExtents, i, mNeedsR32Packing);
+ if (loadop == GL_ZERO || loadop == GL_CLEAR_ANGLE)
+ {
+ plane.attachToDrawFramebuffer(
+ context, plsExtents,
+ GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(pendingClears.size()));
+ pendingClears.push_back(i); // Defer the clear for later.
+ }
+ }
+ // Clear in batches to be more efficient with GL state.
+ for (size_t drawBufferIdx = 0; drawBufferIdx < pendingClears.size(); ++drawBufferIdx)
+ {
+ int plsIdx = pendingClears[drawBufferIdx];
+ getPlane(plsIdx).performLoadOperationClear(
+ context, static_cast<GLint>(drawBufferIdx), loadops[plsIdx],
+ cleardata + plsIdx * 4 * 4);
+ }
+ 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, GLsizei numActivePLSPlanes) override
+ {
+ // 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>(numActivePLSPlanes));
+ 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();
+
+ // 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.
+ const bool mNeedsR32Packing;
+ FramebufferID mScratchFramebufferForClearing{};
+
+ // Saved values to restore during onEnd().
+ GLint mSavedFramebufferDefaultWidth;
+ GLint mSavedFramebufferDefaultHeight;
+ std::vector<ImageUnit> mSavedImageBindings;
+};
+
+// Implements pixel local storage via framebuffer fetch.
+class PixelLocalStorageFramebufferFetch : public PixelLocalStorage
+{
+ public:
+ void onContextObjectsLost() override {}
+
+ void onDeleteContextObjects(Context *) override {}
+
+ void onBegin(Context *context,
+ GLsizei n,
+ const GLenum loadops[],
+ const char *cleardata,
+ 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();
+ mInvalidateList.clear();
+ 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())
+ {
+ context->disable(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]))
+ {
+ context->colorMask(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];
+ if (loadop == GL_DISABLE_ANGLE)
+ {
+ plsDrawBuffers[drawBufferIdx] = GL_NONE;
+ continue;
+ }
+
+ 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, plsExtents, colorAttachment);
+ plsDrawBuffers[drawBufferIdx] = colorAttachment;
+
+ if (hasIndexedBlendAndColorMask)
+ {
+ // Ensure blend and color mask are disabled for this draw buffer.
+ if (state.isBlendEnabledIndexed(drawBufferIdx))
+ {
+ context->disablei(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]))
+ {
+ context->colorMaski(drawBufferIdx, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ mColorMasksToRestore.set(drawBufferIdx);
+ }
+ }
+
+ if (plane.isMemoryless())
+ {
+ // Memoryless planes don't need to be preserved after glEndPixelLocalStorageANGLE().
+ mInvalidateList.push_back(colorAttachment);
+ }
+
+ needsClear = needsClear || (loadop != GL_KEEP);
+ }
+
+ // Turn on the PLS draw buffers.
+ context->drawBuffers(caps.maxCombinedDrawBuffersAndPixelLocalStoragePlanes,
+ plsDrawBuffers.data());
+
+ // Clear the non-KEEP PLS planes now that their draw buffers are turned on.
+ if (needsClear)
+ {
+ ScopedDisableScissor scopedDisableScissor(context);
+ for (GLsizei i = 0; i < n; ++i)
+ {
+ GLenum loadop = loadops[i];
+ if (loadop != GL_DISABLE_ANGLE && loadop != GL_KEEP)
+ {
+ GLuint drawBufferIdx = getDrawBufferIdx(caps, i);
+ getPlane(i).performLoadOperationClear(context, drawBufferIdx, loadop,
+ cleardata + i * 4 * 4);
+ }
+ }
+ }
+
+ 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, GLint numActivePLSPlanes) override
+ {
+
+ const Caps &caps = context->getCaps();
+
+ // Invalidate the memoryless PLS attachments.
+ if (!mInvalidateList.empty())
+ {
+ context->invalidateFramebuffer(GL_DRAW_FRAMEBUFFER,
+ static_cast<GLsizei>(mInvalidateList.size()),
+ mInvalidateList.data());
+ mInvalidateList.clear();
+ }
+
+ 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])
+ {
+ context->enable(GL_BLEND);
+ }
+ if (mColorMasksToRestore[0])
+ {
+ const std::array<bool, 4> &mask = mSavedColorMasks[0];
+ context->colorMask(mask[0], mask[1], mask[2], mask[3]);
+ }
+ }
+
+ for (GLsizei i = 0; i < numActivePLSPlanes; ++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])
+ {
+ context->enablei(GL_BLEND, drawBufferIdx);
+ }
+ if (mColorMasksToRestore[drawBufferIdx])
+ {
+ const std::array<bool, 4> &mask = mSavedColorMasks[drawBufferIdx];
+ context->colorMaski(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:
+ 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;
+ DrawBuffersVector<GLenum> mInvalidateList;
+};
+} // namespace
+
+std::unique_ptr<PixelLocalStorage> PixelLocalStorage::Make(const Context *context)
+{
+ switch (context->getImplementation()->getNativePixelLocalStorageType())
+ {
+ case ShPixelLocalStorageType::ImageStoreR32PackedFormats:
+ return std::make_unique<PixelLocalStorageImageLoadStore>(true);
+ case ShPixelLocalStorageType::ImageStoreNativeFormats:
+ return std::make_unique<PixelLocalStorageImageLoadStore>(false);
+ case ShPixelLocalStorageType::FramebufferFetch:
+ return std::make_unique<PixelLocalStorageFramebufferFetch>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+} // namespace gl