Skip to content

Commit 5a14fb4

Browse files
authored
Cache prewarm base case (#9552)
Prevents hitching for most pipelines, on certain devices with Turnip-based drivers This does not address external samplers yet. This simply handles the case where we want to prewarm a pipeline with the base configuration. Refactor program's descriptor set layout bindings, clean up some leaking resources
1 parent 22a1aac commit 5a14fb4

File tree

8 files changed

+213
-31
lines changed

8 files changed

+213
-31
lines changed

filament/backend/include/backend/Platform.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ class UTILS_PUBLIC Platform {
320320
*/
321321
StereoscopicType stereoscopicType = StereoscopicType::NONE;
322322

323+
/*
324+
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
325+
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
326+
*/
327+
uint8_t stereoscopicEyeCount = 2;
328+
323329
/**
324330
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
325331
* This is only supported for:

filament/backend/include/backend/Program.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,13 @@ class Program {
5050
descriptor_binding_t binding;
5151
};
5252

53+
struct DescriptorSetLayoutBinding {
54+
descriptor_set_t set;
55+
DescriptorSetLayout layout;
56+
};
57+
5358
using SpecializationConstant = std::variant<int32_t, float, bool>;
54-
using DescriptorSetLayoutArray = std::array<DescriptorSetLayout, MAX_DESCRIPTOR_SET_COUNT>;
59+
using DescriptorSetLayoutArray = utils::FixedCapacityVector<DescriptorSetLayoutBinding>;
5560

5661
struct Uniform { // For ES2 support
5762
utils::CString name; // full qualified name of the uniform field
@@ -150,7 +155,10 @@ class Program {
150155

151156
inline Program& descriptorLayout(backend::descriptor_set_t set,
152157
DescriptorSetLayout descriptorLayout) noexcept {
153-
mDescriptorLayouts[set] = std::move(descriptorLayout);
158+
mDescriptorLayouts.push_back({
159+
.set = set,
160+
.layout = std::move(descriptorLayout),
161+
});
154162
return *this;
155163
}
156164

@@ -189,7 +197,8 @@ class Program {
189197

190198
// Descriptions for descriptor set layouts that may be used for this Program, which
191199
// can be useful for attempting to compile the pipeline ahead of time.
192-
DescriptorSetLayoutArray mDescriptorLayouts;
200+
DescriptorSetLayoutArray mDescriptorLayouts =
201+
DescriptorSetLayoutArray::with_capacity(MAX_DESCRIPTOR_SET_COUNT);
193202

194203
// For ES2 support only
195204
AttributesInfo mAttributes;

filament/backend/include/backend/platforms/VulkanPlatform.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <utils/Hash.h>
2929
#include <utils/PrivateImplementation.h>
3030

31+
#include <cstring>
3132
#include <cstddef>
3233
#include <functional>
3334
#include <string>
@@ -61,8 +62,15 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation<VulkanPlatf
6162
return std::hash<std::string>{}(s.data());
6263
}
6364
};
65+
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
66+
// Instead, we'll provide our own comparator.
67+
struct ExtensionEqualFn {
68+
bool operator()(utils::CString const& a, utils::CString const& b) const noexcept {
69+
return strcmp(a.c_str(), b.c_str()) == 0;
70+
}
71+
};
6472
// Utility for managing device or instance extensions during initialization.
65-
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn>;
73+
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn, ExtensionEqualFn>;
6674

6775
/**
6876
* A collection of handles to objects and metadata that comprises a Vulkan context. The client

filament/backend/src/vulkan/VulkanDriver.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
251251
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
252252
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
253253
mStereoscopicType(driverConfig.stereoscopicType),
254+
mStereoscopicEyeCount(driverConfig.stereoscopicEyeCount),
254255
mAsynchronousMode(driverConfig.asynchronousMode) {
255256

256257
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -796,6 +797,32 @@ void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program, utils
796797
program);
797798
vprogram.inc();
798799
mResourceManager.associateHandle(ph.getId(), std::move(tag));
800+
801+
if (!mContext.shouldUsePipelineCachePrewarming()) {
802+
return;
803+
}
804+
805+
// If async prewarming is enabled, let's find the proper layout and build the pipeline.
806+
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vkLayouts {};
807+
for (const auto& layoutBinding : program.getDescriptorSetLayouts()) {
808+
DescriptorSetLayout layoutDescription = layoutBinding.layout;
809+
auto layoutHandle = mResourceManager.allocHandle<VulkanDescriptorSetLayout>();
810+
auto layout = mDescriptorSetLayoutCache.createLayout(layoutHandle, std::move(layoutDescription));
811+
vkLayouts[layoutBinding.set] = layout->getVkLayout();
812+
}
813+
814+
StereoscopicType stereoscopicType = mStereoscopicType;
815+
if (stereoscopicType == StereoscopicType::MULTIVIEW && !program.isMultiview()) {
816+
stereoscopicType = StereoscopicType::NONE;
817+
}
818+
819+
VkPipelineLayout layout = mPipelineLayoutCache.getLayout(vkLayouts, vprogram);
820+
mPipelineCache.asyncPrewarmCache(
821+
*vprogram.get(),
822+
layout,
823+
stereoscopicType,
824+
mStereoscopicEyeCount,
825+
program.getPriorityQueue());
799826
}
800827

801828
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
@@ -1800,7 +1827,11 @@ void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
18001827
void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
18011828
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
18021829
if (callback) {
1803-
scheduleCallback(handler, user, callback);
1830+
if (mContext.shouldUsePipelineCachePrewarming()) {
1831+
mPipelineCache.addCachePrewarmCallback(handler, callback, user);
1832+
} else {
1833+
scheduleCallback(handler, user, callback);
1834+
}
18041835
}
18051836
}
18061837

filament/backend/src/vulkan/VulkanDriver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ class VulkanDriver final : public DriverBase {
206206
bool const mIsSRGBSwapChainSupported;
207207
bool const mIsMSAASwapChainSupported;
208208
backend::StereoscopicType const mStereoscopicType;
209+
uint8_t const mStereoscopicEyeCount;
209210
backend::AsynchronousMode const mAsynchronousMode;
210211

211212
// setAcquiredImage is a DECL_DRIVER_API_SYNCHRONOUS_N which means we don't necessarily have the

filament/backend/src/vulkan/VulkanPipelineCache.cpp

Lines changed: 121 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipelin
107107
.handle = createPipeline(mPipelineRequirements),
108108
.lastUsed = mCurrentTime,
109109
};
110-
assert_invariant(cacheEntry.handle != VK_NULL_HANDLE && "Pipeline handle is nullptr");
110+
assert_invariant(cacheEntry.handle != VK_NULL_HANDLE && "Pipeline handle is VK_NULL_HANDLE");
111111
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
112112
}
113113

@@ -126,7 +126,66 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
126126
}
127127
}
128128

129-
VkPipeline VulkanPipelineCache::createPipeline(const PipelineKey& key) noexcept {
129+
void VulkanPipelineCache::asyncPrewarmCache(const VulkanProgram& program,
130+
VkPipelineLayout layout,
131+
StereoscopicType stereoscopicType,
132+
uint8_t stereoscopicViewCount, CompilerPriorityQueue priority) {
133+
PipelineKey key {
134+
.shaders = {
135+
program.getVertexShader(),
136+
program.getFragmentShader(),
137+
},
138+
// We're using dynamic rendering, so this should be empty.
139+
.renderPass = VK_NULL_HANDLE,
140+
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
141+
.subpassIndex = 0,
142+
// We're using vertex input dynamic state, so these should be empty.
143+
.vertexAttributes = {},
144+
.vertexBuffers = {},
145+
// Create a reasonable default raster state; we're assuming this is not cached.
146+
.rasterState = {
147+
.cullMode = VK_CULL_MODE_NONE,
148+
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
149+
.depthBiasEnable = VK_FALSE,
150+
.blendEnable = VK_FALSE,
151+
.depthWriteEnable = VK_FALSE,
152+
.alphaToCoverageEnable = VK_FALSE,
153+
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
154+
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE,
155+
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
156+
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
157+
.colorWriteMask = 0,
158+
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
159+
.depthClamp = VK_FALSE,
160+
.colorTargetCount = 1,
161+
.colorBlendOp = BlendEquation::SUBTRACT,
162+
.alphaBlendOp = BlendEquation::SUBTRACT,
163+
.depthCompareOp = SamplerCompareFunc::L,
164+
.depthBiasConstantFactor = 0.f,
165+
.depthBiasSlopeFactor = 0.f,
166+
},
167+
.layout = layout,
168+
};
169+
PipelineDynamicOptions dynamicOptions {
170+
.useDynamicVertexInputState = true,
171+
.useDynamicRenderPasses = true,
172+
.stereoscopicType = stereoscopicType,
173+
.stereoscopicViewCount = stereoscopicViewCount,
174+
};
175+
176+
CallbackManager::Handle cmh = mCallbackManager.get();
177+
auto token = std::make_shared<ProgramToken>();
178+
mCompilerThreadPool.queue(priority, token, [this, key, dynamicOptions, cmh]() mutable {
179+
VkPipeline pipeline = createPipeline(key, dynamicOptions);
180+
mCallbackManager.put(cmh);
181+
// We don't actually need this pipeline, we just wanted to force the driver to cache
182+
// the pipeline's information.
183+
vkDestroyPipeline(mDevice, pipeline, VKALLOC);
184+
});
185+
}
186+
187+
VkPipeline VulkanPipelineCache::createPipeline(
188+
const PipelineKey& key, const PipelineDynamicOptions& dynamicOptions) noexcept {
130189
assert_invariant(key.shaders[0] && "Vertex shader is not bound.");
131190
assert_invariant(key.layout && "No pipeline layout specified");
132191

@@ -150,27 +209,30 @@ VkPipeline VulkanPipelineCache::createPipeline(const PipelineKey& key) noexcept
150209
.pAttachments = colorBlendAttachments,
151210
};
152211

153-
// Expand our size-optimized structs into the proper Vk structs.
154-
uint32_t numVertexAttribs = 0;
155-
uint32_t numVertexBuffers = 0;
156-
212+
VkPipelineVertexInputStateCreateInfo vertexInputState;
157213
VkVertexInputAttributeDescription vertexAttributes[VERTEX_ATTRIBUTE_COUNT];
158214
VkVertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT];
159-
for (uint32_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
160-
if (key.vertexAttributes[i].format > 0) {
161-
vertexAttributes[numVertexAttribs++] = key.vertexAttributes[i];
162-
}
163-
if (key.vertexBuffers[i].stride > 0) {
164-
vertexBuffers[numVertexBuffers++] = key.vertexBuffers[i];
215+
if (!dynamicOptions.useDynamicVertexInputState) {
216+
uint32_t numVertexAttribs = 0;
217+
uint32_t numVertexBuffers = 0;
218+
219+
for (uint32_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
220+
if (key.vertexAttributes[i].format > 0) {
221+
vertexAttributes[numVertexAttribs++] = key.vertexAttributes[i];
222+
}
223+
if (key.vertexBuffers[i].stride > 0) {
224+
vertexBuffers[numVertexBuffers++] = key.vertexBuffers[i];
225+
}
165226
}
227+
vertexInputState = {
228+
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
229+
.vertexBindingDescriptionCount = numVertexBuffers,
230+
.pVertexBindingDescriptions = vertexBuffers,
231+
.vertexAttributeDescriptionCount = numVertexAttribs,
232+
.pVertexAttributeDescriptions = vertexAttributes,
233+
};
166234
}
167-
VkPipelineVertexInputStateCreateInfo vertexInputState = {
168-
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
169-
.vertexBindingDescriptionCount = numVertexBuffers,
170-
.pVertexBindingDescriptions = vertexBuffers,
171-
.vertexAttributeDescriptionCount = numVertexAttribs,
172-
.pVertexAttributeDescriptions = vertexAttributes,
173-
};
235+
174236
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {
175237
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
176238
.topology = (VkPrimitiveTopology) key.topology,
@@ -180,15 +242,22 @@ VkPipeline VulkanPipelineCache::createPipeline(const PipelineKey& key) noexcept
180242
.viewportCount = 1,
181243
.scissorCount = 1,
182244
};
183-
VkDynamicState dynamicStateEnables[] = {
245+
246+
constexpr size_t maxDynamicStates = 3;
247+
size_t numDynamicStates = 2;
248+
VkDynamicState enabledDynamicStates[maxDynamicStates] = {
184249
VK_DYNAMIC_STATE_VIEWPORT,
185250
VK_DYNAMIC_STATE_SCISSOR,
186251
};
252+
if (dynamicOptions.useDynamicVertexInputState) {
253+
enabledDynamicStates[numDynamicStates++] = VK_DYNAMIC_STATE_VERTEX_INPUT_EXT;
254+
}
187255
VkPipelineDynamicStateCreateInfo dynamicState = {
188256
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
189-
.dynamicStateCount = 2,
190-
.pDynamicStates = dynamicStateEnables,
257+
.dynamicStateCount = static_cast<uint32_t>(numDynamicStates),
258+
.pDynamicStates = enabledDynamicStates,
191259
};
260+
192261
auto const& raster = key.rasterState;
193262
VkPipelineRasterizationStateCreateInfo vkRaster = {
194263
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -237,7 +306,7 @@ VkPipeline VulkanPipelineCache::createPipeline(const PipelineKey& key) noexcept
237306
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
238307
.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1,
239308
.pStages = shaderStages,
240-
.pVertexInputState = &vertexInputState,
309+
.pVertexInputState = dynamicOptions.useDynamicVertexInputState ? nullptr : &vertexInputState,
241310
.pInputAssemblyState = &inputAssemblyState,
242311
.pViewportState = &viewportState,
243312
.pRasterizationState = &vkRaster,
@@ -271,6 +340,27 @@ VkPipeline VulkanPipelineCache::createPipeline(const PipelineKey& key) noexcept
271340
}
272341
}
273342

343+
VkPipelineRenderingCreateInfoKHR renderingInfo {};
344+
VkFormat pipelineRenderingColorFormats[] = {VK_FORMAT_UNDEFINED};
345+
if (dynamicOptions.useDynamicRenderPasses) {
346+
renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
347+
renderingInfo.pNext = pipelineCreateInfo.pNext;
348+
// Fill values in with empty values where we do not already have values.
349+
renderingInfo.depthAttachmentFormat = VK_FORMAT_UNDEFINED;
350+
renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
351+
// If multiview, create a bitmask with bits representing each enabled view set to 1;
352+
// otherwise, set the view mask to 0.
353+
renderingInfo.viewMask = dynamicOptions.stereoscopicType == StereoscopicType::MULTIVIEW ?
354+
(1 << dynamicOptions.stereoscopicViewCount) - 1 : 0;
355+
356+
if (hasFragmentShader) {
357+
renderingInfo.colorAttachmentCount = 1;
358+
renderingInfo.pColorAttachmentFormats = pipelineRenderingColorFormats;
359+
}
360+
361+
pipelineCreateInfo.pNext = &renderingInfo;
362+
}
363+
274364
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
275365
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
276366
<< shaderStages[1].module << ")";
@@ -351,6 +441,14 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
351441
}
352442
}
353443

444+
void VulkanPipelineCache::addCachePrewarmCallback(CallbackHandler* handler,
445+
const CallbackHandler::Callback callback,
446+
void* user) {
447+
if (callback) {
448+
mCallbackManager.setCallback(handler, callback, user);
449+
}
450+
}
451+
354452
void VulkanPipelineCache::resetBoundPipeline() {
355453
mBoundPipeline = {};
356454
}

filament/backend/src/vulkan/VulkanPipelineCache.h

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,16 @@ class VulkanPipelineCache {
104104
// compiling the pipeline on the main thread at draw time. This is very dependent
105105
// on the implementation of the driver on the current device; it's expected to work
106106
// on devices with VK_EXT_vertex_input_dynamic_state and VK_KHR_dynamic_rendering.
107-
void asyncPreloadCache(fvkmemory::resource_ptr<VulkanProgram> program,
108-
VkPipelineLayout layout);
107+
void asyncPrewarmCache(const VulkanProgram& program, VkPipelineLayout layout,
108+
StereoscopicType stereoscopicType, uint8_t stereoscopicViewCount,
109+
CompilerPriorityQueue priority);
110+
111+
// Notifies the callback once all in-flight async cache prewarm jobs are complete.
112+
// This typically signals that it is safe to use a material at draw time without
113+
// hitching.
114+
void addCachePrewarmCallback(CallbackHandler* handler,
115+
const CallbackHandler::Callback callback,
116+
void* user);
109117

110118
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
111119
void bindPipeline(VulkanCommandBuffer* commands);
@@ -182,6 +190,19 @@ class VulkanPipelineCache {
182190
VkPipelineLayout layout; // 8 : 304
183191
};
184192

193+
// Provides information about any dynamic state that should be used in creation of the
194+
// pipeline (if supported).
195+
struct PipelineDynamicOptions {
196+
// Requires `VK_EXT_vertex_input_dynamic_state` to enable.
197+
bool useDynamicVertexInputState = false;
198+
// Requires `VK_KHR_dynamic_rendering` to enable.
199+
bool useDynamicRenderPasses = false;
200+
// Only used if `useDynamicRenderPasses` is true.
201+
StereoscopicType stereoscopicType = StereoscopicType::NONE;
202+
// Only used if `stereoscopicType` is `StereoscopicType::MULTIVIEW`.
203+
uint8_t stereoscopicViewCount = 2;
204+
};
205+
185206
static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding.");
186207

187208
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
@@ -213,8 +234,15 @@ class VulkanPipelineCache {
213234

214235
PipelineMap mPipelines;
215236

237+
// Creates a pipeline, with all optional dynamic pipeline states diabled.
238+
// Note - because PipelineDynamicOptions is defined within VulkanPipelineCache,
239+
// we cannot define the function with a default arg for it explicitly.
240+
inline VkPipeline createPipeline(const PipelineKey& key) noexcept {
241+
return createPipeline(key, {});
242+
}
243+
216244
// These helpers all return unstable pointers that should not be stored.
217-
VkPipeline createPipeline(const PipelineKey& key) noexcept;
245+
VkPipeline createPipeline(const PipelineKey& key, const PipelineDynamicOptions& dynamicOptions) noexcept;
218246

219247
// Immutable state.
220248
VkDevice mDevice = VK_NULL_HANDLE;

0 commit comments

Comments
 (0)