Here's a basic gradient which uses hardstops.
1 - type: gradient 2 bounds: 50 0 100 50 3 start: 0 0 4 end: 100 0 5 repeat: false 6 stops: [0.0, red , 0.5, yellow, 7 0.5, blue, 1.0, green ]
Because of the hard stops, these 4 stops cannot be baked into a gradient cache, since upscaling the cached strip would blur the transition from yellow to blue.
Thus emit_segments will detect this and in effect treat this as two separate gradients, side by side; each strip gets its own gradient cache strip, and its own quad primitive. The iterations inside emit_segments will look as follows:
1 emit_segments call: 2 start_offset: 0, end_offset: 1 3 prim_start_offset: 0, prim_end_offset: 1 4 gradient_offset_base: 0 5 caching from offset 0 to 0.5 6 and blitting to Rect(50.0×50.0 at (50.0,0.0)) 7 caching from offset 0.5 to 1 8 and blitting to Rect(50.0×50.0 at (100.0,0.0))
Resulting in these draw calls...
... from this texture cache:
If the above gradient is in a primitive that is 300 pixels wide, with a start at 50 pixels and an end at 150 pixels, then the result is that red and green get clamped:
1 - type: gradient 2 bounds: 0 0 300 50 3 start: 50 0 4 end: 150 0 5 repeat: false 6 stops: [0.0, red , 0.5, yellow, 7 0.5, blue, 1.0, green ]
emit_segments is still called only once, but now the values of prim_start_offset and prim_end_offset are a bit more interesting. Measured in "gradient offsets", the primitive stretches from -0.5 to 2.5.
For simplicity, clamping is implemented by simply duplicating the first stop, on the primitive's left border (with a negative offset) and/or duplicating the last stop, on the primitive's right border (with an offset > 1):
1 emit_segments call: 2 start_offset: -0.5, end_offset: 2.5 3 prim_start_offset: -0.5, prim_end_offset: 2.5 4 gradient_offset_base: 0 5 caching from offset -0.5 to 0.5 6 and blitting to Rect(100.0×50.0 at (0.0,0.0)) 7 caching from offset 0.5 to 2.5 8 and blitting to Rect(200.0×50.0 at (100.0,0.0))
Resulting in these draw calls...
... from this texture cache:
To repeat a gradient, emit_segments is called multiple times to march across the primitive. Each of these calls will be very similar to the "Basic Loop" case: no clamping, no duplicated stops -- just a gradient that we draw as if the primitive that contains it had been shrunk until the gradient no longer repeats.
In other words, emit_segments will pretend that the gradient is made up of multiple gradients side by side, to get around the limitation of "max 4 stops and no hard stops per cache". Then, an outer loop around that will pretend that the primitive is made up of multiple primitives side by side, each containing exactly one non-repeating gradient:
1 - type: gradient 2 bounds: 0 0 300 50 3 start: 50 0 4 end: 150 0 5 repeat: true 6 stops: [0.0, red , 0.5, yellow, 7 0.5, blue, 1.0, green ]
Visual result:
As we march along,
The example gradient results in these calls to emit_segments:
1 emit_segments call: 2 start_offset: 0.5, end_offset: 1 3 prim_start_offset: -0.5, prim_end_offset: 2.5 4 gradient_offset_base: -1 5 caching from offset 0.5 to 1 6 and blitting to Rect(50.0×50.0 at (0.0,0.0)) 7 emit_segments call: 8 start_offset: 0, end_offset: 1 9 prim_start_offset: -0.5, prim_end_offset: 2.5 10 gradient_offset_base: 0 11 caching from offset 0 to 0.5 12 and blitting to Rect(50.0×50.0 at (50.0,0.0)) 13 caching from offset 0.5 to 1 14 and blitting to Rect(50.0×50.0 at (100.0,0.0)) 15 emit_segments call: 16 start_offset: 0, end_offset: 1 17 prim_start_offset: -0.5, prim_end_offset: 2.5 18 gradient_offset_base: 1 19 caching from offset 0 to 0.5 20 and blitting to Rect(50.0×50.0 at (150.0,0.0)) 21 caching from offset 0.5 to 1 22 and blitting to Rect(50.0×50.0 at (200.0,0.0)) 23 emit_segments call: 24 start_offset: 0, end_offset: 0.5 25 prim_start_offset: -0.5, prim_end_offset: 2.5 26 gradient_offset_base: 2 27 caching from offset 0 to 0.5 28 and blitting to Rect(50.0×50.0 at (250.00002,0.0))
... and these drawcalls (a single DrawElementsInstanced(6,6)):
There are 4 calls to emit_segments, here's the second one:
... and the first one:
gradient_offset_base is effectively the counter that we need to figure out the sub-primitive's position relative to the real primitive. By not folding this offset into start_offset and end_offset, each copy will use the same offsets to build a cache key, so we end up baking only a single red-to-orange and a single blue-to-green into the gradient cache.
∎ QED