Vulkan: Use one pipeline layout for all Programs.
This simplifies the pipeline state object caching. We will not need
to use any extra bits to cache based on program properties - instead
all programs will be compatible. The pipeline layout strucutre is
described in the design docs. It currently only has two bind groups:
the first for default uniforms, and the second for Textures. In the
future we might re-organize this to handle driver uniforms, dynamic
push constants, and/or program uniform buffers with ES 3.0.
Instead of storing only the Textures that are required by a Program,
we reserve space for the maximum possible Texture units. We might have
to revisit this very simple design in the future to support texture
arrays, which are handled specially in Vulkan.
Bug: angleproject:2163
Change-Id: I3e1656c2c73045aed56838a5f1267b246a623362
Reviewed-on: https://chromium-review.googlesource.com/837943
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/common/mathutil.h b/src/common/mathutil.h
index ca6efc5..73b31b7 100644
--- a/src/common/mathutil.h
+++ b/src/common/mathutil.h
@@ -628,6 +628,12 @@
T low() const { return mLow; }
T high() const { return mHigh; }
+ void invalidate()
+ {
+ mLow = std::numeric_limits<T>::max();
+ mHigh = std::numeric_limits<T>::min();
+ }
+
private:
T mLow;
T mHigh;
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 1b20c0d..14e76aa 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -259,10 +259,8 @@
VkDevice device = mRenderer->getDevice();
const auto &state = mState.getState();
- const gl::Program *programGL = state.getProgram();
const gl::VertexArray *vao = state.getVertexArray();
const gl::Framebuffer *drawFBO = state.getDrawFramebuffer();
- ProgramVk *programVk = vk::GetImpl(programGL);
FramebufferVk *vkFBO = vk::GetImpl(drawFBO);
VertexArrayVk *vkVAO = vk::GetImpl(vao);
@@ -288,7 +286,7 @@
ANGLE_TRY(mRenderer->getCompatibleRenderPass(desc, &renderPass));
ASSERT(renderPass && renderPass->valid());
- const vk::PipelineLayout &pipelineLayout = programVk->getPipelineLayout();
+ const vk::PipelineLayout &pipelineLayout = mRenderer->getGraphicsPipelineLayout();
ASSERT(pipelineLayout.valid());
mCurrentPipelineInfo.layout = pipelineLayout.getHandle();
@@ -395,14 +393,14 @@
// Bind the graphics descriptor sets.
// TODO(jmadill): Handle multiple command buffers.
const auto &descriptorSets = programVk->getDescriptorSets();
- uint32_t firstSet = programVk->getDescriptorSetOffset();
- uint32_t setCount = static_cast<uint32_t>(descriptorSets.size());
- if (!descriptorSets.empty() && ((setCount - firstSet) > 0))
+ const gl::RangeUI &usedRange = programVk->getUsedDescriptorSetRange();
+ if (!usedRange.empty())
{
- const vk::PipelineLayout &pipelineLayout = programVk->getPipelineLayout();
+ ASSERT(!descriptorSets.empty());
+ const vk::PipelineLayout &pipelineLayout = mRenderer->getGraphicsPipelineLayout();
(*commandBuffer)
- ->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, firstSet,
- setCount - firstSet, &descriptorSets[firstSet], 0, nullptr);
+ ->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, usedRange.low(),
+ usedRange.length(), &descriptorSets[usedRange.low()], 0, nullptr);
}
return gl::NoError();
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 82f9fe7..9e15ee7 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -139,8 +139,9 @@
}
ProgramVk::ProgramVk(const gl::ProgramState &state)
- : ProgramImpl(state), mDefaultUniformBlocks(), mDescriptorSetOffset(0), mDirtyTextures(true)
+ : ProgramImpl(state), mDefaultUniformBlocks(), mUsedDescriptorSetRange(), mDirtyTextures(true)
{
+ mUsedDescriptorSetRange.invalidate();
}
ProgramVk::~ProgramVk()
@@ -164,18 +165,12 @@
mEmptyUniformBlockStorage.memory.destroy(device);
mEmptyUniformBlockStorage.buffer.destroy(device);
- for (auto &descriptorSetLayout : mDescriptorSetLayouts)
- {
- descriptorSetLayout.destroy(device);
- }
-
mLinkedFragmentModule.destroy(device);
mLinkedVertexModule.destroy(device);
- mPipelineLayout.destroy(device);
// Descriptor Sets are pool allocated, so do not need to be explicitly freed.
mDescriptorSets.clear();
- mDescriptorSetOffset = 0;
+ mUsedDescriptorSetRange.invalidate();
mDirtyTextures = false;
}
@@ -246,10 +241,16 @@
ANGLE_TRY(mLinkedFragmentModule.init(device, fragmentShaderInfo));
}
- ANGLE_TRY(initPipelineLayout(contextVk));
ANGLE_TRY(initDescriptorSets(contextVk));
ANGLE_TRY(initDefaultUniformBlocks(glContext));
+ if (!mState.getSamplerUniformRange().empty())
+ {
+ // Ensure the descriptor set range includes the textures at position 1.
+ mUsedDescriptorSetRange.extend(1);
+ mDirtyTextures = true;
+ }
+
return true;
}
@@ -358,11 +359,9 @@
}
ANGLE_TRY(updateDefaultUniformsDescriptorSet(contextVk));
- }
- else
- {
- // If the program has no uniforms, note this in the offset.
- mDescriptorSetOffset = 1;
+
+ // Ensure the descriptor set range includes the uniform buffers at position 0.
+ mUsedDescriptorSetRange.extend(0);
}
return gl::NoError();
@@ -553,145 +552,27 @@
return mLinkedFragmentModule;
}
-const vk::PipelineLayout &ProgramVk::getPipelineLayout() const
-{
- return mPipelineLayout;
-}
-
-vk::Error ProgramVk::initPipelineLayout(ContextVk *context)
-{
- ASSERT(!mPipelineLayout.valid());
-
- VkDevice device = context->getDevice();
-
- // Create two descriptor set layouts: one for default uniform info, and one for textures.
- // Skip one or both if there are no uniforms.
- VkDescriptorSetLayoutBinding uniformBindings[2];
- uint32_t blockCount = 0;
-
- {
- auto &layoutBinding = uniformBindings[blockCount];
-
- layoutBinding.binding = blockCount;
- layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- layoutBinding.descriptorCount = 1;
- layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
- layoutBinding.pImmutableSamplers = nullptr;
-
- blockCount++;
- }
-
- {
- auto &layoutBinding = uniformBindings[blockCount];
-
- layoutBinding.binding = blockCount;
- layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- layoutBinding.descriptorCount = 1;
- layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
- layoutBinding.pImmutableSamplers = nullptr;
-
- blockCount++;
- }
-
- {
- VkDescriptorSetLayoutCreateInfo uniformInfo;
- uniformInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
- uniformInfo.pNext = nullptr;
- uniformInfo.flags = 0;
- uniformInfo.bindingCount = blockCount;
- uniformInfo.pBindings = uniformBindings;
-
- vk::DescriptorSetLayout uniformLayout;
- ANGLE_TRY(uniformLayout.init(device, uniformInfo));
- mDescriptorSetLayouts.push_back(std::move(uniformLayout));
- }
-
- const auto &samplerBindings = mState.getSamplerBindings();
-
- if (!samplerBindings.empty())
- {
- std::vector<VkDescriptorSetLayoutBinding> textureBindings;
- uint32_t textureCount = 0;
- const auto &uniforms = mState.getUniforms();
- for (unsigned int uniformIndex : mState.getSamplerUniformRange())
- {
- const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
- unsigned int samplerIndex = mState.getSamplerIndexFromUniformIndex(uniformIndex);
- const gl::SamplerBinding &samplerBinding = samplerBindings[samplerIndex];
-
- ASSERT(!samplerBinding.unreferenced);
-
- VkDescriptorSetLayoutBinding layoutBinding;
-
- uint32_t elementCount = samplerUniform.getBasicTypeElementCount();
-
- layoutBinding.binding = textureCount;
- layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
- layoutBinding.descriptorCount = elementCount;
-
- layoutBinding.stageFlags = 0;
- if (samplerUniform.vertexStaticUse)
- {
- layoutBinding.stageFlags |= VK_SHADER_STAGE_VERTEX_BIT;
- }
- if (samplerUniform.fragmentStaticUse)
- {
- layoutBinding.stageFlags |= VK_SHADER_STAGE_FRAGMENT_BIT;
- }
-
- layoutBinding.pImmutableSamplers = nullptr;
-
- textureCount += elementCount;
-
- textureBindings.push_back(layoutBinding);
- }
-
- VkDescriptorSetLayoutCreateInfo textureInfo;
- textureInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
- textureInfo.pNext = nullptr;
- textureInfo.flags = 0;
- textureInfo.bindingCount = static_cast<uint32_t>(textureBindings.size());
- textureInfo.pBindings = textureBindings.data();
-
- vk::DescriptorSetLayout textureLayout;
- ANGLE_TRY(textureLayout.init(device, textureInfo));
- mDescriptorSetLayouts.push_back(std::move(textureLayout));
-
- mDirtyTextures = true;
- }
-
- VkPipelineLayoutCreateInfo createInfo;
- createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
- createInfo.pNext = nullptr;
- createInfo.flags = 0;
- createInfo.setLayoutCount = static_cast<uint32_t>(mDescriptorSetLayouts.size());
- createInfo.pSetLayouts = mDescriptorSetLayouts[0].ptr();
- createInfo.pushConstantRangeCount = 0;
- createInfo.pPushConstantRanges = nullptr;
-
- ANGLE_TRY(mPipelineLayout.init(device, createInfo));
-
- return vk::NoError();
-}
-
vk::Error ProgramVk::initDescriptorSets(ContextVk *contextVk)
{
ASSERT(mDescriptorSets.empty());
+ RendererVk *renderer = contextVk->getRenderer();
VkDevice device = contextVk->getDevice();
// Write out to a new a descriptor set.
// TODO(jmadill): Handle descriptor set lifetime.
vk::DescriptorPool *descriptorPool = contextVk->getDescriptorPool();
- uint32_t descriptorSetCount = static_cast<uint32_t>(mDescriptorSetLayouts.size());
+ const auto &descriptorSetLayouts = renderer->getGraphicsDescriptorSetLayouts();
+
+ uint32_t descriptorSetCount = static_cast<uint32_t>(descriptorSetLayouts.size());
VkDescriptorSetAllocateInfo allocInfo;
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.pNext = nullptr;
allocInfo.descriptorPool = descriptorPool->getHandle();
allocInfo.descriptorSetCount = descriptorSetCount;
- allocInfo.pSetLayouts = mDescriptorSetLayouts[0].ptr();
+ allocInfo.pSetLayouts = descriptorSetLayouts[0].ptr();
mDescriptorSets.resize(descriptorSetCount, VK_NULL_HANDLE);
ANGLE_TRY(descriptorPool->allocateDescriptorSets(device, allocInfo, &mDescriptorSets[0]));
@@ -721,7 +602,7 @@
return vk::NoError();
}
- ASSERT(mDescriptorSetOffset == 0);
+ ASSERT(mUsedDescriptorSetRange.contains(0));
VkDevice device = contextVk->getDevice();
@@ -790,9 +671,9 @@
return mDescriptorSets;
}
-uint32_t ProgramVk::getDescriptorSetOffset() const
+const gl::RangeUI &ProgramVk::getUsedDescriptorSetRange() const
{
- return mDescriptorSetOffset;
+ return mUsedDescriptorSetRange;
}
void ProgramVk::updateTexturesDescriptorSet(ContextVk *contextVk)
@@ -802,7 +683,8 @@
return;
}
- VkDescriptorSet descriptorSet = mDescriptorSets.back();
+ ASSERT(mUsedDescriptorSetRange.contains(1));
+ VkDescriptorSet descriptorSet = mDescriptorSets[1];
// TODO(jmadill): Don't hard-code the texture limit.
ShaderTextureArray<VkDescriptorImageInfo> descriptorImageInfo;
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h
index 4da6d38..e26c516 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.h
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.h
@@ -101,7 +101,6 @@
const vk::ShaderModule &getLinkedVertexModule() const;
const vk::ShaderModule &getLinkedFragmentModule() const;
- const vk::PipelineLayout &getPipelineLayout() const;
vk::Error updateUniforms(ContextVk *contextVk);
@@ -111,14 +110,15 @@
// However, it's valid to leave them in an undefined, unbound state, if they are never used.
// This means when we want to ignore a descriptor set index, we need to pass in an offset
// parameter to BindDescriptorSets, which is an offset into the getDescriptorSets array.
- uint32_t getDescriptorSetOffset() const;
+ // This allows us to skip binding blank descriptor sets when the Program doesn't use Uniforms
+ // or Textures.
+ const gl::RangeUI &getUsedDescriptorSetRange() const;
void updateTexturesDescriptorSet(ContextVk *contextVk);
void invalidateTextures();
private:
void reset(VkDevice device);
- vk::Error initPipelineLayout(ContextVk *context);
vk::Error initDescriptorSets(ContextVk *contextVk);
gl::Error initDefaultUniformBlocks(const gl::Context *glContext);
vk::Error updateDefaultUniformsDescriptorSet(ContextVk *contextVk);
@@ -128,8 +128,6 @@
vk::ShaderModule mLinkedVertexModule;
vk::ShaderModule mLinkedFragmentModule;
- vk::PipelineLayout mPipelineLayout;
- std::vector<vk::DescriptorSetLayout> mDescriptorSetLayouts;
// State for the default uniform blocks.
struct DefaultUniformBlock final : private angle::NonCopyable
@@ -157,7 +155,7 @@
// Descriptor sets for uniform blocks and textures for this program.
std::vector<VkDescriptorSet> mDescriptorSets;
- uint32_t mDescriptorSetOffset;
+ gl::RangeUI mUsedDescriptorSetRange;
bool mDirtyTextures;
template <typename T>
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index dc620dc..3ac7573 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -231,6 +231,13 @@
}
}
+ for (auto &descriptorSetLayout : mGraphicsDescriptorSetLayouts)
+ {
+ descriptorSetLayout.destroy(mDevice);
+ }
+
+ mGraphicsPipelineLayout.destroy(mDevice);
+
mRenderPassCache.destroy(mDevice);
if (mGlslangWrapper)
@@ -462,6 +469,9 @@
// Initialize the format table.
mFormatTable.initialize(mPhysicalDevice, &mNativeTextureCaps);
+ // Initialize the pipeline layout for GL programs.
+ ANGLE_TRY(initGraphicsPipelineLayout());
+
return vk::NoError();
}
@@ -961,4 +971,102 @@
return vk::NoError();
}
+const vk::PipelineLayout &RendererVk::getGraphicsPipelineLayout() const
+{
+ return mGraphicsPipelineLayout;
+}
+
+const std::vector<vk::DescriptorSetLayout> &RendererVk::getGraphicsDescriptorSetLayouts() const
+{
+ return mGraphicsDescriptorSetLayouts;
+}
+
+vk::Error RendererVk::initGraphicsPipelineLayout()
+{
+ ASSERT(!mGraphicsPipelineLayout.valid());
+
+ // Create two descriptor set layouts: one for default uniform info, and one for textures.
+ // Skip one or both if there are no uniforms.
+ VkDescriptorSetLayoutBinding uniformBindings[2];
+ uint32_t blockCount = 0;
+
+ {
+ auto &layoutBinding = uniformBindings[blockCount];
+
+ layoutBinding.binding = blockCount;
+ layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ layoutBinding.descriptorCount = 1;
+ layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+ layoutBinding.pImmutableSamplers = nullptr;
+
+ blockCount++;
+ }
+
+ {
+ auto &layoutBinding = uniformBindings[blockCount];
+
+ layoutBinding.binding = blockCount;
+ layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ layoutBinding.descriptorCount = 1;
+ layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+ layoutBinding.pImmutableSamplers = nullptr;
+
+ blockCount++;
+ }
+
+ {
+ VkDescriptorSetLayoutCreateInfo uniformInfo;
+ uniformInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ uniformInfo.pNext = nullptr;
+ uniformInfo.flags = 0;
+ uniformInfo.bindingCount = blockCount;
+ uniformInfo.pBindings = uniformBindings;
+
+ vk::DescriptorSetLayout uniformLayout;
+ ANGLE_TRY(uniformLayout.init(mDevice, uniformInfo));
+ mGraphicsDescriptorSetLayouts.push_back(std::move(uniformLayout));
+ }
+
+ std::array<VkDescriptorSetLayoutBinding, gl::IMPLEMENTATION_MAX_ACTIVE_TEXTURES>
+ textureBindings;
+
+ // TODO(jmadill): This approach might not work well for texture arrays.
+ for (uint32_t textureIndex = 0; textureIndex < gl::IMPLEMENTATION_MAX_ACTIVE_TEXTURES;
+ ++textureIndex)
+ {
+ VkDescriptorSetLayoutBinding &layoutBinding = textureBindings[textureIndex];
+
+ layoutBinding.binding = textureIndex;
+ layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ layoutBinding.descriptorCount = 1;
+ layoutBinding.stageFlags = (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
+ layoutBinding.pImmutableSamplers = nullptr;
+ }
+
+ {
+ VkDescriptorSetLayoutCreateInfo textureInfo;
+ textureInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ textureInfo.pNext = nullptr;
+ textureInfo.flags = 0;
+ textureInfo.bindingCount = static_cast<uint32_t>(textureBindings.size());
+ textureInfo.pBindings = textureBindings.data();
+
+ vk::DescriptorSetLayout textureLayout;
+ ANGLE_TRY(textureLayout.init(mDevice, textureInfo));
+ mGraphicsDescriptorSetLayouts.push_back(std::move(textureLayout));
+ }
+
+ VkPipelineLayoutCreateInfo createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ createInfo.pNext = nullptr;
+ createInfo.flags = 0;
+ createInfo.setLayoutCount = static_cast<uint32_t>(mGraphicsDescriptorSetLayouts.size());
+ createInfo.pSetLayouts = mGraphicsDescriptorSetLayouts[0].ptr();
+ createInfo.pushConstantRangeCount = 0;
+ createInfo.pPushConstantRanges = nullptr;
+
+ ANGLE_TRY(mGraphicsPipelineLayout.init(mDevice, createInfo));
+
+ return vk::NoError();
+}
} // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.h b/src/libANGLE/renderer/vulkan/RendererVk.h
index d2b98f5..98b0f5e 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.h
+++ b/src/libANGLE/renderer/vulkan/RendererVk.h
@@ -144,6 +144,9 @@
// TODO(jmadill): Keep in ContextVk to enable threaded rendering.
vk::CommandBufferNode *allocateCommandNode();
+ const vk::PipelineLayout &getGraphicsPipelineLayout() const;
+ const std::vector<vk::DescriptorSetLayout> &getGraphicsDescriptorSetLayouts() const;
+
private:
vk::Error initializeDevice(uint32_t queueFamilyIndex);
void ensureCapsInitialized() const;
@@ -156,6 +159,7 @@
void freeAllInFlightResources();
vk::Error flushCommandGraph(const gl::Context *context, vk::CommandBuffer *commandBatch);
void resetCommandGraph();
+ vk::Error initGraphicsPipelineLayout();
mutable bool mCapsInitialized;
mutable gl::Caps mNativeCaps;
@@ -197,6 +201,11 @@
RenderPassCache mRenderPassCache;
std::vector<vk::CommandBufferNode *> mOpenCommandGraph;
+
+ // ANGLE uses a single pipeline layout for all GL programs. It is owned here in the Renderer.
+ // See the design doc for an overview of the pipeline layout structure.
+ vk::PipelineLayout mGraphicsPipelineLayout;
+ std::vector<vk::DescriptorSetLayout> mGraphicsDescriptorSetLayouts;
};
} // namespace rx