| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:typed_data'; |
| |
| import 'package:ui/ui.dart' as ui; |
| |
| import 'shader_builder.dart'; |
| import 'webgl_context.dart'; |
| |
| /// Converts colors and stops to typed array of bias, scale and threshold to use |
| /// in shaders. |
| /// |
| /// A color is generated by taking a t value [0..1] and computing |
| /// t * scale + bias. |
| /// |
| /// Example: For stops 0.0 t1, t2, 1.0 and colors c0, c1, c2, c3 |
| /// Given t1<t<t2 outputColor = t * scale + bias. |
| /// = c1 + (t - t1)/(t2 - t1) * (c2 - c1) |
| /// = t * (c2 - c1)/(t2 - t1) + c1 - t1/(t2 - t1) * (c2 - c1) |
| /// scale = (c2 - c1) / (t2 - t1) |
| /// bias = c1 - t1 / (t2 - t1) * (c2 - c1) |
| class NormalizedGradient { |
| final Float32List _thresholds; |
| final Float32List _bias; |
| final Float32List _scale; |
| final int thresholdCount; |
| |
| factory NormalizedGradient(List<ui.Color> colors, {List<double>? stops}) { |
| // If colorStops is not provided, then only two stops, at 0.0 and 1.0, |
| // are implied (and colors must therefore only have two entries). |
| assert(stops != null || colors.length == 2); |
| stops ??= const <double>[0.0, 1.0]; |
| final int colorCount = colors.length; |
| int normalizedCount = colorCount; |
| final bool addFirst = stops[0] != 0.0; |
| final bool addLast = stops.last != 1.0; |
| if (addFirst) { |
| normalizedCount++; |
| } |
| if (addLast) { |
| normalizedCount++; |
| } |
| final Float32List bias = Float32List(normalizedCount * 4); |
| final Float32List scale = Float32List(normalizedCount * 4); |
| final Float32List thresholds = |
| Float32List(4 * ((normalizedCount - 1) ~/ 4 + 1)); |
| int targetIndex = 0; |
| int thresholdIndex = 0; |
| if (addFirst) { |
| final ui.Color c = colors[0]; |
| bias[targetIndex++] = c.red / 255.0; |
| bias[targetIndex++] = c.green / 255.0; |
| bias[targetIndex++] = c.blue / 255.0; |
| bias[targetIndex++] = c.alpha / 255.0; |
| thresholds[thresholdIndex++] = 0.0; |
| } |
| for (final ui.Color c in colors) { |
| bias[targetIndex++] = c.red / 255.0; |
| bias[targetIndex++] = c.green / 255.0; |
| bias[targetIndex++] = c.blue / 255.0; |
| bias[targetIndex++] = c.alpha / 255.0; |
| } |
| for (final double stop in stops) { |
| thresholds[thresholdIndex++] = stop; |
| } |
| if (addLast) { |
| final ui.Color c = colors.last; |
| bias[targetIndex++] = c.red / 255.0; |
| bias[targetIndex++] = c.green / 255.0; |
| bias[targetIndex++] = c.blue / 255.0; |
| bias[targetIndex++] = c.alpha / 255.0; |
| thresholds[thresholdIndex++] = 1.0; |
| } |
| // Now that we have bias for each color stop, we can compute scale based |
| // on delta between colors. |
| final int lastColorIndex = 4 * (normalizedCount - 1); |
| for (int i = 0; i < lastColorIndex; i++) { |
| final int thresholdIndex = i >> 2; |
| scale[i] = (bias[i + 4] - bias[i]) / |
| (thresholds[thresholdIndex + 1] - thresholds[thresholdIndex]); |
| } |
| scale[lastColorIndex] = 0.0; |
| scale[lastColorIndex + 1] = 0.0; |
| scale[lastColorIndex + 2] = 0.0; |
| scale[lastColorIndex + 3] = 0.0; |
| // Compute bias = colorAtStop - stopValue * (scale). |
| for (int i = 0; i < normalizedCount; i++) { |
| final double t = thresholds[i]; |
| final int colorIndex = i * 4; |
| bias[colorIndex] -= t * scale[colorIndex]; |
| bias[colorIndex + 1] -= t * scale[colorIndex + 1]; |
| bias[colorIndex + 2] -= t * scale[colorIndex + 2]; |
| bias[colorIndex + 3] -= t * scale[colorIndex + 3]; |
| } |
| return NormalizedGradient._(normalizedCount, thresholds, scale, bias); |
| } |
| |
| NormalizedGradient._( |
| this.thresholdCount, this._thresholds, this._scale, this._bias); |
| |
| /// Sets uniforms for threshold, bias and scale for program. |
| void setupUniforms(GlContext gl, GlProgram glProgram) { |
| for (int i = 0; i < thresholdCount; i++) { |
| final Object biasId = gl.getUniformLocation(glProgram.program, 'bias_$i'); |
| gl.setUniform4f(biasId, _bias[i * 4], _bias[i * 4 + 1], _bias[i * 4 + 2], |
| _bias[i * 4 + 3]); |
| final Object scaleId = gl.getUniformLocation(glProgram.program, 'scale_$i'); |
| gl.setUniform4f(scaleId, _scale[i * 4], _scale[i * 4 + 1], |
| _scale[i * 4 + 2], _scale[i * 4 + 3]); |
| } |
| for (int i = 0; i < _thresholds.length; i += 4) { |
| final Object thresId = |
| gl.getUniformLocation(glProgram.program, 'threshold_${i ~/ 4}'); |
| gl.setUniform4f(thresId, _thresholds[i], _thresholds[i + 1], |
| _thresholds[i + 2], _thresholds[i + 3]); |
| } |
| } |
| |
| /// Returns bias component at index. |
| double biasAt(int index) => _bias[index]; |
| |
| /// Returns scale component at index. |
| double scaleAt(int index) => _scale[index]; |
| |
| /// Returns threshold at index. |
| double thresholdAt(int index) => _thresholds[index]; |
| } |
| |
| /// Writes fragment shader code to search for probe value in source data and set |
| /// bias and scale to be used for computation. |
| /// |
| /// Source data for thresholds is provided using ceil(count/4) packed vec4 |
| /// uniforms. |
| /// |
| /// Bias and scale data are vec4 uniforms that hold color data. |
| void writeUnrolledBinarySearch(ShaderMethod method, int start, int end, |
| {required String probe, |
| required String sourcePrefix, |
| required String biasName, |
| required String scaleName}) { |
| if (start == end) { |
| final String biasSource = '${biasName}_$start'; |
| method.addStatement('$biasName = $biasSource;'); |
| final String scaleSource = '${scaleName}_$start'; |
| method.addStatement('$scaleName = $scaleSource;'); |
| } else { |
| // Add probe check. |
| final int mid = (start + end) ~/ 2; |
| String thresholdAtMid = '${sourcePrefix}_${(mid + 1) ~/ 4}'; |
| thresholdAtMid += '.${vectorComponentIndexToName((mid + 1) % 4)}'; |
| method.addStatement('if ($probe < $thresholdAtMid) {'); |
| method.indent(); |
| writeUnrolledBinarySearch(method, start, mid, |
| probe: probe, |
| sourcePrefix: sourcePrefix, |
| biasName: biasName, |
| scaleName: scaleName); |
| method.unindent(); |
| method.addStatement('} else {'); |
| method.indent(); |
| writeUnrolledBinarySearch(method, mid + 1, end, |
| probe: probe, |
| sourcePrefix: sourcePrefix, |
| biasName: biasName, |
| scaleName: scaleName); |
| method.unindent(); |
| method.addStatement('}'); |
| } |
| } |
| |
| String vectorComponentIndexToName(int index) { |
| assert(index >= 0 && index <= 4); |
| return 'xyzw'[index]; |
| } |