Skip to content

Commit 768086f

Browse files
Merge pull request #421 from SixLabors/js/webgpu-first-flush
Cut WebGPU first-flush compile time and scratch memory
2 parents d1daf50 + 620eae3 commit 768086f

10 files changed

Lines changed: 561 additions & 193 deletions

File tree

‎src/ImageSharp.Drawing.WebGPU/Shaders/WgslSource/Shared/blend.wgsl‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,16 @@ fn compose_source(backdrop: vec4<f32>, source: vec4<f32>, opacity: f32, mode: u3
369369
}
370370

371371
let normal = mix_mode == MIX_NORMAL;
372+
373+
// The overlap term is evaluated once, with the operand order of the compose mode
374+
// that consumes it. Evaluating it inside each case inlines the whole mix-mode switch
375+
// once per case, and the driver compile time of this shader grows with every copy.
376+
var overlap = vec3(0.0);
377+
if !normal {
378+
let dest_first = compose_mode == COMPOSE_DEST_OVER || compose_mode == COMPOSE_DEST_ATOP;
379+
overlap = blend_overlap(select(backdrop, scaled, dest_first), select(scaled, backdrop, dest_first), mix_mode);
380+
}
381+
372382
switch compose_mode {
373383
case COMPOSE_CLEAR: {
374384
return vec4(0.0);
@@ -384,7 +394,7 @@ fn compose_source(backdrop: vec4<f32>, source: vec4<f32>, opacity: f32, mode: u3
384394
return compose_over_normal(scaled, backdrop);
385395
}
386396

387-
return compose_over(scaled, backdrop, blend_overlap(scaled, backdrop, mix_mode));
397+
return compose_over(scaled, backdrop, overlap);
388398
}
389399
case COMPOSE_SRC_IN: {
390400
return compose_in(backdrop, scaled);
@@ -403,14 +413,14 @@ fn compose_source(backdrop: vec4<f32>, source: vec4<f32>, opacity: f32, mode: u3
403413
return compose_atop_normal(backdrop, scaled);
404414
}
405415

406-
return compose_atop(backdrop, scaled, blend_overlap(backdrop, scaled, mix_mode));
416+
return compose_atop(backdrop, scaled, overlap);
407417
}
408418
case COMPOSE_DEST_ATOP: {
409419
if normal {
410420
return compose_atop_normal(scaled, backdrop);
411421
}
412422

413-
return compose_atop(scaled, backdrop, blend_overlap(scaled, backdrop, mix_mode));
423+
return compose_atop(scaled, backdrop, overlap);
414424
}
415425
case COMPOSE_XOR: {
416426
return compose_xor(backdrop, scaled);
@@ -420,14 +430,14 @@ fn compose_source(backdrop: vec4<f32>, source: vec4<f32>, opacity: f32, mode: u3
420430
return compose_plus_normal(backdrop, scaled);
421431
}
422432

423-
return compose_plus(backdrop, scaled, blend_overlap(backdrop, scaled, mix_mode));
433+
return compose_plus(backdrop, scaled, overlap);
424434
}
425435
default: {
426436
if normal {
427437
return compose_over_normal(backdrop, scaled);
428438
}
429439

430-
return compose_over(backdrop, scaled, blend_overlap(backdrop, scaled, mix_mode));
440+
return compose_over(backdrop, scaled, overlap);
431441
}
432442
}
433443
}

‎src/ImageSharp.Drawing.WebGPU/Shaders/WgslSource/Shared/ptcl.wgsl‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@ const CMD_END_CLIP = 11u;
3838
const CMD_JUMP = 12u;
3939
const CMD_PATH_GRAD = 16u;
4040

41+
// CMD_FILL and CMD_SOLID reference their raster interest rectangle by its offset in the
42+
// info stream. This value marks a draw without an interest block: the whole target.
43+
const CMD_INTEREST_FULL_TARGET = 0xffffffffu;
44+
4145
// The individual PTCL structs are written here, but read/write is by
4246
// hand in the relevant shaders
4347

4448
// Fill coverage command: rasterize the tile's segment slice into coverage.
45-
// Written by coarse write_path, read by fine read_fill.
49+
// Written by coarse write_path, read by fine read_fill. The command holds five words after
50+
// the tag: size_and_rule, seg_data, backdrop, coverage_data, and the info-stream offset of
51+
// the interest rectangle, which read_fill resolves into the vec4 below.
4652
struct CmdFill {
4753
size_and_rule: u32, // bit 0 = even-odd, bit 1 = aliased coverage, bits 2.. = segment count
4854
seg_data: u32, // index of the tile's first Segment in segment storage
@@ -59,7 +65,9 @@ struct CmdJump {
5965
new_ix: u32,
6066
}
6167

62-
// Solid color paint.
68+
// Solid color paint. The command holds two words after the tag: the scene offset of the two
69+
// packed color words and the info offset of the draw flags, which read_color resolves into
70+
// the struct below.
6371
struct CmdColor {
6472
color_rg: u32, // associated RG packed as binary16
6573
color_ba: u32, // associated BA packed as binary16

‎src/ImageSharp.Drawing.WebGPU/Shaders/WgslSource/coarse.wgsl‎

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,15 @@ fn solid_tile_has_coverage(draw_flags: u32, backdrop: i32) -> bool {
144144
// When emit_empty_solid is false, solid tiles whose backdrop resolves to
145145
// zero coverage are skipped entirely. Returns true when a command was
146146
// written, meaning the caller should emit the matching paint command.
147-
fn write_path(tile: Tile, tile_ix: u32, path: Path, global_x: u32, global_y: u32, draw_flags: u32, coverage_data: f32, interest: vec4<f32>, emit_empty_solid: bool) -> bool {
147+
fn write_path(tile: Tile, tile_ix: u32, path: Path, global_x: u32, global_y: u32, draw_flags: u32, coverage_data: f32, interest_ref: u32, emit_empty_solid: bool) -> bool {
148148
// We overload the "segments" field to store both count (written by
149149
// path_count stage) and segment allocation (used by path_tiling and
150150
// fine).
151151
let n_segs = tile.segment_count_or_ix;
152152
if n_segs != 0u {
153153
var seg_ix = atomicAdd(&bump.segments, n_segs);
154154
tiles[tile_ix].segment_count_or_ix = ~seg_ix;
155-
alloc_cmd(9u);
155+
alloc_cmd(6u);
156156
ptcl[cmd_offset] = CMD_FILL;
157157
let even_odd = (draw_flags & DRAW_INFO_FLAGS_FILL_RULE_BIT) != 0u;
158158
let aliased = (draw_flags & DRAW_INFO_FLAGS_ALIASED_BIT) != 0u;
@@ -186,44 +186,37 @@ fn write_path(tile: Tile, tile_ix: u32, path: Path, global_x: u32, global_y: u32
186186

187187
// size_and_rule: bit 0 = even-odd, bit 1 = aliased coverage, bits 2.. = segment count.
188188
let size_and_rule = (n_segs << 2u) | (u32(aliased) << 1u) | u32(even_odd);
189-
let fill = CmdFill(size_and_rule, seg_ix, tile.backdrop, packed_coverage_data, interest);
190-
ptcl[cmd_offset + 1u] = fill.size_and_rule;
191-
ptcl[cmd_offset + 2u] = fill.seg_data;
192-
ptcl[cmd_offset + 3u] = u32(fill.backdrop);
193-
ptcl[cmd_offset + 4u] = fill.coverage_data;
194-
ptcl[cmd_offset + 5u] = bitcast<u32>(fill.interest.x);
195-
ptcl[cmd_offset + 6u] = bitcast<u32>(fill.interest.y);
196-
ptcl[cmd_offset + 7u] = bitcast<u32>(fill.interest.z);
197-
ptcl[cmd_offset + 8u] = bitcast<u32>(fill.interest.w);
189+
ptcl[cmd_offset + 1u] = size_and_rule;
190+
ptcl[cmd_offset + 2u] = seg_ix;
191+
ptcl[cmd_offset + 3u] = u32(tile.backdrop);
192+
ptcl[cmd_offset + 4u] = packed_coverage_data;
193+
ptcl[cmd_offset + 5u] = interest_ref;
198194
// The winding backdrop is now in PTCL. Reuse its tile field for the original segment
199195
// count so fine can read adjacent slices after segment_count_or_ix becomes the allocation.
200196
tiles[tile_ix].backdrop = i32(n_segs);
201-
cmd_offset += 9u;
197+
cmd_offset += 6u;
202198
return true;
203199
} else {
204200
if !emit_empty_solid && !solid_tile_has_coverage(draw_flags, tile.backdrop) {
205201
return false;
206202
}
207203

208-
alloc_cmd(5u);
204+
alloc_cmd(2u);
209205
ptcl[cmd_offset] = CMD_SOLID;
210-
ptcl[cmd_offset + 1u] = bitcast<u32>(interest.x);
211-
ptcl[cmd_offset + 2u] = bitcast<u32>(interest.y);
212-
ptcl[cmd_offset + 3u] = bitcast<u32>(interest.z);
213-
ptcl[cmd_offset + 4u] = bitcast<u32>(interest.w);
214-
cmd_offset += 5u;
206+
ptcl[cmd_offset + 1u] = interest_ref;
207+
cmd_offset += 2u;
215208
return true;
216209
}
217210
}
218211

219-
// Emits a CMD_COLOR paint command (binary16 RGBA color plus draw flags).
220-
fn write_color(color: CmdColor) {
221-
alloc_cmd(4u);
212+
// Emits a CMD_COLOR paint command referencing the draw's packed color words in the scene
213+
// stream and its draw flags in the info stream.
214+
fn write_color(scene_offset: u32, info_offset: u32) {
215+
alloc_cmd(3u);
222216
ptcl[cmd_offset] = CMD_COLOR;
223-
ptcl[cmd_offset + 1u] = color.color_rg;
224-
ptcl[cmd_offset + 2u] = color.color_ba;
225-
ptcl[cmd_offset + 3u] = color.draw_flags;
226-
cmd_offset += 4u;
217+
ptcl[cmd_offset + 1u] = scene_offset;
218+
ptcl[cmd_offset + 2u] = info_offset;
219+
cmd_offset += 3u;
227220
}
228221

229222
// Emits a CMD_RECOLOR command referencing one target-specialized auxiliary record.
@@ -597,18 +590,14 @@ fn main(
597590
let di = dm.info_offset;
598591
let draw_flags = info_bin_data[di];
599592
var coverage_data = 0.0;
600-
var interest = vec4<f32>(0.0, 0.0, f32(config.target_width), f32(config.target_height));
593+
var interest_ref = CMD_INTEREST_FULL_TARGET;
601594
// Draw tags whose info block spans at least five words append a
602595
// coverage data plus interest rectangle at the end of it.
603596
let drawtag_info_size = (drawtag >> 6u) & 0xfu;
604597
if drawtag_info_size >= 5u {
605598
let interest_offset = di + drawtag_info_size - 5u;
606599
coverage_data = bitcast<f32>(info_bin_data[interest_offset]);
607-
interest = vec4<f32>(
608-
bitcast<f32>(info_bin_data[interest_offset + 1u]),
609-
bitcast<f32>(info_bin_data[interest_offset + 2u]),
610-
bitcast<f32>(info_bin_data[interest_offset + 3u]),
611-
bitcast<f32>(info_bin_data[interest_offset + 4u]));
600+
interest_ref = interest_offset + 1u;
612601
}
613602

614603
if clip_zero_depth == 0u {
@@ -622,50 +611,50 @@ fn main(
622611
let tile = tiles[tile_ix];
623612
switch drawtag {
624613
case DRAWTAG_FILL_COLOR: {
625-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
626-
write_color(CmdColor(scene[dd], scene[dd + 1u], draw_flags));
614+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
615+
write_color(dd, di);
627616
}
628617
}
629618
case DRAWTAG_FILL_RECOLOR: {
630-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
619+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
631620
write_recolor(config.brush_data_base + scene[dd], draw_flags);
632621
}
633622
}
634623
case DRAWTAG_FILL_LIN_GRADIENT: {
635-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
624+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
636625
let index = scene[dd];
637626
let info_offset = di + 1u;
638627
write_grad(CMD_LIN_GRAD, index, info_offset);
639628
}
640629
}
641630
case DRAWTAG_FILL_RAD_GRADIENT: {
642-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
631+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
643632
let index = scene[dd];
644633
let info_offset = di + 1u;
645634
write_grad(CMD_RAD_GRAD, index, info_offset);
646635
}
647636
}
648637
case DRAWTAG_FILL_ELLIPTIC_GRADIENT: {
649-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
638+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
650639
let index = scene[dd];
651640
let info_offset = di + 1u;
652641
write_grad(CMD_ELLIPTIC_GRAD, index, info_offset);
653642
}
654643
}
655644
case DRAWTAG_FILL_SWEEP_GRADIENT: {
656-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
645+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
657646
let index = scene[dd];
658647
let info_offset = di + 1u;
659648
write_grad(CMD_SWEEP_GRAD, index, info_offset);
660649
}
661650
}
662651
case DRAWTAG_FILL_PATH_GRADIENT: {
663-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
652+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
664653
write_path_grad(config.brush_data_base + scene[dd], scene[dd + 1u], scene[dd + 2u], draw_flags);
665654
}
666655
}
667656
case DRAWTAG_FILL_IMAGE: {
668-
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, false) {
657+
if write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, false) {
669658
write_image(di + 1u);
670659
}
671660
}
@@ -689,7 +678,7 @@ fn main(
689678
case DRAWTAG_END_CLIP: {
690679
clip_depth -= 1u;
691680
let blend = scene[dd];
692-
write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest, true);
681+
write_path(tile, tile_ix, path, bin_tile_x + tile_x, bin_tile_y + tile_y, draw_flags, coverage_data, interest_ref, true);
693682
let alpha = bitcast<f32>(scene[dd + 1u]);
694683
write_end_clip(CmdEndClip(blend, alpha));
695684
render_blend_depth -= 1u;

0 commit comments

Comments
 (0)