Commit 000a79f1 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Better handling of texture level redefinition

If a texture level is being redefined, there are two scenarios to consider: 1. The level is outside the base/max level, for which the image was allocated. 2. The level is within the base/max level, but it's being redefined to a different size or format. In the former case, we simply don't need to release the image. The latter case itself has two possibilities: 2.1. There is only one level in the image. 2.2. There are multiple levels in the image. In case 2.1, the whole image is being redefined (as it has only a single level), so the image can (and should) be released. Prior to this change, this behavior was adopted for all cases. This change retains this behavior for this case only. In case 2.2, the texture is becoming incomplete. However, the image shouldn't yet be released because another one of its mips may be bound to a framebuffer. In such cases as glCopyTexImage2D(), that framebuffer may in fact be the source of the copy operation (which would be destroyed if the image is released). If the base/max level of the texture doesn't change, redefining the level and making the texture incomplete doesn't make the framebuffer incomplete; this is achieved at the same time by not releasing the image. This change ensures that updates to the redefined level are staged in cases 1 and 2.2. Bug: angleproject:4274 Change-Id: I3fac3203c2fbbc16e8e4a35b1334b767120b2dcf Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2230853 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 61d6f87f
...@@ -634,6 +634,8 @@ using StorageBuffersMask = angle::BitSet<IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFE ...@@ -634,6 +634,8 @@ using StorageBuffersMask = angle::BitSet<IMPLEMENTATION_MAX_SHADER_STORAGE_BUFFE
template <typename T> template <typename T>
using TexLevelArray = std::array<T, IMPLEMENTATION_MAX_TEXTURE_LEVELS>; using TexLevelArray = std::array<T, IMPLEMENTATION_MAX_TEXTURE_LEVELS>;
using TexLevelMask = angle::BitSet<IMPLEMENTATION_MAX_TEXTURE_LEVELS>;
enum class ComponentType enum class ComponentType
{ {
Float = 0, Float = 0,
......
...@@ -98,8 +98,8 @@ egl::Error ImageVk::initialize(const egl::Display *display) ...@@ -98,8 +98,8 @@ egl::Error ImageVk::initialize(const egl::Display *display)
} }
// Make sure a staging buffer is ready to use to upload data // Make sure a staging buffer is ready to use to upload data
mImage->initStagingBuffer(renderer, mImage->getFormat(), vk::kStagingBufferFlags, mImage->initStagingBuffer(renderer, mImage->getFormat().getImageCopyBufferAlignment(),
vk::kStagingBufferSize); vk::kStagingBufferFlags, vk::kStagingBufferSize);
mOwnsImage = false; mOwnsImage = false;
......
...@@ -42,6 +42,41 @@ constexpr VkFormatFeatureFlags kBlitFeatureFlags = ...@@ -42,6 +42,41 @@ constexpr VkFormatFeatureFlags kBlitFeatureFlags =
constexpr angle::SubjectIndex kTextureImageSubjectIndex = 0; constexpr angle::SubjectIndex kTextureImageSubjectIndex = 0;
// Test whether a texture level is within the range of levels for which the current image is
// allocated. This is used to ensure out-of-range updates are staged in the image, and not
// attempted to be directly applied.
bool IsTextureLevelInAllocatedImage(const vk::ImageHelper &image, uint32_t textureLevelIndexGL)
{
uint32_t imageBaseLevel = image.getBaseLevel();
if (textureLevelIndexGL < imageBaseLevel)
{
return false;
}
uint32_t imageLevelIndexVK = textureLevelIndexGL - imageBaseLevel;
return imageLevelIndexVK < image.getLevelCount();
}
// Test whether a redefined texture level is compatible with the currently allocated image. Returns
// true if the given size and format match the corresponding mip in the allocated image (taking
// base level into account). This could return false when:
//
// - Defining a texture level that is outside the range of the image levels. In this case, changes
// to this level should remain staged until the texture is redefined to include this level.
// - Redefining a texture level that is within the range of the image levels, but has a different
// size or format. In this case too, changes to this level should remain staged as the texture
// is no longer complete as is.
bool IsTextureLevelDefinitionCompatibleWithImage(const vk::ImageHelper &image,
uint32_t textureLevelIndexGL,
const gl::Extents &size,
const vk::Format &format)
{
ASSERT(IsTextureLevelInAllocatedImage(image, textureLevelIndexGL));
uint32_t imageLevelIndexVK = textureLevelIndexGL - image.getBaseLevel();
return size == image.getLevelExtents(imageLevelIndexVK) && format == image.getFormat();
}
bool CanCopyWithTransfer(RendererVk *renderer, bool CanCopyWithTransfer(RendererVk *renderer,
const vk::Format &srcFormat, const vk::Format &srcFormat,
const vk::Format &destFormat) const vk::Format &destFormat)
...@@ -223,7 +258,7 @@ angle::Result TextureVk::setImageImpl(const gl::Context *context, ...@@ -223,7 +258,7 @@ angle::Result TextureVk::setImageImpl(const gl::Context *context,
const vk::Format &vkFormat = renderer->getFormat(formatInfo.sizedInternalFormat); const vk::Format &vkFormat = renderer->getFormat(formatInfo.sizedInternalFormat);
ANGLE_TRY(redefineImage(context, index, vkFormat, size)); ANGLE_TRY(redefineLevel(context, index, vkFormat, size));
// Early-out on empty textures, don't create a zero-sized storage. // Early-out on empty textures, don't create a zero-sized storage.
if (size.empty()) if (size.empty())
...@@ -235,6 +270,37 @@ angle::Result TextureVk::setImageImpl(const gl::Context *context, ...@@ -235,6 +270,37 @@ angle::Result TextureVk::setImageImpl(const gl::Context *context,
formatInfo, type, unpack, unpackBuffer, pixels, vkFormat); formatInfo, type, unpack, unpackBuffer, pixels, vkFormat);
} }
bool TextureVk::isFastUnpackPossible(const vk::Format &vkFormat, size_t offset) const
{
// Conditions to determine if fast unpacking is possible
// 1. Image must be well defined to unpack directly to it
// TODO(http://anglebug.com/4222) Create and stage a temp image instead
// 2. Can't perform a fast copy for emulated formats
// 3. vkCmdCopyBufferToImage requires byte offset to be a multiple of 4
return mImage->valid() && vkFormat.intendedFormatID == vkFormat.actualImageFormatID &&
(offset & (kBufferOffsetMultiple - 1)) == 0;
}
bool TextureVk::shouldUpdateBeStaged(uint32_t textureLevelIndexGL) const
{
ASSERT(mImage);
// If update is outside the range of image levels, it must be staged.
if (!IsTextureLevelInAllocatedImage(*mImage, textureLevelIndexGL))
{
return true;
}
uint32_t imageLevelIndexVK = textureLevelIndexGL - mImage->getBaseLevel();
// Can't have more than 32 mips for the foreseeable future.
ASSERT(imageLevelIndexVK < 32);
// Otherwise, it can only be directly applied to the image if the level is not previously
// incompatibly redefined.
return mRedefinedLevels.test(imageLevelIndexVK);
}
angle::Result TextureVk::setSubImageImpl(const gl::Context *context, angle::Result TextureVk::setSubImageImpl(const gl::Context *context,
const gl::ImageIndex &index, const gl::ImageIndex &index,
const gl::Box &area, const gl::Box &area,
...@@ -262,7 +328,8 @@ angle::Result TextureVk::setSubImageImpl(const gl::Context *context, ...@@ -262,7 +328,8 @@ angle::Result TextureVk::setSubImageImpl(const gl::Context *context,
size_t offsetBytes = static_cast<size_t>(offset + inputSkipBytes); size_t offsetBytes = static_cast<size_t>(offset + inputSkipBytes);
if (isFastUnpackPossible(vkFormat, offsetBytes)) if (!shouldUpdateBeStaged(index.getLevelIndex()) &&
isFastUnpackPossible(vkFormat, offsetBytes))
{ {
GLuint pixelSize = formatInfo.pixelBytes; GLuint pixelSize = formatInfo.pixelBytes;
GLuint blockWidth = formatInfo.compressedBlockWidth; GLuint blockWidth = formatInfo.compressedBlockWidth;
...@@ -322,7 +389,8 @@ angle::Result TextureVk::copyImage(const gl::Context *context, ...@@ -322,7 +389,8 @@ angle::Result TextureVk::copyImage(const gl::Context *context,
gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE); gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);
const vk::Format &vkFormat = renderer->getFormat(internalFormatInfo.sizedInternalFormat); const vk::Format &vkFormat = renderer->getFormat(internalFormatInfo.sizedInternalFormat);
ANGLE_TRY(redefineImage(context, index, vkFormat, newImageSize)); ANGLE_TRY(redefineLevel(context, index, vkFormat, newImageSize));
return copySubImageImpl(context, index, gl::Offset(0, 0, 0), sourceArea, internalFormatInfo, return copySubImageImpl(context, index, gl::Offset(0, 0, 0), sourceArea, internalFormatInfo,
source); source);
} }
...@@ -357,7 +425,7 @@ angle::Result TextureVk::copyTexture(const gl::Context *context, ...@@ -357,7 +425,7 @@ angle::Result TextureVk::copyTexture(const gl::Context *context,
const gl::InternalFormat &destFormatInfo = gl::GetInternalFormatInfo(internalFormat, type); const gl::InternalFormat &destFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
const vk::Format &destVkFormat = renderer->getFormat(destFormatInfo.sizedInternalFormat); const vk::Format &destVkFormat = renderer->getFormat(destFormatInfo.sizedInternalFormat);
ANGLE_TRY(redefineImage(context, index, destVkFormat, sourceImageDesc.size)); ANGLE_TRY(redefineLevel(context, index, destVkFormat, sourceImageDesc.size));
return copySubTextureImpl(vk::GetImpl(context), index, gl::kOffsetZero, destFormatInfo, return copySubTextureImpl(vk::GetImpl(context), index, gl::kOffsetZero, destFormatInfo,
sourceLevel, sourceArea, unpackFlipY, unpackPremultiplyAlpha, sourceLevel, sourceArea, unpackFlipY, unpackPremultiplyAlpha,
...@@ -399,7 +467,7 @@ angle::Result TextureVk::copyCompressedTexture(const gl::Context *context, ...@@ -399,7 +467,7 @@ angle::Result TextureVk::copyCompressedTexture(const gl::Context *context,
static_cast<int>(source->getHeight(sourceTarget, sourceLevel)), 1); static_cast<int>(source->getHeight(sourceTarget, sourceLevel)), 1);
const gl::ImageIndex destIndex = gl::ImageIndex::MakeFromTarget(sourceTarget, destLevel, 1); const gl::ImageIndex destIndex = gl::ImageIndex::MakeFromTarget(sourceTarget, destLevel, 1);
ANGLE_TRY(redefineImage(context, destIndex, vkFormat, size)); ANGLE_TRY(redefineLevel(context, destIndex, vkFormat, size));
ANGLE_TRY(sourceVk->ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels)); ANGLE_TRY(sourceVk->ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels));
...@@ -604,7 +672,7 @@ angle::Result TextureVk::copySubImageImplWithTransfer(ContextVk *contextVk, ...@@ -604,7 +672,7 @@ angle::Result TextureVk::copySubImageImplWithTransfer(ContextVk *contextVk,
} }
// If destination is valid, copy the source directly into it. // If destination is valid, copy the source directly into it.
if (mImage->valid()) if (mImage->valid() && !shouldUpdateBeStaged(level))
{ {
// Make sure any updates to the image are already flushed. // Make sure any updates to the image are already flushed.
ANGLE_TRY(ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels)); ANGLE_TRY(ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels));
...@@ -694,7 +762,7 @@ angle::Result TextureVk::copySubImageImplWithDraw(ContextVk *contextVk, ...@@ -694,7 +762,7 @@ angle::Result TextureVk::copySubImageImplWithDraw(ContextVk *contextVk,
uint32_t layerCount = index.getLayerCount(); uint32_t layerCount = index.getLayerCount();
// If destination is valid, copy the source directly into it. // If destination is valid, copy the source directly into it.
if (mImage->valid()) if (mImage->valid() && !shouldUpdateBeStaged(level))
{ {
// Make sure any updates to the image are already flushed. // Make sure any updates to the image are already flushed.
ANGLE_TRY(ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels)); ANGLE_TRY(ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels));
...@@ -897,6 +965,7 @@ void TextureVk::releaseAndDeleteImage(ContextVk *contextVk) ...@@ -897,6 +965,7 @@ void TextureVk::releaseAndDeleteImage(ContextVk *contextVk)
mImageObserverBinding.bind(nullptr); mImageObserverBinding.bind(nullptr);
SafeDelete(mImage); SafeDelete(mImage);
} }
mRedefinedLevels.reset();
} }
angle::Result TextureVk::ensureImageAllocated(ContextVk *contextVk, const vk::Format &format) angle::Result TextureVk::ensureImageAllocated(ContextVk *contextVk, const vk::Format &format)
...@@ -907,7 +976,12 @@ angle::Result TextureVk::ensureImageAllocated(ContextVk *contextVk, const vk::Fo ...@@ -907,7 +976,12 @@ angle::Result TextureVk::ensureImageAllocated(ContextVk *contextVk, const vk::Fo
} }
else else
{ {
updateImageHelper(contextVk, format); // Note: one possible path here is when an image level is being redefined to a different
// format. In that case, staged updates with the new format should succeed, but otherwise
// the format should not affect the currently allocated image. The following function only
// takes the alignment requirement to make sure the format is not accidentally used for any
// other purpose.
updateImageHelper(contextVk, format.getImageCopyBufferAlignment());
} }
mImageUsageFlags = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | mImageUsageFlags = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
...@@ -946,8 +1020,7 @@ void TextureVk::setImageHelper(ContextVk *contextVk, ...@@ -946,8 +1020,7 @@ void TextureVk::setImageHelper(ContextVk *contextVk,
mImageLevelOffset = imageLevelOffset; mImageLevelOffset = imageLevelOffset;
mImageLayerOffset = imageLayerOffset; mImageLayerOffset = imageLayerOffset;
mImage = imageHelper; mImage = imageHelper;
mImage->initStagingBuffer(contextVk->getRenderer(), format, vk::kStagingBufferFlags, updateImageHelper(contextVk, format.getImageCopyBufferAlignment());
mStagingBufferInitialSize);
// Force re-creation of render targets next time they are needed // Force re-creation of render targets next time they are needed
for (RenderTargetVector &renderTargetLevels : mRenderTargets) for (RenderTargetVector &renderTargetLevels : mRenderTargets)
...@@ -959,14 +1032,14 @@ void TextureVk::setImageHelper(ContextVk *contextVk, ...@@ -959,14 +1032,14 @@ void TextureVk::setImageHelper(ContextVk *contextVk,
mSerial = contextVk->generateTextureSerial(); mSerial = contextVk->generateTextureSerial();
} }
void TextureVk::updateImageHelper(ContextVk *contextVk, const vk::Format &format) void TextureVk::updateImageHelper(ContextVk *contextVk, size_t imageCopyBufferAlignment)
{ {
ASSERT(mImage != nullptr); ASSERT(mImage != nullptr);
mImage->initStagingBuffer(contextVk->getRenderer(), format, vk::kStagingBufferFlags, mImage->initStagingBuffer(contextVk->getRenderer(), imageCopyBufferAlignment,
mStagingBufferInitialSize); vk::kStagingBufferFlags, mStagingBufferInitialSize);
} }
angle::Result TextureVk::redefineImage(const gl::Context *context, angle::Result TextureVk::redefineLevel(const gl::Context *context,
const gl::ImageIndex &index, const gl::ImageIndex &index,
const vk::Format &format, const vk::Format &format,
const gl::Extents &size) const gl::Extents &size)
...@@ -982,22 +1055,63 @@ angle::Result TextureVk::redefineImage(const gl::Context *context, ...@@ -982,22 +1055,63 @@ angle::Result TextureVk::redefineImage(const gl::Context *context,
{ {
// If there is any staged changes for this index, we can remove them since we're going to // If there is any staged changes for this index, we can remove them since we're going to
// override them with this call. // override them with this call.
uint32_t levelIndex = index.getLevelIndex(); uint32_t levelIndexGL = index.getLevelIndex();
uint32_t layerIndex = index.hasLayer() ? index.getLayerIndex() : 0; uint32_t layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
mImage->removeStagedUpdates(contextVk, levelIndex, layerIndex); mImage->removeStagedUpdates(contextVk, levelIndexGL, layerIndex);
if (mImage->valid()) if (mImage->valid())
{ {
// Calculate the expected size for the index we are defining. If the size is different // If the level that's being redefined is outside the level range of the allocated
// from the given size, or the format is different, we are redefining the image so we // image, the application is free to use any size or format. Any data uploaded to it
// must release it. // will live in staging area until the texture base/max level is adjusted to include
if (mImage->getFormat() != format || size != mImage->getSize(index)) // this level, at which point the image will be recreated.
//
// Otherwise, if the level that's being redefined has a different format or size,
// only release the image if it's single-mip, and keep the uploaded data staged.
// Otherwise the image is mip-incomplete anyway and will be eventually recreated when
// needed. Only exception to this latter is if all the levels of the texture are
// redefined such that the image becomes mip-complete in the end.
// mRedefinedLevels is used during syncState to support this use-case.
//
// Note that if the image has multiple mips, there could be a copy from one mip
// happening to the other, which means the image cannot be released.
//
// In summary:
//
// - If the image has a single level, and that level is being redefined, release the
// image.
// - Otherwise keep the image intact (another mip may be the source of a copy), and
// make sure any updates to this level are staged.
bool isInAllocatedImage = IsTextureLevelInAllocatedImage(*mImage, levelIndexGL);
bool isCompatibleRedefinition =
isInAllocatedImage &&
IsTextureLevelDefinitionCompatibleWithImage(*mImage, levelIndexGL, size, format);
// Mark the level as incompatibly redefined if that's the case. Note that if the level
// was previously incompatibly defined, then later redefined to be compatible, the
// corresponding bit should clear.
if (isInAllocatedImage)
{
mRedefinedLevels.set(levelIndexGL - mImage->getBaseLevel(),
!isCompatibleRedefinition);
}
bool isUpdateToSingleLevelImage =
mImage->getLevelCount() == 1 && mImage->getBaseLevel() == levelIndexGL;
// If incompatible, and redefining the single-level image, release it so it can be
// recreated immediately. This is an optimization to avoid an extra copy.
if (!isCompatibleRedefinition && isUpdateToSingleLevelImage)
{ {
releaseImage(contextVk); releaseImage(contextVk);
} }
} }
} }
// If image is not released due to an out-of-range or incompatible level definition, the image
// is still valid and we shouldn't redefine it to use the new format. In that case,
// ensureImageAllocated will only use the format to update the staging buffer's alignment to
// support both the previous and the new formats.
if (!size.empty()) if (!size.empty())
{ {
ANGLE_TRY(ensureImageAllocated(contextVk, format)); ANGLE_TRY(ensureImageAllocated(contextVk, format));
...@@ -1119,7 +1233,7 @@ angle::Result TextureVk::generateMipmapsWithCPU(const gl::Context *context) ...@@ -1119,7 +1233,7 @@ angle::Result TextureVk::generateMipmapsWithCPU(const gl::Context *context)
vk::CommandBuffer *commandBuffer = nullptr; vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer)); ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer));
return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), mImage->getLevelCount(), return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), mImage->getLevelCount(),
getNativeImageLayer(0), mImage->getLayerCount(), getNativeImageLayer(0), mImage->getLayerCount(), {},
commandBuffer); commandBuffer);
} }
...@@ -1165,7 +1279,7 @@ angle::Result TextureVk::generateMipmap(const gl::Context *context) ...@@ -1165,7 +1279,7 @@ angle::Result TextureVk::generateMipmap(const gl::Context *context)
ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer)); ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer));
ANGLE_TRY(mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), ANGLE_TRY(mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0),
mImage->getLevelCount(), getNativeImageLayer(0), mImage->getLevelCount(), getNativeImageLayer(0),
mImage->getLayerCount(), commandBuffer)); mImage->getLayerCount(), {}, commandBuffer));
} }
// Redefine the images with mipmaps. // Redefine the images with mipmaps.
...@@ -1289,6 +1403,7 @@ angle::Result TextureVk::respecifyImageAttributes(ContextVk *contextVk) ...@@ -1289,6 +1403,7 @@ angle::Result TextureVk::respecifyImageAttributes(ContextVk *contextVk)
mState.getEffectiveBaseLevel(), mState.getEffectiveBaseLevel(),
mState.getEffectiveMaxLevel()); mState.getEffectiveMaxLevel());
} }
angle::Result TextureVk::respecifyImageAttributesAndLevels(ContextVk *contextVk, angle::Result TextureVk::respecifyImageAttributesAndLevels(ContextVk *contextVk,
GLuint previousBaseLevel, GLuint previousBaseLevel,
GLuint baseLevel, GLuint baseLevel,
...@@ -1300,9 +1415,9 @@ angle::Result TextureVk::respecifyImageAttributesAndLevels(ContextVk *contextVk, ...@@ -1300,9 +1415,9 @@ angle::Result TextureVk::respecifyImageAttributesAndLevels(ContextVk *contextVk,
{ {
vk::CommandBuffer *commandBuffer = nullptr; vk::CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer)); ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer));
ANGLE_TRY(mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), ANGLE_TRY(mImage->flushStagedUpdates(
mImage->getLevelCount(), getNativeImageLayer(0), contextVk, getNativeImageLevel(0), mImage->getLevelCount(), getNativeImageLayer(0),
mImage->getLayerCount(), commandBuffer)); mImage->getLayerCount(), mRedefinedLevels, commandBuffer));
} }
// After flushing, track the new levels (they are used in the flush, hence the wait) // After flushing, track the new levels (they are used in the flush, hence the wait)
...@@ -1321,6 +1436,14 @@ angle::Result TextureVk::respecifyImageAttributesAndLevels(ContextVk *contextVk, ...@@ -1321,6 +1436,14 @@ angle::Result TextureVk::respecifyImageAttributesAndLevels(ContextVk *contextVk,
// Vulkan level 0 previously aligned with whatever the base level was. // Vulkan level 0 previously aligned with whatever the base level was.
uint32_t levelGL = levelVK + previousBaseLevel; uint32_t levelGL = levelVK + previousBaseLevel;
if (mRedefinedLevels.test(levelVK))
{
// Note: if this level is incompatibly redefined, there will necessarily be a staged
// update, and the contents of the image are to be thrown away.
ASSERT(mImage->isUpdateStaged(levelGL, layer));
continue;
}
ASSERT(!mImage->isUpdateStaged(levelGL, layer)); ASSERT(!mImage->isUpdateStaged(levelGL, layer));
// Pull data from the current image and stage it as an update for the new image // Pull data from the current image and stage it as an update for the new image
...@@ -1426,6 +1549,8 @@ angle::Result TextureVk::ensureImageInitializedImpl(ContextVk *contextVk, ...@@ -1426,6 +1549,8 @@ angle::Result TextureVk::ensureImageInitializedImpl(ContextVk *contextVk,
if (!mImage->valid()) if (!mImage->valid())
{ {
ASSERT(!mRedefinedLevels.any());
const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc(); const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc();
ANGLE_TRY(initImage(contextVk, format, baseLevelDesc.format.info->sized, baseLevelExtents, ANGLE_TRY(initImage(contextVk, format, baseLevelDesc.format.info->sized, baseLevelExtents,
...@@ -1436,7 +1561,7 @@ angle::Result TextureVk::ensureImageInitializedImpl(ContextVk *contextVk, ...@@ -1436,7 +1561,7 @@ angle::Result TextureVk::ensureImageInitializedImpl(ContextVk *contextVk,
ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer)); ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer));
return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), mImage->getLevelCount(), return mImage->flushStagedUpdates(contextVk, getNativeImageLevel(0), mImage->getLevelCount(),
getNativeImageLayer(0), mImage->getLayerCount(), getNativeImageLayer(0), mImage->getLayerCount(),
commandBuffer); mRedefinedLevels, commandBuffer);
} }
angle::Result TextureVk::initRenderTargets(ContextVk *contextVk, angle::Result TextureVk::initRenderTargets(ContextVk *contextVk,
...@@ -1489,11 +1614,6 @@ angle::Result TextureVk::syncState(const gl::Context *context, ...@@ -1489,11 +1614,6 @@ angle::Result TextureVk::syncState(const gl::Context *context,
} }
} }
if (oldUsageFlags != mImageUsageFlags || oldCreateFlags != mImageCreateFlags)
{
ANGLE_TRY(respecifyImageAttributes(contextVk));
}
// Set base and max level before initializing the image // Set base and max level before initializing the image
if (dirtyBits.test(gl::Texture::DIRTY_BIT_MAX_LEVEL) || if (dirtyBits.test(gl::Texture::DIRTY_BIT_MAX_LEVEL) ||
dirtyBits.test(gl::Texture::DIRTY_BIT_BASE_LEVEL)) dirtyBits.test(gl::Texture::DIRTY_BIT_BASE_LEVEL))
...@@ -1502,6 +1622,17 @@ angle::Result TextureVk::syncState(const gl::Context *context, ...@@ -1502,6 +1622,17 @@ angle::Result TextureVk::syncState(const gl::Context *context,
mState.getEffectiveMaxLevel())); mState.getEffectiveMaxLevel()));
} }
// Respecify the image if it's changed in usage, or if any of its levels are redefined and no
// update to base/max levels were done (otherwise the above call would have already taken care
// of this). Note that if both base/max and image usage are changed, the image is recreated
// twice, which incurs unncessary copies. This is not expected to be happening in real
// applications.
if (oldUsageFlags != mImageUsageFlags || oldCreateFlags != mImageCreateFlags ||
mRedefinedLevels.any())
{
ANGLE_TRY(respecifyImageAttributes(contextVk));
}
// Initialize the image storage and flush the pixel buffer. // Initialize the image storage and flush the pixel buffer.
ANGLE_TRY(ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels)); ANGLE_TRY(ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels));
...@@ -1728,6 +1859,7 @@ void TextureVk::releaseImage(ContextVk *contextVk) ...@@ -1728,6 +1859,7 @@ void TextureVk::releaseImage(ContextVk *contextVk)
mRenderTargets.clear(); mRenderTargets.clear();
onStateChange(angle::SubjectMessage::SubjectChanged); onStateChange(angle::SubjectMessage::SubjectChanged);
mRedefinedLevels.reset();
} }
void TextureVk::releaseStagingBuffer(ContextVk *contextVk) void TextureVk::releaseStagingBuffer(ContextVk *contextVk)
......
...@@ -152,22 +152,6 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface ...@@ -152,22 +152,6 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface
angle::Result initializeContents(const gl::Context *context, angle::Result initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex) override; const gl::ImageIndex &imageIndex) override;
ANGLE_INLINE bool isFastUnpackPossible(const vk::Format &vkFormat, size_t offset)
{
// Conditions to determine if fast unpacking is possible
// 1. Image must be well defined to unpack directly to it
// TODO(http://anglebug.com/3777) Create and stage a temp image instead
// 2. Can't perform a fast copy for emulated formats
// 3. vkCmdCopyBufferToImage requires byte offset to be a multiple of 4
if (mImage->valid() && (vkFormat.intendedFormatID == vkFormat.actualImageFormatID) &&
((offset & (kBufferOffsetMultiple - 1)) == 0))
{
return true;
}
return false;
}
const vk::ImageHelper &getImage() const const vk::ImageHelper &getImage() const
{ {
ASSERT(mImage && mImage->valid()); ASSERT(mImage && mImage->valid());
...@@ -247,9 +231,16 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface ...@@ -247,9 +231,16 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface
uint32_t imageLayerOffset, uint32_t imageLayerOffset,
uint32_t imageBaseLevel, uint32_t imageBaseLevel,
bool selfOwned); bool selfOwned);
void updateImageHelper(ContextVk *contextVk, const vk::Format &internalFormat); void updateImageHelper(ContextVk *contextVk, size_t imageCopyBufferAlignment);
angle::Result redefineImage(const gl::Context *context, // Redefine a mip level of the texture. If the new size and format don't match the allocated
// image, the image may be released. When redefining a mip of a multi-level image, updates are
// forced to be staged, as another mip of the image may be bound to a framebuffer. For example,
// assume texture has two mips, and framebuffer is bound to mip 0. Redefining mip 1 to an
// incompatible size shouldn't affect the framebuffer, especially if the redefinition comes from
// something like glCopyTexSubImage2D() (which simultaneously is reading from said framebuffer,
// i.e. mip 0 of the texture).
angle::Result redefineLevel(const gl::Context *context,
const gl::ImageIndex &index, const gl::ImageIndex &index,
const vk::Format &format, const vk::Format &format,
const gl::Extents &size); const gl::Extents &size);
...@@ -387,6 +378,10 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface ...@@ -387,6 +378,10 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface
// Update base and max levels, and re-create image if needed. // Update base and max levels, and re-create image if needed.
angle::Result updateBaseMaxLevels(ContextVk *contextVk, GLuint baseLevel, GLuint maxLevel); angle::Result updateBaseMaxLevels(ContextVk *contextVk, GLuint baseLevel, GLuint maxLevel);
bool isFastUnpackPossible(const vk::Format &vkFormat, size_t offset) const;
bool shouldUpdateBeStaged(uint32_t textureLevelIndexGL) const;
// We monitor the staging buffer and set dirty bits if the staging buffer changes. Note that we // We monitor the staging buffer and set dirty bits if the staging buffer changes. Note that we
// support changes in the staging buffer even outside the TextureVk class. // support changes in the staging buffer even outside the TextureVk class.
void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override; void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;
...@@ -433,6 +428,18 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface ...@@ -433,6 +428,18 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface
// Additional image create flags // Additional image create flags
VkImageCreateFlags mImageCreateFlags; VkImageCreateFlags mImageCreateFlags;
// If an image level is incompatibly redefined, the image lives through the call that did this
// (i.e. set and copy levels), because the image may be used by the framebuffer in the very same
// call. As a result, updates to this redefined level are staged (in both the call that
// redefines it, and any future calls such as subimage updates). This bitset flags redefined
// levels so that their updates will be force-staged until image is recreated.
//
// In common cases with mipmapped textures, the base/max level would need adjusting as the
// texture is no longer mip-complete. However, if every level is redefined such that at the end
// the image becomes mip-complete again, no reinitialization of the image is done. This bitset
// is additionally used to ensure the image is recreated in the next syncState, if not already.
gl::TexLevelMask mRedefinedLevels;
angle::ObserverBinding mImageObserverBinding; angle::ObserverBinding mImageObserverBinding;
}; };
......
...@@ -935,7 +935,7 @@ void DynamicBuffer::initWithFlags(RendererVk *renderer, ...@@ -935,7 +935,7 @@ void DynamicBuffer::initWithFlags(RendererVk *renderer,
mSize = std::min<size_t>(mSize, 0x1000); mSize = std::min<size_t>(mSize, 0x1000);
} }
updateAlignment(renderer, alignment); requireAlignment(renderer, alignment);
} }
DynamicBuffer::~DynamicBuffer() DynamicBuffer::~DynamicBuffer()
...@@ -1147,31 +1147,39 @@ void DynamicBuffer::destroy(RendererVk *renderer) ...@@ -1147,31 +1147,39 @@ void DynamicBuffer::destroy(RendererVk *renderer)
} }
} }
void DynamicBuffer::updateAlignment(RendererVk *renderer, size_t alignment) void DynamicBuffer::requireAlignment(RendererVk *renderer, size_t alignment)
{ {
ASSERT(alignment > 0); ASSERT(alignment > 0);
size_t atomSize = size_t prevAlignment = mAlignment;
static_cast<size_t>(renderer->getPhysicalDeviceProperties().limits.nonCoherentAtomSize);
// We need lcm(alignment, atomSize). Usually, one divides the other so std::max() could be used // If alignment was never set, initialize it with the atom size limit.
// instead. Only known case where this assumption breaks is for 3-component types with 16- or if (prevAlignment == 0)
// 32-bit channels, so that's special-cased to avoid a full-fledged lcm implementation. {
prevAlignment =
static_cast<size_t>(renderer->getPhysicalDeviceProperties().limits.nonCoherentAtomSize);
ASSERT(gl::isPow2(prevAlignment));
}
// We need lcm(prevAlignment, alignment). Usually, one divides the other so std::max() could be
// used instead. Only known case where this assumption breaks is for 3-component types with
// 16- or 32-bit channels, so that's special-cased to avoid a full-fledged lcm implementation.
if (gl::isPow2(alignment)) if (gl::isPow2(prevAlignment * alignment))
{ {
ASSERT(alignment % atomSize == 0 || atomSize % alignment == 0); ASSERT(alignment % prevAlignment == 0 || prevAlignment % alignment == 0);
ASSERT(gl::isPow2(atomSize));
alignment = std::max(alignment, atomSize); alignment = std::max(prevAlignment, alignment);
} }
else else
{ {
ASSERT(gl::isPow2(atomSize)); ASSERT(prevAlignment % 3 != 0 || gl::isPow2(prevAlignment / 3));
ASSERT(alignment % 3 == 0); ASSERT(alignment % 3 != 0 || gl::isPow2(alignment / 3));
ASSERT(gl::isPow2(alignment / 3));
alignment = std::max(alignment / 3, atomSize) * 3; prevAlignment = prevAlignment % 3 == 0 ? prevAlignment / 3 : prevAlignment;
alignment = alignment % 3 == 0 ? alignment / 3 : alignment;
alignment = std::max(prevAlignment, alignment) * 3;
} }
// If alignment has changed, make sure the next allocation is done at an aligned offset. // If alignment has changed, make sure the next allocation is done at an aligned offset.
...@@ -2485,12 +2493,11 @@ void ImageHelper::resetCachedProperties() ...@@ -2485,12 +2493,11 @@ void ImageHelper::resetCachedProperties()
} }
void ImageHelper::initStagingBuffer(RendererVk *renderer, void ImageHelper::initStagingBuffer(RendererVk *renderer,
const Format &format, size_t imageCopyBufferAlignment,
VkBufferUsageFlags usageFlags, VkBufferUsageFlags usageFlags,
size_t initialSize) size_t initialSize)
{ {
mStagingBuffer.init(renderer, usageFlags, format.getImageCopyBufferAlignment(), initialSize, mStagingBuffer.init(renderer, usageFlags, imageCopyBufferAlignment, initialSize, true);
true);
} }
angle::Result ImageHelper::init(Context *context, angle::Result ImageHelper::init(Context *context,
...@@ -2833,12 +2840,21 @@ VkImageLayout ImageHelper::getCurrentLayout() const ...@@ -2833,12 +2840,21 @@ VkImageLayout ImageHelper::getCurrentLayout() const
return kImageMemoryBarrierData[mCurrentLayout].layout; return kImageMemoryBarrierData[mCurrentLayout].layout;
} }
gl::Extents ImageHelper::getLevelExtents2D(uint32_t level) const gl::Extents ImageHelper::getLevelExtents(uint32_t level) const
{ {
// Level 0 should be the size of the extents, after that every time you increase a level
// you shrink the extents by half.
uint32_t width = std::max(mExtents.width >> level, 1u); uint32_t width = std::max(mExtents.width >> level, 1u);
uint32_t height = std::max(mExtents.height >> level, 1u); uint32_t height = std::max(mExtents.height >> level, 1u);
return gl::Extents(width, height, 1); return gl::Extents(width, height, mExtents.depth);
}
gl::Extents ImageHelper::getLevelExtents2D(uint32_t level) const
{
gl::Extents extents = getLevelExtents(level);
extents.depth = 1;
return extents;
} }
bool ImageHelper::isLayoutChangeNecessary(ImageLayout newLayout) const bool ImageHelper::isLayoutChangeNecessary(ImageLayout newLayout) const
...@@ -2902,11 +2918,6 @@ bool ImageHelper::isReleasedToExternal() const ...@@ -2902,11 +2918,6 @@ bool ImageHelper::isReleasedToExternal() const
#endif #endif
} }
uint32_t ImageHelper::getBaseLevel()
{
return mBaseLevel;
}
void ImageHelper::setBaseAndMaxLevels(uint32_t baseLevel, uint32_t maxLevel) void ImageHelper::setBaseAndMaxLevels(uint32_t baseLevel, uint32_t maxLevel)
{ {
mBaseLevel = baseLevel; mBaseLevel = baseLevel;
...@@ -3104,15 +3115,6 @@ void ImageHelper::clear(VkImageAspectFlags aspectFlags, ...@@ -3104,15 +3115,6 @@ void ImageHelper::clear(VkImageAspectFlags aspectFlags,
} }
} }
gl::Extents ImageHelper::getSize(const gl::ImageIndex &index) const
{
GLint mipLevel = index.getLevelIndex();
// Level 0 should be the size of the extents, after that every time you increase a level
// you shrink the extents by half.
return gl::Extents(std::max(1u, mExtents.width >> mipLevel),
std::max(1u, mExtents.height >> mipLevel), mExtents.depth);
}
Serial ImageHelper::getAssignSerial(ContextVk *contextVk) Serial ImageHelper::getAssignSerial(ContextVk *contextVk)
{ {
if (mSerial.getValue() == 0) if (mSerial.getValue() == 0)
...@@ -3896,7 +3898,7 @@ angle::Result ImageHelper::flushSingleSubresourceStagedUpdates(ContextVk *contex ...@@ -3896,7 +3898,7 @@ angle::Result ImageHelper::flushSingleSubresourceStagedUpdates(ContextVk *contex
} }
uint32_t levelVK = levelGL - mBaseLevel; uint32_t levelVK = levelGL - mBaseLevel;
return flushStagedUpdates(contextVk, levelVK, levelVK + 1, layer, layer + 1, commandBuffer); return flushStagedUpdates(contextVk, levelVK, levelVK + 1, layer, layer + 1, {}, commandBuffer);
} }
angle::Result ImageHelper::flushStagedUpdates(ContextVk *contextVk, angle::Result ImageHelper::flushStagedUpdates(ContextVk *contextVk,
...@@ -3904,6 +3906,7 @@ angle::Result ImageHelper::flushStagedUpdates(ContextVk *contextVk, ...@@ -3904,6 +3906,7 @@ angle::Result ImageHelper::flushStagedUpdates(ContextVk *contextVk,
uint32_t levelVKEnd, uint32_t levelVKEnd,
uint32_t layerStart, uint32_t layerStart,
uint32_t layerEnd, uint32_t layerEnd,
gl::TexLevelMask skipLevelsMask,
CommandBuffer *commandBuffer) CommandBuffer *commandBuffer)
{ {
if (mSubresourceUpdates.empty()) if (mSubresourceUpdates.empty())
...@@ -3968,16 +3971,21 @@ angle::Result ImageHelper::flushStagedUpdates(ContextVk *contextVk, ...@@ -3968,16 +3971,21 @@ angle::Result ImageHelper::flushStagedUpdates(ContextVk *contextVk,
const bool areUpdateLayersOutsideRange = const bool areUpdateLayersOutsideRange =
updateBaseLayer + updateLayerCount <= layerStart || updateBaseLayer >= layerEnd; updateBaseLayer + updateLayerCount <= layerStart || updateBaseLayer >= layerEnd;
if (isUpdateLevelOutsideRange || areUpdateLayersOutsideRange) uint32_t updateMipLevelVK = updateMipLevelGL - mBaseLevel;
// Additionally, if updates to this level are specifically asked to be skipped, skip them.
// This can happen when recreating an image that has been partially incompatibly redefined,
// in which case only updates to the levels that haven't been redefined should be flushed.
if (isUpdateLevelOutsideRange || areUpdateLayersOutsideRange ||
skipLevelsMask.test(updateMipLevelVK))
{ {
updatesToKeep.emplace_back(update); updatesToKeep.emplace_back(update);
continue; continue;
} }
uint32_t updateMipLevelVK = updateMipLevelGL - mBaseLevel;
if (mBaseLevel > 0) if (mBaseLevel > 0)
{ {
// We need to shift the miplevel in the update to fall into the vkiamge // We need to shift the miplevel in the update to fall into the Vulkan image.
if (update.updateSource == UpdateSource::Clear) if (update.updateSource == UpdateSource::Clear)
{ {
update.clear.levelIndex -= mBaseLevel; update.clear.levelIndex -= mBaseLevel;
...@@ -4062,7 +4070,7 @@ angle::Result ImageHelper::flushAllStagedUpdates(ContextVk *contextVk) ...@@ -4062,7 +4070,7 @@ angle::Result ImageHelper::flushAllStagedUpdates(ContextVk *contextVk)
// Clear the image. // Clear the image.
CommandBuffer *commandBuffer = nullptr; CommandBuffer *commandBuffer = nullptr;
ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer)); ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer));
return flushStagedUpdates(contextVk, 0, mLevelCount, 0, mLayerCount, commandBuffer); return flushStagedUpdates(contextVk, 0, mLevelCount, 0, mLayerCount, {}, commandBuffer);
} }
bool ImageHelper::isUpdateStaged(uint32_t levelGL, uint32_t layer) bool ImageHelper::isUpdateStaged(uint32_t levelGL, uint32_t layer)
......
...@@ -107,7 +107,11 @@ class DynamicBuffer : angle::NonCopyable ...@@ -107,7 +107,11 @@ class DynamicBuffer : angle::NonCopyable
BufferHelper *getCurrentBuffer() { return mBuffer; } BufferHelper *getCurrentBuffer() { return mBuffer; }
void updateAlignment(RendererVk *renderer, size_t alignment); // **Accumulate** an alignment requirement. A dynamic buffer is used as the staging buffer for
// image uploads, which can contain updates to unrelated mips, possibly with different formats.
// The staging buffer should have an alignment that can satisfy all those formats, i.e. it's the
// lcm of all alignments set in its lifetime.
void requireAlignment(RendererVk *renderer, size_t alignment);
// For testing only! // For testing only!
void setMinimumSizeForTesting(size_t minSize); void setMinimumSizeForTesting(size_t minSize);
...@@ -1024,7 +1028,7 @@ class ImageHelper final : public Resource, public angle::Subject ...@@ -1024,7 +1028,7 @@ class ImageHelper final : public Resource, public angle::Subject
~ImageHelper() override; ~ImageHelper() override;
void initStagingBuffer(RendererVk *renderer, void initStagingBuffer(RendererVk *renderer,
const Format &format, size_t imageCopyBufferAlignment,
VkBufferUsageFlags usageFlags, VkBufferUsageFlags usageFlags,
size_t initialSize); size_t initialSize);
...@@ -1130,6 +1134,7 @@ class ImageHelper final : public Resource, public angle::Subject ...@@ -1130,6 +1134,7 @@ class ImageHelper final : public Resource, public angle::Subject
ImageLayout getCurrentImageLayout() const { return mCurrentLayout; } ImageLayout getCurrentImageLayout() const { return mCurrentLayout; }
VkImageLayout getCurrentLayout() const; VkImageLayout getCurrentLayout() const;
gl::Extents getLevelExtents(uint32_t level) const;
// Helper function to calculate the extents of a render target created for a certain mip of the // Helper function to calculate the extents of a render target created for a certain mip of the
// image. // image.
gl::Extents getLevelExtents2D(uint32_t level) const; gl::Extents getLevelExtents2D(uint32_t level) const;
...@@ -1142,8 +1147,6 @@ class ImageHelper final : public Resource, public angle::Subject ...@@ -1142,8 +1147,6 @@ class ImageHelper final : public Resource, public angle::Subject
uint32_t layerCount, uint32_t layerCount,
CommandBuffer *commandBuffer); CommandBuffer *commandBuffer);
gl::Extents getSize(const gl::ImageIndex &index) const;
// Return unique Serial for underlying image, first assigning it if it hasn't been set yet // Return unique Serial for underlying image, first assigning it if it hasn't been set yet
Serial getAssignSerial(ContextVk *contextVk); Serial getAssignSerial(ContextVk *contextVk);
void resetSerial() { mSerial = rx::kZeroSerial; } void resetSerial() { mSerial = rx::kZeroSerial; }
...@@ -1260,6 +1263,7 @@ class ImageHelper final : public Resource, public angle::Subject ...@@ -1260,6 +1263,7 @@ class ImageHelper final : public Resource, public angle::Subject
uint32_t levelEnd, uint32_t levelEnd,
uint32_t layerStart, uint32_t layerStart,
uint32_t layerEnd, uint32_t layerEnd,
gl::TexLevelMask skipLevelsMask,
CommandBuffer *commandBuffer); CommandBuffer *commandBuffer);
// Creates a command buffer and flushes all staged updates. This is used for one-time // Creates a command buffer and flushes all staged updates. This is used for one-time
...@@ -1320,7 +1324,7 @@ class ImageHelper final : public Resource, public angle::Subject ...@@ -1320,7 +1324,7 @@ class ImageHelper final : public Resource, public angle::Subject
// Returns true if the image is owned by an external API or instance. // Returns true if the image is owned by an external API or instance.
bool isReleasedToExternal() const; bool isReleasedToExternal() const;
uint32_t getBaseLevel(); uint32_t getBaseLevel() const { return mBaseLevel; }
void setBaseAndMaxLevels(uint32_t baseLevel, uint32_t maxLevel); void setBaseAndMaxLevels(uint32_t baseLevel, uint32_t maxLevel);
angle::Result copyImageDataToBuffer(ContextVk *contextVk, angle::Result copyImageDataToBuffer(ContextVk *contextVk,
......
...@@ -485,7 +485,7 @@ TEST_P(CopyTexImageTest, CopyTexSubImageToNonCubeCompleteDestination) ...@@ -485,7 +485,7 @@ TEST_P(CopyTexImageTest, CopyTexSubImageToNonCubeCompleteDestination)
// Deleting textures after copying to them. http://anglebug.com/4267 // Deleting textures after copying to them. http://anglebug.com/4267
TEST_P(CopyTexImageTest, DeleteAfterCopyingToTextures) TEST_P(CopyTexImageTest, DeleteAfterCopyingToTextures)
{ {
// Asserts on Vulkan backend. http://anglebug.com/4274 // Vulkan does not support copying from a texture to itself. http://anglebug.com/2914
ANGLE_SKIP_TEST_IF(IsVulkan()); ANGLE_SKIP_TEST_IF(IsVulkan());
GLTexture texture; GLTexture texture;
......
...@@ -1182,6 +1182,60 @@ TEST_P(CopyTextureTest, CopyToMipmap) ...@@ -1182,6 +1182,60 @@ TEST_P(CopyTextureTest, CopyToMipmap)
} }
} }
// Test that copying outside the mipmap range works
TEST_P(CopyTextureTest, CopyOutsideMipmap)
{
if (!checkExtensions())
{
return;
}
// http://anglebug.com/4716
ANGLE_SKIP_TEST_IF(IsD3D());
// Failing on older drivers. http://anglebug.com/4718
ANGLE_SKIP_TEST_IF(IsLinux() && IsNVIDIA() && IsOpenGL());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
glUseProgram(program);
GLint textureLoc = glGetUniformLocation(program, essl1_shaders::Texture2DUniform());
ASSERT_NE(-1, textureLoc);
glUniform1i(textureLoc, 0);
GLTexture textures[2];
// Create two single-mip textures.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, textures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Commit texture 0
glBindTexture(GL_TEXTURE_2D, textures[0]);
drawQuad(program, std::string(essl1_shaders::PositionAttrib()), 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Copy texture 1 into mip 1 of texture 0. This mip is outside the range of the image allocated
// for texture 0.
glCopyTextureCHROMIUM(textures[1], 0, GL_TEXTURE_2D, textures[0], 1, GL_RGBA, GL_UNSIGNED_BYTE,
false, false, false);
EXPECT_GL_NO_ERROR();
// Draw with texture 0 again
drawQuad(program, std::string(essl1_shaders::PositionAttrib()), 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
// Test that copying from an RGBA8 texture to RGBA4 results in exactly 4-bit precision in the result // Test that copying from an RGBA8 texture to RGBA4 results in exactly 4-bit precision in the result
TEST_P(CopyTextureTest, DownsampleRGBA4444) TEST_P(CopyTextureTest, DownsampleRGBA4444)
{ {
......
...@@ -1249,9 +1249,6 @@ TEST_P(MipmapTestES3, BaseLevelTextureBug) ...@@ -1249,9 +1249,6 @@ TEST_P(MipmapTestES3, BaseLevelTextureBug)
// Probably not Intel. // Probably not Intel.
ANGLE_SKIP_TEST_IF(IsOSX() && (IsNVIDIA() || IsIntel())); ANGLE_SKIP_TEST_IF(IsOSX() && (IsNVIDIA() || IsIntel()));
// TODO(cnorthrop): Figure out what's going on here: http://anglebug.com/3950
ANGLE_SKIP_TEST_IF(IsVulkan());
std::vector<GLColor> texDataRed(2u * 2u, GLColor::red); std::vector<GLColor> texDataRed(2u * 2u, GLColor::red);
glBindTexture(GL_TEXTURE_2D, mTexture); glBindTexture(GL_TEXTURE_2D, mTexture);
......
...@@ -417,7 +417,7 @@ class Texture2DTestES3 : public Texture2DTest ...@@ -417,7 +417,7 @@ class Texture2DTestES3 : public Texture2DTest
class Texture2DBaseMaxTestES3 : public ANGLETest class Texture2DBaseMaxTestES3 : public ANGLETest
{ {
protected: protected:
static constexpr size_t kMip0Size = 8; static constexpr size_t kMip0Size = 13;
static constexpr uint32_t kMipCount = 4; static constexpr uint32_t kMipCount = 4;
Texture2DBaseMaxTestES3() : ANGLETest(), mTextureLocation(0), mLodLocation(0) Texture2DBaseMaxTestES3() : ANGLETest(), mTextureLocation(0), mLodLocation(0)
...@@ -432,8 +432,8 @@ class Texture2DBaseMaxTestES3 : public ANGLETest ...@@ -432,8 +432,8 @@ class Texture2DBaseMaxTestES3 : public ANGLETest
static constexpr size_t getMipDataSize(size_t mip0Size, size_t mip) static constexpr size_t getMipDataSize(size_t mip0Size, size_t mip)
{ {
ASSERT(mip0Size % (1ull << mip) == 0); size_t mipSize = std::max<size_t>(1u, mip0Size >> mip);
return mip0Size * mip0Size / (1ull << (2 * mip)); return mipSize * mipSize;
} }
static constexpr size_t getTotalMipDataSize(size_t mip0Size) static constexpr size_t getTotalMipDataSize(size_t mip0Size)
...@@ -469,7 +469,8 @@ class Texture2DBaseMaxTestES3 : public ANGLETest ...@@ -469,7 +469,8 @@ class Texture2DBaseMaxTestES3 : public ANGLETest
return offset; return offset;
} }
void fillMipData(GLColor *data, size_t mip0Size, const GLColor mipColors[kMipCount]) template <typename colorType = GLColor>
void fillMipData(colorType *data, size_t mip0Size, const colorType mipColors[kMipCount])
{ {
for (size_t mip = 0; mip < kMipCount; ++mip) for (size_t mip = 0; mip < kMipCount; ++mip)
{ {
...@@ -1788,6 +1789,40 @@ TEST_P(Texture2DTest, ZeroSizedUploads) ...@@ -1788,6 +1789,40 @@ TEST_P(Texture2DTest, ZeroSizedUploads)
EXPECT_GL_NO_ERROR(); EXPECT_GL_NO_ERROR();
} }
TEST_P(Texture2DTest, DefineMultipleLevelsWithoutMipmapping)
{
setUpProgram();
constexpr size_t kImageSize = 256;
std::array<GLColor, kImageSize * kImageSize> kMipColors[2];
std::fill(kMipColors[0].begin(), kMipColors[0].end(), GLColor::red);
std::fill(kMipColors[1].begin(), kMipColors[1].end(), GLColor::green);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kImageSize, kImageSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
kMipColors[0].data());
EXPECT_GL_NO_ERROR();
// Draw so the image is created.
glUseProgram(mProgram);
glUniform1i(mTexture2DUniformLocation, 0);
drawQuad(mProgram, "position", 0.5f);
// Define level 1 of the texture.
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, kImageSize, kImageSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
kMipColors[1].data());
EXPECT_GL_NO_ERROR();
// Draw again.
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[0][0]);
}
// Test drawing with two texture types, to trigger an ANGLE bug in validation // Test drawing with two texture types, to trigger an ANGLE bug in validation
TEST_P(TextureCubeTest, CubeMapBug) TEST_P(TextureCubeTest, CubeMapBug)
{ {
...@@ -2553,10 +2588,10 @@ TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine) ...@@ -2553,10 +2588,10 @@ TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine)
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Mip 1 is green. Verify this. // Mip 1 is green. Verify this.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[1]);
// http://anglebug.com/4696 // http://anglebug.com/4709
ANGLE_SKIP_TEST_IF(IsVulkan()); ANGLE_SKIP_TEST_IF(IsOpenGL() && (IsIntel() || IsAMD()) && IsWindows());
// Add mip 0 and rebase the mip chain. // Add mip 0 and rebase the mip chain.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE, glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
...@@ -2578,6 +2613,9 @@ TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine) ...@@ -2578,6 +2613,9 @@ TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// http://anglebug.com/4704
ANGLE_SKIP_TEST_IF(IsVulkan());
// Mip 0 data seems to have disappeared in this configuration! http://anglebug.com/4698 // Mip 0 data seems to have disappeared in this configuration! http://anglebug.com/4698
ANGLE_SKIP_TEST_IF(IsOpenGL() && IsNVIDIA() && IsLinux()); ANGLE_SKIP_TEST_IF(IsOpenGL() && IsNVIDIA() && IsLinux());
...@@ -2587,6 +2625,12 @@ TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine) ...@@ -2587,6 +2625,12 @@ TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine)
// Test that changing the base level of a texture multiple times preserves the data. // Test that changing the base level of a texture multiple times preserves the data.
TEST_P(Texture2DBaseMaxTestES3, PingPongBaseLevel) TEST_P(Texture2DBaseMaxTestES3, PingPongBaseLevel)
{ {
// http://anglebug.com/4710
ANGLE_SKIP_TEST_IF(IsD3D());
// http://anglebug.com/4711
ANGLE_SKIP_TEST_IF(IsOpenGL() && IsAMD() && IsWindows());
// http://anglebug.com/4701 // http://anglebug.com/4701
ANGLE_SKIP_TEST_IF(IsOpenGL() && IsIntel() && IsOSX()); ANGLE_SKIP_TEST_IF(IsOpenGL() && IsIntel() && IsOSX());
...@@ -2683,6 +2727,93 @@ TEST_P(Texture2DBaseMaxTestES3, SubImageAfterRedefine) ...@@ -2683,6 +2727,93 @@ TEST_P(Texture2DBaseMaxTestES3, SubImageAfterRedefine)
} }
} }
// Test that incompatibly redefining a level then redefining it back to its original size works.
TEST_P(Texture2DBaseMaxTestES3, IncompatiblyRedefineLevelThenRevert)
{
initTest();
// Test that all mips have the expected data initially (this makes sure the texture image is
// created already).
for (uint32_t lod = 0; lod < kMipCount; ++lod)
{
setLodUniform(lod);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
}
// Redefine Mip 1 to be larger.
constexpr size_t kLargeMip1Size = getMipDataSize(kMip0Size * 2, 1);
std::array<GLColor, kLargeMip1Size> interimMipData;
std::fill(interimMipData.data(), interimMipData.data() + kLargeMip1Size, GLColor::yellow);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
interimMipData.data());
// Redefine Mip 1 back to its original size.
constexpr size_t kNormalMip1Size = getMipDataSize(kMip0Size, 1);
std::array<GLColor, kLargeMip1Size> newMipData;
std::fill(newMipData.data(), newMipData.data() + kNormalMip1Size, GLColor::cyan);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kMip0Size / 2, kMip0Size / 2, 0, GL_RGBA,
GL_UNSIGNED_BYTE, newMipData.data());
// Verify texture colors.
for (uint32_t lod = 0; lod < kMipCount; ++lod)
{
setLodUniform(lod);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, lod == 1 ? GLColor::cyan : kMipColors[lod]);
}
}
// Test that redefining every level of a texture to another format works. The format uses more
// bits per component, to ensure alignment requirements for the new format are taken into account.
TEST_P(Texture2DBaseMaxTestES3, RedefineEveryLevelToAnotherFormat)
{
initTest();
// Test that all mips have the expected data initially (this makes sure the texture image is
// created already).
for (uint32_t lod = 0; lod < kMipCount; ++lod)
{
setLodUniform(lod);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
}
const GLColor32F kNewMipColors[kMipCount] = {
GLColor32F(1.0, 1.0, 0.0, 1.0f),
GLColor32F(1.0, 0.0, 1.0, 1.0f),
GLColor32F(0.0, 1.0, 1.0, 1.0f),
GLColor32F(1.0, 1.0, 1.0, 1.0f),
};
std::array<GLColor32F, getTotalMipDataSize(kMip0Size)> newMipData;
fillMipData(newMipData.data(), kMip0Size, kNewMipColors);
// Redefine every level with the new format.
for (size_t mip = 0; mip < kMipCount; ++mip)
{
glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA32F, kMip0Size >> mip, kMip0Size >> mip, 0, GL_RGBA,
GL_FLOAT, newMipData.data() + getMipDataOffset(kMip0Size, mip));
}
// Verify texture colors.
for (uint32_t lod = 0; lod < kMipCount; ++lod)
{
setLodUniform(lod);
drawQuad(mProgram, "position", 0.5f);
GLColor32F mipColor32F = kNewMipColors[lod];
GLColor mipColor(static_cast<GLubyte>(std::roundf(mipColor32F.R * 255)),
static_cast<GLubyte>(std::roundf(mipColor32F.G * 255)),
static_cast<GLubyte>(std::roundf(mipColor32F.B * 255)),
static_cast<GLubyte>(std::roundf(mipColor32F.A * 255)));
EXPECT_PIXEL_COLOR_EQ(0, 0, mipColor);
}
}
// Test to check that texture completeness is determined correctly when the texture base level is // Test to check that texture completeness is determined correctly when the texture base level is
// greater than 0, and also that level 0 is not sampled when base level is greater than 0. // greater than 0, and also that level 0 is not sampled when base level is greater than 0.
TEST_P(Texture2DTestES3, DrawWithBaseLevel1) TEST_P(Texture2DTestES3, DrawWithBaseLevel1)
......
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