Commit cc551385 by Charlie Lao Committed by Commit Bot

Vulkan: Add cache for default uniform descriptor set

Instead of free and allocate a descriptor set whenever buffer changes, add it into the descriptorset cache. Every time you get the buffer, we also look in the cache first to see if there is a descriptorset for that buffer or not. If yes, we use the cached descriptor set instead of allocate one. Bug: b/159457348 Change-Id: I64c252dfe22db66ea8188415e1b384af371b82ab Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2273698 Commit-Queue: Charlie Lao <cclao@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarCourtney Goeltzenleuchter <courtneygo@google.com> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent 67980f13
...@@ -1474,9 +1474,13 @@ angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffersEmulation( ...@@ -1474,9 +1474,13 @@ angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffersEmulation(
} }
// TODO(http://anglebug.com/3570): Need to update to handle Program Pipelines // TODO(http://anglebug.com/3570): Need to update to handle Program Pipelines
vk::BufferHelper *uniformBuffer = mProgram->getDefaultUniformBuffer();
vk::UniformsAndXfbDesc xfbBufferDesc = transformFeedbackVk->getTransformFeedbackDesc();
xfbBufferDesc.updateDefaultUniformBuffer(uniformBuffer ? uniformBuffer->getBufferSerial()
: kInvalidBufferSerial);
return mProgram->getExecutable().updateTransformFeedbackDescriptorSet( return mProgram->getExecutable().updateTransformFeedbackDescriptorSet(
mProgram->getState(), mProgram->getDefaultUniformBlocks(), mProgram->getState(), mProgram->getDefaultUniformBlocks(), uniformBuffer, this,
mProgram->getDefaultUniformBuffer(), this); xfbBufferDesc);
} }
angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffersExtension( angle::Result ContextVk::handleDirtyGraphicsTransformFeedbackBuffersExtension(
......
...@@ -193,6 +193,7 @@ void ProgramExecutableVk::reset(ContextVk *contextVk) ...@@ -193,6 +193,7 @@ void ProgramExecutableVk::reset(ContextVk *contextVk)
mTextureDescriptorsCache.clear(); mTextureDescriptorsCache.clear();
mDescriptorBuffersCache.clear(); mDescriptorBuffersCache.clear();
mUniformsAndXfbDescriptorSetCache.clear();
for (ProgramInfo &programInfo : mGraphicsProgramInfos) for (ProgramInfo &programInfo : mGraphicsProgramInfos)
{ {
...@@ -336,6 +337,38 @@ uint32_t GetInterfaceBlockArraySize(const std::vector<gl::InterfaceBlock> &block ...@@ -336,6 +337,38 @@ uint32_t GetInterfaceBlockArraySize(const std::vector<gl::InterfaceBlock> &block
return arraySize; return arraySize;
} }
angle::Result ProgramExecutableVk::allocUniformAndXfbDescriptorSet(
ContextVk *contextVk,
const vk::UniformsAndXfbDesc &xfbBufferDesc,
bool *newDescriptorSetAllocated)
{
// Look up in the cache first
auto iter = mUniformsAndXfbDescriptorSetCache.find(xfbBufferDesc);
if (iter != mUniformsAndXfbDescriptorSetCache.end())
{
mDescriptorSets[kUniformsAndXfbDescriptorSetIndex] = iter->second;
*newDescriptorSetAllocated = false;
return angle::Result::Continue;
}
bool newPoolAllocated;
ANGLE_TRY(allocateDescriptorSetAndGetInfo(contextVk, kUniformsAndXfbDescriptorSetIndex,
&newPoolAllocated));
// Clear descriptor set cache. It may no longer be valid.
if (newPoolAllocated)
{
mUniformsAndXfbDescriptorSetCache.clear();
}
// Add the descriptor set into cache
mUniformsAndXfbDescriptorSetCache.emplace(xfbBufferDesc,
mDescriptorSets[kUniformsAndXfbDescriptorSetIndex]);
*newDescriptorSetAllocated = true;
return angle::Result::Continue;
}
angle::Result ProgramExecutableVk::allocateDescriptorSet(ContextVk *contextVk, angle::Result ProgramExecutableVk::allocateDescriptorSet(ContextVk *contextVk,
uint32_t descriptorSetIndex) uint32_t descriptorSetIndex)
{ {
...@@ -852,7 +885,7 @@ angle::Result ProgramExecutableVk::createPipelineLayout(const gl::Context *glCon ...@@ -852,7 +885,7 @@ angle::Result ProgramExecutableVk::createPipelineLayout(const gl::Context *glCon
void ProgramExecutableVk::updateDefaultUniformsDescriptorSet( void ProgramExecutableVk::updateDefaultUniformsDescriptorSet(
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks, const DefaultUniformBlock &defaultUniformBlock,
vk::BufferHelper *defaultUniformBuffer, vk::BufferHelper *defaultUniformBuffer,
ContextVk *contextVk) ContextVk *contextVk)
{ {
...@@ -863,11 +896,10 @@ void ProgramExecutableVk::updateDefaultUniformsDescriptorSet( ...@@ -863,11 +896,10 @@ void ProgramExecutableVk::updateDefaultUniformsDescriptorSet(
return; return;
} }
DefaultUniformBlock &uniformBlock = defaultUniformBlocks[shaderType];
VkWriteDescriptorSet &writeInfo = contextVk->allocWriteInfo(); VkWriteDescriptorSet &writeInfo = contextVk->allocWriteInfo();
VkDescriptorBufferInfo &bufferInfo = contextVk->allocBufferInfo(); VkDescriptorBufferInfo &bufferInfo = contextVk->allocBufferInfo();
if (!uniformBlock.uniformData.empty()) if (!defaultUniformBlock.uniformData.empty())
{ {
bufferInfo.buffer = defaultUniformBuffer->getBuffer().getHandle(); bufferInfo.buffer = defaultUniformBuffer->getBuffer().getHandle();
mDescriptorBuffersCache.emplace_back(defaultUniformBuffer); mDescriptorBuffersCache.emplace_back(defaultUniformBuffer);
...@@ -1187,22 +1219,27 @@ angle::Result ProgramExecutableVk::updateTransformFeedbackDescriptorSet( ...@@ -1187,22 +1219,27 @@ angle::Result ProgramExecutableVk::updateTransformFeedbackDescriptorSet(
const gl::ProgramState &programState, const gl::ProgramState &programState,
gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks, gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
vk::BufferHelper *defaultUniformBuffer, vk::BufferHelper *defaultUniformBuffer,
ContextVk *contextVk) ContextVk *contextVk,
const vk::UniformsAndXfbDesc &xfbBufferDesc)
{ {
const gl::ProgramExecutable &executable = programState.getExecutable(); const gl::ProgramExecutable &executable = programState.getExecutable();
ASSERT(executable.hasTransformFeedbackOutput()); ASSERT(executable.hasTransformFeedbackOutput());
ANGLE_TRY(allocateDescriptorSet(contextVk, kUniformsAndXfbDescriptorSetIndex)); bool newDescriptorSetAllocated;
ANGLE_TRY(
allocUniformAndXfbDescriptorSet(contextVk, xfbBufferDesc, &newDescriptorSetAllocated));
mDescriptorBuffersCache.clear(); if (newDescriptorSetAllocated)
for (const gl::ShaderType shaderType : executable.getLinkedShaderStages())
{ {
updateDefaultUniformsDescriptorSet(shaderType, defaultUniformBlocks, defaultUniformBuffer, mDescriptorBuffersCache.clear();
contextVk); for (const gl::ShaderType shaderType : executable.getLinkedShaderStages())
{
updateDefaultUniformsDescriptorSet(shaderType, defaultUniformBlocks[shaderType],
defaultUniformBuffer, contextVk);
}
updateTransformFeedbackDescriptorSetImpl(programState, contextVk);
} }
updateTransformFeedbackDescriptorSetImpl(programState, contextVk);
return angle::Result::Continue; return angle::Result::Continue;
} }
......
...@@ -146,7 +146,8 @@ class ProgramExecutableVk ...@@ -146,7 +146,8 @@ class ProgramExecutableVk
const gl::ProgramState &programState, const gl::ProgramState &programState,
gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks, gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks,
vk::BufferHelper *defaultUniformBuffer, vk::BufferHelper *defaultUniformBuffer,
ContextVk *contextVk); ContextVk *contextVk,
const vk::UniformsAndXfbDesc &xfbBufferDesc);
angle::Result updateDescriptorSets(ContextVk *contextVk, vk::CommandBuffer *commandBuffer); angle::Result updateDescriptorSets(ContextVk *contextVk, vk::CommandBuffer *commandBuffer);
...@@ -167,6 +168,10 @@ class ProgramExecutableVk ...@@ -167,6 +168,10 @@ class ProgramExecutableVk
friend class ProgramVk; friend class ProgramVk;
friend class ProgramPipelineVk; friend class ProgramPipelineVk;
angle::Result allocUniformAndXfbDescriptorSet(ContextVk *contextVk,
const vk::UniformsAndXfbDesc &xfbBufferDesc,
bool *newDescriptorSetAllocated);
angle::Result allocateDescriptorSet(ContextVk *contextVk, uint32_t descriptorSetIndex); angle::Result allocateDescriptorSet(ContextVk *contextVk, uint32_t descriptorSetIndex);
angle::Result allocateDescriptorSetAndGetInfo(ContextVk *contextVk, angle::Result allocateDescriptorSetAndGetInfo(ContextVk *contextVk,
uint32_t descriptorSetIndex, uint32_t descriptorSetIndex,
...@@ -186,11 +191,10 @@ class ProgramExecutableVk ...@@ -186,11 +191,10 @@ class ProgramExecutableVk
const gl::ActiveTextureArray<vk::TextureUnit> *activeTextures, const gl::ActiveTextureArray<vk::TextureUnit> *activeTextures,
vk::DescriptorSetLayoutDesc *descOut); vk::DescriptorSetLayoutDesc *descOut);
void updateDefaultUniformsDescriptorSet( void updateDefaultUniformsDescriptorSet(const gl::ShaderType shaderType,
const gl::ShaderType shaderType, const DefaultUniformBlock &defaultUniformBlock,
gl::ShaderMap<DefaultUniformBlock> &defaultUniformBlocks, vk::BufferHelper *defaultUniformBuffer,
vk::BufferHelper *defaultUniformBuffer, ContextVk *contextVk);
ContextVk *contextVk);
void updateTransformFeedbackDescriptorSetImpl(const gl::ProgramState &programState, void updateTransformFeedbackDescriptorSetImpl(const gl::ProgramState &programState,
ContextVk *contextVk); ContextVk *contextVk);
void updateBuffersDescriptorSet(ContextVk *contextVk, void updateBuffersDescriptorSet(ContextVk *contextVk,
...@@ -221,6 +225,7 @@ class ProgramExecutableVk ...@@ -221,6 +225,7 @@ class ProgramExecutableVk
std::vector<vk::BufferHelper *> mDescriptorBuffersCache; std::vector<vk::BufferHelper *> mDescriptorBuffersCache;
size_t mNumDefaultUniformDescriptors; size_t mNumDefaultUniformDescriptors;
std::unordered_map<vk::UniformsAndXfbDesc, VkDescriptorSet> mUniformsAndXfbDescriptorSetCache;
std::unordered_map<vk::TextureDescriptorDesc, VkDescriptorSet> mTextureDescriptorsCache; std::unordered_map<vk::TextureDescriptorDesc, VkDescriptorSet> mTextureDescriptorsCache;
// We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get // We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get
......
...@@ -123,7 +123,7 @@ angle::Result ProgramPipelineVk::updateUniforms(ContextVk *contextVk) ...@@ -123,7 +123,7 @@ angle::Result ProgramPipelineVk::updateUniforms(ContextVk *contextVk)
if (programVk) if (programVk)
{ {
mExecutable.updateDefaultUniformsDescriptorSet( mExecutable.updateDefaultUniformsDescriptorSet(
shaderType, programVk->getDefaultUniformBlocks(), shaderType, programVk->getDefaultUniformBlock(shaderType),
programVk->getDefaultUniformBuffer(), contextVk); programVk->getDefaultUniformBuffer(), contextVk);
mExecutable.updateTransformFeedbackDescriptorSetImpl(programVk->getState(), mExecutable.updateTransformFeedbackDescriptorSetImpl(programVk->getState(),
contextVk); contextVk);
......
...@@ -823,16 +823,43 @@ angle::Result ProgramVk::updateUniforms(ContextVk *contextVk) ...@@ -823,16 +823,43 @@ angle::Result ProgramVk::updateUniforms(ContextVk *contextVk)
{ {
// We need to reinitialize the descriptor sets if we newly allocated buffers since we can't // We need to reinitialize the descriptor sets if we newly allocated buffers since we can't
// modify the descriptor sets once initialized. // modify the descriptor sets once initialized.
ANGLE_TRY(mExecutable.allocateDescriptorSet(contextVk, kUniformsAndXfbDescriptorSetIndex));
mExecutable.mDescriptorBuffersCache.clear(); mExecutable.mDescriptorBuffersCache.clear();
for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages()) vk::BufferHelper *defaultUniformBuffer = mDefaultUniformStorage.getCurrentBuffer();
vk::UniformsAndXfbDesc defaultUniformsDesc;
vk::UniformsAndXfbDesc *uniformsAndXfbBufferDesc;
if (glExecutable.hasTransformFeedbackOutput())
{
const gl::State &glState = contextVk->getState();
TransformFeedbackVk *transformFeedbackVk =
vk::GetImpl(glState.getCurrentTransformFeedback());
uniformsAndXfbBufferDesc = &transformFeedbackVk->getTransformFeedbackDesc();
uniformsAndXfbBufferDesc->updateDefaultUniformBuffer(
defaultUniformBuffer->getBufferSerial());
}
else
{
defaultUniformsDesc.updateDefaultUniformBuffer(defaultUniformBuffer->getBufferSerial());
uniformsAndXfbBufferDesc = &defaultUniformsDesc;
}
bool newDescriptorSetAllocated;
ANGLE_TRY(mExecutable.allocUniformAndXfbDescriptorSet(contextVk, *uniformsAndXfbBufferDesc,
&newDescriptorSetAllocated));
if (newDescriptorSetAllocated)
{
// Update the descriptor set with the bufferInfo
for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
{
mExecutable.updateDefaultUniformsDescriptorSet(
shaderType, mDefaultUniformBlocks[shaderType], defaultUniformBuffer, contextVk);
}
mExecutable.updateTransformFeedbackDescriptorSetImpl(mState, contextVk);
}
else
{ {
mExecutable.updateDefaultUniformsDescriptorSet( mExecutable.mDescriptorBuffersCache.emplace_back(defaultUniformBuffer);
shaderType, mDefaultUniformBlocks, mDefaultUniformStorage.getCurrentBuffer(),
contextVk);
} }
mExecutable.updateTransformFeedbackDescriptorSetImpl(mState, contextVk);
} }
return angle::Result::Continue; return angle::Result::Continue;
......
...@@ -123,6 +123,10 @@ class ProgramVk : public ProgramImpl ...@@ -123,6 +123,10 @@ class ProgramVk : public ProgramImpl
ProgramExecutableVk &getExecutable() { return mExecutable; } ProgramExecutableVk &getExecutable() { return mExecutable; }
gl::ShaderMap<DefaultUniformBlock> &getDefaultUniformBlocks() { return mDefaultUniformBlocks; } gl::ShaderMap<DefaultUniformBlock> &getDefaultUniformBlocks() { return mDefaultUniformBlocks; }
const DefaultUniformBlock &getDefaultUniformBlock(const gl::ShaderType shaderType) const
{
return mDefaultUniformBlocks[shaderType];
}
vk::BufferHelper *getDefaultUniformBuffer() const vk::BufferHelper *getDefaultUniformBuffer() const
{ {
return mDefaultUniformStorage.getCurrentBuffer(); return mDefaultUniformStorage.getCurrentBuffer();
......
...@@ -55,6 +55,7 @@ angle::Result TransformFeedbackVk::begin(const gl::Context *context, ...@@ -55,6 +55,7 @@ angle::Result TransformFeedbackVk::begin(const gl::Context *context,
ASSERT(executable); ASSERT(executable);
size_t xfbBufferCount = executable->getTransformFeedbackBufferCount(); size_t xfbBufferCount = executable->getTransformFeedbackBufferCount();
mXFBBuffersDesc.reset();
for (size_t bufferIndex = 0; bufferIndex < xfbBufferCount; ++bufferIndex) for (size_t bufferIndex = 0; bufferIndex < xfbBufferCount; ++bufferIndex)
{ {
const gl::OffsetBindingPointer<gl::Buffer> &binding = mState.getIndexedBuffer(bufferIndex); const gl::OffsetBindingPointer<gl::Buffer> &binding = mState.getIndexedBuffer(bufferIndex);
...@@ -78,6 +79,8 @@ angle::Result TransformFeedbackVk::begin(const gl::Context *context, ...@@ -78,6 +79,8 @@ angle::Result TransformFeedbackVk::begin(const gl::Context *context,
} }
mBufferHandles[bufferIndex] = mBufferHelpers[bufferIndex]->getBuffer().getHandle(); mBufferHandles[bufferIndex] = mBufferHelpers[bufferIndex]->getBuffer().getHandle();
mXFBBuffersDesc.updateTransformFeedbackBuffer(
bufferIndex, mBufferHelpers[bufferIndex]->getBufferSerial());
if (contextVk->getFeatures().supportsTransformFeedbackExtension.enabled) if (contextVk->getFeatures().supportsTransformFeedbackExtension.enabled)
{ {
......
...@@ -91,6 +91,8 @@ class TransformFeedbackVk : public TransformFeedbackImpl ...@@ -91,6 +91,8 @@ class TransformFeedbackVk : public TransformFeedbackImpl
return mCounterBufferHandles; return mCounterBufferHandles;
} }
vk::UniformsAndXfbDesc &getTransformFeedbackDesc() { return mXFBBuffersDesc; }
private: private:
void writeDescriptorSet(ContextVk *contextVk, void writeDescriptorSet(ContextVk *contextVk,
size_t xfbBufferCount, size_t xfbBufferCount,
...@@ -114,6 +116,9 @@ class TransformFeedbackVk : public TransformFeedbackImpl ...@@ -114,6 +116,9 @@ class TransformFeedbackVk : public TransformFeedbackImpl
// Counter buffer used for pause and resume. // Counter buffer used for pause and resume.
gl::TransformFeedbackBuffersArray<vk::BufferHelper> mCounterBufferHelpers; gl::TransformFeedbackBuffersArray<vk::BufferHelper> mCounterBufferHelpers;
gl::TransformFeedbackBuffersArray<VkBuffer> mCounterBufferHandles; gl::TransformFeedbackBuffersArray<VkBuffer> mCounterBufferHandles;
// Keys to look up in the descriptor set cache
vk::UniformsAndXfbDesc mXFBBuffersDesc;
}; };
} // namespace rx } // namespace rx
......
...@@ -1687,6 +1687,37 @@ bool TextureDescriptorDesc::operator==(const TextureDescriptorDesc &other) const ...@@ -1687,6 +1687,37 @@ bool TextureDescriptorDesc::operator==(const TextureDescriptorDesc &other) const
return memcmp(mSerials.data(), other.mSerials.data(), sizeof(TexUnitSerials) * mMaxIndex) == 0; return memcmp(mSerials.data(), other.mSerials.data(), sizeof(TexUnitSerials) * mMaxIndex) == 0;
} }
// UniformsAndXfbDesc implementation.
UniformsAndXfbDesc::UniformsAndXfbDesc()
{
reset();
}
UniformsAndXfbDesc::~UniformsAndXfbDesc() = default;
UniformsAndXfbDesc::UniformsAndXfbDesc(const UniformsAndXfbDesc &other) = default;
UniformsAndXfbDesc &UniformsAndXfbDesc::operator=(const UniformsAndXfbDesc &other) = default;
size_t UniformsAndXfbDesc::hash() const
{
return angle::ComputeGenericHash(&mBufferSerials, sizeof(BufferSerial) * mBufferCount);
}
void UniformsAndXfbDesc::reset()
{
mBufferCount = 0;
memset(&mBufferSerials, 0, sizeof(BufferSerial) * kMaxBufferCount);
}
bool UniformsAndXfbDesc::operator==(const UniformsAndXfbDesc &other) const
{
if (mBufferCount != other.mBufferCount)
{
return false;
}
return memcmp(&mBufferSerials, &other.mBufferSerials, sizeof(BufferSerial) * mBufferCount) == 0;
}
// FramebufferDesc implementation. // FramebufferDesc implementation.
FramebufferDesc::FramebufferDesc() FramebufferDesc::FramebufferDesc()
......
...@@ -793,6 +793,39 @@ class TextureDescriptorDesc ...@@ -793,6 +793,39 @@ class TextureDescriptorDesc
gl::ActiveTextureArray<TexUnitSerials> mSerials; gl::ActiveTextureArray<TexUnitSerials> mSerials;
}; };
class UniformsAndXfbDesc
{
public:
UniformsAndXfbDesc();
~UniformsAndXfbDesc();
UniformsAndXfbDesc(const UniformsAndXfbDesc &other);
UniformsAndXfbDesc &operator=(const UniformsAndXfbDesc &other);
void updateDefaultUniformBuffer(BufferSerial bufferSerial)
{
mBufferSerials[kDefaultUniformBufferIndex] = bufferSerial;
mBufferCount = std::max(mBufferCount, static_cast<uint32_t>(1));
}
void updateTransformFeedbackBuffer(size_t xfbIndex, BufferSerial bufferSerial)
{
uint32_t bufferIndex = static_cast<uint32_t>(xfbIndex) + 1;
mBufferSerials[bufferIndex] = bufferSerial;
mBufferCount = std::max(mBufferCount, (bufferIndex + 1));
}
size_t hash() const;
void reset();
bool operator==(const UniformsAndXfbDesc &other) const;
private:
uint32_t mBufferCount;
// The array index 0 is used for default uniform buffer
static constexpr size_t kDefaultUniformBufferIndex = 0;
static constexpr size_t kMaxBufferCount = 1 + gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS;
std::array<BufferSerial, kMaxBufferCount> mBufferSerials;
};
// This is IMPLEMENTATION_MAX_DRAW_BUFFERS + 1 for DS attachment // This is IMPLEMENTATION_MAX_DRAW_BUFFERS + 1 for DS attachment
constexpr size_t kMaxFramebufferAttachments = gl::IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS; constexpr size_t kMaxFramebufferAttachments = gl::IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS;
// Color serials are at index [0:gl::IMPLEMENTATION_MAX_DRAW_BUFFERS-1] // Color serials are at index [0:gl::IMPLEMENTATION_MAX_DRAW_BUFFERS-1]
...@@ -874,6 +907,12 @@ struct hash<rx::vk::TextureDescriptorDesc> ...@@ -874,6 +907,12 @@ struct hash<rx::vk::TextureDescriptorDesc>
}; };
template <> template <>
struct hash<rx::vk::UniformsAndXfbDesc>
{
size_t operator()(const rx::vk::UniformsAndXfbDesc &key) const { return key.hash(); }
};
template <>
struct hash<rx::vk::FramebufferDesc> struct hash<rx::vk::FramebufferDesc>
{ {
size_t operator()(const rx::vk::FramebufferDesc &key) const { return key.hash(); } size_t operator()(const rx::vk::FramebufferDesc &key) const { return key.hash(); }
...@@ -898,6 +937,12 @@ struct hash<rx::vk::LayerLevel> ...@@ -898,6 +937,12 @@ struct hash<rx::vk::LayerLevel>
return layerLevel.layer | (layerLevel.level << 11); return layerLevel.layer | (layerLevel.level << 11);
} }
}; };
template <>
struct hash<rx::BufferSerial>
{
size_t operator()(const rx::BufferSerial &key) const { return key.getValue(); }
};
} // namespace std } // namespace std
namespace rx namespace rx
......
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