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;
......
...@@ -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