Commit 65d10f3b by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Implement robust resource initialization

If a texture or renderbuffer needs to be cleared for robust access or due to having emulated channels, it is immediately cleared. The former relies on a front-end feature that optimizes robust access clears only to levels and layers that are not fully initialized through data upload. Bug: angleproject:2722 Change-Id: Icdab856eb4ffe963f78569b6d80d9ff5cb27ff9b Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1535056 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent 8413faba
......@@ -172,9 +172,6 @@ void DisplayVk::generateExtensions(egl::DisplayExtensions *outExtensions) const
outExtensions->createContextRobustness = true;
outExtensions->surfaceOrientation = true;
outExtensions->displayTextureShareGroup = true;
// TODO(geofflang): Extension is exposed but not implemented so that other aspects of the Vulkan
// backend can be tested in Chrome. http://anglebug.com/2722
outExtensions->robustResourceInitialization = true;
// The Vulkan implementation will always say that EGL_KHR_swap_buffers_with_damage is supported.
......
......@@ -182,8 +182,11 @@ angle::Result RenderTargetVk::ensureImageInitialized(ContextVk *contextVk)
{
if (mOwner)
{
// If the render target source is a texture, make sure the image is initialized and its
// staged updates flushed.
return mOwner->ensureImageInitialized(contextVk);
}
return angle::Result::Continue;
}
......
......@@ -17,14 +17,6 @@
namespace rx
{
namespace
{
constexpr VkClearDepthStencilValue kDefaultClearDepthStencilValue = {0.0f, 1};
constexpr VkClearColorValue kBlackClearColorValue = {{0}};
} // anonymous namespace
RenderbufferVk::RenderbufferVk(const gl::RenderbufferState &state)
: RenderbufferImpl(state), mOwnsImage(false), mImage(nullptr)
{}
......@@ -92,18 +84,12 @@ angle::Result RenderbufferVk::setStorage(const gl::Context *context,
ANGLE_TRY(mImage->initImageView(contextVk, gl::TextureType::_2D, aspect, gl::SwizzleState(),
&mImageView, 0, 1));
// TODO(jmadill): Fold this into the RenderPass load/store ops. http://anglebug.com/2361
vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(mImage->recordCommands(contextVk, &commandBuffer));
if (isDepthOrStencilFormat)
// Clear the renderbuffer if it has emulated channels.
if (vkFormat.hasEmulatedChannels())
{
mImage->clearDepthStencil(aspect, aspect, kDefaultClearDepthStencilValue,
commandBuffer);
}
else
{
mImage->clearColor(kBlackClearColorValue, 0, 1, commandBuffer);
mImage->stageSubresourceEmulatedClear(gl::ImageIndex::Make2D(0),
vkFormat.angleFormat());
ANGLE_TRY(mImage->flushAllStagedUpdates(vk::GetImpl(context)));
}
mRenderTarget.init(mImage, &mImageView, 0, 0, nullptr);
......@@ -172,8 +158,8 @@ angle::Result RenderbufferVk::getAttachmentRenderTarget(const gl::Context *conte
angle::Result RenderbufferVk::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
UNIMPLEMENTED();
return angle::Result::Continue;
mImage->stageSubresourceRobustClear(imageIndex, mImage->getFormat().angleFormat());
return mImage->flushAllStagedUpdates(vk::GetImpl(context));
}
void RenderbufferVk::releaseOwnershipOfImage(const gl::Context *context)
......
......@@ -256,7 +256,21 @@ angle::Result OffscreenSurfaceVk::getAttachmentRenderTarget(
angle::Result OffscreenSurfaceVk::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
UNIMPLEMENTED();
ContextVk *contextVk = vk::GetImpl(context);
if (mColorAttachment.image.valid())
{
mColorAttachment.image.stageSubresourceRobustClear(
imageIndex, mColorAttachment.image.getFormat().angleFormat());
ANGLE_TRY(mColorAttachment.image.flushAllStagedUpdates(contextVk));
}
if (mDepthStencilAttachment.image.valid())
{
mDepthStencilAttachment.image.stageSubresourceRobustClear(
imageIndex, mDepthStencilAttachment.image.getFormat().angleFormat());
ANGLE_TRY(mDepthStencilAttachment.image.flushAllStagedUpdates(contextVk));
}
return angle::Result::Continue;
}
......@@ -544,12 +558,6 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
ANGLE_VK_TRY(displayVk,
vkGetSwapchainImagesKHR(device, mSwapchain, &imageCount, swapchainImages.data()));
VkClearColorValue transparentBlack = {};
transparentBlack.float32[0] = 0.0f;
transparentBlack.float32[1] = 0.0f;
transparentBlack.float32[2] = 0.0f;
transparentBlack.float32[3] = 0.0f;
mSwapchainImages.resize(imageCount);
ANGLE_TRY(resizeSwapHistory(displayVk, imageCount));
......@@ -561,13 +569,6 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
ANGLE_TRY(member.image.initImageView(displayVk, gl::TextureType::_2D,
VK_IMAGE_ASPECT_COLOR_BIT, gl::SwizzleState(),
&member.imageView, 0, 1));
// Allocate a command buffer for clearing our images to black.
vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(member.image.recordCommands(displayVk, &commandBuffer));
// Set transfer dest layout, and clear the image to black.
member.image.clearColor(transparentBlack, 0, 1, commandBuffer);
}
// Initialize depth/stencil if requested.
......@@ -583,13 +584,6 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
const VkImageAspectFlags aspect = vk::GetDepthStencilAspectFlags(dsFormat.textureFormat());
VkClearDepthStencilValue depthStencilClearValue = {1.0f, 0};
// Clear the image.
vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(mDepthStencilImage.recordCommands(displayVk, &commandBuffer));
mDepthStencilImage.clearDepthStencil(aspect, aspect, depthStencilClearValue, commandBuffer);
ANGLE_TRY(mDepthStencilImage.initImageView(displayVk, gl::TextureType::_2D, aspect,
gl::SwizzleState(), &mDepthStencilImageView, 0,
1));
......@@ -1093,7 +1087,22 @@ angle::Result WindowSurfaceVk::generateSemaphoresForFlush(vk::Context *context,
angle::Result WindowSurfaceVk::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
UNIMPLEMENTED();
ContextVk *contextVk = vk::GetImpl(context);
ASSERT(mSwapchainImages.size() > 0);
ASSERT(mCurrentSwapchainImageIndex < mSwapchainImages.size());
vk::ImageHelper *image = &mSwapchainImages[mCurrentSwapchainImageIndex].image;
image->stageSubresourceRobustClear(imageIndex, image->getFormat().angleFormat());
ANGLE_TRY(image->flushAllStagedUpdates(contextVk));
if (mDepthStencilImage.valid())
{
mDepthStencilImage.stageSubresourceRobustClear(
gl::ImageIndex::Make2D(0), mDepthStencilImage.getFormat().angleFormat());
ANGLE_TRY(mDepthStencilImage.flushAllStagedUpdates(contextVk));
}
return angle::Result::Continue;
}
......
......@@ -382,17 +382,28 @@ angle::Result TextureVk::copySubImageImpl(const gl::Context *context,
return angle::Result::Continue;
}
gl::Rectangle destArea(destOffset.x, destOffset.y, clippedSourceArea.width,
clippedSourceArea.height);
ContextVk *contextVk = vk::GetImpl(context);
RendererVk *renderer = contextVk->getRenderer();
FramebufferVk *framebufferVk = vk::GetImpl(source);
const gl::ImageIndex offsetImageIndex = getNativeImageIndex(index);
const gl::Offset modifiedDestOffset(destOffset.x, destOffset.y, 0);
const vk::Format &srcFormat = framebufferVk->getColorReadRenderTarget()->getImageFormat();
// If negative offsets are given, clippedSourceArea ensures we don't read from those offsets.
// However, that changes the sourceOffset->destOffset mapping. Here, destOffset is shifted by
// the same amount as clipped to correct the error.
//
// TODO(syoussefi): a bug here is that we need to clip the extents to make sure the copy
// region does not overflow the image size. For example, if an FBO of size 16x16 is used as
// source and glCopyTexImage2D(..., -8, -8, 16, 16, ...) is used, then we will be copying to
// the region expanding from (8, 8) through (23, 23), while the image is only 16x16.
// http://anglebug.com/3355
const gl::Offset modifiedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x,
destOffset.y + clippedSourceArea.y - sourceArea.y, 0);
RenderTargetVk *colorReadRT = framebufferVk->getColorReadRenderTarget();
ANGLE_TRY(colorReadRT->ensureImageInitialized(contextVk));
const vk::Format &srcFormat = colorReadRT->getImageFormat();
const vk::Format &destFormat = renderer->getFormat(internalFormat.sizedInternalFormat);
bool isViewportFlipY = contextVk->isViewportFlipEnabledForDrawFBO();
......@@ -400,8 +411,6 @@ angle::Result TextureVk::copySubImageImpl(const gl::Context *context,
// If it's possible to perform the copy with a transfer, that's the best option.
if (!isViewportFlipY && CanCopyWithTransfer(renderer, srcFormat, destFormat))
{
RenderTargetVk *colorReadRT = framebufferVk->getColorReadRenderTarget();
return copySubImageImplWithTransfer(contextVk, offsetImageIndex, modifiedDestOffset,
destFormat, 0, clippedSourceArea,
&colorReadRT->getImage());
......@@ -412,8 +421,6 @@ angle::Result TextureVk::copySubImageImpl(const gl::Context *context,
// If it's possible to perform the copy with a draw call, do that.
if (CanCopyWithDraw(renderer, srcFormat, destFormat) && !forceCpuPath)
{
RenderTargetVk *colorReadRT = framebufferVk->getColorReadRenderTarget();
// Layer count can only be 1 as the source is a framebuffer.
ASSERT(offsetImageIndex.getLayerCount() == 1);
......@@ -1016,7 +1023,8 @@ angle::Result TextureVk::generateMipmapsWithCPU(const gl::Context *context)
vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(mImage->recordCommands(contextVk, &commandBuffer));
return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), getLevelCount(),
return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), mImage->getLevelCount(),
getNativeImageLayer(0), mImage->getLayerCount(),
commandBuffer);
}
......@@ -1027,7 +1035,7 @@ angle::Result TextureVk::generateMipmap(const gl::Context *context)
// Some data is pending, or the image has not been defined at all yet
if (!mImage->valid())
{
// lets initialize the image so we can generate the next levels.
// Let's initialize the image so we can generate the next levels.
if (mImage->hasStagedUpdates())
{
ANGLE_TRY(ensureImageInitialized(contextVk));
......@@ -1142,6 +1150,7 @@ angle::Result TextureVk::ensureImageInitializedImpl(ContextVk *contextVk,
{
return angle::Result::Continue;
}
vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(mImage->recordCommands(contextVk, &commandBuffer));
......@@ -1150,7 +1159,9 @@ angle::Result TextureVk::ensureImageInitializedImpl(ContextVk *contextVk,
ANGLE_TRY(initImage(contextVk, format, baseLevelExtents, levelCount, commandBuffer));
}
return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), levelCount, commandBuffer);
return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), mImage->getLevelCount(),
getNativeImageLayer(0), mImage->getLayerCount(),
commandBuffer);
}
angle::Result TextureVk::initCubeMapRenderTargets(ContextVk *contextVk)
......@@ -1229,7 +1240,12 @@ angle::Result TextureVk::setStorageMultisample(const gl::Context *context,
angle::Result TextureVk::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
UNIMPLEMENTED();
const gl::ImageDesc &desc = mState.getImageDesc(imageIndex);
const vk::Format &format =
vk::GetImpl(context)->getRenderer()->getFormat(desc.format.info->sizedInternalFormat);
mImage->stageSubresourceRobustClear(imageIndex, format.angleFormat());
return angle::Result::Continue;
}
......@@ -1302,14 +1318,14 @@ angle::Result TextureVk::initImage(ContextVk *contextVk,
const uint32_t levelCount,
vk::CommandBuffer *commandBuffer)
{
const RendererVk *renderer = contextVk->getRenderer();
const angle::Format &angleFormat = format.textureFormat();
const RendererVk *renderer = contextVk->getRenderer();
const angle::Format &textureFormat = format.textureFormat();
VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT;
if (!angleFormat.isBlock)
if (!textureFormat.isBlock)
{
imageUsageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
......@@ -1324,13 +1340,20 @@ angle::Result TextureVk::initImage(ContextVk *contextVk,
ANGLE_TRY(initImageViews(contextVk, format, levelCount));
if (!angleFormat.isBlock)
// If the image has an emulated channel, always clear it. These channels will be masked out in
// future writes, and shouldn't contain uninitialized values.
if (format.hasEmulatedChannels())
{
// TODO(jmadill): Fold this into the RenderPass load/store ops if possible, or defer to
// first use. This is only necessary if robustness is required. http://anglebug.com/2361
VkClearColorValue black = {{0, 0, 0, 1.0f}};
mImage->clearColor(black, 0, levelCount, commandBuffer);
uint32_t levelCount = mImage->getLevelCount();
uint32_t layerCount = mImage->getLayerCount();
for (uint32_t level = 0; level < levelCount; ++level)
{
gl::ImageIndex index = gl::ImageIndex::Make2DArrayRange(level, 0, layerCount);
mImage->stageSubresourceEmulatedClear(index, format.angleFormat());
}
}
return angle::Result::Continue;
}
......
......@@ -178,6 +178,18 @@ size_t Format::getImageCopyBufferAlignment() const
return alignment;
}
bool Format::hasEmulatedChannels() const
{
const angle::Format &angleFmt = angleFormat();
const angle::Format &textureFmt = textureFormat();
return (angleFmt.alphaBits == 0 && textureFmt.alphaBits > 0) ||
(angleFmt.blueBits == 0 && textureFmt.blueBits > 0) ||
(angleFmt.greenBits == 0 && textureFmt.greenBits > 0) ||
(angleFmt.depthBits == 0 && textureFmt.depthBits > 0) ||
(angleFmt.stencilBits == 0 && textureFmt.stencilBits > 0);
}
bool operator==(const Format &lhs, const Format &rhs)
{
return &lhs == &rhs;
......
......@@ -73,6 +73,8 @@ struct Format final : private angle::NonCopyable
}
size_t getImageCopyBufferAlignment() const;
bool hasEmulatedChannels() const;
angle::FormatID angleFormatID;
GLenum internalFormat;
angle::FormatID textureFormatID;
......
......@@ -599,14 +599,14 @@ class ImageHelper final : public CommandGraphResource
GLint samples);
void resetImageWeakReference();
const Image &getImage() const;
const DeviceMemory &getDeviceMemory() const;
const Image &getImage() const { return mImage; }
const DeviceMemory &getDeviceMemory() const { return mDeviceMemory; }
const gl::Extents &getExtents() const;
const gl::Extents &getExtents() const { return mExtents; }
uint32_t getLayerCount() const { return mLayerCount; }
uint32_t getLevelCount() const { return mLevelCount; }
const Format &getFormat() const;
GLint getSamples() const;
const Format &getFormat() const { return *mFormat; }
GLint getSamples() const { return mSamples; }
VkImageLayout getCurrentLayout() const;
......@@ -614,22 +614,13 @@ class ImageHelper final : public CommandGraphResource
// image.
gl::Extents getLevelExtents2D(uint32_t level) const;
void clearColor(const VkClearColorValue &color,
uint32_t baseMipLevel,
uint32_t levelCount,
vk::CommandBuffer *commandBuffer);
void clearColorLayer(const VkClearColorValue &color,
uint32_t baseMipLevel,
uint32_t levelCount,
uint32_t baseArrayLayer,
uint32_t layerCount,
vk::CommandBuffer *commandBuffer);
// Clear either color or depth/stencil based on image format.
void clear(const VkClearValue &value,
uint32_t mipLevel,
uint32_t baseArrayLayer,
uint32_t layerCount,
vk::CommandBuffer *commandBuffer);
void clearDepthStencil(VkImageAspectFlags imageAspectFlags,
VkImageAspectFlags clearAspectFlags,
const VkClearDepthStencilValue &depthStencil,
vk::CommandBuffer *commandBuffer);
gl::Extents getSize(const gl::ImageIndex &index) const;
static void Copy(ImageHelper *srcImage,
......@@ -676,6 +667,13 @@ class ImageHelper final : public CommandGraphResource
const gl::Offset &destOffset,
const gl::Extents &extents);
// Stage a clear operation to a clear value based on WebGL requirements.
void stageSubresourceRobustClear(const gl::ImageIndex &index, const angle::Format &format);
// Stage a clear operation to a clear value that initializes emulated channels to the desired
// values.
void stageSubresourceEmulatedClear(const gl::ImageIndex &index, const angle::Format &format);
// This will use the underlying dynamic buffer to allocate some memory to be used as a src or
// dst.
angle::Result allocateStagingMemory(ContextVk *contextVk,
......@@ -685,12 +683,21 @@ class ImageHelper final : public CommandGraphResource
VkDeviceSize *offsetOut,
bool *newBufferAllocatedOut);
// Flushes staged updates to a range of levels and layers from start to (but not including) end.
// Due to the nature of updates (done wholly to a VkImageSubresourceLayers), some unsolicited
// layers may also be updated.
angle::Result flushStagedUpdates(Context *context,
uint32_t baseLevel,
uint32_t levelCount,
uint32_t levelStart,
uint32_t levelEnd,
uint32_t layerStart,
uint32_t layerEnd,
vk::CommandBuffer *commandBuffer);
// Creates a command buffer and flushes all staged updates. This is used for one-time
// initialization of resources that we don't expect to accumulate further staged updates, such
// as with renderbuffers or surface images.
angle::Result flushAllStagedUpdates(Context *context);
bool hasStagedUpdates() const;
bool hasStagedUpdates() const { return !mSubresourceUpdates.empty(); }
// changeLayout automatically skips the layout change if it's unnecessary. This function can be
// used to prevent creating a command graph node and subsequently a command buffer for the sole
......@@ -717,17 +724,36 @@ class ImageHelper final : public CommandGraphResource
uint32_t newQueueFamilyIndex,
vk::CommandBuffer *commandBuffer);
void stageSubresourceClear(const gl::ImageIndex &index,
const angle::Format &format,
const VkClearColorValue &colorValue,
const VkClearDepthStencilValue &depthStencilValue);
void clearColor(const VkClearColorValue &color,
uint32_t baseMipLevel,
uint32_t levelCount,
uint32_t baseArrayLayer,
uint32_t layerCount,
vk::CommandBuffer *commandBuffer);
void clearDepthStencil(VkImageAspectFlags imageAspectFlags,
VkImageAspectFlags clearAspectFlags,
const VkClearDepthStencilValue &depthStencil,
vk::CommandBuffer *commandBuffer);
struct SubresourceUpdate
{
SubresourceUpdate();
SubresourceUpdate(VkBuffer bufferHandle, const VkBufferImageCopy &copyRegion);
SubresourceUpdate(vk::ImageHelper *image, const VkImageCopy &copyRegion);
SubresourceUpdate(const VkClearValue &clearValue, const gl::ImageIndex &imageIndex);
SubresourceUpdate(const SubresourceUpdate &other);
void release(RendererVk *renderer);
const VkImageSubresourceLayers &dstSubresource() const
{
ASSERT(updateSource == UpdateSource::Buffer || updateSource == UpdateSource::Image);
return updateSource == UpdateSource::Buffer ? buffer.copyRegion.imageSubresource
: image.copyRegion.dstSubresource;
}
......@@ -735,9 +761,17 @@ class ImageHelper final : public CommandGraphResource
enum class UpdateSource
{
Clear,
Buffer,
Image,
};
struct ClearUpdate
{
VkClearValue value;
uint32_t levelIndex;
uint32_t layerIndex;
uint32_t layerCount;
};
struct BufferUpdate
{
VkBuffer bufferHandle;
......@@ -752,6 +786,7 @@ class ImageHelper final : public CommandGraphResource
UpdateSource updateSource;
union
{
ClearUpdate clear;
BufferUpdate buffer;
ImageUpdate image;
};
......
......@@ -262,8 +262,11 @@ VkImageAspectFlags GetDepthStencilAspectFlags(const angle::Format &format)
VkImageAspectFlags GetFormatAspectFlags(const angle::Format &format)
{
return (format.redBits > 0 ? VK_IMAGE_ASPECT_COLOR_BIT : 0) |
GetDepthStencilAspectFlags(format);
VkImageAspectFlags dsAspect = GetDepthStencilAspectFlags(format);
// If the image is not depth stencil, assume color aspect. Note that detecting color formats
// is less trivial than depth/stencil, e.g. as block formats don't indicate any bits for RGBA
// channels.
return dsAspect != 0 ? dsAspect : VK_IMAGE_ASPECT_COLOR_BIT;
}
VkImageAspectFlags GetDepthStencilAspectFlagsForCopy(bool copyDepth, bool copyStencil)
......
......@@ -307,7 +307,8 @@ class RobustResourceInitTestES31 : public RobustResourceInitTest
// it only works on the implemented renderers
TEST_P(RobustResourceInitTest, ExpectedRendererSupport)
{
bool shouldHaveSupport = IsD3D11() || IsD3D11_FL93() || IsD3D9() || IsOpenGL() || IsOpenGLES();
bool shouldHaveSupport =
IsD3D11() || IsD3D11_FL93() || IsD3D9() || IsOpenGL() || IsOpenGLES() || IsVulkan();
EXPECT_EQ(shouldHaveSupport, hasGLExtension());
EXPECT_EQ(shouldHaveSupport, hasEGLExtension());
EXPECT_EQ(shouldHaveSupport, hasRobustSurfaceInit());
......@@ -775,7 +776,7 @@ TEST_P(RobustResourceInitTest, UninitializedPartsOfCopied2DTexturesAreBlack)
// succeed with all bytes set to 0. Regression test for a bug where the zeroing out of the
// texture was done via the same code path as glTexImage2D, causing the PIXEL_UNPACK_BUFFER
// to be used.
TEST_P(RobustResourceInitTestES3, ReadingOutOfboundsCopiedTextureWithUnpackBuffer)
TEST_P(RobustResourceInitTestES3, ReadingOutOfBoundsCopiedTextureWithUnpackBuffer)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// TODO(geofflang@chromium.org): CopyTexImage from GL_RGBA4444 to GL_ALPHA fails when looking
......@@ -834,7 +835,7 @@ TEST_P(RobustResourceInitTestES3, ReadingOutOfboundsCopiedTextureWithUnpackBuffe
// Reading an uninitialized portion of a texture (copyTexImage2D with negative x and y) should
// succeed with all bytes set to 0.
TEST_P(RobustResourceInitTest, ReadingOutOfboundsCopiedTexture)
TEST_P(RobustResourceInitTest, ReadingOutOfBoundsCopiedTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
......@@ -1827,6 +1828,9 @@ TEST_P(RobustResourceInitTestES31, Multisample2DTextureArray)
// Tests that using an out of bounds draw offset with a dynamic array succeeds.
TEST_P(RobustResourceInitTest, DynamicVertexArrayOffsetOutOfBounds)
{
// Not implemented on Vulkan. http://anglebug.com/3350
ANGLE_SKIP_TEST_IF(IsVulkan());
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program);
......@@ -1851,7 +1855,8 @@ ANGLE_INSTANTIATE_TEST(RobustResourceInitTest,
ES2_OPENGL(),
ES3_OPENGL(),
ES2_OPENGLES(),
ES3_OPENGLES());
ES3_OPENGLES(),
ES2_VULKAN());
ANGLE_INSTANTIATE_TEST(RobustResourceInitTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
......
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