Commit 44e690ca by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Support unaligned atomic counter buffer binding

GLES doesn't require any implementation-specified alignment requirement for atomic counter buffers. They are emulated with Vulkan storage buffers, which do have restrictions. The storage buffers are bound at aligned offsets, and the remaining offsets are passed to the shader as uniform values. This means that the driver uniforms are now also bound to the compute pipeline. Bug: angleproject:3566 Change-Id: I1a3429438f76d95e33cb5c6ef2c9370a10d900d6 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1713095 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 6201d134
......@@ -172,12 +172,17 @@ constexpr const char kViewportYScale[] = "viewportYScale";
constexpr const char kNegViewportYScale[] = "negViewportYScale";
constexpr const char kXfbActiveUnpaused[] = "xfbActiveUnpaused";
constexpr const char kXfbBufferOffsets[] = "xfbBufferOffsets";
constexpr const char kAcbBufferOffsets[] = "acbBufferOffsets";
constexpr const char kDepthRange[] = "depthRange";
constexpr size_t kNumDriverUniforms = 7;
constexpr std::array<const char *, kNumDriverUniforms> kDriverUniformNames = {
constexpr size_t kNumGraphicsDriverUniforms = 8;
constexpr std::array<const char *, kNumGraphicsDriverUniforms> kGraphicsDriverUniformNames = {
{kViewport, kHalfRenderAreaHeight, kViewportYScale, kNegViewportYScale, kXfbActiveUnpaused,
kXfbBufferOffsets, kDepthRange}};
kXfbBufferOffsets, kAcbBufferOffsets, kDepthRange}};
constexpr size_t kNumComputeDriverUniforms = 1;
constexpr std::array<const char *, kNumComputeDriverUniforms> kComputeDriverUniformNames = {
{kAcbBufferOffsets}};
size_t FindFieldIndex(const TFieldList &fieldList, const char *fieldName)
{
......@@ -324,10 +329,12 @@ void AppendVertexShaderTransformFeedbackOutputToMain(TIntermBlock *root, TSymbol
RunAtTheEndOfShader(root, new TIntermSymbol(xfbPlaceholder), symbolTable);
}
// The AddDriverUniformsToShader operation adds an internal uniform block to a shader. The driver
// The Add*DriverUniformsToShader operation adds an internal uniform block to a shader. The driver
// block is used to implement Vulkan-specific features and workarounds. Returns the driver uniforms
// variable.
const TVariable *AddDriverUniformsToShader(TIntermBlock *root, TSymbolTable *symbolTable)
//
// There are Graphics and Compute variations as they require different uniforms.
const TVariable *AddGraphicsDriverUniformsToShader(TIntermBlock *root, TSymbolTable *symbolTable)
{
// Init the depth range type.
TFieldList *depthRangeParamsFields = new TFieldList();
......@@ -354,24 +361,50 @@ const TVariable *AddDriverUniformsToShader(TIntermBlock *root, TSymbolTable *sym
DeclareGlobalVariable(root, depthRangeVar);
// This field list mirrors the structure of ContextVk::DriverUniforms.
// This field list mirrors the structure of GraphicsDriverUniforms in ContextVk.cpp.
TFieldList *driverFieldList = new TFieldList;
const std::array<TType *, kNumDriverUniforms> kDriverUniformTypes = {{
const std::array<TType *, kNumGraphicsDriverUniforms> kDriverUniformTypes = {{
new TType(EbtFloat, 4),
new TType(EbtFloat),
new TType(EbtFloat),
new TType(EbtFloat),
new TType(EbtUInt),
new TType(EbtInt, 4),
new TType(EbtUInt, 4),
emulatedDepthRangeType,
}};
for (size_t uniformIndex = 0; uniformIndex < kNumDriverUniforms; ++uniformIndex)
for (size_t uniformIndex = 0; uniformIndex < kNumGraphicsDriverUniforms; ++uniformIndex)
{
TField *driverUniformField =
new TField(kDriverUniformTypes[uniformIndex],
ImmutableString(kGraphicsDriverUniformNames[uniformIndex]), TSourceLoc(),
SymbolType::AngleInternal);
driverFieldList->push_back(driverUniformField);
}
// Define a driver uniform block "ANGLEUniformBlock" with instance name "ANGLEUniforms".
return DeclareInterfaceBlock(root, symbolTable, driverFieldList, EvqUniform,
TMemoryQualifier::Create(), 0, kUniformsBlockName,
kUniformsVarName);
}
const TVariable *AddComputeDriverUniformsToShader(TIntermBlock *root, TSymbolTable *symbolTable)
{
// This field list mirrors the structure of ComputeDriverUniforms in ContextVk.cpp.
TFieldList *driverFieldList = new TFieldList;
const std::array<TType *, kNumComputeDriverUniforms> kDriverUniformTypes = {{
new TType(EbtUInt, 4),
}};
for (size_t uniformIndex = 0; uniformIndex < kNumComputeDriverUniforms; ++uniformIndex)
{
TField *driverUniformField = new TField(kDriverUniformTypes[uniformIndex],
ImmutableString(kDriverUniformNames[uniformIndex]),
TSourceLoc(), SymbolType::AngleInternal);
TField *driverUniformField =
new TField(kDriverUniformTypes[uniformIndex],
ImmutableString(kComputeDriverUniformNames[uniformIndex]), TSourceLoc(),
SymbolType::AngleInternal);
driverFieldList->push_back(driverUniformField);
}
......@@ -653,7 +686,7 @@ void TranslatorVulkan::translate(TIntermBlock *root,
}
}
// TODO(lucferron): Refactor this function to do less tree traversals.
// TODO(lucferron): Refactor this function to do fewer tree traversals.
// http://anglebug.com/2461
if (structTypesUsedForUniforms > 0)
{
......@@ -678,15 +711,27 @@ void TranslatorVulkan::translate(TIntermBlock *root,
sink << "};\n";
}
const TVariable *driverUniforms;
if (getShaderType() == GL_COMPUTE_SHADER)
{
driverUniforms = AddComputeDriverUniformsToShader(root, &getSymbolTable());
}
else
{
driverUniforms = AddGraphicsDriverUniformsToShader(root, &getSymbolTable());
}
if (atomicCounterCount > 0)
{
RewriteAtomicCounters(root, &getSymbolTable());
// ANGLEUniforms.acbBufferOffsets
const TIntermBinary *acbBufferOffsets =
CreateDriverUniformRef(driverUniforms, kAcbBufferOffsets);
RewriteAtomicCounters(root, &getSymbolTable(), acbBufferOffsets);
}
const TVariable *driverUniforms = nullptr;
if (getShaderType() != GL_COMPUTE_SHADER)
{
driverUniforms = AddDriverUniformsToShader(root, &getSymbolTable());
ReplaceGLDepthRangeWithDriverUniform(root, driverUniforms, &getSymbolTable());
}
......
......@@ -72,7 +72,9 @@ TIntermTyped *CreateAtomicCounterConstant(TType *atomicCounterType,
return TIntermAggregate::CreateConstructor(*atomicCounterType, arguments);
}
TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, TIntermTyped *bindingOffset)
TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters,
const TIntermTyped *bindingOffset,
const TIntermTyped *bufferOffsets)
{
// The atomic counters storage buffer declaration looks as such:
//
......@@ -87,6 +89,8 @@ TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, TIntermTy
// return:
//
// atomicCounters[binding].counters[offset]
//
// The offset itself is the provided one plus an offset given through uniforms.
TIntermSymbol *atomicCountersRef = new TIntermSymbol(atomicCounters);
......@@ -96,8 +100,9 @@ TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, TIntermTy
// Create references to bindingOffset.binding and bindingOffset.offset.
TIntermBinary *binding =
new TIntermBinary(EOpIndexDirectStruct, bindingOffset, bindingFieldRef);
TIntermBinary *offset = new TIntermBinary(EOpIndexDirectStruct, bindingOffset, offsetFieldRef);
new TIntermBinary(EOpIndexDirectStruct, bindingOffset->deepCopy(), bindingFieldRef);
TIntermBinary *offset =
new TIntermBinary(EOpIndexDirectStruct, bindingOffset->deepCopy(), offsetFieldRef);
// Create reference to atomicCounters[bindingOffset.binding]
TIntermBinary *countersBlock = new TIntermBinary(EOpIndexDirect, atomicCountersRef, binding);
......@@ -106,7 +111,27 @@ TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, TIntermTy
TIntermBinary *counters =
new TIntermBinary(EOpIndexDirectInterfaceBlock, countersBlock, countersFieldRef);
// return atomicCounters[bindingOffset.binding].counters[bindingOffset.offset]
// Create bufferOffsets[binding / 4]. Each uint in bufferOffsets contains offsets for 4
// bindings.
TIntermBinary *bindingDivFour =
new TIntermBinary(EOpDiv, binding->deepCopy(), CreateUIntConstant(4));
TIntermBinary *bufferOffsetUint =
new TIntermBinary(EOpIndexDirect, bufferOffsets->deepCopy(), bindingDivFour);
// Create (binding % 4) * 8
TIntermBinary *bindingModFour =
new TIntermBinary(EOpIMod, binding->deepCopy(), CreateUIntConstant(4));
TIntermBinary *bufferOffsetShift =
new TIntermBinary(EOpMul, bindingModFour, CreateUIntConstant(8));
// Create bufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF
TIntermBinary *bufferOffsetShifted =
new TIntermBinary(EOpBitShiftRight, bufferOffsetUint, bufferOffsetShift);
TIntermBinary *bufferOffset =
new TIntermBinary(EOpBitwiseAnd, bufferOffsetShifted, CreateUIntConstant(0xFF));
// return atomicCounters[bindingOffset.binding].counters[bindingOffset.offset + bufferOffset]
offset = new TIntermBinary(EOpAdd, offset, bufferOffset);
return new TIntermBinary(EOpIndexDirect, counters, offset);
}
......@@ -119,9 +144,12 @@ TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, TIntermTy
class RewriteAtomicCountersTraverser : public TIntermTraverser
{
public:
RewriteAtomicCountersTraverser(TSymbolTable *symbolTable, const TVariable *atomicCounters)
RewriteAtomicCountersTraverser(TSymbolTable *symbolTable,
const TVariable *atomicCounters,
const TIntermTyped *acbBufferOffsets)
: TIntermTraverser(true, true, true, symbolTable),
mAtomicCounters(atomicCounters),
mAcbBufferOffsets(acbBufferOffsets),
mCurrentAtomicCounterOffset(0),
mCurrentAtomicCounterBinding(0),
mCurrentAtomicCounterDecl(nullptr),
......@@ -506,7 +534,8 @@ class RewriteAtomicCountersTraverser : public TIntermTraverser
TIntermTyped *bindingOffset = mAtomicCounterFunctionCallArgs[param];
TIntermSequence *substituteArguments = new TIntermSequence;
substituteArguments->push_back(CreateAtomicCounterRef(mAtomicCounters, bindingOffset));
substituteArguments->push_back(
CreateAtomicCounterRef(mAtomicCounters, bindingOffset, mAcbBufferOffsets));
substituteArguments->push_back(CreateUIntConstant(valueChange));
TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
......@@ -559,6 +588,7 @@ class RewriteAtomicCountersTraverser : public TIntermTraverser
}
const TVariable *mAtomicCounters;
const TIntermTyped *mAcbBufferOffsets;
// A map from the atomic_uint variable to the binding/offset declaration.
std::unordered_map<const TVariable *, TVariable *> mAtomicCounterBindingOffsets;
......@@ -585,11 +615,13 @@ class RewriteAtomicCountersTraverser : public TIntermTraverser
} // anonymous namespace
void RewriteAtomicCounters(TIntermBlock *root, TSymbolTable *symbolTable)
void RewriteAtomicCounters(TIntermBlock *root,
TSymbolTable *symbolTable,
const TIntermTyped *acbBufferOffsets)
{
const TVariable *atomicCounters = DeclareAtomicCountersBuffers(root, symbolTable);
RewriteAtomicCountersTraverser traverser(symbolTable, atomicCounters);
RewriteAtomicCountersTraverser traverser(symbolTable, atomicCounters, acbBufferOffsets);
root->traverse(&traverser);
traverser.updateTree();
......
......@@ -12,10 +12,13 @@
namespace sh
{
class TIntermBlock;
class TIntermTyped;
class TSymbolTable;
class TVariable;
void RewriteAtomicCounters(TIntermBlock *root, TSymbolTable *symbolTable);
void RewriteAtomicCounters(TIntermBlock *root,
TSymbolTable *symbolTable,
const TIntermTyped *acbBufferOffsets);
} // namespace sh
#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_
......@@ -1589,19 +1589,19 @@ angle::Result State::setIndexedBufferBinding(const Context *context,
const OffsetBindingPointer<Buffer> &State::getIndexedUniformBuffer(size_t index) const
{
ASSERT(static_cast<size_t>(index) < mUniformBuffers.size());
ASSERT(index < mUniformBuffers.size());
return mUniformBuffers[index];
}
const OffsetBindingPointer<Buffer> &State::getIndexedAtomicCounterBuffer(size_t index) const
{
ASSERT(static_cast<size_t>(index) < mAtomicCounterBuffers.size());
ASSERT(index < mAtomicCounterBuffers.size());
return mAtomicCounterBuffers[index];
}
const OffsetBindingPointer<Buffer> &State::getIndexedShaderStorageBuffer(size_t index) const
{
ASSERT(static_cast<size_t>(index) < mShaderStorageBuffers.size());
ASSERT(index < mShaderStorageBuffers.size());
return mShaderStorageBuffers[index];
}
......
......@@ -349,6 +349,8 @@ class State : angle::NonCopyable
GLintptr offset,
GLsizeiptr size);
size_t getAtomicCounterBufferCount() const { return mAtomicCounterBuffers.size(); }
const OffsetBindingPointer<Buffer> &getIndexedUniformBuffer(size_t index) const;
const OffsetBindingPointer<Buffer> &getIndexedAtomicCounterBuffer(size_t index) const;
const OffsetBindingPointer<Buffer> &getIndexedShaderStorageBuffer(size_t index) const;
......
......@@ -300,7 +300,8 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO
RenderPassCache &getRenderPassCache() { return mRenderPassCache; }
vk::DescriptorSetLayoutDesc getDriverUniformsDescriptorSetDesc() const;
vk::DescriptorSetLayoutDesc getDriverUniformsDescriptorSetDesc(
VkShaderStageFlags shaderStages) const;
// We use texture serials to optimize texture binding updates. Each permutation of a
// {VkImage/VkSampler} generates a unique serial. These serials are combined to form a unique
......@@ -335,6 +336,15 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO
std::array<DirtyBitHandler, DIRTY_BIT_MAX> mGraphicsDirtyBitHandlers;
std::array<DirtyBitHandler, DIRTY_BIT_MAX> mComputeDirtyBitHandlers;
enum class PipelineType
{
Graphics = 0,
Compute = 1,
InvalidEnum = 2,
EnumCount = 2,
};
angle::Result setupDraw(const gl::Context *context,
gl::PrimitiveMode mode,
GLint firstVertex,
......@@ -386,6 +396,7 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO
void invalidateCurrentTextures();
void invalidateCurrentShaderResources();
void invalidateGraphicsDriverUniforms();
void invalidateDriverUniforms();
// Handlers for graphics pipeline dirty bits.
......@@ -413,6 +424,8 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO
vk::CommandBuffer *commandBuffer);
angle::Result handleDirtyComputeTextures(const gl::Context *context,
vk::CommandBuffer *commandBuffer);
angle::Result handleDirtyComputeDriverUniforms(const gl::Context *context,
vk::CommandBuffer *commandBuffer);
angle::Result handleDirtyComputeShaderResources(const gl::Context *context,
vk::CommandBuffer *commandBuffer);
angle::Result handleDirtyComputeDescriptorSets(const gl::Context *context,
......@@ -425,6 +438,21 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO
angle::Result handleDirtyShaderResourcesImpl(const gl::Context *context,
vk::CommandBuffer *commandBuffer,
vk::CommandGraphResource *recorder);
struct DriverUniformsDescriptorSet;
angle::Result handleDirtyDescriptorSetsImpl(vk::CommandBuffer *commandBuffer,
VkPipelineBindPoint bindPoint,
const DriverUniformsDescriptorSet &driverUniforms);
angle::Result allocateDriverUniforms(size_t driverUniformsSize,
DriverUniformsDescriptorSet *driverUniforms,
VkBuffer *bufferOut,
uint8_t **ptrOut,
bool *newBufferOut);
angle::Result updateDriverUniformsDescriptorSet(VkBuffer buffer,
bool newBuffer,
size_t driverUniformsSize,
DriverUniformsDescriptorSet *driverUniforms);
void writeAtomicCounterBufferDriverUniformOffsets(uint32_t *offsetsOut, size_t offsetsSize);
angle::Result submitFrame(const VkSubmitInfo &submitInfo,
vk::PrimaryCommandBuffer &&commandBuffer);
......@@ -503,27 +531,22 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO
// at the end of the command buffer to make that write available to the host.
bool mIsAnyHostVisibleBufferWritten;
// For shader uniforms such as gl_DepthRange and the viewport size.
struct DriverUniforms
struct DriverUniformsDescriptorSet
{
std::array<float, 4> viewport;
float halfRenderAreaHeight;
float viewportYScale;
float negViewportYScale;
uint32_t xfbActiveUnpaused;
vk::DynamicBuffer dynamicBuffer;
VkDescriptorSet descriptorSet;
uint32_t dynamicOffset;
vk::BindingPointer<vk::DescriptorSetLayout> descriptorSetLayout;
vk::RefCountedDescriptorPoolBinding descriptorPoolBinding;
std::array<int32_t, 4> xfbBufferOffsets;
DriverUniformsDescriptorSet();
~DriverUniformsDescriptorSet();
// We'll use x, y, z for near / far / diff respectively.
std::array<float, 4> depthRange;
void init(RendererVk *rendererVk);
void destroy(VkDevice device);
};
vk::DynamicBuffer mDriverUniformsBuffer;
VkDescriptorSet mDriverUniformsDescriptorSet;
uint32_t mDriverUniformsDynamicOffset;
vk::BindingPointer<vk::DescriptorSetLayout> mDriverUniformsSetLayout;
vk::RefCountedDescriptorPoolBinding mDriverUniformsDescriptorPoolBinding;
angle::PackedEnumMap<PipelineType, DriverUniformsDescriptorSet> mDriverUniforms;
// This cache should also probably include the texture index (shader location) and array
// index (also in the shader). This info is used in the descriptor update step.
......
......@@ -668,12 +668,6 @@ void AssignUniformBindings(gl::ShaderMap<IntermediateShaderSource> *shaderSource
}
}
if (!(*shaderSources)[gl::ShaderType::Compute].empty())
{
// Compute doesn't need driver uniforms.
return;
}
// Substitute layout and qualifier strings for the driver uniforms block.
const std::string driverBlockLayoutString =
"set = " + Str(kDriverUniformsDescriptorSetIndex) + ", binding = 0";
......
......@@ -229,6 +229,7 @@ void WriteBufferDescriptorSetBinding(const gl::OffsetBindingPointer<gl::Buffer>
VkDescriptorType descType,
uint32_t bindingIndex,
uint32_t arrayElement,
VkDeviceSize requiredOffsetAlignment,
VkDescriptorBufferInfo *bufferInfoOut,
VkWriteDescriptorSet *writeInfoOut)
{
......@@ -241,7 +242,7 @@ void WriteBufferDescriptorSetBinding(const gl::OffsetBindingPointer<gl::Buffer>
ASSERT(bufferBinding.getSize() >= 0);
BufferVk *bufferVk = vk::GetImpl(buffer);
GLintptr offset = bufferBinding.getOffset();
VkDeviceSize offset = bufferBinding.getOffset();
VkDeviceSize size = bufferBinding.getSize();
vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
......@@ -255,6 +256,19 @@ void WriteBufferDescriptorSetBinding(const gl::OffsetBindingPointer<gl::Buffer>
size = std::min(size, maxSize);
}
// If requiredOffsetAlignment is 0, the buffer offset is guaranteed to have the necessary
// alignment through other means (the backend specifying the alignment through a GLES limit that
// the frontend then enforces). If it's not 0, we need to bind the buffer at an offset that's
// aligned. The difference in offsets is communicated to the shader via driver uniforms.
if (requiredOffsetAlignment)
{
VkDeviceSize alignedOffset = (offset / requiredOffsetAlignment) * requiredOffsetAlignment;
VkDeviceSize offsetDiff = offset - alignedOffset;
offset = alignedOffset;
size += offsetDiff;
}
bufferInfoOut->buffer = bufferHelper.getBuffer().getHandle();
bufferInfoOut->offset = offset;
bufferInfoOut->range = size;
......@@ -565,8 +579,10 @@ angle::Result ProgramVk::linkImpl(const gl::Context *glContext, gl::InfoLog &inf
ANGLE_TRY(renderer->getDescriptorSetLayout(contextVk, texturesSetDesc,
&mDescriptorSetLayouts[kTextureDescriptorSetIndex]));
VkShaderStageFlags driverUniformsStages =
mState.isCompute() ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_ALL_GRAPHICS;
vk::DescriptorSetLayoutDesc driverUniformsSetDesc =
contextVk->getDriverUniformsDescriptorSetDesc();
contextVk->getDriverUniformsDescriptorSetDesc(driverUniformsStages);
ANGLE_TRY(renderer->getDescriptorSetLayout(
contextVk, driverUniformsSetDesc,
&mDescriptorSetLayouts[kDriverUniformsDescriptorSetIndex]));
......@@ -1238,7 +1254,7 @@ void ProgramVk::updateBuffersDescriptorSet(ContextVk *contextVk,
VkWriteDescriptorSet &writeInfo = writeDescriptorInfo[writeCount];
WriteBufferDescriptorSetBinding(bufferBinding, maxBlockSize, descriptorSet, descriptorType,
binding, arrayElement, &bufferInfo, &writeInfo);
binding, arrayElement, 0, &bufferInfo, &writeInfo);
BufferVk *bufferVk = vk::GetImpl(bufferBinding.get());
vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
......@@ -1281,6 +1297,10 @@ void ProgramVk::updateAtomicCounterBuffersDescriptorSet(ContextVk *contextVk,
gl::AtomicCounterBuffersArray<VkWriteDescriptorSet> writeDescriptorInfo;
gl::AtomicCounterBufferMask writtenBindings;
RendererVk *rendererVk = contextVk->getRenderer();
const VkDeviceSize requiredOffsetAlignment =
rendererVk->getPhysicalDeviceProperties().limits.minStorageBufferOffsetAlignment;
// Write atomic counter buffers.
for (uint32_t bufferIndex = 0; bufferIndex < atomicCounterBuffers.size(); ++bufferIndex)
{
......@@ -1299,7 +1319,7 @@ void ProgramVk::updateAtomicCounterBuffersDescriptorSet(ContextVk *contextVk,
WriteBufferDescriptorSetBinding(bufferBinding, 0, descriptorSet,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, bindingStart, binding,
&bufferInfo, &writeInfo);
requiredOffsetAlignment, &bufferInfo, &writeInfo);
BufferVk *bufferVk = vk::GetImpl(bufferBinding.get());
vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
......
......@@ -105,9 +105,6 @@
// Blend equations:
3586 VULKAN : KHR-GLES31.core.blend_equation_advanced.* = SKIP
// Atomic Counter buffers:
3566 VULKAN : KHR-GLES31.core.shader_atomic_counters.advanced-usage-multi-stage = FAIL
// Storage image:
3563 VULKAN : KHR-GLES31.core.layout_binding.sampler2D_layout_binding_texture_ComputeShader = FAIL
3563 VULKAN : KHR-GLES31.core.layout_binding.block_layout_binding_block_ComputeShader = FAIL
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment