Commit 2a0c3596 by Jamie Madill Committed by Commit Bot

Vulkan: Clean up robust and emulated image clears.

This change consolidates image clears in multiple places into a single site in ImageHelper initialization. It adds support for appending clear image commands as well as prepend (the default). We prepend clears because image initialization happens after data upload. The Vulkan robust clear path now works like the other back-ends. The change flushed out a bug where partially uninitialized CopyTexImage was not correctly initializing a texture before triggering a full resource clear. Texture::copyImage now uses a workaround where we first init the image before clearing it. After the init we upload the new data. We'll use the appending clears path when implementing deferred clears. Bug: angleproject:4517 Change-Id: If9212f3b8cdd0fc8b7e729d364530801a644e164 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2130627 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCody Northrop <cnorthrop@google.com>
parent 78a471ba
......@@ -524,7 +524,7 @@ GLuint TextureState::getEnabledLevelCount() const
}
ImageDesc::ImageDesc()
: ImageDesc(Extents(0, 0, 0), Format::Invalid(), 0, GL_TRUE, InitState::Initialized)
: ImageDesc(Extents(0, 0, 0), Format::Invalid(), 0, GL_TRUE, InitState::MayNeedInit)
{}
ImageDesc::ImageDesc(const Extents &size, const Format &format, const InitState initState)
......@@ -1161,23 +1161,54 @@ angle::Result Texture::copyImage(Context *context,
ImageIndex index = ImageIndex::MakeFromTarget(target, level, 1);
// Use the source FBO size as the init image area.
Box destBox(0, 0, 0, sourceArea.width, sourceArea.height, 1);
ANGLE_TRY(ensureSubImageInitialized(context, index, destBox));
ANGLE_TRY(mTexture->copyImage(context, index, sourceArea, internalFormat, source));
const InternalFormat &internalFormatInfo =
GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);
// Most if not all renderers clip these copies to the size of the source framebuffer, leaving
// other pixels untouched. For safety in robust resource initialization, assume that that
// clipping is going to occur when computing the region for which to ensure initialization. If
// the copy lies entirely off the source framebuffer, initialize as though a zero-size box is
// going to be set during the copy operation.
Box destBox;
if (context->isRobustResourceInitEnabled())
{
Extents fbSize = source->getReadColorAttachment()->getSize();
Rectangle clippedArea;
if (ClipRectangle(sourceArea, Rectangle(0, 0, fbSize.width, fbSize.height), &clippedArea))
{
const Offset clippedOffset(clippedArea.x - sourceArea.x, clippedArea.y - sourceArea.y,
0);
destBox = Box(clippedOffset.x, clippedOffset.y, clippedOffset.z, clippedArea.width,
clippedArea.height, 1);
}
}
// If we need to initialize the destination texture we split the call into a create call,
// an initializeContents call, and then a copySubImage call. This ensures the destination
// texture exists before we try to clear it.
Extents size(sourceArea.width, sourceArea.height, 1);
if (doesSubImageNeedInit(context, index, destBox))
{
ANGLE_TRY(mTexture->setImage(context, index, internalFormat, size,
internalFormatInfo.format, internalFormatInfo.type,
PixelUnpackState(), nullptr, nullptr));
mState.setImageDesc(target, level,
ImageDesc(size, Format(internalFormatInfo), InitState::MayNeedInit));
ANGLE_TRY(ensureSubImageInitialized(context, index, destBox));
ANGLE_TRY(mTexture->copySubImage(context, index, Offset(), sourceArea, source));
}
else
{
ANGLE_TRY(mTexture->copyImage(context, index, sourceArea, internalFormat, source));
}
mState.setImageDesc(target, level,
ImageDesc(Extents(sourceArea.width, sourceArea.height, 1),
Format(internalFormatInfo), InitState::Initialized));
ImageDesc(size, Format(internalFormatInfo), InitState::Initialized));
ANGLE_TRY(handleMipmapGenerationHint(context, level));
// We need to initialize this texture only if the source attachment is not initialized.
signalDirtyStorage(InitState::Initialized);
// Because this could affect the texture storage we might need to init other layers/levels.
signalDirtyStorage(InitState::MayNeedInit);
return angle::Result::Continue;
}
......@@ -1795,7 +1826,7 @@ angle::Result Texture::ensureInitialized(const Context *context)
const ImageIndex index = it.next();
ImageDesc &desc =
mState.mImageDescs[GetImageDescIndex(index.getTarget(), index.getLevelIndex())];
if (desc.initState == InitState::MayNeedInit)
if (desc.initState == InitState::MayNeedInit && !desc.size.empty())
{
ASSERT(mState.mInitState == InitState::MayNeedInit);
ANGLE_TRY(initializeContents(context, index));
......@@ -1891,7 +1922,6 @@ angle::Result Texture::ensureSubImageInitialized(const Context *context,
angle::Result Texture::handleMipmapGenerationHint(Context *context, int level)
{
if (getGenerateMipmapHint() == GL_TRUE && level == 0)
{
ANGLE_TRY(generateMipmap(context));
......@@ -1912,8 +1942,18 @@ void Texture::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMess
signalDirtyStorage(InitState::Initialized);
break;
case angle::SubjectMessage::DirtyBitsFlagged:
signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
// Notify siblings that we are dirty.
if (index == rx::kTextureImageImplObserverMessageIndex)
{
notifySiblings(message);
}
break;
case angle::SubjectMessage::SubjectChanged:
mState.mInitState = InitState::MayNeedInit;
signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
onStateChange(angle::SubjectMessage::ContentsChanged);
// Notify siblings that we are dirty.
if (index == rx::kTextureImageImplObserverMessageIndex)
......
......@@ -4316,6 +4316,11 @@ void ContextVk::endOcclusionQuery(QueryVk *queryVk)
}
}
bool ContextVk::isRobustResourceInitEnabled() const
{
return mState.isRobustResourceInitEnabled();
}
CommandBufferHelper::CommandBufferHelper()
: mImageBarrierSrcStageMask(0),
mImageBarrierDstStageMask(0),
......
......@@ -662,7 +662,7 @@ class ContextVk : public ContextImpl, public vk::Context
const ProgramExecutableVk *getExecutable() const { return mExecutable; }
ProgramExecutableVk *getExecutable() { return mExecutable; }
ProgramVk *getShaderProgram(const gl::State &glState, gl::ShaderType shaderType) const;
bool isRobustResourceInitEnabled() const override;
// occlusion query
void beginOcclusionQuery(QueryVk *queryVk);
......
......@@ -23,7 +23,10 @@ namespace rx
{
DisplayVk::DisplayVk(const egl::DisplayState &state)
: DisplayImpl(state), vk::Context(new RendererVk()), mScratchBuffer(1000u)
: DisplayImpl(state),
vk::Context(new RendererVk()),
mScratchBuffer(1000u),
mHasSurfaceWithRobustInit(false)
{}
DisplayVk::~DisplayVk()
......@@ -105,12 +108,22 @@ SurfaceImpl *DisplayVk::createWindowSurface(const egl::SurfaceState &state,
EGLNativeWindowType window,
const egl::AttributeMap &attribs)
{
if (attribs.get(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, EGL_FALSE) == EGL_TRUE)
{
mHasSurfaceWithRobustInit = true;
}
return createWindowSurfaceVk(state, window);
}
SurfaceImpl *DisplayVk::createPbufferSurface(const egl::SurfaceState &state,
const egl::AttributeMap &attribs)
{
if (attribs.get(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, EGL_FALSE) == EGL_TRUE)
{
mHasSurfaceWithRobustInit = true;
}
ASSERT(mRenderer);
return new OffscreenSurfaceVk(state);
}
......@@ -259,4 +272,9 @@ void DisplayVk::populateFeatureList(angle::FeatureList *features)
mRenderer->getFeatures().populateFeatureList(features);
}
bool DisplayVk::isRobustResourceInitEnabled() const
{
// We return true if any surface was created with robust resource init enabled.
return mHasSurfaceWithRobustInit;
}
} // namespace rx
......@@ -95,6 +95,8 @@ class DisplayVk : public DisplayImpl, public vk::Context
void populateFeatureList(angle::FeatureList *features) override;
bool isRobustResourceInitEnabled() const override;
protected:
void generateExtensions(egl::DisplayExtensions *outExtensions) const override;
......@@ -108,6 +110,7 @@ class DisplayVk : public DisplayImpl, public vk::Context
mutable angle::ScratchBuffer mScratchBuffer;
std::string mStoredErrorString;
bool mHasSurfaceWithRobustInit;
};
} // namespace rx
......
......@@ -78,9 +78,6 @@ angle::Result RenderbufferVk::setStorageImpl(const gl::Context *context,
VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
ANGLE_TRY(mImage->initMemory(contextVk, renderer->getMemoryProperties(), flags));
// Clear the renderbuffer if it has emulated channels.
mImage->stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), vkFormat);
mRenderTarget.init(mImage, &mImageViews, 0, 0);
}
......@@ -160,7 +157,7 @@ angle::Result RenderbufferVk::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
// Note: stageSubresourceRobustClear only uses the intended format to count channels.
mImage->stageSubresourceRobustClear(imageIndex, mImage->getFormat());
mImage->stageSubresourceClear(imageIndex);
return mImage->flushAllStagedUpdates(vk::GetImpl(context));
}
......
......@@ -162,9 +162,6 @@ angle::Result OffscreenSurfaceVk::AttachmentImage::initialize(DisplayVk *display
VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
ANGLE_TRY(image.initMemory(displayVk, renderer->getMemoryProperties(), flags));
// Clear the image if it has emulated channels.
image.stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), vkFormat);
return angle::Result::Continue;
}
......@@ -205,9 +202,6 @@ angle::Result OffscreenSurfaceVk::AttachmentImage::initializeWithExternalMemory(
externalMemoryRequirements, &importMemoryHostPointerInfo,
VK_QUEUE_FAMILY_EXTERNAL, flags));
// Clear the image if it has emulated channels.
image.stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), vkFormat);
return angle::Result::Continue;
}
......@@ -357,15 +351,13 @@ angle::Result OffscreenSurfaceVk::initializeContents(const gl::Context *context,
if (mColorAttachment.image.valid())
{
mColorAttachment.image.stageSubresourceRobustClear(imageIndex,
mColorAttachment.image.getFormat());
mColorAttachment.image.stageSubresourceClear(imageIndex);
ANGLE_TRY(mColorAttachment.image.flushAllStagedUpdates(contextVk));
}
if (mDepthStencilAttachment.image.valid())
{
mDepthStencilAttachment.image.stageSubresourceRobustClear(
imageIndex, mDepthStencilAttachment.image.getFormat());
mDepthStencilAttachment.image.stageSubresourceClear(imageIndex);
ANGLE_TRY(mDepthStencilAttachment.image.flushAllStagedUpdates(contextVk));
}
return angle::Result::Continue;
......@@ -899,9 +891,6 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
// Initialize the color render target with the multisampled targets. If not multisampled,
// the render target will be updated to refer to a swapchain image on every acquire.
mColorRenderTarget.init(&mColorImageMS, &mColorImageMSViews, 0, 0);
// Clear the image if it has emulated channels.
mColorImageMS.stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), format);
}
ANGLE_TRY(resizeSwapchainImages(context, imageCount));
......@@ -909,15 +898,7 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
for (uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex)
{
SwapchainImage &member = mSwapchainImages[imageIndex];
member.image.init2DWeakReference(swapchainImages[imageIndex], extents, format, 1);
if (!mColorImageMS.valid())
{
// Clear the image if it has emulated channels. If a multisampled image exists, this
// image will be unused until a pre-present resolve, at which point it will be fully
// initialized and wouldn't need a clear.
member.image.stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), format);
}
member.image.init2DWeakReference(context, swapchainImages[imageIndex], extents, format, 1);
}
// Initialize depth/stencil if requested.
......@@ -935,9 +916,6 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageViews, 0, 0);
// We will need to pass depth/stencil image views to the RenderTargetVk in the future.
// Clear the image if it has emulated channels.
mDepthStencilImage.stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), dsFormat);
}
return angle::Result::Continue;
......@@ -1504,13 +1482,12 @@ angle::Result WindowSurfaceVk::initializeContents(const gl::Context *context,
vk::ImageHelper *image =
isMultiSampled() ? &mColorImageMS : &mSwapchainImages[mCurrentSwapchainImageIndex].image;
image->stageSubresourceRobustClear(imageIndex, image->getFormat());
image->stageSubresourceClear(imageIndex);
ANGLE_TRY(image->flushAllStagedUpdates(contextVk));
if (mDepthStencilImage.valid())
{
mDepthStencilImage.stageSubresourceRobustClear(gl::ImageIndex::Make2D(0),
mDepthStencilImage.getFormat());
mDepthStencilImage.stageSubresourceClear(gl::ImageIndex::Make2D(0));
ANGLE_TRY(mDepthStencilImage.flushAllStagedUpdates(contextVk));
}
......
......@@ -1549,11 +1549,13 @@ angle::Result TextureVk::syncState(const gl::Context *context,
angle::Result TextureVk::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
ContextVk *contextVk = vk::GetImpl(context);
const gl::ImageDesc &desc = mState.getImageDesc(imageIndex);
const vk::Format &format =
vk::GetImpl(context)->getRenderer()->getFormat(desc.format.info->sizedInternalFormat);
contextVk->getRenderer()->getFormat(desc.format.info->sizedInternalFormat);
mImage->stageSubresourceRobustClear(imageIndex, format);
ASSERT(mImage);
mImage->stageRobustResourceClear(imageIndex, format);
// Note that we cannot ensure the image is initialized because we might be calling subImage
// on a non-complete cube map.
......@@ -1648,20 +1650,6 @@ angle::Result TextureVk::initImage(ContextVk *contextVk,
ANGLE_TRY(initImageViews(contextVk, format, sized, levelCount, layerCount));
// If the image has an emulated channel or robust resource init is enabled, always clear it.
// These channels will be masked out in future writes, and shouldn't contain uninitialized
// values.
if (contextVk->getState().isRobustResourceInitEnabled() || format.hasEmulatedImageChannels())
{
uint32_t levelCount = mImage->getLevelCount();
for (uint32_t level = 0; level < levelCount; ++level)
{
gl::ImageIndex index = gl::ImageIndex::Make2DArrayRange(level, 0, layerCount);
mImage->stageSubresourceRobustClear(index, format);
}
}
mSerial = contextVk->generateTextureSerial();
return angle::Result::Continue;
......
......@@ -27,13 +27,13 @@ namespace vk
{
namespace
{
// WebGL requires color textures to be initialized to transparent black.
constexpr VkClearColorValue kWebGLInitColorValue = {{0, 0, 0, 0}};
// ANGLE_robust_resource_initialization requires color textures to be initialized to zero.
constexpr VkClearColorValue kRobustInitColorValue = {{0, 0, 0, 0}};
// When emulating a texture, we want the emulated channels to be 0, with alpha 1.
constexpr VkClearColorValue kEmulatedInitColorValue = {{0, 0, 0, 1.0f}};
// WebGL requires depth/stencil textures to be initialized to depth=1, stencil=0. We are fine with
// these values for emulated depth/stencil textures too.
constexpr VkClearDepthStencilValue kWebGLInitDepthStencilValue = {1.0f, 0};
// ANGLE_robust_resource_initialization requires depth to be initialized to 1 and stencil to 0.
// We are fine with these values for emulated depth/stencil textures too.
constexpr VkClearDepthStencilValue kRobustInitDepthStencilValue = {1.0f, 0};
constexpr VkBufferUsageFlags kLineLoopDynamicBufferUsage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
......@@ -431,6 +431,21 @@ const angle::Format &GetDepthStencilImageToBufferFormat(const angle::Format &ima
return imageFormat;
}
}
VkClearValue GetClearValue(const vk::Format &format)
{
VkClearValue clearValue;
if (format.intendedFormat().hasDepthOrStencilBits())
{
clearValue.depthStencil = kRobustInitDepthStencilValue;
}
else
{
clearValue.color =
format.hasEmulatedImageChannels() ? kEmulatedInitColorValue : kRobustInitColorValue;
}
return clearValue;
}
} // anonymous namespace
// DynamicBuffer implementation.
......@@ -2035,6 +2050,8 @@ angle::Result ImageHelper::initExternal(Context *context,
ANGLE_VK_TRY(context, mImage.init(context->getDevice(), imageInfo));
stageClearIfEmulatedFormat(context);
return angle::Result::Continue;
}
......@@ -2206,7 +2223,8 @@ void ImageHelper::destroy(RendererVk *renderer)
mSerial = rx::kZeroSerial;
}
void ImageHelper::init2DWeakReference(VkImage handle,
void ImageHelper::init2DWeakReference(Context *context,
VkImage handle,
const gl::Extents &glExtents,
const Format &format,
GLint samples)
......@@ -2221,6 +2239,8 @@ void ImageHelper::init2DWeakReference(VkImage handle,
mLevelCount = 1;
mImage.setHandle(handle);
stageClearIfEmulatedFormat(context);
}
angle::Result ImageHelper::init2DStaging(Context *context,
......@@ -3068,48 +3088,45 @@ void ImageHelper::stageSubresourceUpdateFromImage(ImageHelper *image,
appendSubresourceUpdate(SubresourceUpdate(image, copyToImage));
}
void ImageHelper::stageSubresourceRobustClear(const gl::ImageIndex &index, const Format &format)
void ImageHelper::stageSubresourceClear(const gl::ImageIndex &index)
{
VkClearColorValue initValue =
format.hasEmulatedImageChannels() ? kEmulatedInitColorValue : kWebGLInitColorValue;
stageSubresourceClear(index, format.intendedFormat(), initValue, kWebGLInitDepthStencilValue);
ASSERT(mFormat);
VkClearValue clearValue = GetClearValue(*mFormat);
appendSubresourceUpdate(SubresourceUpdate(clearValue, index));
}
void ImageHelper::stageSubresourceEmulatedClear(const gl::ImageIndex &index,
const angle::Format &format)
void ImageHelper::stageRobustResourceClear(const gl::ImageIndex &index, const vk::Format &format)
{
stageSubresourceClear(index, format, kEmulatedInitColorValue, kWebGLInitDepthStencilValue);
// Robust clears must only be staged if we do not have any prior data for this subresource.
ASSERT(!isUpdateStaged(index.getLevelIndex(), index.getLayerIndex()));
VkClearValue clearValue = GetClearValue(format);
appendSubresourceUpdate(SubresourceUpdate(clearValue, index));
}
void ImageHelper::stageClearIfEmulatedFormat(const gl::ImageIndex &index, const Format &format)
void ImageHelper::stageClearIfEmulatedFormat(Context *context)
{
if (format.hasEmulatedImageChannels())
{
stageSubresourceEmulatedClear(index, format.intendedFormat());
}
}
// Skip staging extra clears if robust resource init is enabled.
if (!mFormat->hasEmulatedImageChannels() || context->isRobustResourceInitEnabled())
return;
void ImageHelper::stageSubresourceClear(const gl::ImageIndex &index,
const angle::Format &format,
const VkClearColorValue &colorValue,
const VkClearDepthStencilValue &depthStencilValue)
{
VkClearValue clearValue;
bool isDepthStencil = format.depthBits > 0 || format.stencilBits > 0;
if (isDepthStencil)
if (mFormat->intendedFormat().hasDepthOrStencilBits())
{
clearValue.depthStencil = depthStencilValue;
clearValue.depthStencil = kRobustInitDepthStencilValue;
}
else
{
clearValue.color = colorValue;
clearValue.color = kEmulatedInitColorValue;
}
// Note that clears can arrive out of order from the front-end with respect to staged changes,
// but they are intended to be done first.
mSubresourceUpdates.emplace(mSubresourceUpdates.begin(), clearValue, index);
onStateChange(angle::SubjectMessage::SubjectChanged);
// If the image has an emulated channel and robust resource init is not enabled, always clear
// it. These channels will be masked out in future writes, and shouldn't contain uninitialized
// values.
for (uint32_t level = 0; level < mLevelCount; ++level)
{
gl::ImageIndex index = gl::ImageIndex::Make2DArrayRange(level, 0, mLayerCount);
prependSubresourceUpdate(SubresourceUpdate(clearValue, index));
}
}
angle::Result ImageHelper::allocateStagingMemory(ContextVk *contextVk,
......@@ -3741,6 +3758,12 @@ void ImageHelper::appendSubresourceUpdate(SubresourceUpdate &&update)
onStateChange(angle::SubjectMessage::SubjectChanged);
}
void ImageHelper::prependSubresourceUpdate(SubresourceUpdate &&update)
{
mSubresourceUpdates.insert(mSubresourceUpdates.begin(), std::move(update));
onStateChange(angle::SubjectMessage::SubjectChanged);
}
// FramebufferHelper implementation.
FramebufferHelper::FramebufferHelper() = default;
......
......@@ -793,7 +793,8 @@ class ImageHelper final : public Resource, public angle::Subject
void destroy(RendererVk *renderer);
void release(RendererVk *renderer) { destroy(renderer); }
void init2DWeakReference(VkImage handle,
void init2DWeakReference(Context *context,
VkImage handle,
const gl::Extents &glExtents,
const Format &format,
GLint samples);
......@@ -900,16 +901,9 @@ class ImageHelper final : public Resource, public angle::Subject
const gl::Extents &glExtents,
const VkImageType imageType);
// Stage a clear operation to a clear value based on WebGL requirements.
void stageSubresourceRobustClear(const gl::ImageIndex &index, const 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);
// If the image has emulated channels, we clear them once so as not to leave garbage on those
// channels.
void stageClearIfEmulatedFormat(const gl::ImageIndex &index, const Format &format);
// Stage a clear based on robust resource init.
void stageRobustResourceClear(const gl::ImageIndex &index, const vk::Format &format);
void stageSubresourceClear(const gl::ImageIndex &index);
// This will use the underlying dynamic buffer to allocate some memory to be used as a src or
// dst.
......@@ -935,7 +929,6 @@ class ImageHelper final : public Resource, public angle::Subject
angle::Result flushAllStagedUpdates(ContextVk *contextVk);
bool isUpdateStaged(uint32_t level, uint32_t layer);
bool hasStagedUpdates() const { return !mSubresourceUpdates.empty(); }
// changeLayout automatically skips the layout change if it's unnecessary. This function can be
......@@ -1080,10 +1073,9 @@ class ImageHelper final : public Resource, public angle::Subject
uint32_t newQueueFamilyIndex,
CommandBufferT *commandBuffer);
void stageSubresourceClear(const gl::ImageIndex &index,
const angle::Format &format,
const VkClearColorValue &colorValue,
const VkClearDepthStencilValue &depthStencilValue);
// If the image has emulated channels, we clear them once so as not to leave garbage on those
// channels.
void stageClearIfEmulatedFormat(Context *context);
void clearColor(const VkClearColorValue &color,
uint32_t baseMipLevel,
......@@ -1104,6 +1096,7 @@ class ImageHelper final : public Resource, public angle::Subject
angle::Result initializeNonZeroMemory(Context *context, VkDeviceSize size);
void appendSubresourceUpdate(SubresourceUpdate &&update);
void prependSubresourceUpdate(SubresourceUpdate &&update);
// Vulkan objects.
Image mImage;
......
......@@ -141,6 +141,10 @@ class Context : angle::NonCopyable
VkDevice getDevice() const;
RendererVk *getRenderer() const { return mRenderer; }
// This is a special override needed so we can determine if we need to initialize images.
// It corresponds to the EGL or GL extensions depending on the vk::Context type.
virtual bool isRobustResourceInitEnabled() const = 0;
protected:
RendererVk *const mRenderer;
};
......
......@@ -1486,6 +1486,26 @@ void main() {
eglDestroyContext(display, context2);
}
// Ensure cube-incomplete attachments cause incomplete Framebuffers.
TEST_P(FramebufferTest, IncompleteCubeMap)
{
constexpr GLuint kSize = 2;
GLTexture srcTex;
glBindTexture(GL_TEXTURE_CUBE_MAP, srcTex);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
srcTex, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER),
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
}
ANGLE_INSTANTIATE_TEST_ES2(AddDummyTextureNoRenderTargetTest);
ANGLE_INSTANTIATE_TEST_ES2(FramebufferTest);
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(FramebufferFormatsTest);
......
......@@ -2000,6 +2000,58 @@ TEST_P(RobustResourceInitTestES3, InitializeMultisampledDepthRenderbufferAfterCo
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Corner case for robust resource init: CopyTexImage to a cube map.
TEST_P(RobustResourceInitTest, CopyTexImageToOffsetCubeMap)
{
// http://anglebug.com/4549
ANGLE_SKIP_TEST_IF(IsMetal());
constexpr GLuint kSize = 2;
std::vector<GLColor> redPixels(kSize * kSize, GLColor::red);
GLTexture srcTex;
glBindTexture(GL_TEXTURE_2D, srcTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
redPixels.data());
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTex, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
GLTexture dstTex;
glBindTexture(GL_TEXTURE_CUBE_MAP, dstTex);
glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 1, 1, kSize, kSize, 0);
glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, -1, -1, kSize, kSize, 0);
glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, 2, 2, kSize, kSize, 0);
glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, -2, -2, kSize, kSize, 0);
glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, 0, 0, kSize, kSize, 0);
glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, 0, 0, kSize, kSize, 0);
ASSERT_GL_NO_ERROR();
// Verify the offset attachments.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
dstTex, 0);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::transparentBlack);
EXPECT_PIXEL_COLOR_EQ(0, 1, GLColor::transparentBlack);
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::transparentBlack);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
dstTex, 0);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::transparentBlack);
EXPECT_PIXEL_COLOR_EQ(0, 1, GLColor::transparentBlack);
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
}
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(RobustResourceInitTest,
WithAllocateNonZeroMemory(ES2_VULKAN()));
......
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