gpui_web: Flatten webgl texel loading (#64337)
The webGL backend uses textures to upload quads to the GPU, since it doesn't have storage buffers. Each quad is stored in 10 texels, and we previously fetched each texel 4 times. Although texel fetches are cached, the extra bookkeeping optimized badly, and lead to bad performance. This change replaces the `read_word` calls with `fetch_texel_instance`, and manually unrolls this loop. This gave the following frame time improvements scrolling through delta web: - desktop, 9950x3d iGPU, 1440p, firefox: ~3.3x faster - mobile, Pixel 10 Pro, firefox: ~23x faster --- Release Notes: - [GPUI] Improved performance of webGL quad upload, around a ~3x frame time reduction in typical cases
This commit is contained in:
1 parent
a001a76198
commit
bbacc15a4f
2 files changed
+175
-12
No files matched your search
@@ -1,7 +1,7 @@
|
||||
@group(1) @binding(0) var t_instances: texture_2d<u32>;
|
||||
|
||||
// Each texel of `t_instances` packs four 32-bit words of instance data. Records
|
||||
// are read strictly front to back, so all readers share a cursor that keeps the
|
||||
// Each texel of `t_instances` packs four 32-bit words of instance data. Most
|
||||
// records are read strictly front to back through a cursor that keeps the
|
||||
// most recently fetched texel and only touches the texture again when the next
|
||||
// word crosses a texel boundary. This fetches each texel exactly once per
|
||||
// record load instead of once per word. The `read_*` functions must therefore
|
||||
@@ -136,16 +136,57 @@ fn read_transformation(cursor: ptr<function, InstanceCursor>) -> TransformationM
|
||||
}
|
||||
|
||||
fn load_quad(instance_id: u32) -> Quad {
|
||||
var cursor = instance_cursor(instance_id * 40u);
|
||||
// Keep this fixed-layout decoder explicit. Some WebGL shader compilers fail
|
||||
// to optimize the cursor's branches and dynamic vector indexing for quads.
|
||||
let first_texel_index = instance_id * 10u;
|
||||
let width = textureDimensions(t_instances).x;
|
||||
let texel0 = fetch_instance_texel(first_texel_index, width);
|
||||
let texel1 = fetch_instance_texel(first_texel_index + 1u, width);
|
||||
let texel2 = fetch_instance_texel(first_texel_index + 2u, width);
|
||||
let texel3 = fetch_instance_texel(first_texel_index + 3u, width);
|
||||
let texel4 = fetch_instance_texel(first_texel_index + 4u, width);
|
||||
let texel5 = fetch_instance_texel(first_texel_index + 5u, width);
|
||||
let texel6 = fetch_instance_texel(first_texel_index + 6u, width);
|
||||
let texel7 = fetch_instance_texel(first_texel_index + 7u, width);
|
||||
let texel8 = fetch_instance_texel(first_texel_index + 8u, width);
|
||||
let texel9 = fetch_instance_texel(first_texel_index + 9u, width);
|
||||
|
||||
let values0 = bitcast<vec4<f32>>(texel0);
|
||||
let values1 = bitcast<vec4<f32>>(texel1);
|
||||
let values2 = bitcast<vec4<f32>>(texel2);
|
||||
let values3 = bitcast<vec4<f32>>(texel3);
|
||||
let values4 = bitcast<vec4<f32>>(texel4);
|
||||
let values5 = bitcast<vec4<f32>>(texel5);
|
||||
let values6 = bitcast<vec4<f32>>(texel6);
|
||||
let values7 = bitcast<vec4<f32>>(texel7);
|
||||
let values8 = bitcast<vec4<f32>>(texel8);
|
||||
let values9 = bitcast<vec4<f32>>(texel9);
|
||||
|
||||
return Quad(
|
||||
read_word(&cursor),
|
||||
read_word(&cursor),
|
||||
read_bounds(&cursor),
|
||||
read_bounds(&cursor),
|
||||
read_background(&cursor),
|
||||
read_hsla(&cursor),
|
||||
read_corners(&cursor),
|
||||
read_edges(&cursor),
|
||||
texel0.x,
|
||||
texel0.y,
|
||||
Bounds(values0.zw, values1.xy),
|
||||
Bounds(values1.zw, values2.xy),
|
||||
Background(
|
||||
texel2.z,
|
||||
texel2.w,
|
||||
Hsla(values3.x, values3.y, values3.z, values3.w),
|
||||
values4.x,
|
||||
array<LinearColorStop, 2>(
|
||||
LinearColorStop(
|
||||
Hsla(values4.y, values4.z, values4.w, values5.x),
|
||||
values5.y,
|
||||
),
|
||||
LinearColorStop(
|
||||
Hsla(values5.z, values5.w, values6.x, values6.y),
|
||||
values6.z,
|
||||
),
|
||||
),
|
||||
texel6.w,
|
||||
),
|
||||
Hsla(values7.x, values7.y, values7.z, values7.w),
|
||||
Corners(values8.x, values8.y, values8.z, values8.w),
|
||||
Edges(values9.x, values9.y, values9.z, values9.w),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2201,7 +2201,11 @@ impl RenderingParameters {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{MonochromeSprite, PolychromeSprite, Quad, Shadow, SubpixelSprite, Underline};
|
||||
use gpui::{
|
||||
BorderStyle, ColorSpace, ContentMask, Corners, Edges, Hsla, MonochromeSprite,
|
||||
PolychromeSprite, Quad, Shadow, SubpixelSprite, Underline, linear_color_stop,
|
||||
linear_gradient,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
|
||||
@@ -2240,4 +2244,122 @@ mod tests {
|
||||
assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
|
||||
assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn webgl_quad_layout_matches_fixed_decoder() {
|
||||
let quad = Quad {
|
||||
order: 41,
|
||||
border_style: BorderStyle::Dashed,
|
||||
bounds: Bounds {
|
||||
origin: Point {
|
||||
x: 1.0.into(),
|
||||
y: 2.0.into(),
|
||||
},
|
||||
size: Size {
|
||||
width: 3.0.into(),
|
||||
height: 4.0.into(),
|
||||
},
|
||||
},
|
||||
content_mask: ContentMask {
|
||||
bounds: Bounds {
|
||||
origin: Point {
|
||||
x: 5.0.into(),
|
||||
y: 6.0.into(),
|
||||
},
|
||||
size: Size {
|
||||
width: 7.0.into(),
|
||||
height: 8.0.into(),
|
||||
},
|
||||
},
|
||||
},
|
||||
background: linear_gradient(
|
||||
11.0,
|
||||
linear_color_stop(
|
||||
Hsla {
|
||||
h: 12.0,
|
||||
s: 13.0,
|
||||
l: 14.0,
|
||||
a: 15.0,
|
||||
},
|
||||
16.0,
|
||||
),
|
||||
linear_color_stop(
|
||||
Hsla {
|
||||
h: 17.0,
|
||||
s: 18.0,
|
||||
l: 19.0,
|
||||
a: 20.0,
|
||||
},
|
||||
21.0,
|
||||
),
|
||||
)
|
||||
.color_space(ColorSpace::Oklab),
|
||||
border_color: Hsla {
|
||||
h: 22.0,
|
||||
s: 23.0,
|
||||
l: 24.0,
|
||||
a: 25.0,
|
||||
},
|
||||
corner_radii: Corners {
|
||||
top_left: 26.0.into(),
|
||||
top_right: 27.0.into(),
|
||||
bottom_right: 28.0.into(),
|
||||
bottom_left: 29.0.into(),
|
||||
},
|
||||
border_widths: Edges {
|
||||
top: 30.0.into(),
|
||||
right: 31.0.into(),
|
||||
bottom: 32.0.into(),
|
||||
left: 33.0.into(),
|
||||
},
|
||||
};
|
||||
|
||||
let bytes = unsafe { WgpuRenderer::instance_bytes(std::slice::from_ref(&quad)) };
|
||||
let words: &[u32] = bytemuck::cast_slice(bytes);
|
||||
assert_eq!(
|
||||
words,
|
||||
&[
|
||||
41,
|
||||
1,
|
||||
1.0_f32.to_bits(),
|
||||
2.0_f32.to_bits(),
|
||||
3.0_f32.to_bits(),
|
||||
4.0_f32.to_bits(),
|
||||
5.0_f32.to_bits(),
|
||||
6.0_f32.to_bits(),
|
||||
7.0_f32.to_bits(),
|
||||
8.0_f32.to_bits(),
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
11.0_f32.to_bits(),
|
||||
12.0_f32.to_bits(),
|
||||
13.0_f32.to_bits(),
|
||||
14.0_f32.to_bits(),
|
||||
15.0_f32.to_bits(),
|
||||
16.0_f32.to_bits(),
|
||||
17.0_f32.to_bits(),
|
||||
18.0_f32.to_bits(),
|
||||
19.0_f32.to_bits(),
|
||||
20.0_f32.to_bits(),
|
||||
21.0_f32.to_bits(),
|
||||
0,
|
||||
22.0_f32.to_bits(),
|
||||
23.0_f32.to_bits(),
|
||||
24.0_f32.to_bits(),
|
||||
25.0_f32.to_bits(),
|
||||
26.0_f32.to_bits(),
|
||||
27.0_f32.to_bits(),
|
||||
28.0_f32.to_bits(),
|
||||
29.0_f32.to_bits(),
|
||||
30.0_f32.to_bits(),
|
||||
31.0_f32.to_bits(),
|
||||
32.0_f32.to_bits(),
|
||||
33.0_f32.to_bits(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user