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 ¶ms);
+ 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