Commit 44168468 by Jamie Madill Committed by Commit Bot

Vulkan: Sync image in TextureVk::syncState.

We can use the DIRTY_BIT_IMPLEMENTATION internal dirty bit in the gl::Texture class to force calls to ContextVk::syncState. In syncState we can ensure we call ensureImageInitialized before we get to the ContextVk. This in turn means we can remove some of the command graph breaks from TextureVk. We need to make sure the dirty bits are propagated to EGL Image siblings with this method. This fixes a potential implementation issue with EGL images with the GL back-end. Also makes a state change test a little better by removing some of the VAO and program init calls before the draw. Improves perf on the texture change microbenchmark by 12.5%. Bug: angleproject:3539 Bug: angleproject:3117 Change-Id: I2b5481690801fa98f859a6c02e3f4b974590cd3d Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1663839 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org>
parent 3ea463bf
......@@ -49,7 +49,6 @@ const Display *DisplayFromContext(const gl::Context *context)
{
return (context ? context->getDisplay() : nullptr);
}
} // anonymous namespace
ImageSibling::ImageSibling() : FramebufferAttachmentObject(), mSourcesOf(), mTargetOf() {}
......@@ -129,6 +128,18 @@ bool ImageSibling::isRenderable(const gl::Context *context,
return mTargetOf->isRenderable(context);
}
void ImageSibling::notifySiblings(angle::SubjectMessage message)
{
if (mTargetOf.get())
{
mTargetOf->notifySiblings(this, message);
}
for (Image *source : mSourcesOf)
{
source->notifySiblings(this, message);
}
}
ExternalImageSibling::ExternalImageSibling(rx::EGLImplFactory *factory,
const gl::Context *context,
EGLenum target,
......@@ -412,4 +423,20 @@ void Image::setInitState(gl::InitState initState)
return mState.source->setInitState(mState.imageIndex, initState);
}
void Image::notifySiblings(const ImageSibling *notifier, angle::SubjectMessage message)
{
if (mState.source && mState.source != notifier)
{
mState.source->onSubjectStateChange(rx::kTextureImageSiblingMessageIndex, message);
}
for (ImageSibling *target : mState.targets)
{
if (target != notifier)
{
target->onSubjectStateChange(rx::kTextureImageSiblingMessageIndex, message);
}
}
}
} // namespace egl
......@@ -24,6 +24,10 @@ namespace rx
class EGLImplFactory;
class ImageImpl;
class ExternalImageSiblingImpl;
// Used for distinguishing dirty bit messages from gl::Texture/rx::TexureImpl/gl::Image.
constexpr size_t kTextureImageImplObserverMessageIndex = 0;
constexpr size_t kTextureImageSiblingMessageIndex = 1;
} // namespace rx
namespace egl
......@@ -34,7 +38,7 @@ class Display;
// Only currently Renderbuffers and Textures can be bound with images. This makes the relationship
// explicit, and also ensures that an image sibling can determine if it's been initialized or not,
// which is important for the robust resource init extension with Textures and EGLImages.
class ImageSibling : public gl::FramebufferAttachmentObject
class ImageSibling : public gl::FramebufferAttachmentObject, public angle::ObserverInterface
{
public:
ImageSibling();
......@@ -48,6 +52,12 @@ class ImageSibling : public gl::FramebufferAttachmentObject
GLenum binding,
const gl::ImageIndex &imageIndex) const override;
// ObserverInterface implementation
void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override
{
// default to no-op.
}
protected:
// Set the image target of this sibling
void setTargetImage(const gl::Context *context, egl::Image *imageTarget);
......@@ -55,6 +65,8 @@ class ImageSibling : public gl::FramebufferAttachmentObject
// Orphan all EGL image sources and targets
angle::Result orphanImages(const gl::Context *context);
void notifySiblings(angle::SubjectMessage message);
private:
friend class Image;
......@@ -166,6 +178,8 @@ class Image final : public RefCountObject, public LabeledObject
// been respecified and state tracking should be updated.
angle::Result orphanSibling(const gl::Context *context, ImageSibling *sibling);
void notifySiblings(const ImageSibling *notifier, angle::SubjectMessage message);
ImageState mState;
rx::ImageImpl *mImplementation;
bool mOrphanedAndNeedsInit;
......
......@@ -48,7 +48,6 @@ InitState DetermineInitState(const Context *context, const uint8_t *pixels)
? InitState::MayNeedInit
: InitState::Initialized;
}
} // namespace
bool IsMipmapFiltered(const SamplerState &samplerState)
......@@ -620,7 +619,7 @@ Texture::Texture(rx::GLImplFactory *factory, GLuint id, TextureType type)
: RefCountObject(id),
mState(type),
mTexture(factory->createTexture(mState)),
mImplObserver(this, 0),
mImplObserver(this, rx::kTextureImageImplObserverMessageIndex),
mLabel(),
mBoundSurface(nullptr),
mBoundStream(nullptr)
......@@ -1842,5 +1841,11 @@ void Texture::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMess
ASSERT(message == angle::SubjectMessage::SubjectChanged);
mDirtyBits.set(DIRTY_BIT_IMPLEMENTATION);
signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
// Notify siblings that we are dirty.
if (index == rx::kTextureImageImplObserverMessageIndex)
{
notifySiblings(message);
}
}
} // namespace gl
......@@ -211,10 +211,7 @@ class TextureState final : private angle::NonCopyable
bool operator==(const TextureState &a, const TextureState &b);
bool operator!=(const TextureState &a, const TextureState &b);
class Texture final : public RefCountObject,
public egl::ImageSibling,
public LabeledObject,
public angle::ObserverInterface
class Texture final : public RefCountObject, public egl::ImageSibling, public LabeledObject
{
public:
Texture(rx::GLImplFactory *factory, GLuint id, TextureType type);
......
......@@ -2042,11 +2042,12 @@ angle::Result ContextVk::updateActiveTextures(const gl::Context *context)
TextureVk *textureVk = vk::GetImpl(texture);
// Ensure any writes to the textures are flushed before we read from them.
ANGLE_TRY(textureVk->ensureImageInitialized(this));
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.
// Ensure the image is in read-only layout
if (image.isLayoutChangeNecessary(vk::ImageLayout::FragmentShaderReadOnly))
{
......
......@@ -99,6 +99,7 @@ angle::Result TextureVk::generateMipmapLevelsWithCPU(ContextVk *contextVk,
contextVk, mipAllocationSize,
gl::ImageIndex::MakeFromType(mState.getType(), currentMipLevel, layer), mipLevelExtents,
gl::Offset(), &destData));
onStagingBufferChange();
// Generate the mipmap into that new buffer
sourceFormat.mipGenerationFunction(previousLevelWidth, previousLevelHeight, 1,
......@@ -235,9 +236,7 @@ angle::Result TextureVk::setSubImageImpl(const gl::Context *context,
ANGLE_TRY(mImage->stageSubresourceUpdate(
contextVk, getNativeImageIndex(index), gl::Extents(area.width, area.height, area.depth),
gl::Offset(area.x, area.y, area.z), formatInfo, unpack, type, pixels, vkFormat));
// Create a new graph node to store image initialization commands.
mImage->finishCurrentCommands(contextVk);
onStagingBufferChange();
}
return angle::Result::Continue;
......@@ -408,8 +407,8 @@ angle::Result TextureVk::copySubImageImpl(const gl::Context *context,
context, offsetImageIndex, clippedSourceArea, modifiedDestOffset,
gl::Extents(clippedSourceArea.width, clippedSourceArea.height, 1), internalFormat,
framebufferVk));
onStagingBufferChange();
mImage->finishCurrentCommands(contextVk);
framebufferVk->getFramebuffer()->addReadDependency(mImage);
return angle::Result::Continue;
}
......@@ -473,6 +472,7 @@ angle::Result TextureVk::copySubTextureImpl(ContextVk *contextVk,
ANGLE_TRY(mImage->stageSubresourceUpdateAndGetData(
contextVk, destinationAllocationSize, offsetImageIndex,
gl::Extents(sourceArea.width, sourceArea.height, 1), destOffset, &destData));
onStagingBufferChange();
// Source and dest data is tightly packed
GLuint sourceDataRowPitch = sourceArea.width * sourceTextureFormat.pixelBytes;
......@@ -499,9 +499,6 @@ angle::Result TextureVk::copySubTextureImpl(ContextVk *contextVk,
sourceArea.width, sourceArea.height, 1, unpackFlipY, unpackPremultiplyAlpha,
unpackUnmultiplyAlpha);
// Create a new graph node to store image initialization commands.
mImage->finishCurrentCommands(contextVk);
return angle::Result::Continue;
}
......@@ -588,6 +585,7 @@ angle::Result TextureVk::copySubImageImplWithTransfer(ContextVk *contextVk,
// Stage the copy for when the image storage is actually created.
mImage->stageSubresourceUpdateFromImage(stagingImage.release(), index, destOffset, extents);
onStagingBufferChange();
}
return angle::Result::Continue;
......@@ -684,6 +682,7 @@ angle::Result TextureVk::copySubImageImplWithDraw(ContextVk *contextVk,
mImage->stageSubresourceUpdateFromImage(
stagingImage.release(), index, destOffset,
gl::Extents(sourceArea.width, sourceArea.height, 1));
onStagingBufferChange();
}
return angle::Result::Continue;
......@@ -1041,9 +1040,6 @@ angle::Result TextureVk::generateMipmap(const gl::Context *context)
ANGLE_TRY(generateMipmapsWithCPU(context));
}
// We're changing this textureVk content, make sure we let the graph know.
mImage->finishCurrentCommands(contextVk);
return angle::Result::Continue;
}
......@@ -1174,12 +1170,16 @@ angle::Result TextureVk::initCubeMapRenderTargets(ContextVk *contextVk)
angle::Result TextureVk::syncState(const gl::Context *context,
const gl::Texture::DirtyBits &dirtyBits)
{
ContextVk *contextVk = vk::GetImpl(context);
// Initialize the image storage and flush the pixel buffer.
ANGLE_TRY(ensureImageInitialized(contextVk));
if (dirtyBits.none() && mSampler.valid())
{
return angle::Result::Continue;
}
ContextVk *contextVk = vk::GetImpl(context);
RendererVk *renderer = contextVk->getRenderer();
if (mSampler.valid())
{
......@@ -1378,6 +1378,7 @@ angle::Result TextureVk::initImage(ContextVk *contextVk,
{
gl::ImageIndex index = gl::ImageIndex::Make2DArrayRange(level, 0, layerCount);
mImage->stageSubresourceEmulatedClear(index, format.angleFormat());
onStagingBufferChange();
}
}
......@@ -1451,6 +1452,8 @@ void TextureVk::releaseImage(ContextVk *contextVk)
releaseImageViews(contextVk);
mCubeMapRenderTargets.clear();
onStagingBufferChange();
}
void TextureVk::releaseImageViews(ContextVk *contextVk)
......
......@@ -280,6 +280,8 @@ class TextureVk : public TextureImpl
uint32_t levelCount,
const vk::Format &format);
void onStagingBufferChange() { onStateChange(angle::SubjectMessage::SubjectChanged); }
bool mOwnsImage;
gl::TextureType mImageNativeType;
......
......@@ -1761,6 +1761,20 @@ TEST_P(SimpleStateChangeTest, UpdateTextureInUse)
{
std::array<GLColor, 4> rgby = {{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
// Set up 2D quad resources.
GLuint program = get2DTexturedQuadProgram();
glUseProgram(program);
ASSERT_EQ(0, glGetAttribLocation(program, "position"));
const auto &quadVerts = GetQuadVertices();
GLBuffer vbo;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0);
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgby.data());
......@@ -1768,29 +1782,32 @@ TEST_P(SimpleStateChangeTest, UpdateTextureInUse)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Draw RGBY to the Framebuffer. The texture is now in-use by GL.
draw2DTexturedQuad(0.5f, 1.0f, true);
const int w = getWindowWidth() - 2;
const int h = getWindowHeight() - 2;
const int w2 = w >> 1;
glViewport(0, 0, w2, h);
glDrawArrays(GL_TRIANGLES, 0, 6);
// Update the texture to be YBGR, while the Texture is in-use. Should not affect the draw.
std::array<GLColor, 4> ybgr = {{GLColor::yellow, GLColor::blue, GLColor::green, GLColor::red}};
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, ybgr.data());
ASSERT_GL_NO_ERROR();
// Check the Framebuffer. The draw call should have completed with the original RGBY data.
int w = getWindowWidth() - 2;
int h = getWindowHeight() - 2;
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
// Draw again to the Framebuffer. The second draw call should use the updated YBGR data.
draw2DTexturedQuad(0.5f, 1.0f, true);
glViewport(w2, 0, w2, h);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::red);
// Check the Framebuffer. Both draws should have completed.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(w2 - 1, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, h - 1, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w2 - 1, h - 1, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(w2 + 1, 0, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(w - 1, 0, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w2 + 1, h - 1, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(w - 1, h - 1, GLColor::red);
ASSERT_GL_NO_ERROR();
}
......
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