-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Impeller] add support for specialization constants. #47432
Changes from 10 commits
f0b499d
0bfb741
3c32f10
2baf3f6
432700d
bdab80d
ea7ce18
e10ebb2
ea91c9f
bd3e5dd
43e2309
a1ab22f
2bea539
153072a
967dfff
82e6d2b
8d2b6de
9fabef1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,90 @@ | ||||||
# Specialization Constants | ||||||
|
||||||
Specialization constants have two possible benefits when used in a shader: | ||||||
|
||||||
* Improving performance, by removing branching and conditional code. | ||||||
* Code organization/size, by removing the number of shader source files required. | ||||||
|
||||||
These goals are interrrelated, we can always reduce the number of shaders by creating | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
ubershaders. Or we can reduce branching by adding more shader variants. Specialization | ||||||
constants provide a happy medium where the source files would be highly reptitive. | ||||||
|
||||||
## Example Usage | ||||||
|
||||||
Consider the case of the "decal" texture sampling mode. This is implement via clamp-to-border with | ||||||
a border color set to transparent black. While this functionality is well supported on the Metal and | ||||||
Vulkan backends, the GLES backend needs to support devices that do not have this extension. As a | ||||||
result, the following code was used to conditionally decal: | ||||||
|
||||||
```glsl | ||||||
// Decal sample if necessary. | ||||||
vec4 Sample(sampler2D sampler, vec2 coord) { | ||||||
#ifdef GLES | ||||||
return IPSampleDecal(sampler, coord) | ||||||
#else | ||||||
return texture(sampler, coord); | ||||||
#endif | ||||||
} | ||||||
``` | ||||||
|
||||||
This works great as long as we know that the GLES backend can never do the decal sample mode. This is also "free" as the ifdef branch is evaluated in the compiler. But eventually, we added a runtime check for decal mode as we need to support this on GLES. So the code turned into (approximately) the following: | ||||||
|
||||||
```glsl | ||||||
#ifdef GLES | ||||||
uniform float supports_decal; | ||||||
#endif | ||||||
|
||||||
// Decal sample if necessary. | ||||||
vec4 Sample(sampler2D sampler, vec2 coord) { | ||||||
#ifdef GLES | ||||||
if (supports_decal) { | ||||||
return texture(sampler, coord); | ||||||
} | ||||||
return IPSampleDecal(sampler, coord) | ||||||
#else | ||||||
return texture(sampler, coord); | ||||||
#endif | ||||||
} | ||||||
``` | ||||||
|
||||||
Now we've got decal support, but we've also got new problems: | ||||||
|
||||||
* The code is actually quite messy. We have to track different uniform values depending on the backend. | ||||||
* The GLES backend is still paying some cost for branching, even though we "know" that decal is or isn't supported when the shader is compiled. | ||||||
|
||||||
### Specialization constants to the rescue | ||||||
|
||||||
Instead of using a runtime check, we can create a specialization constant that is set when compiling the | ||||||
shader. This constant will be `1` if decal is supported and `0` otherwise. | ||||||
|
||||||
```glsl | ||||||
layout(constant_id = 0) const int supports_decal = 1; | ||||||
|
||||||
vec4 Sample(sampler2D sampler, vec2 coord) { | ||||||
if (supports_decal) { | ||||||
return texture(sampler, coord); | ||||||
} | ||||||
return IPSampleDecal(sampler, coord) | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
Immediately we realize a number of benefits: | ||||||
|
||||||
* Code is the same across all backends | ||||||
* Runtime branching cost is removed as the branch is compiled out. | ||||||
|
||||||
|
||||||
## Implementation | ||||||
|
||||||
Currently the only constant values that are support are 32bit ints. This should be sufficient for now as we | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would re-write this paragraph (tried to do it via GH suggestions) to be more opinionated. Something like: 32-bit integers are supported as constant values and should represent:
- `true`/`false`
(example)
- select-style constants:
(example)
AVOID adding constant color values or anything more complex. |
||||||
should generally only use these values for true/false or select-style constants. Please don't try to add | ||||||
constant color values or anything like that. | ||||||
|
||||||
Specialization constants are provided to the CreateDefault argument in content_context.cc and aren't a | ||||||
part of variants. This is intentional: specialization constants shouldn't be used to create (potentially unlimited) runtime variants of a shader. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This along with restriction to 32bit ints both seem reasonable to me... Best not to hide our shader volume lest we accidentally land combinatorial explosions. |
||||||
|
||||||
Backend specific information: | ||||||
* In the Metal backend, the specialization constants are mapped to a MTLFunctionConstantValues. See also: https://developer.apple.com/documentation/metal/using_function_specialization_to_build_pipeline_variants?language=objc | ||||||
* In the Vulkan backend, the specialization constants are mapped to VkSpecializationINfo. See also: https://blogs.igalia.com/itoral/2018/03/20/improving-shader-performance-with-vulkans-specialization-constants/ | ||||||
* In the GLES backend, the SPIRV Cross compiler will generate defines named `#ifdef SPIRV_CROSS_CONSTANT_i`, where i is the index of constant. The Impeller runtime will insert `#define SPIRV_CROSS_CONSTANT_i` in the header of the shader. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to start with 1-2 sentences describing what a specialization constant is. This is my first time hearing the phrase, and I tried a Google search, and as you can see it's not clear where to start.
Maybe (assuming I understand now):
"""
A specialization constant, or a shader constant that can be set at runtime, allowing for more efficient code generation and flexibility.
"""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!