-
Notifications
You must be signed in to change notification settings - Fork 374
add variable rasterization rate proposal #6271
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
Open
cabanier
wants to merge
1
commit into
gpuweb:main
Choose a base branch
from
cabanier:VariableRasterizationRate
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,285 @@ | ||
| # Variable Rasterization Rate | ||
|
|
||
| * Status: [Draft](README.md#status-draft) | ||
| * Created: 2026-05-27 | ||
| * Issue: [#450](https://github.com/gpuweb/gpuweb/issues/450) | ||
|
|
||
| ## Motivation | ||
|
|
||
| Variable rasterization rate lets an application vary the density of rasterization across a render | ||
| target. A common use case is foveated rendering: the application renders the region near the user's | ||
| gaze at full density and renders peripheral regions at lower density. This can reduce fragment | ||
| shader invocations and attachment traffic while keeping the highest quality in the most important | ||
| part of the image. | ||
|
|
||
| The feature is useful for XR, high-resolution displays, and other workloads where a uniform | ||
| one-fragment-per-pixel rate spends significant work in areas where the user is less sensitive to | ||
| detail. | ||
|
|
||
| This proposal uses the name "variable rasterization rate" because the intended portable behavior is | ||
| to change rasterization density. This is distinct from variable rate shading, which changes fragment | ||
| shading rate while preserving full-rate rasterization. | ||
|
|
||
| ## Scope | ||
|
|
||
| This proposal adds an optional render pass attachment that provides a two-dimensional rasterization | ||
| density map. Each texel in the map controls the requested horizontal and vertical density for a tile | ||
| of the render target. | ||
|
|
||
| The initial proposal does not add: | ||
|
|
||
| * per-draw rasterization rate state | ||
| * per-primitive shader outputs such as `SV_ShadingRate` | ||
| * Vulkan-style shading rate palettes | ||
| * D3D12-style shading rate combiners | ||
| * WGSL built-ins or enable directives | ||
|
|
||
| Those features are related, but they are not equivalent to variable rasterization rate and are better | ||
| considered separately. | ||
|
|
||
| ## Native API Availability | ||
|
|
||
| **Vulkan**: | ||
|
|
||
| * [`VK_EXT_fragment_density_map`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_fragment_density_map.html) | ||
| exposes a render pass fragment density map attachment. | ||
| * [`VK_EXT_fragment_density_map2`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_fragment_density_map2.html) | ||
| exposes additional capabilities, including dynamic density maps and non-subsampled image support. | ||
| * Fragment density map images use `VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT`. | ||
| * A fragment density map image contains normalized `(x, y)` float component values for framebuffer | ||
| regions. Each component is in `(0.0, 1.0]`, with `1.0` meaning full density along that axis. | ||
| * Implementations use the density values as hints to optimize low-density regions. Vulkan also | ||
| allows subpass color and depth attachments to be subsampled for further optimization. | ||
|
|
||
| **Metal**: | ||
|
|
||
| * [`MTLRasterizationRateMap`](https://developer.apple.com/documentation/metal/mtlrasterizationratemap) | ||
| exposes rasterization rate maps through render pass descriptors. | ||
| * Metal represents the map using horizontal and vertical rate arrays rather than an arbitrary 2D | ||
| texture. A Metal implementation may expose this WebGPU feature if it can translate the WebGPU map | ||
| to the supported native representation, or if the WebGPU API is revised to use a separable map. | ||
|
|
||
| **D3D12**: | ||
|
|
||
| * D3D12 Variable Rate Shading controls shading rate, not rasterization rate. It preserves full-rate | ||
| rasterization and writes the full framebuffer. | ||
| * D3D12 VRS is not considered a faithful implementation of this feature unless the specification | ||
| explicitly allows a VRS approximation. | ||
|
|
||
| ## API | ||
|
|
||
| ### Feature | ||
|
|
||
| Add a new feature name: | ||
|
|
||
| ```webidl | ||
| partial enum GPUFeatureName { | ||
| "variable-rasterization-rate", | ||
| }; | ||
| ``` | ||
|
|
||
| ### Texture Usage | ||
|
|
||
| Add a new texture usage for rasterization rate map textures: | ||
|
|
||
| ```webidl | ||
| partial namespace GPUTextureUsage { | ||
| const GPUFlagsConstant VARIABLE_RASTERIZATION_RATE = 0x80; | ||
| }; | ||
| ``` | ||
|
|
||
| The exact bit value is provisional and should be assigned with the rest of the WebGPU texture usage | ||
| bits. | ||
|
|
||
| ### Limits | ||
|
|
||
| Add limits describing the supported size of the render-target region controlled by one map texel. | ||
|
|
||
| | Limit name | Type | Limit class | Default | | ||
| | --- | --- | --- | --- | | ||
| | `minVariableRasterizationRateTileWidth` | `GPUSize32` | minimum | 1 | | ||
| | `minVariableRasterizationRateTileHeight` | `GPUSize32` | minimum | 1 | | ||
| | `maxVariableRasterizationRateTileWidth` | `GPUSize32` | maximum | 1 | | ||
| | `maxVariableRasterizationRateTileHeight` | `GPUSize32` | maximum | 1 | | ||
|
|
||
| If `"variable-rasterization-rate"` is supported, the maximum tile dimensions must be at least the | ||
| minimum tile dimensions. A larger maximum allows a lower-resolution map to cover a larger render | ||
| target. | ||
|
|
||
| ### Render Pass Descriptor | ||
|
|
||
| Add an optional rasterization rate map view to `GPURenderPassDescriptor`: | ||
|
|
||
| ```webidl | ||
| partial dictionary GPURenderPassDescriptor { | ||
| GPUTextureView? variableRasterizationRateMap = null; | ||
| }; | ||
| ``` | ||
|
|
||
| If `variableRasterizationRateMap` is `null`, the render pass uses the normal full rasterization | ||
| rate. | ||
|
|
||
| ## Rasterization Rate Map | ||
|
|
||
| The rasterization rate map is a 2D texture view whose first two color components contain normalized | ||
| `(x, y)` floating-point density values. The API should define the supported map formats separately | ||
| from the density-value semantics. A compact unsigned-normalized two-component format such as | ||
| `rg8unorm` is a plausible MVP baseline, but Vulkan's fragment density map model is described in | ||
| terms of normalized component values rather than as a requirement that WebGPU expose exactly that | ||
| format. Implementations may expose additional uncompressed, non-sRGB, two-or-more-component | ||
| unsigned-normalized or floating-point color formats if they are supported natively as rasterization | ||
| rate maps. | ||
|
|
||
| For each texel: | ||
|
|
||
| * the red channel is the requested horizontal rasterization density | ||
| * the green channel is the requested vertical rasterization density | ||
| * `1.0` requests full density | ||
| * values greater than `0.0` and less than `1.0` request lower density | ||
|
|
||
| Each map texel applies to a rectangular tile of the render pass. Given render pass dimensions | ||
| `renderWidth` and `renderHeight`, and map dimensions `mapWidth` and `mapHeight`: | ||
|
|
||
| ```text | ||
| tileWidth = ceil(renderWidth / mapWidth) | ||
| tileHeight = ceil(renderHeight / mapHeight) | ||
| ``` | ||
|
|
||
| `tileWidth` and `tileHeight` must be within the device's variable rasterization rate tile size | ||
| limits. | ||
|
|
||
| Implementations may clamp or quantize the requested density to native supported rates. The map | ||
| values are hints: implementations may choose the effective rasterization density for correctness, | ||
| quality, security, or native API constraints. A component value of `0.0` is invalid as a fragment | ||
| density value; implementations must reject, sanitize, clamp, or otherwise avoid passing invalid zero | ||
| density values to native APIs. | ||
|
|
||
| ## Validation | ||
|
|
||
| If `descriptor.variableRasterizationRateMap` is not `null`: | ||
|
|
||
| * The `"variable-rasterization-rate"` feature must be enabled on the device. | ||
| * `descriptor.variableRasterizationRateMap` must be a valid `GPUTextureView`. | ||
| * The texture view's format must be a supported rasterization rate map format with at least two | ||
| uncompressed, non-sRGB, unsigned-normalized or floating-point color components. | ||
| * The texture view's texture usage must include `GPUTextureUsage.VARIABLE_RASTERIZATION_RATE`. | ||
| * The texture view must reference exactly one mip level. | ||
| * The texture view's texture sample count must be `1`. | ||
| * The texture view dimension must be `"2d"` for a single-view render pass. | ||
| * The texture view's map tile size must be within the device limits described above. | ||
| * The texture subresource used as the map must not overlap any color, depth/stencil, or resolve | ||
| attachment in the same render pass. | ||
| * The texture subresource used as the map must not be used for another purpose in the same render | ||
| pass. | ||
|
|
||
| When this proposal is combined with multi-view rendering: | ||
|
|
||
| * If the render pass `viewCount` is `1`, a `"2d"` map view is used for the single view. | ||
| * If the render pass `viewCount` is greater than `1`, the map view must be a `"2d-array"` view with | ||
| `arrayLayerCount == viewCount`; array layer `i` controls view `i`. | ||
|
|
||
| ## Behavior | ||
|
|
||
| Variable rasterization rate affects the density of rasterization and fragment processing within the | ||
| render pass. It may reduce the number of fragment shader invocations and may cause one fragment | ||
| shader result to cover multiple framebuffer pixels. It does not change the logical render target | ||
| dimensions. | ||
|
|
||
| Existing shader inputs and outputs keep their existing meanings: | ||
|
|
||
| * `@builtin(position)` is still expressed in framebuffer coordinates. | ||
| * Fragment shader derivatives are computed using the implementation's effective rasterization rate. | ||
| * Depth, stencil, blending, and attachment stores apply to the covered framebuffer pixels according | ||
| to the effective rasterization rate. | ||
|
|
||
| The map is read-only for the duration of the render pass. Its contents are consumed at render pass | ||
| execution time. Implementations that cannot read a GPU-produced map at render pass execution time | ||
| must either avoid exposing this feature, restrict the allowed usages, or insert implementation | ||
| workarounds that preserve the specified behavior. | ||
|
|
||
| ## WGSL | ||
|
|
||
| No WGSL changes are required. | ||
|
|
||
| This proposal intentionally avoids adding a shader-visible rate built-in in the initial version. A | ||
| future extension could expose the effective rasterization rate or tile information if applications | ||
| need it. | ||
|
|
||
| ## JavaScript Example | ||
|
|
||
| ```js | ||
| const adapter = await navigator.gpu.requestAdapter(); | ||
| if (!adapter.features.has("variable-rasterization-rate")) { | ||
| throw new Error("Variable rasterization rate is not available"); | ||
| } | ||
|
|
||
| const device = await adapter.requestDevice({ | ||
| requiredFeatures: ["variable-rasterization-rate"], | ||
| }); | ||
|
|
||
| const rateMap = device.createTexture({ | ||
| size: [64, 64], | ||
| format: "rg8unorm", | ||
| usage: | ||
| GPUTextureUsage.COPY_DST | | ||
| GPUTextureUsage.TEXTURE_BINDING | | ||
| GPUTextureUsage.VARIABLE_RASTERIZATION_RATE, | ||
| }); | ||
|
|
||
| // Upload an rg8unorm map. Red and green values of 255 request full density. | ||
| // Lower non-zero values request lower horizontal or vertical density. | ||
| device.queue.writeTexture( | ||
| { texture: rateMap }, | ||
| rateMapData, | ||
| { bytesPerRow: 64 * 2 }, | ||
| [64, 64], | ||
| ); | ||
|
|
||
| const pass = encoder.beginRenderPass({ | ||
| colorAttachments: [{ | ||
| view: colorTexture.createView(), | ||
| loadOp: "clear", | ||
| storeOp: "store", | ||
| clearValue: [0, 0, 0, 1], | ||
| }], | ||
| variableRasterizationRateMap: rateMap.createView(), | ||
| }); | ||
| ``` | ||
|
|
||
| ## Security and Robustness | ||
|
|
||
| The map contents are ordinary texture data and cannot be fully validated at API call time. | ||
| Implementations must sanitize, clamp, or otherwise safely handle map values that are not accepted by | ||
| the native backend. Invalid map contents must not cause undefined behavior, memory disclosure, or | ||
| device loss. | ||
|
|
||
| Because variable rasterization rate changes rendering quality and can affect timing, applications | ||
| should treat it as an optional performance feature. User agents may clamp requested rates upward or | ||
| disable the feature in contexts where exposing it is not appropriate. | ||
|
|
||
| ## Open Questions | ||
|
|
||
| * Should the WebGPU API expose a full 2D texture map, as proposed here, or a separable horizontal and | ||
| vertical map that more closely matches Metal? | ||
| * Should WebGPU require one MVP map format such as `rg8unorm`, or should adapters expose a set of | ||
| supported rasterization rate map formats? | ||
| * Should GPU-generated maps in the same command buffer be required? On Vulkan this likely requires | ||
| `fragmentDensityMapDynamic` or an implementation workaround. | ||
| * What exact robustness behavior should be required for `0.0` map values, which are outside the | ||
| native Vulkan fragment density value range? | ||
| * Should render target restrictions from Vulkan subsampled images be exposed to applications, or | ||
| must implementations hide them by requiring native non-subsampled image support or using internal | ||
| intermediate images? | ||
| * Should D3D12 VRS be allowed as an approximation, or should VRS remain a separate WebGPU feature? | ||
| * How should render bundles declare compatibility with render passes that use a rasterization rate | ||
| map? | ||
|
|
||
| ## Resources | ||
|
|
||
| * https://github.com/gpuweb/gpuweb/issues/450 | ||
| * https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_fragment_density_map.html | ||
| * https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_fragment_density_map2.html | ||
| * https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_fragment_shading_rate.html | ||
| * https://developer.apple.com/documentation/metal/mtlrasterizationratemap | ||
| * https://learn.microsoft.com/en-us/windows/win32/direct3d12/vrs | ||
| * https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/fragment_density_map | ||
Oops, something went wrong.
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.
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.
I'll add the proposal for discussion in the group. That said, right now it looks like VRS is fairly non-portable and potentially difficult to secure. We'll likely need a lot more work before it can make it in the spec.
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.
Do you have a suggestion on how we could add this? I'd like to avoid adding another WebXR-only API like we did for WebGL...
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.
@mwyrzykowski doesn't Apple jump through some hoops to get this feature to work? If we can't have a WebGPU extension, would you be ok with a WebXR only solution?