Use re-spirv in the Vulkan driver to optimize shaders. #111452
Draft
+8,308
−14
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Disclaimer
re-spirv is a project I started with another contributor on our own with the express purpose of doing shader optimization in SPIR-V in real time environments. The project uses the MIT License and should be compatible with Godot's licensing. This PR adds a new dependency to the third party folder.
Background
Since the ubershaders PR was merged, Godot has been making use of specialization constants to heavily eliminate parts of code that are unused by materials depending on the environment's configuration or proximity to nodes such as lights or reflection probes. This is specially important in the mobile renderer, which will generate specializations based on the amount of lights close to it and compile them on the fly on the background while relying on the ubershader to display it while it's getting ready.
Specialization constants are used as the main flags for eliminating unused code, but not all drivers will prioritize eliminating code based on them first. re-spirv was developed with the explicit goal of prioritizing DCE based on eliminating code branches that can be determined to be dead once the values for the constants are known. As measured in that project and in Godot itself, it is possible to get significant reductions in pipeline creation time by eliminating code from the SPIR-V instead on the application side at the cost of some very small extra processing time.
Why not apply spirv-opt instead?
We should eventually add shader optimization to Godot using spirv-opt. There's even a PR here which adds it and gives substantial benefits to shader sizes and pipeline creation times too. spirv-opt even features options to freeze specialization constants to certain values and run optimization passes.
However, the performance leaves a lot to be desired and is basically not applicable for real-time cases. While re-spirv is unlikely to ever feature the great size reductions that spirv-opt can achieve, the size reduction that is possible is very much worth it for the very little extra processing time it adds to the pipeline creation.
At the time being, I think spirv-opt will be best suited for optimizing shaders when using the new shader baker option, which can afford to take more processing time during a step where the user does not expect real-time performance.
For purposes of comparison to spirv-opt, re-spirv currently only features the following optimizations:
Procedure
We have multiple possible places to apply SPIR-V optimization, and for the purposes of this PR, it has been moved to live solely inside the Vulkan driver. Some benefits have been found by running re-spirv for non-specialized shaders because they're currently compiled without optimizations, but I believe it would be best suited to rely on spirv-opt at some point in the future to optimize these shaders and leave re-spirv solely for specialized shaders.
The implementation of re-spirv in the driver is very simple: a parsing step is performed during shader creation to generate the analysis DAG that will be used during optimization. The results of this step are reusable and the cost only has to be paid once per shader and isn't paid again for each specialization that is created.
Before a pipeline is created, the optimizer is called with the known values of the specialization constants. The resulting SPIR-V is passed to the driver to create the pipeline. This means a new unique SPIR-V is generated on the fly and there's no need for the driver to parse the specialization constants.
Results
In theory, we shouldn't be seeing substantial decreases in pipeline creation time, as drivers already apply several optimization steps to achieve optimal shader performance when converting the SPIR-V to actual GPU code. However, not all drivers are built the same, and not all of them may prioritize the passes featured by re-spirv which are known to be the most effective first given the nature of the shaders. These drivers may reach the optimal point by applying different passes first or may pay a higher price from converting SPIR-V as-is to their own intermediate format first.
Some of these measurements might be within a margin of error due to the highly parallelized nature of the workload depending on the system and the driver in question. However, there's hardware that shows significant improvements and it should make it clear as to why we pursued this path.
Not all of these were measured using the same project or renderer so the numerical values can't be compared between platforms. The main purpose of the chart is to show the reductions on each platform. For example, the NVIDIA measurement was done on a much larger project using Forward+ to better represent the improvements on a project that fits the platform's demands better.
Testing
PRINT_PIPELINE_COMPILATION_TIMESare provided on the source code itself, but my hope is to further polish the measuring and provide proper monitors and statistics as the PR progresses more.TODO