Commit 4f7edbe1 by Jamie Madill Committed by Commit Bot

State: Add dirty object for active textures.

Checking sampler completeness after every sampler related state change is inefficient. Defer sampler completeness check to draw time. Based on a CL by hckim.kim@samsung.com. Bug: angleproject:4765 Tests: angle_perftests.exe --gtest_filter=TexturesBenchmark.* Change-Id: I7e971371e40b3044ca3d5ca39bfe7fc600620c3e Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2268577 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent 24c2f0e7
......@@ -481,6 +481,7 @@ void Context::initialize()
// Initialize dirty bit masks
mAllDirtyBits.set();
mDrawDirtyObjects.set(State::DIRTY_OBJECT_ACTIVE_TEXTURES);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_DRAW_FRAMEBUFFER);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_VERTEX_ARRAY);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_TEXTURES);
......@@ -532,6 +533,7 @@ void Context::initialize()
mComputeDirtyBits.set(State::DIRTY_BIT_SAMPLER_BINDINGS);
mComputeDirtyBits.set(State::DIRTY_BIT_IMAGE_BINDINGS);
mComputeDirtyBits.set(State::DIRTY_BIT_DISPATCH_INDIRECT_BUFFER_BINDING);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_ACTIVE_TEXTURES);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_TEXTURES);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM_PIPELINE);
......
......@@ -504,13 +504,15 @@ void State::initialize(Context *context)
void State::reset(const Context *context)
{
// Force a sync so clear doesn't end up deferencing stale pointers.
(void)syncActiveTextures(context, Command::Other);
mActiveTexturesCache.clear();
for (auto &bindingVec : mSamplerTextures)
for (TextureBindingVector &bindingVec : mSamplerTextures)
{
for (size_t textureIdx = 0; textureIdx < bindingVec.size(); textureIdx++)
for (BindingPointer<Texture> &texBinding : bindingVec)
{
bindingVec[textureIdx].set(context, nullptr);
texBinding.set(context, nullptr);
}
}
for (size_t samplerIdx = 0; samplerIdx < mSamplers.size(); samplerIdx++)
......@@ -518,7 +520,7 @@ void State::reset(const Context *context)
mSamplers[samplerIdx].set(context, nullptr);
}
for (auto &imageUnit : mImageUnits)
for (ImageUnit &imageUnit : mImageUnits)
{
imageUnit.texture.set(context, nullptr);
imageUnit.level = 0;
......@@ -530,7 +532,7 @@ void State::reset(const Context *context)
mRenderbuffer.set(context, nullptr);
for (auto type : angle::AllEnums<BufferBinding>())
for (BufferBinding type : angle::AllEnums<BufferBinding>())
{
UpdateBufferBinding(context, &mBoundBuffers[type], nullptr, type);
}
......@@ -554,17 +556,17 @@ void State::reset(const Context *context)
mActiveQueries[type].set(context, nullptr);
}
for (auto &buf : mUniformBuffers)
for (OffsetBindingPointer<Buffer> &buf : mUniformBuffers)
{
UpdateIndexedBufferBinding(context, &buf, nullptr, BufferBinding::Uniform, 0, 0);
}
for (auto &buf : mAtomicCounterBuffers)
for (OffsetBindingPointer<Buffer> &buf : mAtomicCounterBuffers)
{
UpdateIndexedBufferBinding(context, &buf, nullptr, BufferBinding::AtomicCounter, 0, 0);
}
for (auto &buf : mShaderStorageBuffers)
for (OffsetBindingPointer<Buffer> &buf : mShaderStorageBuffers)
{
UpdateIndexedBufferBinding(context, &buf, nullptr, BufferBinding::ShaderStorage, 0, 0);
}
......@@ -584,10 +586,10 @@ ANGLE_INLINE void State::unsetActiveTextures(ActiveTextureMask textureMask)
}
}
ANGLE_INLINE void State::updateActiveTextureState(const Context *context,
size_t textureIndex,
const Sampler *sampler,
Texture *texture)
ANGLE_INLINE void State::updateActiveTextureStateOnSync(const Context *context,
size_t textureIndex,
const Sampler *sampler,
Texture *texture)
{
if (!texture || !texture->isSamplerComplete(context, sampler))
{
......@@ -596,20 +598,36 @@ ANGLE_INLINE void State::updateActiveTextureState(const Context *context,
else
{
mActiveTexturesCache.set(textureIndex, texture);
}
if (texture->hasAnyDirtyBit())
{
setTextureDirty(textureIndex);
}
mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
}
if (mRobustResourceInit && texture->initState() == InitState::MayNeedInit)
{
mDirtyObjects.set(DIRTY_OBJECT_TEXTURES_INIT);
}
ANGLE_INLINE void State::setActiveTextureDirty(size_t textureIndex, Texture *texture)
{
mDirtyObjects.set(DIRTY_OBJECT_ACTIVE_TEXTURES);
mDirtyActiveTextures.set(textureIndex);
if (!texture)
{
return;
}
if (texture->hasAnyDirtyBit())
{
setTextureDirty(textureIndex);
}
if (mRobustResourceInit && texture->initState() == InitState::MayNeedInit)
{
mDirtyObjects.set(DIRTY_OBJECT_TEXTURES_INIT);
}
// This cache is updated immediately because we use the cache in the validation layer.
// If we defer the update until syncState it's too late and we've already passed validation.
if (texture && mExecutable)
{
const Sampler *sampler = mSamplers[textureIndex].get();
const SamplerState &samplerState =
sampler ? sampler->getSamplerState() : texture->getSamplerState();
mTexturesIncompatibleWithSamplers[textureIndex] =
......@@ -620,26 +638,15 @@ ANGLE_INLINE void State::updateActiveTextureState(const Context *context,
{
mTexturesIncompatibleWithSamplers[textureIndex] = false;
}
mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
}
ANGLE_INLINE void State::updateActiveTexture(const Context *context,
size_t textureIndex,
Texture *texture)
ANGLE_INLINE void State::updateTextureBinding(const Context *context,
size_t textureIndex,
Texture *texture)
{
const Sampler *sampler = mSamplers[textureIndex].get();
mCompleteTextureBindings[textureIndex].bind(texture);
if (!texture)
{
mActiveTexturesCache.reset(textureIndex);
mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
return;
}
updateActiveTextureState(context, textureIndex, sampler, texture);
mActiveTexturesCache.reset(textureIndex);
setActiveTextureDirty(textureIndex, texture);
}
ANGLE_INLINE bool State::hasConstantColor(GLenum sourceRGB, GLenum destRGB) const
......@@ -1465,7 +1472,7 @@ void State::setSamplerTexture(const Context *context, TextureType type, Texture
if (mExecutable && mExecutable->getActiveSamplersMask()[mActiveSampler] &&
IsTextureCompatibleWithSampler(type, mExecutable->getActiveSamplerTypes()[mActiveSampler]))
{
updateActiveTexture(context, mActiveSampler, texture);
updateTextureBinding(context, mActiveSampler, texture);
}
mSamplerTextures[type][mActiveSampler].set(context, texture);
......@@ -1509,7 +1516,7 @@ void State::detachTexture(const Context *context, const TextureMap &zeroTextures
ASSERT(zeroTexture != nullptr);
if (mCompleteTextureBindings[bindingIndex].getSubject() == binding.get())
{
updateActiveTexture(context, bindingIndex, zeroTexture);
updateTextureBinding(context, bindingIndex, zeroTexture);
}
binding.set(context, zeroTexture);
}
......@@ -1573,7 +1580,6 @@ void State::setSamplerBinding(const Context *context, GLuint textureUnit, Sample
// This is overly conservative as it assumes the sampler has never been bound.
setSamplerDirty(textureUnit);
onActiveTextureChange(context, textureUnit);
onActiveTextureStateChange(context, textureUnit);
}
void State::detachSampler(const Context *context, SamplerID sampler)
......@@ -3044,6 +3050,31 @@ Texture *State::getTextureForActiveSampler(TextureType type, size_t index)
return mSamplerTextures[type][index].get();
}
angle::Result State::syncActiveTextures(const Context *context, Command command)
{
if (mDirtyActiveTextures.none())
{
return angle::Result::Continue;
}
for (size_t textureUnit : mDirtyActiveTextures)
{
if (mExecutable)
{
TextureType type = mExecutable->getActiveSamplerTypes()[textureUnit];
Texture *activeTexture = (type != TextureType::InvalidEnum)
? getTextureForActiveSampler(type, textureUnit)
: nullptr;
const Sampler *sampler = mSamplers[textureUnit].get();
updateActiveTextureStateOnSync(context, textureUnit, sampler, activeTexture);
}
}
mDirtyActiveTextures.reset();
return angle::Result::Continue;
}
angle::Result State::syncTexturesInit(const Context *context, Command command)
{
ASSERT(mRobustResourceInit);
......@@ -3276,7 +3307,7 @@ angle::Result State::onProgramExecutableChange(const Context *context, Program *
continue;
Texture *texture = getTextureForActiveSampler(type, textureIndex);
updateActiveTexture(context, textureIndex, texture);
updateTextureBinding(context, textureIndex, texture);
}
for (size_t imageUnitIndex : executable.getActiveImagesMask())
......@@ -3321,7 +3352,7 @@ angle::Result State::onProgramPipelineExecutableChange(const Context *context,
continue;
Texture *texture = getTextureForActiveSampler(type, textureIndex);
updateActiveTexture(context, textureIndex, texture);
updateTextureBinding(context, textureIndex, texture);
}
for (size_t imageUnitIndex : programPipeline->getExecutable().getActiveImagesMask())
......@@ -3391,7 +3422,7 @@ void State::onActiveTextureChange(const Context *context, size_t textureUnit)
Texture *activeTexture = (type != TextureType::InvalidEnum)
? getTextureForActiveSampler(type, textureUnit)
: nullptr;
updateActiveTexture(context, textureUnit, activeTexture);
updateTextureBinding(context, textureUnit, activeTexture);
mExecutable->onStateChange(angle::SubjectMessage::SubjectChanged);
}
......@@ -3405,8 +3436,7 @@ void State::onActiveTextureStateChange(const Context *context, size_t textureUni
Texture *activeTexture = (type != TextureType::InvalidEnum)
? getTextureForActiveSampler(type, textureUnit)
: nullptr;
const Sampler *sampler = mSamplers[textureUnit].get();
updateActiveTextureState(context, textureUnit, sampler, activeTexture);
setActiveTextureDirty(textureUnit, activeTexture);
}
}
......
......@@ -646,6 +646,7 @@ class State : angle::NonCopyable
// TODO(jmadill): Consider storing dirty objects in a list instead of by binding.
enum DirtyObjectType
{
DIRTY_OBJECT_ACTIVE_TEXTURES, // Top-level dirty bit. Also see mDirtyActiveTextures.
DIRTY_OBJECT_TEXTURES_INIT,
DIRTY_OBJECT_IMAGES_INIT,
DIRTY_OBJECT_READ_ATTACHMENTS,
......@@ -720,7 +721,7 @@ class State : angle::NonCopyable
// "onActiveTextureChange" is called when a texture binding changes.
void onActiveTextureChange(const Context *context, size_t textureUnit);
// "onActiveTextureStateChange" calls when the Texture itself changed but the binding did not.
// "onActiveTextureStateChange" is called when the Texture changed but the binding did not.
void onActiveTextureStateChange(const Context *context, size_t textureUnit);
void onImageStateChange(const Context *context, size_t unit);
......@@ -855,17 +856,19 @@ class State : angle::NonCopyable
friend class Context;
void unsetActiveTextures(ActiveTextureMask textureMask);
void updateActiveTexture(const Context *context, size_t textureIndex, Texture *texture);
void updateActiveTextureState(const Context *context,
size_t textureIndex,
const Sampler *sampler,
Texture *texture);
void setActiveTextureDirty(size_t textureIndex, Texture *texture);
void updateTextureBinding(const Context *context, size_t textureIndex, Texture *texture);
void updateActiveTextureStateOnSync(const Context *context,
size_t textureIndex,
const Sampler *sampler,
Texture *texture);
Texture *getTextureForActiveSampler(TextureType type, size_t index);
bool hasConstantColor(GLenum sourceRGB, GLenum destRGB) const;
bool hasConstantAlpha(GLenum sourceRGB, GLenum destRGB) const;
// Functions to synchronize dirty states
angle::Result syncActiveTextures(const Context *context, Command command);
angle::Result syncTexturesInit(const Context *context, Command command);
angle::Result syncImagesInit(const Context *context, Command command);
angle::Result syncReadAttachments(const Context *context, Command command);
......@@ -881,30 +884,33 @@ class State : angle::NonCopyable
using DirtyObjectHandler = angle::Result (State::*)(const Context *context, Command command);
static constexpr DirtyObjectHandler kDirtyObjectHandlers[DIRTY_OBJECT_MAX] = {
&State::syncTexturesInit, &State::syncImagesInit, &State::syncReadAttachments,
&State::syncDrawAttachments, &State::syncReadFramebuffer, &State::syncDrawFramebuffer,
&State::syncVertexArray, &State::syncTextures, &State::syncImages,
&State::syncSamplers, &State::syncProgram, &State::syncProgramPipeline,
&State::syncActiveTextures, &State::syncTexturesInit, &State::syncImagesInit,
&State::syncReadAttachments, &State::syncDrawAttachments, &State::syncReadFramebuffer,
&State::syncDrawFramebuffer, &State::syncVertexArray, &State::syncTextures,
&State::syncImages, &State::syncSamplers, &State::syncProgram,
&State::syncProgramPipeline,
};
// Robust init must happen before Framebuffer init for the Vulkan back-end.
static_assert(DIRTY_OBJECT_ACTIVE_TEXTURES < DIRTY_OBJECT_TEXTURES_INIT, "init order");
static_assert(DIRTY_OBJECT_TEXTURES_INIT < DIRTY_OBJECT_DRAW_FRAMEBUFFER, "init order");
static_assert(DIRTY_OBJECT_IMAGES_INIT < DIRTY_OBJECT_DRAW_FRAMEBUFFER, "init order");
static_assert(DIRTY_OBJECT_DRAW_ATTACHMENTS < DIRTY_OBJECT_DRAW_FRAMEBUFFER, "init order");
static_assert(DIRTY_OBJECT_READ_ATTACHMENTS < DIRTY_OBJECT_READ_FRAMEBUFFER, "init order");
static_assert(DIRTY_OBJECT_TEXTURES_INIT == 0, "check DIRTY_OBJECT_TEXTURES_INIT index");
static_assert(DIRTY_OBJECT_IMAGES_INIT == 1, "check DIRTY_OBJECT_IMAGES_INIT index");
static_assert(DIRTY_OBJECT_READ_ATTACHMENTS == 2, "check DIRTY_OBJECT_READ_ATTACHMENTS index");
static_assert(DIRTY_OBJECT_DRAW_ATTACHMENTS == 3, "check DIRTY_OBJECT_DRAW_ATTACHMENTS index");
static_assert(DIRTY_OBJECT_READ_FRAMEBUFFER == 4, "check DIRTY_OBJECT_READ_FRAMEBUFFER index");
static_assert(DIRTY_OBJECT_DRAW_FRAMEBUFFER == 5, "check DIRTY_OBJECT_DRAW_FRAMEBUFFER index");
static_assert(DIRTY_OBJECT_VERTEX_ARRAY == 6, "check DIRTY_OBJECT_VERTEX_ARRAY index");
static_assert(DIRTY_OBJECT_TEXTURES == 7, "check DIRTY_OBJECT_TEXTURES index");
static_assert(DIRTY_OBJECT_IMAGES == 8, "check DIRTY_OBJECT_IMAGES index");
static_assert(DIRTY_OBJECT_SAMPLERS == 9, "check DIRTY_OBJECT_SAMPLERS index");
static_assert(DIRTY_OBJECT_PROGRAM == 10, "check DIRTY_OBJECT_PROGRAM index");
static_assert(DIRTY_OBJECT_PROGRAM_PIPELINE == 11, "check DIRTY_OBJECT_PROGRAM_PIPELINE index");
static_assert(DIRTY_OBJECT_ACTIVE_TEXTURES == 0, "check DIRTY_OBJECT_ACTIVE_TEXTURES index");
static_assert(DIRTY_OBJECT_TEXTURES_INIT == 1, "check DIRTY_OBJECT_TEXTURES_INIT index");
static_assert(DIRTY_OBJECT_IMAGES_INIT == 2, "check DIRTY_OBJECT_IMAGES_INIT index");
static_assert(DIRTY_OBJECT_READ_ATTACHMENTS == 3, "check DIRTY_OBJECT_READ_ATTACHMENTS index");
static_assert(DIRTY_OBJECT_DRAW_ATTACHMENTS == 4, "check DIRTY_OBJECT_DRAW_ATTACHMENTS index");
static_assert(DIRTY_OBJECT_READ_FRAMEBUFFER == 5, "check DIRTY_OBJECT_READ_FRAMEBUFFER index");
static_assert(DIRTY_OBJECT_DRAW_FRAMEBUFFER == 6, "check DIRTY_OBJECT_DRAW_FRAMEBUFFER index");
static_assert(DIRTY_OBJECT_VERTEX_ARRAY == 7, "check DIRTY_OBJECT_VERTEX_ARRAY index");
static_assert(DIRTY_OBJECT_TEXTURES == 8, "check DIRTY_OBJECT_TEXTURES index");
static_assert(DIRTY_OBJECT_IMAGES == 9, "check DIRTY_OBJECT_IMAGES index");
static_assert(DIRTY_OBJECT_SAMPLERS == 10, "check DIRTY_OBJECT_SAMPLERS index");
static_assert(DIRTY_OBJECT_PROGRAM == 11, "check DIRTY_OBJECT_PROGRAM index");
static_assert(DIRTY_OBJECT_PROGRAM_PIPELINE == 12, "check DIRTY_OBJECT_PROGRAM_PIPELINE index");
// Dispatch table for buffer update functions.
static const angle::PackedEnumMap<BufferBinding, BufferBindingSetter> kBufferSetters;
......@@ -995,20 +1001,15 @@ class State : angle::NonCopyable
TextureBindingMap mSamplerTextures;
// Texture Completeness Caching
// ----------------------------
// The texture completeness cache uses dirty bits to avoid having to scan the list of textures
// each draw call. This gl::State class implements angle::Observer interface. When subject
// Textures have state changes, messages reach 'State' (also any observing Framebuffers) via the
// onSubjectStateChange method (above). This then invalidates the completeness cache.
// Active Textures Cache
// ---------------------
// The active textures cache gives ANGLE components access to a complete array of textures
// on a draw call. gl::State implements angle::Observer and watches gl::Texture for state
// changes via the onSubjectStateChange method above. We update the cache before draws.
// See Observer.h and the design doc linked there for more info on Subject/Observer events.
//
// Note this requires that we also invalidate the completeness cache manually on events like
// re-binding textures/samplers or a change in the program. For more information see the
// Observer.h header and the design doc linked there.
// A cache of complete textures. nullptr indicates unbound or incomplete.
// Don't use BindingPointer because this cache is only valid within a draw call.
// Also stores a notification channel to the texture itself to handle texture change events.
// On state change events (re-binding textures, samplers, programs etc) we clear the cache
// and flag dirty bits. nullptr indicates unbound or incomplete.
ActiveTexturesCache mActiveTexturesCache;
std::vector<angle::ObserverBinding> mCompleteTextureBindings;
......@@ -1068,6 +1069,7 @@ class State : angle::NonCopyable
DirtyBits mDirtyBits;
DirtyObjects mDirtyObjects;
mutable AttributesMask mDirtyCurrentValues;
ActiveTextureMask mDirtyActiveTextures;
ActiveTextureMask mDirtyTextures;
ActiveTextureMask mDirtySamplers;
ImageUnitMask mDirtyImages;
......
......@@ -467,6 +467,11 @@ bool ValidateVertexShaderAttributeTypeMatch(const Context *context)
const Program *program = context->getActiveLinkedProgram();
const VertexArray *vao = context->getState().getVertexArray();
if (!program)
{
return false;
}
unsigned long stateCurrentValuesTypeBits = glState.getCurrentValuesTypeMask().to_ulong();
unsigned long vaoAttribTypeBits = vao->getAttributesTypeMask().to_ulong();
unsigned long vaoAttribEnabledMask = vao->getAttributesMask().to_ulong();
......@@ -475,10 +480,10 @@ bool ValidateVertexShaderAttributeTypeMatch(const Context *context)
vaoAttribTypeBits = (vaoAttribEnabledMask & vaoAttribTypeBits);
vaoAttribTypeBits |= (~vaoAttribEnabledMask & stateCurrentValuesTypeBits);
return program &&
ValidateComponentTypeMasks(
program->getExecutable().getAttributesTypeMask().to_ulong(), vaoAttribTypeBits,
program->getExecutable().getAttributesMask().to_ulong(), 0xFFFF);
const ProgramExecutable &executable = program->getExecutable();
return ValidateComponentTypeMasks(executable.getAttributesTypeMask().to_ulong(),
vaoAttribTypeBits, executable.getAttributesMask().to_ulong(),
0xFFFF);
}
bool IsCompatibleDrawModeWithGeometryShader(PrimitiveMode drawMode,
......
......@@ -273,27 +273,42 @@ void TexturesBenchmark::drawBenchmark()
ASSERT_GL_NO_ERROR();
}
TexturesParams D3D11Params(bool webglCompat)
TexturesParams D3D11Params(bool webglCompat, bool frequentUpdate)
{
TexturesParams params;
params.eglParameters = egl_platform::D3D11_NULL();
params.webgl = webglCompat;
if (frequentUpdate)
{
params.textureRebindFrequency = 1;
params.textureStateUpdateFrequency = 1;
}
return params;
}
TexturesParams OpenGLOrGLESParams(bool webglCompat)
TexturesParams OpenGLOrGLESParams(bool webglCompat, bool frequentUpdate)
{
TexturesParams params;
params.eglParameters = egl_platform::OPENGL_OR_GLES_NULL();
params.webgl = webglCompat;
if (frequentUpdate)
{
params.textureRebindFrequency = 1;
params.textureStateUpdateFrequency = 1;
}
return params;
}
TexturesParams VulkanParams(bool webglCompat)
TexturesParams VulkanParams(bool webglCompat, bool frequentUpdate)
{
TexturesParams params;
params.eglParameters = egl_platform::VULKAN_NULL();
params.webgl = webglCompat;
if (frequentUpdate)
{
params.textureRebindFrequency = 1;
params.textureStateUpdateFrequency = 1;
}
return params;
}
......@@ -303,10 +318,16 @@ TEST_P(TexturesBenchmark, Run)
}
ANGLE_INSTANTIATE_TEST(TexturesBenchmark,
D3D11Params(false),
D3D11Params(true),
OpenGLOrGLESParams(false),
OpenGLOrGLESParams(true),
VulkanParams(false),
VulkanParams(true));
D3D11Params(false, false),
D3D11Params(true, false),
D3D11Params(false, true),
D3D11Params(true, true),
OpenGLOrGLESParams(false, false),
OpenGLOrGLESParams(true, false),
OpenGLOrGLESParams(false, true),
OpenGLOrGLESParams(true, true),
VulkanParams(false, false),
VulkanParams(true, false),
VulkanParams(false, true),
VulkanParams(true, true));
} // namespace angle
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