Vulkan: Emulate RGB32 uniform texel buffers when unsupported

Applies to: GL_RGB32F, GL_RGB32I, GL_RGB32UI

When VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT is specified for these
formats by the Vulkan driver, behavior is the same as before.

When it is not speficied: previously ANGLE wouldn't enable
GL_EXT_texture_buffer unless exposeNonConformantExtensionsAndVersions
was enabled; now ANGLE always enables it and does the RGB->RGBA
conversion (GPU) under the hood and tracks buffer content updates using
the paths added for tracking this for Vertex Arrays.

Bug: b/278585075
Bug: angleproject:8128
Change-Id: I4605719bf3f51c5a10c1a35ecae767833dcd45d7
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4456498
Commit-Queue: Roman Lavrov <romanl@google.com>
Reviewed-by: Charlie Lao <cclao@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/src/libANGLE/Buffer.cpp b/src/libANGLE/Buffer.cpp
index 440cfd8..676af4f 100644
--- a/src/libANGLE/Buffer.cpp
+++ b/src/libANGLE/Buffer.cpp
@@ -57,6 +57,8 @@
 
 void Buffer::onDestroy(const Context *context)
 {
+    mContentsObservers.clear();
+
     // In tests, mImpl might be null.
     if (mImpl)
         mImpl->destroy(context);
@@ -403,12 +405,12 @@
     onStateChange(message);
 }
 
-size_t Buffer::getContentsObserverIndex(VertexArray *vertexArray, uint32_t bufferIndex) const
+size_t Buffer::getContentsObserverIndex(void *observer, uint32_t bufferIndex) const
 {
+    ContentsObserver contentsObserver{bufferIndex, observer};
     for (size_t observerIndex = 0; observerIndex < mContentsObservers.size(); ++observerIndex)
     {
-        const ContentsObserver &observer = mContentsObservers[observerIndex];
-        if (observer.vertexArray == vertexArray && observer.bufferIndex == bufferIndex)
+        if (mContentsObservers[observerIndex] == contentsObserver)
         {
             return observerIndex;
         }
@@ -419,15 +421,16 @@
 
 void Buffer::addContentsObserver(VertexArray *vertexArray, uint32_t bufferIndex)
 {
+    ASSERT(bufferIndex != ContentsObserver::kBufferTextureIndex);
     if (getContentsObserverIndex(vertexArray, bufferIndex) == kInvalidContentsObserverIndex)
     {
-        mContentsObservers.push_back({vertexArray, bufferIndex});
+        mContentsObservers.push_back({bufferIndex, vertexArray});
     }
 }
 
-void Buffer::removeContentsObserver(VertexArray *vertexArray, uint32_t bufferIndex)
+void Buffer::removeContentsObserverImpl(void *observer, uint32_t bufferIndex)
 {
-    size_t foundObserver = getContentsObserverIndex(vertexArray, bufferIndex);
+    size_t foundObserver = getContentsObserverIndex(observer, bufferIndex);
     if (foundObserver != kInvalidContentsObserverIndex)
     {
         size_t lastObserverIndex = mContentsObservers.size() - 1;
@@ -439,11 +442,43 @@
     }
 }
 
+void Buffer::removeContentsObserver(VertexArray *vertexArray, uint32_t bufferIndex)
+{
+    removeContentsObserverImpl(vertexArray, bufferIndex);
+}
+
+void Buffer::addContentsObserver(Texture *texture)
+{
+    if (!hasContentsObserver(texture))
+    {
+        mContentsObservers.push_back({ContentsObserver::kBufferTextureIndex, texture});
+    }
+}
+
+void Buffer::removeContentsObserver(Texture *texture)
+{
+    removeContentsObserverImpl(texture, ContentsObserver::kBufferTextureIndex);
+}
+
+bool Buffer::hasContentsObserver(Texture *texture) const
+{
+    return getContentsObserverIndex(texture, ContentsObserver::kBufferTextureIndex) !=
+           kInvalidContentsObserverIndex;
+}
+
 void Buffer::onContentsChange()
 {
-    for (const ContentsObserver &observer : mContentsObservers)
+    for (const ContentsObserver &contentsObserver : mContentsObservers)
     {
-        observer.vertexArray->onBufferContentsChange(observer.bufferIndex);
+        if (contentsObserver.bufferIndex != ContentsObserver::kBufferTextureIndex)
+        {
+            static_cast<VertexArray *>(contentsObserver.observer)
+                ->onBufferContentsChange(contentsObserver.bufferIndex);
+        }
+        else
+        {
+            static_cast<Texture *>(contentsObserver.observer)->onBufferContentsChange();
+        }
     }
 }
 }  // namespace gl
diff --git a/src/libANGLE/Buffer.h b/src/libANGLE/Buffer.h
index b71f6af..ac87bf9 100644
--- a/src/libANGLE/Buffer.h
+++ b/src/libANGLE/Buffer.h
@@ -69,16 +69,19 @@
     GLboolean mExternal;
 };
 
-// Some Vertex Array Objects track buffer data updates.
+// Vertex Array and Texture track buffer data updates.
 struct ContentsObserver
 {
-    VertexArray *vertexArray = nullptr;
-    uint32_t bufferIndex     = 0;
+    static constexpr uint32_t kBufferTextureIndex = std::numeric_limits<uint32_t>::max();
+    uint32_t bufferIndex                          = 0;
+
+    // VertexArray* (bufferIndex != kBufferTextureIndex) or Texture*
+    void *observer = nullptr;
 };
 
 ANGLE_INLINE bool operator==(const ContentsObserver &lhs, const ContentsObserver &rhs)
 {
-    return lhs.vertexArray == rhs.vertexArray && lhs.bufferIndex == rhs.bufferIndex;
+    return lhs.bufferIndex == rhs.bufferIndex && lhs.observer == rhs.observer;
 }
 
 class Buffer final : public RefCountObject<BufferID>,
@@ -188,6 +191,9 @@
 
     void addContentsObserver(VertexArray *vertexArray, uint32_t bufferIndex);
     void removeContentsObserver(VertexArray *vertexArray, uint32_t bufferIndex);
+    void addContentsObserver(Texture *texture);
+    void removeContentsObserver(Texture *texture);
+    bool hasContentsObserver(Texture *texture) const;
 
   private:
     angle::Result bufferDataImpl(Context *context,
@@ -203,7 +209,8 @@
                                          GLbitfield flags);
 
     void onContentsChange();
-    size_t getContentsObserverIndex(VertexArray *vertexArray, uint32_t bufferIndex) const;
+    size_t getContentsObserverIndex(void *observer, uint32_t bufferIndex) const;
+    void removeContentsObserverImpl(void *observer, uint32_t bufferIndex);
 
     BufferState mState;
     rx::BufferImpl *mImpl;
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index b273959..7eb0207 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -754,6 +754,19 @@
     }
 }
 
+TextureBufferContentsObservers::TextureBufferContentsObservers(Texture *texture) : mTexture(texture)
+{}
+
+void TextureBufferContentsObservers::enableForBuffer(Buffer *buffer)
+{
+    buffer->addContentsObserver(mTexture);
+}
+
+void TextureBufferContentsObservers::disableForBuffer(Buffer *buffer)
+{
+    buffer->removeContentsObserver(mTexture);
+}
+
 Texture::Texture(rx::GLImplFactory *factory, TextureID id, TextureType type)
     : RefCountObject(factory->generateSerial(), id),
       mState(type),
@@ -761,9 +774,14 @@
       mImplObserver(this, rx::kTextureImageImplObserverMessageIndex),
       mBufferObserver(this, kBufferSubjectIndex),
       mBoundSurface(nullptr),
-      mBoundStream(nullptr)
+      mBoundStream(nullptr),
+      mBufferContentsObservers(this)
 {
     mImplObserver.bind(mTexture);
+    if (mTexture)
+    {
+        mTexture->setContentsObservers(&mBufferContentsObservers);
+    }
 
     // Initially assume the implementation is dirty.
     mDirtyBits.set(DIRTY_BIT_IMPLEMENTATION);
@@ -2422,8 +2440,16 @@
         case angle::SubjectMessage::SubjectMapped:
         case angle::SubjectMessage::SubjectUnmapped:
         case angle::SubjectMessage::BindingChanged:
+        {
             ASSERT(index == kBufferSubjectIndex);
-            break;
+            gl::Buffer *buffer = mState.mBuffer.get();
+            ASSERT(buffer != nullptr);
+            if (buffer->hasContentsObserver(this))
+            {
+                onBufferContentsChange();
+            }
+        }
+        break;
         case angle::SubjectMessage::InitializationComplete:
             ASSERT(index == rx::kTextureImageImplObserverMessageIndex);
             setInitState(InitState::Initialized);
@@ -2440,6 +2466,13 @@
     }
 }
 
+void Texture::onBufferContentsChange()
+{
+    mState.mInitState = InitState::MayNeedInit;
+    signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
+    onStateChange(angle::SubjectMessage::ContentsChanged);
+}
+
 GLenum Texture::getImplementationColorReadFormat(const Context *context) const
 {
     return mTexture->getColorReadFormat(context);
diff --git a/src/libANGLE/Texture.h b/src/libANGLE/Texture.h
index 401167e..c026495 100644
--- a/src/libANGLE/Texture.h
+++ b/src/libANGLE/Texture.h
@@ -267,6 +267,18 @@
 bool operator==(const TextureState &a, const TextureState &b);
 bool operator!=(const TextureState &a, const TextureState &b);
 
+class TextureBufferContentsObservers final : angle::NonCopyable
+{
+  public:
+    TextureBufferContentsObservers(Texture *texture);
+    void enableForBuffer(Buffer *buffer);
+    void disableForBuffer(Buffer *buffer);
+    bool isEnabledForBuffer(Buffer *buffer);
+
+  private:
+    Texture *mTexture;
+};
+
 class Texture final : public RefCountObject<TextureID>,
                       public egl::ImageSibling,
                       public LabeledObject
@@ -661,6 +673,9 @@
     // ObserverInterface implementation.
     void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;
 
+    // Texture buffer updates.
+    void onBufferContentsChange();
+
   private:
     rx::FramebufferAttachmentObjectImpl *getAttachmentImpl() const override;
 
@@ -730,6 +745,7 @@
     };
 
     mutable SamplerCompletenessCache mCompletenessCache;
+    TextureBufferContentsObservers mBufferContentsObservers;
 };
 
 inline bool operator==(const TextureState &a, const TextureState &b)
diff --git a/src/libANGLE/renderer/TextureImpl.h b/src/libANGLE/renderer/TextureImpl.h
index 368fba0..d4a0015 100644
--- a/src/libANGLE/renderer/TextureImpl.h
+++ b/src/libANGLE/renderer/TextureImpl.h
@@ -245,8 +245,14 @@
 
     const gl::TextureState &getState() const { return mState; }
 
+    void setContentsObservers(gl::TextureBufferContentsObservers *observers)
+    {
+        mBufferContentsObservers = observers;
+    }
+
   protected:
     const gl::TextureState &mState;
+    gl::TextureBufferContentsObservers *mBufferContentsObservers = nullptr;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index c572fab..2287f45 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -238,6 +238,8 @@
     "VUID-vkCmdDraw-None-07844",
     "VUID-vkCmdDraw-None-07845",
     "VUID-vkCmdDraw-None-07848",
+    // https://anglebug.com/8128#c3
+    "VUID-VkBufferViewCreateInfo-buffer-00934",
 };
 
 // Validation messages that should be ignored only when VK_EXT_primitive_topology_list_restart is
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 43c03a8..ea2c3e5 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -24,6 +24,7 @@
 #include "libANGLE/renderer/vulkan/RenderbufferVk.h"
 #include "libANGLE/renderer/vulkan/RendererVk.h"
 #include "libANGLE/renderer/vulkan/SurfaceVk.h"
+#include "libANGLE/renderer/vulkan/UtilsVk.h"
 #include "libANGLE/renderer/vulkan/vk_format_utils.h"
 #include "libANGLE/renderer/vulkan/vk_helpers.h"
 #include "libANGLE/renderer/vulkan/vk_utils.h"
@@ -287,6 +288,32 @@
 
     return intended;
 }
+
+angle::FormatID GetRGBAEmulationDstFormat(angle::FormatID srcFormatID)
+{
+    switch (srcFormatID)
+    {
+        case angle::FormatID::R32G32B32_UINT:
+            return angle::FormatID::R32G32B32A32_UINT;
+        case angle::FormatID::R32G32B32_SINT:
+            return angle::FormatID::R32G32B32A32_SINT;
+        case angle::FormatID::R32G32B32_FLOAT:
+            return angle::FormatID::R32G32B32A32_FLOAT;
+        default:
+            return angle::FormatID::NONE;
+    }
+}
+
+bool NeedsRGBAEmulation(RendererVk *renderer, angle::FormatID formatID)
+{
+    if (renderer->hasBufferFormatFeatureBits(formatID, VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))
+    {
+        return false;
+    }
+    // Vulkan driver support is required for all formats except the ones we emulate.
+    ASSERT(GetRGBAEmulationDstFormat(formatID) != angle::FormatID::NONE);
+    return true;
+}
 }  // anonymous namespace
 
 // TextureVk implementation.
@@ -1591,6 +1618,11 @@
         contextVk->getShareGroup()->onTextureRelease(this);
     }
 
+    if (getBuffer().get() != nullptr)
+    {
+        mBufferContentsObservers->disableForBuffer(getBuffer().get());
+    }
+
     if (mBufferViews.isInitialized())
     {
         mBufferViews.release(contextVk);
@@ -2833,6 +2865,58 @@
     return angle::Result::Continue;
 }
 
+vk::BufferHelper *TextureVk::getRGBAConversionBufferHelper(RendererVk *renderer,
+                                                           angle::FormatID formatID)
+{
+    BufferVk *bufferVk                                        = vk::GetImpl(getBuffer().get());
+    const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = mState.getBuffer();
+    const VkDeviceSize bindingOffset                          = bufferBinding.getOffset();
+    ConversionBuffer *conversion                              = bufferVk->getVertexConversionBuffer(
+        renderer, formatID, 16, static_cast<uint32_t>(bindingOffset), false);
+    return conversion->data.get();
+}
+
+angle::Result TextureVk::convertBufferToRGBA(ContextVk *contextVk, size_t &conversionBufferSize)
+{
+    RendererVk *renderer               = contextVk->getRenderer();
+    const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc();
+    const vk::Format *imageUniformFormat =
+        &renderer->getFormat(baseLevelDesc.format.info->sizedInternalFormat);
+    const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = mState.getBuffer();
+    BufferVk *bufferVk                                        = vk::GetImpl(getBuffer().get());
+    const VkDeviceSize bindingOffset                          = bufferBinding.getOffset();
+    const VkDeviceSize bufferSize                             = bufferVk->getSize();
+    const VkDeviceSize bufferSizeFromOffset                   = bufferSize - bindingOffset;
+    conversionBufferSize = roundUpPow2<size_t>(static_cast<size_t>((bufferSizeFromOffset / 3) * 4),
+                                               4 * sizeof(uint32_t));
+
+    ConversionBuffer *conversion =
+        bufferVk->getVertexConversionBuffer(renderer, imageUniformFormat->getIntendedFormatID(), 16,
+                                            static_cast<uint32_t>(bindingOffset), false);
+    mBufferContentsObservers->enableForBuffer(getBuffer().get());
+    vk::BufferHelper *conversionBufferHelper = conversion->data.get();
+    if (!conversionBufferHelper->valid())
+    {
+        ANGLE_TRY(conversionBufferHelper->allocateForVertexConversion(
+            contextVk, conversionBufferSize, vk::MemoryHostVisibility::NonVisible));
+    }
+
+    if (conversion->dirty)
+    {
+        vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
+        UtilsVk &utilsVk               = contextVk->getUtils();
+        const VkDeviceSize pixelSize   = 3 * sizeof(uint32_t);
+        const VkDeviceSize pixelCount  = bufferSizeFromOffset / pixelSize;
+
+        ANGLE_TRY(utilsVk.copyRgbToRgba(contextVk, imageUniformFormat->getIntendedFormat(),
+                                        &bufferHelper, static_cast<uint32_t>(bindingOffset),
+                                        static_cast<uint32_t>(pixelCount), conversionBufferHelper));
+        conversion->dirty = false;
+    }
+
+    return angle::Result::Continue;
+}
+
 angle::Result TextureVk::syncState(const gl::Context *context,
                                    const gl::Texture::DirtyBits &dirtyBits,
                                    gl::Command source)
@@ -2848,8 +2932,16 @@
 
         const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding = mState.getBuffer();
 
-        const VkDeviceSize offset = bufferBinding.getOffset();
-        const VkDeviceSize size   = gl::GetBoundBufferAvailableSize(bufferBinding);
+        VkDeviceSize offset = bufferBinding.getOffset();
+        VkDeviceSize size   = gl::GetBoundBufferAvailableSize(bufferBinding);
+
+        if (NeedsRGBAEmulation(renderer, getBaseLevelFormat(renderer).getIntendedFormatID()))
+        {
+            size_t conversionBufferSize;
+            ANGLE_TRY(convertBufferToRGBA(contextVk, conversionBufferSize));
+            offset = 0;
+            size   = conversionBufferSize;
+        }
 
         mBufferViews.release(contextVk);
         mBufferViews.init(renderer, offset, size);
@@ -3146,6 +3238,17 @@
     const vk::BufferHelper &buffer = vk::GetImpl(mState.getBuffer().get())->getBuffer();
     VkDeviceSize bufferOffset      = buffer.getOffset();
 
+    if (NeedsRGBAEmulation(renderer, imageUniformFormat->getIntendedFormatID()))
+    {
+        vk::BufferHelper *conversionBufferHelper =
+            getRGBAConversionBufferHelper(renderer, imageUniformFormat->getIntendedFormatID());
+        const vk::Format *format = &renderer->getFormat(
+            GetRGBAEmulationDstFormat(imageUniformFormat->getIntendedFormatID()));
+
+        return mBufferViews.getView(context, *conversionBufferHelper,
+                                    conversionBufferHelper->getOffset(), *format, viewOut);
+    }
+
     return mBufferViews.getView(context, buffer, bufferOffset, *imageUniformFormat, viewOut);
 }
 
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.h b/src/libANGLE/renderer/vulkan/TextureVk.h
index 5b12cd4..ab0b5c9 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.h
+++ b/src/libANGLE/renderer/vulkan/TextureVk.h
@@ -565,6 +565,9 @@
 
     angle::Result updateTextureLabel(ContextVk *contextVk);
 
+    vk::BufferHelper *getRGBAConversionBufferHelper(RendererVk *renderer, angle::FormatID formatID);
+    angle::Result convertBufferToRGBA(ContextVk *contextVk, size_t &conversionBufferSize);
+
     bool mOwnsImage;
     // Generated from ImageVk if EGLImage target, or from throw-away generator if Surface target.
     UniqueSerial mImageSiblingSerial;
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.cpp b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
index f8417dd..87f13a1 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.cpp
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
@@ -3324,6 +3324,79 @@
     return angle::Result::Continue;
 }
 
+angle::Result UtilsVk::copyRgbToRgba(ContextVk *contextVk,
+                                     const angle::Format &srcFormat,
+                                     vk::BufferHelper *srcBuffer,
+                                     uint32_t srcOffset,
+                                     uint32_t pixelCount,
+                                     vk::BufferHelper *dstBuffer)
+{
+    vk::OutsideRenderPassCommandBufferHelper *commandBufferHelper;
+    vk::OutsideRenderPassCommandBuffer *commandBuffer;
+    vk::CommandBufferAccess access;
+    access.onBufferComputeShaderRead(srcBuffer);
+    access.onBufferComputeShaderWrite(dstBuffer);
+    ANGLE_TRY(contextVk->getOutsideRenderPassCommandBufferHelper(access, &commandBufferHelper));
+    commandBuffer = &commandBufferHelper->getCommandBuffer();
+
+    rx::UtilsVk::ConvertVertexShaderParams shaderParams;
+    shaderParams.Ns = 3;   // src channels
+    shaderParams.Bs = 4;   // src bytes per channel
+    shaderParams.Ss = 12;  // src stride
+    shaderParams.Nd = 4;   // dest channels
+    shaderParams.Bd = 4;   // dest bytes per channel
+    shaderParams.Sd = 16;  // dest stride
+    shaderParams.Es = 4 / shaderParams.Bs;
+    shaderParams.Ed = 4 / shaderParams.Bd;
+    // Total number of output components is simply the number of pixels by number of components in
+    // each.
+    shaderParams.componentCount = pixelCount * shaderParams.Nd;
+    // Total number of 4-byte outputs is the number of components divided by how many components can
+    // fit in a 4-byte value.  Note that this value is also the invocation size of the shader.
+    shaderParams.outputCount  = UnsignedCeilDivide(shaderParams.componentCount, shaderParams.Ed);
+    shaderParams.srcOffset    = srcOffset;
+    shaderParams.dstOffset    = 0;
+    shaderParams.isSrcHDR     = 0;
+    shaderParams.isSrcA2BGR10 = 0;
+
+    uint32_t flags = 0;
+    switch (srcFormat.id)
+    {
+        case angle::FormatID::R32G32B32_UINT:
+            flags                         = ConvertVertex_comp::kUintToUint;
+            shaderParams.srcEmulatedAlpha = 1;
+            break;
+        case angle::FormatID::R32G32B32_SINT:
+            flags                         = ConvertVertex_comp::kSintToSint;
+            shaderParams.srcEmulatedAlpha = 1;
+            break;
+        case angle::FormatID::R32G32B32_FLOAT:
+            flags                         = ConvertVertex_comp::kFloatToFloat;
+            shaderParams.srcEmulatedAlpha = gl::Float32One;
+            break;
+        default:
+            UNREACHABLE();
+    }
+
+    // Don't need a barrier here, CommandBufferAccess takes care of it.
+
+    ANGLE_TRY(convertVertexBufferImpl(contextVk, dstBuffer, srcBuffer, flags, commandBufferHelper,
+                                      shaderParams));
+
+    // We are circumventing the automatic-barrier management by switching
+    // the buffer view and not the buffer itself, so add a barrier here.
+    VkMemoryBarrier memoryBarrier = {};
+    memoryBarrier.sType           = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+    memoryBarrier.srcAccessMask   = VK_ACCESS_SHADER_WRITE_BIT;
+    memoryBarrier.dstAccessMask   = VK_ACCESS_SHADER_READ_BIT;
+
+    commandBuffer->memoryBarrier(
+        VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+        VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, &memoryBarrier);
+
+    return angle::Result::Continue;
+}
+
 uint32_t GetEtcToBcFlags(const angle::Format &format)
 {
     switch (format.id)
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.h b/src/libANGLE/renderer/vulkan/UtilsVk.h
index 5419422..cc97cc0 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.h
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.h
@@ -260,6 +260,13 @@
                                 vk::ImageHelper *src,
                                 const CopyImageBitsParameters &params);
 
+    angle::Result copyRgbToRgba(ContextVk *contextVk,
+                                const angle::Format &srcFormat,
+                                vk::BufferHelper *srcBuffer,
+                                uint32_t srcOffset,
+                                uint32_t pixelCount,
+                                vk::BufferHelper *dstBuffer);
+
     angle::Result transCodeEtcToBc(ContextVk *contextVk,
                                    vk::BufferHelper *srcBuffer,
                                    vk::ImageHelper *dstImage,
diff --git a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
index ceffe8a..e260e24 100644
--- a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp
@@ -173,78 +173,6 @@
     return true;
 }
 
-bool HasTextureBufferSupport(const RendererVk *rendererVk)
-{
-    //  glTexBuffer page 187 table 8.18.
-    //  glBindImageTexture page 216 table 8.24.
-    //  https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf.
-    //  https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/chap43.html#features-required-format-support
-    //  required image and texture access for texture buffer formats are
-    //                         texture access                image access
-    //    8-bit components, all required by vulkan.
-    //
-    //    GL_R8                        Y                           N
-    //    GL_R8I                       Y                           N
-    //    GL_R8UI                      Y                           N
-    //    GL_RG8                       Y                           N
-    //    GL_RG8I                      Y                           N
-    //    GL_RG8UI                     Y                           N
-    //    GL_RGBA8                     Y                           Y
-    //    GL_RGBA8I                    Y                           Y
-    //    GL_RGBA8UI                   Y                           Y
-    //    GL_RGBA8_SNORM               N                           Y
-    //
-    //    16-bit components,  all required by vulkan.
-    //
-    //    GL_R16F                      Y                           N
-    //    GL_R16I                      Y                           N
-    //    GL_R16UI                     Y                           N
-    //    GL_RG16F                     Y                           N
-    //    GL_RG16I                     Y                           N
-    //    GL_RG16UI                    Y                           N
-    //    GL_RGBA16F                   Y                           Y
-    //    GL_RGBA16I                   Y                           Y
-    //    GL_RGBA16UI                  Y                           Y
-    //
-    //    32-bit components, except RGB32 all others required by vulkan.
-    //
-    //    GL_R32F                      Y                           Y
-    //    GL_R32I                      Y                           Y
-    //    GL_R32UI                     Y                           Y
-    //    GL_RG32F                     Y                           N
-    //    GL_RG32I                     Y                           N
-    //    GL_RG32UI                    Y                           N
-    //    GL_RGB32F                    Y                           N
-    //    GL_RGB32I                    Y                           N
-    //    GL_RGB32UI                   Y                           N
-    //    GL_RGBA32F                   Y                           Y
-    //    GL_RGBA32I                   Y                           Y
-    //    GL_RGBA32UI                  Y                           Y
-
-    // TODO: some platform may not support RGB32 formats as UNIFORM_TEXEL_BUFFER
-    // Despite this limitation, we expose EXT_texture_buffer. http://anglebug.com/3573
-    if (rendererVk->getFeatures().exposeNonConformantExtensionsAndVersions.enabled)
-    {
-        return true;
-    }
-
-    const std::array<GLenum, 3> &optionalFormats = {
-        GL_RGB32F,
-        GL_RGB32I,
-        GL_RGB32UI,
-    };
-
-    for (GLenum formatGL : optionalFormats)
-    {
-        const Format &formatVk = rendererVk->getFormat(formatGL);
-        if (!rendererVk->hasBufferFormatFeatureBits(formatVk.getActualBufferFormat(false).id,
-                                                    VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT))
-            return false;
-    }
-
-    return true;
-}
-
 bool CanSupportYuvInternalFormat(const RendererVk *rendererVk)
 {
     // The following formats are not mandatory in Vulkan, even when VK_KHR_sampler_ycbcr_conversion
@@ -979,14 +907,57 @@
     // R32G32B32_SFLOAT/UINT/SINT which are optional.  For many formats, the STORAGE_TEXEL_BUFFER
     // feature is optional though.  This extension is exposed only if the formats specified in
     // EXT_texture_buffer support the necessary feature bits.
-    if (vk::HasTextureBufferSupport(this))
-    {
-        mNativeExtensions.textureBufferOES = true;
-        mNativeExtensions.textureBufferEXT = true;
-        mNativeCaps.maxTextureBufferSize   = LimitToInt(limitsVk.maxTexelBufferElements);
-        mNativeCaps.textureBufferOffsetAlignment =
-            LimitToInt(limitsVk.minTexelBufferOffsetAlignment);
-    }
+    //
+    //  glTexBuffer page 187 table 8.18.
+    //  glBindImageTexture page 216 table 8.24.
+    //  https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf.
+    //  https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/chap43.html#features-required-format-support
+    //  required image and texture access for texture buffer formats are
+    //                         texture access                image access
+    //    8-bit components, all required by vulkan.
+    //
+    //    GL_R8                        Y                           N
+    //    GL_R8I                       Y                           N
+    //    GL_R8UI                      Y                           N
+    //    GL_RG8                       Y                           N
+    //    GL_RG8I                      Y                           N
+    //    GL_RG8UI                     Y                           N
+    //    GL_RGBA8                     Y                           Y
+    //    GL_RGBA8I                    Y                           Y
+    //    GL_RGBA8UI                   Y                           Y
+    //    GL_RGBA8_SNORM               N                           Y
+    //
+    //    16-bit components,  all required by vulkan.
+    //
+    //    GL_R16F                      Y                           N
+    //    GL_R16I                      Y                           N
+    //    GL_R16UI                     Y                           N
+    //    GL_RG16F                     Y                           N
+    //    GL_RG16I                     Y                           N
+    //    GL_RG16UI                    Y                           N
+    //    GL_RGBA16F                   Y                           Y
+    //    GL_RGBA16I                   Y                           Y
+    //    GL_RGBA16UI                  Y                           Y
+    //
+    //    32-bit components, except RGB32 all others required by vulkan.
+    //                       RGB32 is emulated by ANGLE
+    //
+    //    GL_R32F                      Y                           Y
+    //    GL_R32I                      Y                           Y
+    //    GL_R32UI                     Y                           Y
+    //    GL_RG32F                     Y                           N
+    //    GL_RG32I                     Y                           N
+    //    GL_RG32UI                    Y                           N
+    //    GL_RGB32F                    Y                           N
+    //    GL_RGB32I                    Y                           N
+    //    GL_RGB32UI                   Y                           N
+    //    GL_RGBA32F                   Y                           Y
+    //    GL_RGBA32I                   Y                           Y
+    //    GL_RGBA32UI                  Y                           Y
+    mNativeExtensions.textureBufferOES       = true;
+    mNativeExtensions.textureBufferEXT       = true;
+    mNativeCaps.maxTextureBufferSize         = LimitToInt(limitsVk.maxTexelBufferElements);
+    mNativeCaps.textureBufferOffsetAlignment = LimitToInt(limitsVk.minTexelBufferOffsetAlignment);
 
     // Atomic image operations in the vertex and fragment shaders require the
     // vertexPipelineStoresAndAtomics and fragmentStoresAndAtomics Vulkan features respectively.
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index c535aa4..e01917e 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -1049,6 +1049,13 @@
 7495 OpenGL : EGLContextSharingTestNoFixture.InactiveThreadDoesntPreventCleanup/* = SKIP
 7495 WIN GLES : EGLContextSharingTestNoFixture.InactiveThreadDoesntPreventCleanup/* = SKIP
 
+// GL, GLES seem to ignore mapped texture buffer updates
+8128 WIN OpenGL : RGBTextureBufferTestES31.*/* = SKIP
+8128 WIN GLES : RGBTextureBufferTestES31.*/* = SKIP
+8128 LINUX NVIDIA OpenGL : RGBTextureBufferTestES31.*/* = SKIP
+// Misbehaves on D3D11 (pixels remain black or reading from wrong offsets)
+8128 D3D11 : RGBTextureBufferTestES31.*/* = SKIP
+
 7546 LINUX VULKAN : FramebufferTest_ES3.SurfaceDimensionsChangeAndFragCoord/* = SKIP
 
 7624 WIN INTEL : *EmulateCopyTexImage2DFromRenderbuffers* = SKIP
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 1d99bb8..e2679b7 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -11836,6 +11836,273 @@
     EXPECT_GL_ERROR(GL_INVALID_OPERATION);
 }
 
+// Tests covering RBG->RGBA emulation path
+class RGBTextureBufferTestES31 : public ANGLETest<>
+{
+  protected:
+    RGBTextureBufferTestES31()
+    {
+        setWindowWidth(128);
+        setWindowHeight(128);
+        setConfigRedBits(8);
+        setConfigGreenBits(8);
+        setConfigBlueBits(8);
+        setConfigAlphaBits(8);
+    }
+    void TestInt(GLuint format);
+};
+
+void SetupTextureBufferDrawProgram(GLProgram &program, GLuint format)
+{
+    constexpr char kVS[] = R"(#version 310 es
+    precision highp float;
+    in vec4 inputAttribute;
+
+    void main()
+    {
+        gl_Position = inputAttribute;
+    })";
+
+    if (format == GL_RGB32UI)
+    {
+        constexpr char kFS[] = R"(#version 310 es
+        #extension GL_EXT_texture_buffer : require
+        precision mediump float;
+        uniform highp usamplerBuffer tex;
+        layout(location = 0) out mediump vec4 color;
+
+        void main()
+        {
+            uvec4 v = texelFetch(tex, 1);
+            color = vec4(float(v.r)/255.0, float(v.g)/255.0, float(v.b)/255.0, v.a);
+        })";
+        program.makeRaster(kVS, kFS);
+    }
+    if (format == GL_RGB32I)
+    {
+        constexpr char kFS[] = R"(#version 310 es
+        #extension GL_EXT_texture_buffer : require
+        precision mediump float;
+        uniform highp isamplerBuffer tex;
+        layout(location = 0) out mediump vec4 color;
+
+        void main()
+        {
+            ivec4 v = texelFetch(tex, 1);
+            color = vec4(float(v.r)/255.0, float(v.g)/255.0, float(v.b)/255.0, v.a);
+        })";
+        program.makeRaster(kVS, kFS);
+    }
+    if (format == GL_RGB32F)
+    {
+        constexpr char kFS[] = R"(#version 310 es
+        #extension GL_EXT_texture_buffer : require
+        precision mediump float;
+        uniform highp samplerBuffer tex;
+        layout(location = 0) out mediump vec4 color;
+
+        void main()
+        {
+            vec4 v = texelFetch(tex, 1);
+            color = vec4(float(v.r)/255.0, float(v.g)/255.0, float(v.b)/255.0, v.a);
+        })";
+        program.makeRaster(kVS, kFS);
+    }
+    ASSERT_TRUE(program.valid());
+}
+
+void RGBTextureBufferTestES31::TestInt(GLuint format)
+{
+    const GLint pixelSize = sizeof(GLuint) * 3;
+
+    // Offset must be aligned to GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT (16, 64, ...)
+    GLint offsetAlignment = 0;
+    glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &offsetAlignment);
+    ASSERT(offsetAlignment % sizeof(GLuint) == 0);
+    GLint byteOffset = ((pixelSize * 2) / offsetAlignment + 1) * offsetAlignment;
+
+    GLint intOffset = byteOffset / sizeof(GLuint);
+
+    std::vector<GLuint> texData(intOffset + 3 * 2);
+
+    // first texel(1) col
+    GLColor col = MakeGLColor(11, 22, 33, 255);
+    texData[3]  = col.R;
+    texData[4]  = col.G;
+    texData[5]  = col.B;
+
+    // second texel(1) col2
+    GLColor col2           = MakeGLColor(44, 55, 66, 255);
+    texData[intOffset + 3] = col2.R;
+    texData[intOffset + 4] = col2.G;
+    texData[intOffset + 5] = col2.B;
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_BUFFER, texture);
+
+    GLBuffer buffer;
+    glBindBuffer(GL_TEXTURE_BUFFER, buffer);
+    glBufferData(GL_TEXTURE_BUFFER, sizeof(GLuint) * texData.size(), texData.data(),
+                 GL_STATIC_DRAW);
+    ASSERT_GL_NO_ERROR();
+
+    GLProgram program;
+    SetupTextureBufferDrawProgram(program, format);
+
+    glTexBufferEXT(GL_TEXTURE_BUFFER, format, buffer);
+
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    ASSERT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, col, 1);
+
+    glTexBufferRangeEXT(GL_TEXTURE_BUFFER, format, buffer, byteOffset, pixelSize * 2);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, col2, 1);
+
+    // Now update the buffer to check the converted data also gets updated.
+    GLColor colUpd      = MakeGLColor(77, 88, 99, 255);
+    GLuint texDataUpd[] = {0, 0, 0, colUpd.R, colUpd.G, colUpd.B};  // second texel(1) colUpd
+    glBufferSubData(GL_TEXTURE_BUFFER, byteOffset, sizeof(texDataUpd), texDataUpd);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, colUpd, 1);
+
+    // Update with glMapBuffer (hits a different code path...)
+    GLColor colUpd2      = MakeGLColor(111, 122, 133, 255);
+    GLuint texDataUpd2[] = {0, 0, 0, colUpd2.R, colUpd2.G, colUpd2.B};  // second texel(1) colUpd2
+    void *mappedBuffer =
+        glMapBufferRange(GL_TEXTURE_BUFFER, byteOffset, sizeof(texDataUpd2), GL_MAP_WRITE_BIT);
+    memcpy(mappedBuffer, texDataUpd2, sizeof(texDataUpd2));
+    glUnmapBuffer(GL_TEXTURE_BUFFER);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, colUpd2, 1);
+}
+
+// Tests GL_RGB32UI texture buffer
+TEST_P(RGBTextureBufferTestES31, Uint)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_buffer"));
+
+    TestInt(GL_RGB32UI);
+}
+
+// Tests GL_RGB32I texture buffer
+TEST_P(RGBTextureBufferTestES31, Sint)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_buffer"));
+
+    TestInt(GL_RGB32I);
+}
+
+// Tests GL_RGB32F texture buffer
+TEST_P(RGBTextureBufferTestES31, Float)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_buffer"));
+
+    // first texel(1) col
+    GLColor col = MakeGLColor(11, 22, 33, 255);
+    GLfloat texData[6]{};
+    texData[3] = col.R;
+    texData[4] = col.G;
+    texData[5] = col.B;
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_BUFFER, texture);
+
+    GLBuffer buffer;
+    glBindBuffer(GL_TEXTURE_BUFFER, buffer);
+    glBufferData(GL_TEXTURE_BUFFER, sizeof(texData), texData, GL_STATIC_DRAW);
+    ASSERT_GL_NO_ERROR();
+
+    GLProgram program;
+    SetupTextureBufferDrawProgram(program, GL_RGB32F);
+
+    glTexBufferEXT(GL_TEXTURE_BUFFER, GL_RGB32F, buffer);
+
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    ASSERT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, col, 1);
+
+    // Now update the buffer to check the converted data also gets updated.
+    GLColor colUpd = MakeGLColor(77, 88, 99, 255);
+    GLfloat texDataUpd[6]{};
+    texDataUpd[3] = colUpd.R;
+    texDataUpd[4] = colUpd.G;
+    texDataUpd[5] = colUpd.B;
+    glBufferSubData(GL_TEXTURE_BUFFER, 0, sizeof(texDataUpd), texDataUpd);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, colUpd, 1);
+
+    // Update with glMapBuffer (hits a different code path...)
+    GLColor colUpd2 = MakeGLColor(111, 122, 133, 255);
+    GLfloat texDataUpd2[6]{};
+    texDataUpd2[3] = colUpd2.R;
+    texDataUpd2[4] = colUpd2.G;
+    texDataUpd2[5] = colUpd2.B;
+    void *mappedBuffer =
+        glMapBufferRange(GL_TEXTURE_BUFFER, 0, sizeof(texDataUpd2), GL_MAP_WRITE_BIT);
+    memcpy(mappedBuffer, texDataUpd2, sizeof(texDataUpd2));
+    glUnmapBuffer(GL_TEXTURE_BUFFER);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, colUpd2, 1);
+}
+
+void SetupSSBOProgram(GLProgram &program)
+{
+    constexpr char kVS[] = R"(#version 310 es
+    precision highp float;
+    in vec4 inputAttribute;
+
+    void main()
+    {
+        gl_Position = inputAttribute;
+    })";
+
+    constexpr char kFS[] = R"(#version 310 es
+    layout(location = 0) out mediump vec4 color;
+    layout(std140, binding = 0) buffer outBlock {
+        uvec4 data[2];  // uvec4 to avoid padding
+    };
+    void main (void)
+    {
+        data[0] = uvec4(11u, 22u, 33u, 44u);
+        data[1] = uvec4(55u, 66u, 0u, 0u);
+        color = vec4(0);
+    })";
+
+    program.makeRaster(kVS, kFS);
+    ASSERT_TRUE(program.valid());
+}
+
+// Tests RGB32 texture buffer with a SSBO write
+TEST_P(RGBTextureBufferTestES31, SSBOWrite)
+{
+    GLProgram programSSBO;
+    SetupSSBOProgram(programSSBO);
+
+    GLProgram programBufferDraw;
+    SetupTextureBufferDrawProgram(programBufferDraw, GL_RGB32UI);
+
+    constexpr GLint kBufferSize = 2 * 4 * sizeof(GLuint);
+    GLBuffer buffer;
+    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
+    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);
+
+    drawQuad(programSSBO.get(), "inputAttribute", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
+
+    glTexBufferEXT(GL_TEXTURE_BUFFER, GL_RGB32UI, buffer);
+    drawQuad(programBufferDraw.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(44, 55, 66, 255), 1);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
 #define ES2_EMULATE_COPY_TEX_IMAGE_VIA_SUB()             \
@@ -11981,4 +12248,7 @@
 ANGLE_INSTANTIATE_TEST_ES3_AND(Texture2DDepthStencilTestES3,
                                ES3_VULKAN().enable(Feature::ForceFallbackFormat));
 
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RGBTextureBufferTestES31);
+ANGLE_INSTANTIATE_TEST_ES31(RGBTextureBufferTestES31);
+
 }  // anonymous namespace