diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp index 5c9f231a1..d5650b76b 100644 --- a/src/libANGLE/renderer/vulkan/ContextVk.cpp +++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp @@ -1093,19 +1093,54 @@ ANGLE_INLINE angle::Result ContextVk::handleDirtyTexturesImpl(const gl::Context vk::CommandBuffer *commandBuffer, vk::CommandGraphResource *recorder) { - if (commandGraphEnabled()) - { - ANGLE_TRY(updateActiveTextures(context)); + const gl::ActiveTextureMask &activeTextures = mProgram->getState().getActiveSamplersMask(); - const gl::ActiveTextureMask &activeTextures = mProgram->getState().getActiveSamplersMask(); - for (size_t textureUnit : activeTextures) + for (size_t textureUnit : activeTextures) + { + const vk::TextureUnit &unit = mActiveTextures[textureUnit]; + TextureVk *textureVk = unit.texture; + vk::ImageHelper &image = textureVk->getImage(); + + // The image should be flushed and ready to use at this point. There may still be + // lingering staged updates in its staging buffer for unused texture mip levels or + // layers. Therefore we can't verify it has no staged updates right here. + + vk::ImageLayout textureLayout = vk::ImageLayout::AllGraphicsShadersReadOnly; + if (mProgram->getState().isCompute()) { - vk::TextureUnit &unit = mActiveTextures[textureUnit]; - TextureVk *textureVk = unit.texture; - ASSERT(textureVk); - vk::ImageHelper &image = textureVk->getImage(); + textureLayout = vk::ImageLayout::ComputeShaderReadOnly; + } + + // Ensure the image is in read-only layout + if (commandGraphEnabled()) + { + if (image.isLayoutChangeNecessary(textureLayout)) + { + vk::CommandBuffer *srcLayoutChange; + VkImageAspectFlags aspectFlags = image.getAspectFlags(); + ASSERT(aspectFlags != 0); + ANGLE_TRY(image.recordCommands(this, &srcLayoutChange)); + image.changeLayout(aspectFlags, textureLayout, srcLayoutChange); + } + image.addReadDependency(this, recorder); } + else + { + mRenderPassCommands.imageRead(&mResourceUseList, image.getAspectFlags(), textureLayout, + &image); + } + + textureVk->onImageViewUse(&mResourceUseList); + + if (unit.sampler) + { + unit.sampler->onSamplerAccess(&mResourceUseList); + } + else + { + textureVk->onSamplerUse(&mResourceUseList); + } } if (mProgram->hasTextures()) @@ -2831,10 +2866,7 @@ angle::Result ContextVk::invalidateCurrentTextures(const gl::Context *context) mGraphicsDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS); mComputeDirtyBits.set(DIRTY_BIT_TEXTURES); mComputeDirtyBits.set(DIRTY_BIT_DESCRIPTOR_SETS); - } - if (!commandGraphEnabled()) - { ANGLE_TRY(updateActiveTextures(context)); } @@ -3294,46 +3326,13 @@ angle::Result ContextVk::updateActiveTextures(const gl::Context *context) { samplerVk = nullptr; samplerSerial = kZeroSerial; - textureVk->onSamplerUse(&mResourceUseList); } else { samplerVk = vk::GetImpl(sampler); samplerSerial = samplerVk->getSerial(); - samplerVk->onSamplerAccess(&mResourceUseList); } - vk::ImageHelper &image = textureVk->getImage(); - - // The image should be flushed and ready to use at this point. There may still be - // lingering staged updates in its staging buffer for unused texture mip levels or - // layers. Therefore we can't verify it has no staged updates right here. - - vk::ImageLayout textureLayout = vk::ImageLayout::AllGraphicsShadersReadOnly; - if (program->isCompute()) - { - textureLayout = vk::ImageLayout::ComputeShaderReadOnly; - } - - // Ensure the image is in read-only layout - if (commandGraphEnabled()) - { - if (image.isLayoutChangeNecessary(textureLayout)) - { - vk::CommandBuffer *srcLayoutChange; - VkImageAspectFlags aspectFlags = image.getAspectFlags(); - ASSERT(aspectFlags != 0); - ANGLE_TRY(image.recordCommands(this, &srcLayoutChange)); - image.changeLayout(aspectFlags, textureLayout, srcLayoutChange); - } - } - else - { - ANGLE_TRY(onImageRead(image.getAspectFlags(), textureLayout, &image)); - } - - textureVk->onImageViewUse(&mResourceUseList); - mActiveTextures[textureUnit].texture = textureVk; mActiveTextures[textureUnit].sampler = samplerVk; // Cache serials from sampler and texture, but re-use texture if no sampler bound @@ -3860,8 +3859,17 @@ angle::Result ContextVk::endRenderPass() return mRenderPassCommands.flushToPrimary(this, &mPrimaryCommands); } +void ContextVk::onRenderPassImageWrite(VkImageAspectFlags aspectFlags, + vk::ImageLayout imageLayout, + vk::ImageHelper *image) +{ + mRenderPassCommands.imageWrite(&mResourceUseList, aspectFlags, imageLayout, image); +} + CommandBufferHelper::CommandBufferHelper() - : mGlobalMemoryBarrierSrcAccess(0), + : mImageBarrierSrcStageMask(0), + mImageBarrierDstStageMask(0), + mGlobalMemoryBarrierSrcAccess(0), mGlobalMemoryBarrierDstAccess(0), mGlobalMemoryBarrierStages(0) {} @@ -3888,23 +3896,81 @@ void CommandBufferHelper::bufferWrite(vk::ResourceUseList *resourceUseList, mGlobalMemoryBarrierStages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; } -void CommandBufferHelper::recordBarrier(vk::PrimaryCommandBuffer *primary) +void CommandBufferHelper::imageBarrier(VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + const VkImageMemoryBarrier &imageMemoryBarrier) { - if (mGlobalMemoryBarrierSrcAccess == 0) + ASSERT(imageMemoryBarrier.pNext == nullptr); + mImageBarrierSrcStageMask |= srcStageMask; + mImageBarrierDstStageMask |= dstStageMask; + mImageMemoryBarriers.push_back(imageMemoryBarrier); +} + +void CommandBufferHelper::imageRead(vk::ResourceUseList *resourceUseList, + VkImageAspectFlags aspectFlags, + vk::ImageLayout imageLayout, + vk::ImageHelper *image) +{ + image->onResourceAccess(resourceUseList); + if (image->isLayoutChangeNecessary(imageLayout)) + { + image->changeLayout(aspectFlags, imageLayout, this); + } +} + +void CommandBufferHelper::imageWrite(vk::ResourceUseList *resourceUseList, + VkImageAspectFlags aspectFlags, + vk::ImageLayout imageLayout, + vk::ImageHelper *image) +{ + image->onResourceAccess(resourceUseList); + image->changeLayout(aspectFlags, imageLayout, this); +} + +void CommandBufferHelper::executeBarriers(vk::PrimaryCommandBuffer *primary) +{ + if (mImageMemoryBarriers.empty() && mGlobalMemoryBarrierSrcAccess == 0) { return; } + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + VkMemoryBarrier memoryBarrier = {}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - memoryBarrier.srcAccessMask = mGlobalMemoryBarrierSrcAccess; - memoryBarrier.dstAccessMask = mGlobalMemoryBarrierDstAccess; + uint32_t memoryBarrierCount = 0; - primary->memoryBarrier(mGlobalMemoryBarrierStages, mGlobalMemoryBarrierStages, &memoryBarrier); + if (mGlobalMemoryBarrierSrcAccess != 0) + { + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = mGlobalMemoryBarrierSrcAccess; + memoryBarrier.dstAccessMask = mGlobalMemoryBarrierDstAccess; - mGlobalMemoryBarrierSrcAccess = 0; - mGlobalMemoryBarrierDstAccess = 0; - mGlobalMemoryBarrierStages = 0; + memoryBarrierCount++; + srcStages |= mGlobalMemoryBarrierStages; + dstStages |= mGlobalMemoryBarrierStages; + + mGlobalMemoryBarrierSrcAccess = 0; + mGlobalMemoryBarrierDstAccess = 0; + mGlobalMemoryBarrierStages = 0; + } + + if (!mImageMemoryBarriers.empty()) + { + srcStages |= mImageBarrierSrcStageMask; + dstStages |= mImageBarrierDstStageMask; + primary->pipelineBarrier(srcStages, dstStages, 0, memoryBarrierCount, &memoryBarrier, 0, + nullptr, static_cast(mImageMemoryBarriers.size()), + mImageMemoryBarriers.data()); + mImageMemoryBarriers.clear(); + mImageBarrierSrcStageMask = 0; + mImageBarrierDstStageMask = 0; + } + else + { + primary->pipelineBarrier(srcStages, dstStages, 0, memoryBarrierCount, &memoryBarrier, 0, + nullptr, 0, nullptr); + } } OutsideRenderPassCommandBuffer::OutsideRenderPassCommandBuffer() = default; @@ -3916,7 +3982,7 @@ void OutsideRenderPassCommandBuffer::flushToPrimary(vk::PrimaryCommandBuffer *pr if (empty()) return; - recordBarrier(primary); + executeBarriers(primary); mCommandBuffer.executeCommands(primary->getHandle()); // Restart secondary buffer. @@ -3969,7 +4035,7 @@ angle::Result RenderPassCommandBuffer::flushToPrimary(ContextVk *contextVk, if (empty()) return angle::Result::Continue; - recordBarrier(primary); + executeBarriers(primary); // Pull a RenderPass from the cache. RenderPassCache &renderPassCache = contextVk->getRenderPassCache(); diff --git a/src/libANGLE/renderer/vulkan/ContextVk.h b/src/libANGLE/renderer/vulkan/ContextVk.h index f8afa3868..6dd9d979f 100644 --- a/src/libANGLE/renderer/vulkan/ContextVk.h +++ b/src/libANGLE/renderer/vulkan/ContextVk.h @@ -105,14 +105,31 @@ struct CommandBufferHelper : angle::NonCopyable VkAccessFlags writeAccessType, vk::BufferHelper *buffer); + void imageRead(vk::ResourceUseList *resourceUseList, + VkImageAspectFlags aspectFlags, + vk::ImageLayout imageLayout, + vk::ImageHelper *image); + + void imageWrite(vk::ResourceUseList *resourceUseList, + VkImageAspectFlags aspectFlags, + vk::ImageLayout imageLayout, + vk::ImageHelper *image); + + void imageBarrier(VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + const VkImageMemoryBarrier &imageMemoryBarrier); + vk::CommandBuffer &getCommandBuffer() { return mCommandBuffer; } protected: CommandBufferHelper(); ~CommandBufferHelper(); - void recordBarrier(vk::PrimaryCommandBuffer *primary); + void executeBarriers(vk::PrimaryCommandBuffer *primary); + VkPipelineStageFlags mImageBarrierSrcStageMask; + VkPipelineStageFlags mImageBarrierDstStageMask; + std::vector mImageMemoryBarriers; VkFlags mGlobalMemoryBarrierSrcAccess; VkFlags mGlobalMemoryBarrierDstAccess; VkPipelineStageFlags mGlobalMemoryBarrierStages; @@ -559,6 +576,10 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::RenderPassO vk::ImageLayout imageLayout, vk::ImageHelper *image); + void onRenderPassImageWrite(VkImageAspectFlags aspectFlags, + vk::ImageLayout imageLayout, + vk::ImageHelper *image); + angle::Result getOutsideRenderPassCommandBuffer(vk::CommandBuffer **commandBufferOut) { if (!mRenderPassCommands.empty()) diff --git a/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp b/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp index 55cbea644..154a09449 100644 --- a/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp +++ b/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp @@ -71,8 +71,8 @@ angle::Result RenderTargetVk::onColorDraw(ContextVk *contextVk, } else { - ANGLE_TRY(contextVk->onImageWrite(VK_IMAGE_ASPECT_COLOR_BIT, - vk::ImageLayout::ColorAttachment, mImage)); + contextVk->onRenderPassImageWrite(VK_IMAGE_ASPECT_COLOR_BIT, + vk::ImageLayout::ColorAttachment, mImage); } onImageViewAccess(contextVk); @@ -99,8 +99,8 @@ angle::Result RenderTargetVk::onDepthStencilDraw(ContextVk *contextVk, } else { - ANGLE_TRY( - contextVk->onImageWrite(aspectFlags, vk::ImageLayout::DepthStencilAttachment, mImage)); + contextVk->onRenderPassImageWrite(aspectFlags, vk::ImageLayout::DepthStencilAttachment, + mImage); } onImageViewAccess(contextVk); diff --git a/src/libANGLE/renderer/vulkan/SecondaryCommandBuffer.h b/src/libANGLE/renderer/vulkan/SecondaryCommandBuffer.h index 9bf4acba1..439839752 100644 --- a/src/libANGLE/renderer/vulkan/SecondaryCommandBuffer.h +++ b/src/libANGLE/renderer/vulkan/SecondaryCommandBuffer.h @@ -559,7 +559,7 @@ class SecondaryCommandBuffer final : angle::NonCopyable void imageBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, - const VkImageMemoryBarrier *imageMemoryBarrier); + const VkImageMemoryBarrier &imageMemoryBarrier); void memoryBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, @@ -1129,12 +1129,13 @@ ANGLE_INLINE void SecondaryCommandBuffer::fillBuffer(const Buffer &dstBuffer, ANGLE_INLINE void SecondaryCommandBuffer::imageBarrier( VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, - const VkImageMemoryBarrier *imageMemoryBarrier) + const VkImageMemoryBarrier &imageMemoryBarrier) { ImageBarrierParams *paramStruct = initCommand(CommandID::ImageBarrier); + ASSERT(imageMemoryBarrier.pNext == nullptr); paramStruct->srcStageMask = srcStageMask; paramStruct->dstStageMask = dstStageMask; - paramStruct->imageMemoryBarrier = *imageMemoryBarrier; + paramStruct->imageMemoryBarrier = imageMemoryBarrier; } ANGLE_INLINE void SecondaryCommandBuffer::memoryBarrier(VkPipelineStageFlags srcStageMask, diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp index a754647f6..e12ffb4ba 100644 --- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp +++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp @@ -2053,18 +2053,6 @@ bool ImageHelper::isLayoutChangeNecessary(ImageLayout newLayout) const return !sameLayoutAndNoNeedForBarrier; } -void ImageHelper::changeLayout(VkImageAspectFlags aspectMask, - ImageLayout newLayout, - CommandBuffer *commandBuffer) -{ - if (!isLayoutChangeNecessary(newLayout)) - { - return; - } - - forceChangeLayoutAndQueue(aspectMask, newLayout, mCurrentQueueFamilyIndex, commandBuffer); -} - void ImageHelper::changeLayoutAndQueue(VkImageAspectFlags aspectMask, ImageLayout newLayout, uint32_t newQueueFamilyIndex, @@ -2094,10 +2082,12 @@ void ImageHelper::setBaseAndMaxLevels(uint32_t baseLevel, uint32_t maxLevel) mMaxLevel = maxLevel; } +// Generalized to accept both "primary" and "secondary" command buffers. +template void ImageHelper::forceChangeLayoutAndQueue(VkImageAspectFlags aspectMask, ImageLayout newLayout, uint32_t newQueueFamilyIndex, - CommandBuffer *commandBuffer) + CommandBufferT *commandBuffer) { const ImageMemoryBarrierData &transitionFrom = kImageMemoryBarrierData[mCurrentLayout]; const ImageMemoryBarrierData &transitionTo = kImageMemoryBarrierData[newLayout]; @@ -2112,7 +2102,7 @@ void ImageHelper::forceChangeLayoutAndQueue(VkImageAspectFlags aspectMask, imageMemoryBarrier.dstQueueFamilyIndex = newQueueFamilyIndex; imageMemoryBarrier.image = mImage.getHandle(); - // TODO(jmadill): Is this needed for mipped/layer images? + // Transition the whole resource. imageMemoryBarrier.subresourceRange.aspectMask = aspectMask; imageMemoryBarrier.subresourceRange.baseMipLevel = 0; imageMemoryBarrier.subresourceRange.levelCount = mLevelCount; @@ -2120,11 +2110,17 @@ void ImageHelper::forceChangeLayoutAndQueue(VkImageAspectFlags aspectMask, imageMemoryBarrier.subresourceRange.layerCount = mLayerCount; commandBuffer->imageBarrier(transitionFrom.srcStageMask, transitionTo.dstStageMask, - &imageMemoryBarrier); + imageMemoryBarrier); mCurrentLayout = newLayout; mCurrentQueueFamilyIndex = newQueueFamilyIndex; } +// Explicitly instantiate forceChangeLayoutAndQueue with CommandBufferHelper. +template void ImageHelper::forceChangeLayoutAndQueue(VkImageAspectFlags aspectMask, + ImageLayout newLayout, + uint32_t newQueueFamilyIndex, + CommandBufferHelper *commandBuffer); + void ImageHelper::clearColor(const VkClearColorValue &color, uint32_t baseMipLevel, uint32_t levelCount, @@ -2278,7 +2274,7 @@ angle::Result ImageHelper::generateMipmapsWithBlit(ContextVk *contextVk, GLuint // We can do it for all layers at once. commandBuffer->imageBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - &barrier); + barrier); VkImageBlit blit = {}; blit.srcOffsets[0] = {0, 0, 0}; blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; @@ -2313,7 +2309,7 @@ angle::Result ImageHelper::generateMipmapsWithBlit(ContextVk *contextVk, GLuint // We can do it for all layers at once. commandBuffer->imageBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - &barrier); + barrier); // This is just changing the internal state of the image helper so that the next call // to changeLayout will use this layout as the "oldLayout" argument. mCurrentLayout = ImageLayout::TransferSrc; diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h index ea051e71c..e188896c0 100644 --- a/src/libANGLE/renderer/vulkan/vk_helpers.h +++ b/src/libANGLE/renderer/vulkan/vk_helpers.h @@ -892,9 +892,18 @@ class ImageHelper final : public CommandGraphResource // purpose of performing a transition (which may then not be issued). bool isLayoutChangeNecessary(ImageLayout newLayout) const; + template void changeLayout(VkImageAspectFlags aspectMask, ImageLayout newLayout, - CommandBuffer *commandBuffer); + CommandBufferT *commandBuffer) + { + if (!isLayoutChangeNecessary(newLayout)) + { + return; + } + + forceChangeLayoutAndQueue(aspectMask, newLayout, mCurrentQueueFamilyIndex, commandBuffer); + } bool isQueueChangeNeccesary(uint32_t newQueueFamilyIndex) const { @@ -962,10 +971,12 @@ class ImageHelper final : public CommandGraphResource GLuint *inputSkipBytes); private: + // Generalized to accept both "primary" and "secondary" command buffers. + template void forceChangeLayoutAndQueue(VkImageAspectFlags aspectMask, ImageLayout newLayout, uint32_t newQueueFamilyIndex, - CommandBuffer *commandBuffer); + CommandBufferT *commandBuffer); void stageSubresourceClear(const gl::ImageIndex &index, const angle::Format &format, diff --git a/src/libANGLE/renderer/vulkan/vk_wrapper.h b/src/libANGLE/renderer/vulkan/vk_wrapper.h index a8e1d7e07..97c051eed 100644 --- a/src/libANGLE/renderer/vulkan/vk_wrapper.h +++ b/src/libANGLE/renderer/vulkan/vk_wrapper.h @@ -310,7 +310,7 @@ class CommandBuffer : public WrappedObject void imageBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, - const VkImageMemoryBarrier *imageMemoryBarrier); + const VkImageMemoryBarrier &imageMemoryBarrier); void memoryBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, @@ -702,11 +702,11 @@ ANGLE_INLINE void CommandBuffer::bufferBarrier(VkPipelineStageFlags srcStageMask ANGLE_INLINE void CommandBuffer::imageBarrier(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, - const VkImageMemoryBarrier *imageMemoryBarrier) + const VkImageMemoryBarrier &imageMemoryBarrier) { ASSERT(valid()); vkCmdPipelineBarrier(mHandle, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, - imageMemoryBarrier); + &imageMemoryBarrier); } ANGLE_INLINE void CommandBuffer::destroy(VkDevice device) diff --git a/src/tests/gl_tests/ClearTest.cpp b/src/tests/gl_tests/ClearTest.cpp index aaa9c88c1..f83838fab 100644 --- a/src/tests/gl_tests/ClearTest.cpp +++ b/src/tests/gl_tests/ClearTest.cpp @@ -15,26 +15,6 @@ using namespace angle; namespace { - -Vector4 RandomVec4(int seed, float minValue, float maxValue) -{ - RNG rng(seed); - srand(seed); - return Vector4( - rng.randomFloatBetween(minValue, maxValue), rng.randomFloatBetween(minValue, maxValue), - rng.randomFloatBetween(minValue, maxValue), rng.randomFloatBetween(minValue, maxValue)); -} - -GLColor Vec4ToColor(const Vector4 &vec) -{ - GLColor color; - color.R = static_cast(vec.x() * 255.0f); - color.G = static_cast(vec.y() * 255.0f); - color.B = static_cast(vec.z() * 255.0f); - color.A = static_cast(vec.w() * 255.0f); - return color; -} - class ClearTestBase : public ANGLETest { protected: @@ -1214,9 +1194,9 @@ TEST_P(ClearTestES3, RepeatedClear) { for (int cellX = 0; cellX < numRowsCols; cellX++) { - int seed = cellX + cellY * numRowsCols; - const Vector4 color = RandomVec4(seed, fmtValueMin, fmtValueMax); - GLColor expectedColor = Vec4ToColor(color); + int seed = cellX + cellY * numRowsCols; + const Vector4 color = RandomVec4(seed, fmtValueMin, fmtValueMax); + GLColor expectedColor(color); int testN = cellX * cellSize + cellY * backFBOSize * cellSize + backFBOSize + 1; GLColor actualColor = pixelData[testN]; diff --git a/src/tests/gl_tests/StateChangeTest.cpp b/src/tests/gl_tests/StateChangeTest.cpp index 2a5dd06bd..641734502 100644 --- a/src/tests/gl_tests/StateChangeTest.cpp +++ b/src/tests/gl_tests/StateChangeTest.cpp @@ -10,6 +10,7 @@ #include "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" +#include "util/random_utils.h" using namespace angle; @@ -2596,6 +2597,82 @@ TEST_P(SimpleStateChangeTest, RedefineFramebufferTexture) EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green) << "second draw should be green"; } +// Trips a bug in the Vulkan back-end where a Texture wouldn't transition correctly. +TEST_P(SimpleStateChangeTest, DrawAndClearTextureRepeatedly) +{ + // Fails on 431.02 driver. http://anglebug.com/3748 + ANGLE_SKIP_TEST_IF(IsWindows() && IsNVIDIA() && IsVulkan()); + + // Fails on AMD OpenGL Windows. This configuration isn't maintained. + ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL()); + + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); + + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + 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_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + ASSERT_GL_NO_ERROR(); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + ASSERT_GL_NO_ERROR(); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + glUseProgram(program); + + GLint uniLoc = glGetUniformLocation(program, essl1_shaders::Texture2DUniform()); + ASSERT_NE(-1, uniLoc); + glUniform1i(uniLoc, 0); + + const int numRowsCols = 2; + const int cellSize = getWindowWidth() / 2; + + for (int cellY = 0; cellY < numRowsCols; cellY++) + { + for (int cellX = 0; cellX < numRowsCols; cellX++) + { + int seed = cellX + cellY * numRowsCols; + const Vector4 color = RandomVec4(seed, 0.0f, 1.0f); + + // Set the texture to a constant color using glClear and a user FBO. + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClearColor(color[0], color[1], color[2], color[3]); + glClear(GL_COLOR_BUFFER_BIT); + + // Draw a small colored quad to the default FBO using the viewport. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(cellX * cellSize, cellY * cellSize, cellSize, cellSize); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); + } + } + + // Verify the colored quads were drawn correctly despite no flushing. + std::vector pixelData(getWindowWidth() * getWindowHeight()); + glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE, + pixelData.data()); + + ASSERT_GL_NO_ERROR(); + + for (int cellY = 0; cellY < numRowsCols; cellY++) + { + for (int cellX = 0; cellX < numRowsCols; cellX++) + { + int seed = cellX + cellY * numRowsCols; + const Vector4 color = RandomVec4(seed, 0.0f, 1.0f); + + GLColor expectedColor(color); + + int testN = + cellX * cellSize + cellY * getWindowWidth() * cellSize + getWindowWidth() + 1; + GLColor actualColor = pixelData[testN]; + EXPECT_COLOR_NEAR(expectedColor, actualColor, 1); + } + } +} + // Validates disabling cull face really disables it. TEST_P(SimpleStateChangeTest, EnableAndDisableCullFace) { diff --git a/util/random_utils.h b/util/random_utils.h index 055d60a0a..4cc92dad3 100644 --- a/util/random_utils.h +++ b/util/random_utils.h @@ -10,10 +10,10 @@ #ifndef UTIL_RANDOM_UTILS_H #define UTIL_RANDOM_UTILS_H -// TODO(jmadill): Rework this if Chromium decides to ban #include #include +#include "common/vector_utils.h" #include "util/util_export.h" namespace angle @@ -70,6 +70,14 @@ inline void FillVectorWithRandomUBytes(std::vector *data) FillVectorWithRandomUBytes(&rng, data); } +inline Vector4 RandomVec4(int seed, float minValue, float maxValue) +{ + RNG rng(seed); + srand(seed); + return Vector4( + rng.randomFloatBetween(minValue, maxValue), rng.randomFloatBetween(minValue, maxValue), + rng.randomFloatBetween(minValue, maxValue), rng.randomFloatBetween(minValue, maxValue)); +} } // namespace angle #endif // UTIL_RANDOM_UTILS_H