diff options
Diffstat (limited to 'gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp')
-rw-r--r-- | gfx/angle/checkout/src/libANGLE/PixelLocalStorage.cpp | 826 |
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 |