Commit 4aa4fcd6 by Nicolas Capens Committed by Nicolas Capens

Avoid recompiling identical SPIR-V code

We were creating SpirvShader objects for every shader stage of the pipeline, each with their own unique serial ID. This caused us to compile the same SPIR-V code over an over again when multiple pipelines are created from the same shader module(s). This change essentially moves the serial ID to the shader module. Things that still require us to recompile code from the same shader module are the entry point specification, and specialization constants. The former is taken into account by using a 64-bit ID consisting of the module ID and entry point ID. For the latter we assume any use of specialization constants will result in a unique SPIR-V binary. This is conservative and may still lead to unnecessary recompiles. This change also minimizes the state passed to SpirvShader, to prevent specialization on state not taken into account by the routine caches. Bug: b/135609394 Tests: dEQP-VK.pipeline.render_to_image.core.*.huge.* Change-Id: I204e812265067462f8019af9f6b7b3067ef5dc7f Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/33109 Presubmit-Ready: Nicolas Capens <nicolascapens@google.com> Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Tested-by: 's avatarNicolas Capens <nicolascapens@google.com> Reviewed-by: 's avatarAlexis Hétu <sugoi@google.com>
parent 473f3731
...@@ -34,7 +34,7 @@ namespace sw ...@@ -34,7 +34,7 @@ namespace sw
{ {
unsigned int computeHash(); unsigned int computeHash();
int shaderID; uint64_t shaderID;
VkCompareOp depthCompareMode; VkCompareOp depthCompareMode;
bool depthWriteEnable; bool depthWriteEnable;
......
...@@ -366,16 +366,16 @@ namespace sw ...@@ -366,16 +366,16 @@ namespace sw
} // namespace SIMD } // namespace SIMD
std::atomic<int> SpirvShader::serialCounter(1); // Start at 1, 0 is invalid shader.
SpirvShader::SpirvShader( SpirvShader::SpirvShader(
VkPipelineShaderStageCreateInfo const *createInfo, uint32_t codeSerialID,
VkShaderStageFlagBits pipelineStage,
const char *entryPointName,
InsnStore const &insns, InsnStore const &insns,
vk::RenderPass *renderPass, vk::RenderPass *renderPass,
uint32_t subpassIndex) uint32_t subpassIndex)
: insns{insns}, inputs{MAX_INTERFACE_COMPONENTS}, : insns{insns}, inputs{MAX_INTERFACE_COMPONENTS},
outputs{MAX_INTERFACE_COMPONENTS}, outputs{MAX_INTERFACE_COMPONENTS},
serialID{serialCounter++}, modes{} codeSerialID(codeSerialID), modes{}
{ {
ASSERT(insns.size() > 0); ASSERT(insns.size() > 0);
...@@ -411,7 +411,7 @@ namespace sw ...@@ -411,7 +411,7 @@ namespace sw
auto id = Object::ID(insn.word(2)); auto id = Object::ID(insn.word(2));
auto name = insn.string(3); auto name = insn.string(3);
auto stage = executionModelToStage(executionModel); auto stage = executionModelToStage(executionModel);
if (stage == createInfo->stage && strcmp(name, createInfo->pName) == 0) if (stage == pipelineStage && strcmp(name, entryPointName) == 0)
{ {
ASSERT_MSG(entryPointFunctionId == 0, "Duplicate entry point with name '%s' and stage %d", name, int(stage)); ASSERT_MSG(entryPointFunctionId == 0, "Duplicate entry point with name '%s' and stage %d", name, int(stage));
entryPointFunctionId = id; entryPointFunctionId = id;
...@@ -982,7 +982,7 @@ namespace sw ...@@ -982,7 +982,7 @@ namespace sw
} }
} }
ASSERT_MSG(entryPointFunctionId != 0, "Entry point '%s' not found", createInfo->pName); ASSERT_MSG(entryPointFunctionId != 0, "Entry point '%s' not found", entryPointName);
AssignBlockFields(); AssignBlockFields();
} }
......
...@@ -540,15 +540,19 @@ namespace sw ...@@ -540,15 +540,19 @@ namespace sw
static_assert(sizeof(ImageInstruction) == sizeof(uint32_t), "ImageInstruction must be 32-bit"); static_assert(sizeof(ImageInstruction) == sizeof(uint32_t), "ImageInstruction must be 32-bit");
int getSerialID() const // This method is for retrieving an ID that uniquely identifies the
// shader entry point represented by this object.
uint64_t getSerialID() const
{ {
return serialID; return ((uint64_t)entryPointBlockId.value() << 32) | codeSerialID;
} }
SpirvShader(VkPipelineShaderStageCreateInfo const *createInfo, SpirvShader(uint32_t codeSerialID,
InsnStore const &insns, VkShaderStageFlagBits stage,
vk::RenderPass *renderPass, const char *entryPointName,
uint32_t subpassIndex); InsnStore const &insns,
vk::RenderPass *renderPass,
uint32_t subpassIndex);
struct Modes struct Modes
{ {
...@@ -740,8 +744,7 @@ namespace sw ...@@ -740,8 +744,7 @@ namespace sw
} }
private: private:
const int serialID; const uint32_t codeSerialID;
static std::atomic<int> serialCounter;
Modes modes; Modes modes;
HandleMap<Type> types; HandleMap<Type> types;
HandleMap<Object> defs; HandleMap<Object> defs;
......
...@@ -152,8 +152,7 @@ unsigned char getNumberOfChannels(VkFormat format) ...@@ -152,8 +152,7 @@ unsigned char getNumberOfChannels(VkFormat format)
return 0; return 0;
} }
// preprocessSpirv applies and freezes specializations into constants, inlines // preprocessSpirv applies and freezes specializations into constants, and inlines all functions.
// all functions and performs constant folding.
std::vector<uint32_t> preprocessSpirv( std::vector<uint32_t> preprocessSpirv(
std::vector<uint32_t> const &code, std::vector<uint32_t> const &code,
VkSpecializationInfo const *specializationInfo) VkSpecializationInfo const *specializationInfo)
...@@ -451,12 +450,16 @@ void GraphicsPipeline::compileShaders(const VkAllocationCallbacks* pAllocator, c ...@@ -451,12 +450,16 @@ void GraphicsPipeline::compileShaders(const VkAllocationCallbacks* pAllocator, c
UNIMPLEMENTED("pStage->flags"); UNIMPLEMENTED("pStage->flags");
} }
auto module = vk::Cast(pStage->module); const ShaderModule *module = vk::Cast(pStage->module);
auto code = preprocessSpirv(module->getCode(), pStage->pSpecializationInfo); auto code = preprocessSpirv(module->getCode(), pStage->pSpecializationInfo);
// If the pipeline has specialization constants, assume they're unique and
// use a new serial ID so the shader gets recompiled.
uint32_t codeSerialID = (pStage->pSpecializationInfo ? ShaderModule::nextSerialID() : module->getSerialID());
// FIXME (b/119409619): use an allocator here so we can control all memory allocations // FIXME (b/119409619): use an allocator here so we can control all memory allocations
// TODO: also pass in any pipeline state which will affect shader compilation // TODO: also pass in any pipeline state which will affect shader compilation
auto spirvShader = new sw::SpirvShader{pStage, code, vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass}; auto spirvShader = new sw::SpirvShader(codeSerialID, pStage->stage, pStage->pName, code, vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass);
switch (pStage->stage) switch (pStage->stage)
{ {
...@@ -542,16 +545,21 @@ size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCre ...@@ -542,16 +545,21 @@ size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCre
void ComputePipeline::compileShaders(const VkAllocationCallbacks* pAllocator, const VkComputePipelineCreateInfo* pCreateInfo) void ComputePipeline::compileShaders(const VkAllocationCallbacks* pAllocator, const VkComputePipelineCreateInfo* pCreateInfo)
{ {
auto module = vk::Cast(pCreateInfo->stage.module); auto &stage = pCreateInfo->stage;
const ShaderModule *module = vk::Cast(stage.module);
auto code = preprocessSpirv(module->getCode(), pCreateInfo->stage.pSpecializationInfo); auto code = preprocessSpirv(module->getCode(), stage.pSpecializationInfo);
ASSERT_OR_RETURN(code.size() > 0); ASSERT_OR_RETURN(code.size() > 0);
ASSERT(shader == nullptr); ASSERT(shader == nullptr);
// FIXME(b/119409619): use allocator. // If the pipeline has specialization constants, assume they're unique and
shader = new sw::SpirvShader(&pCreateInfo->stage, code, nullptr, 0); // use a new serial ID so the shader gets recompiled.
uint32_t codeSerialID = (stage.pSpecializationInfo ? ShaderModule::nextSerialID() : module->getSerialID());
// TODO(b/119409619): use allocator.
shader = new sw::SpirvShader(codeSerialID, stage.stage, stage.pName, code, nullptr, 0);
vk::DescriptorSet::Bindings descriptorSets; // FIXME(b/129523279): Delay code generation until invoke time. vk::DescriptorSet::Bindings descriptorSets; // FIXME(b/129523279): Delay code generation until invoke time.
program = new sw::ComputeProgram(shader, layout, descriptorSets); program = new sw::ComputeProgram(shader, layout, descriptorSets);
program->generate(); program->generate();
......
...@@ -19,7 +19,10 @@ ...@@ -19,7 +19,10 @@
namespace vk namespace vk
{ {
ShaderModule::ShaderModule(const VkShaderModuleCreateInfo* pCreateInfo, void* mem) : code(reinterpret_cast<uint32_t*>(mem)) std::atomic<uint32_t> ShaderModule::serialCounter(1); // Start at 1, 0 is invalid shader.
ShaderModule::ShaderModule(const VkShaderModuleCreateInfo* pCreateInfo, void* mem)
: serialID(nextSerialID()), code(reinterpret_cast<uint32_t*>(mem))
{ {
memcpy(code, pCreateInfo->pCode, pCreateInfo->codeSize); memcpy(code, pCreateInfo->pCode, pCreateInfo->codeSize);
wordCount = static_cast<uint32_t>(pCreateInfo->codeSize / sizeof(uint32_t)); wordCount = static_cast<uint32_t>(pCreateInfo->codeSize / sizeof(uint32_t));
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#define VK_SHADER_MODULE_HPP_ #define VK_SHADER_MODULE_HPP_
#include "VkObject.hpp" #include "VkObject.hpp"
#include <atomic>
#include <vector> #include <vector>
namespace rr namespace rr
...@@ -37,7 +39,13 @@ public: ...@@ -37,7 +39,13 @@ public:
// guts' operations, and this copy. // guts' operations, and this copy.
std::vector<uint32_t> getCode() const { return std::vector<uint32_t>{ code, code + wordCount };} std::vector<uint32_t> getCode() const { return std::vector<uint32_t>{ code, code + wordCount };}
uint32_t getSerialID() const { return serialID; }
static uint32_t nextSerialID() { return serialCounter++; }
private: private:
const uint32_t serialID;
static std::atomic<uint32_t> serialCounter;
uint32_t* code = nullptr; uint32_t* code = nullptr;
uint32_t wordCount = 0; uint32_t wordCount = 0;
}; };
......
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