mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Implement sweep gradient (#21873)
This commit is contained in:
parent
2dc43eee1e
commit
149ef70830
@ -489,6 +489,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/recording_canvas.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene_builder.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/shader.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface.dart
|
||||
|
||||
@ -89,6 +89,7 @@ part 'engine/html/recording_canvas.dart';
|
||||
part 'engine/html/render_vertices.dart';
|
||||
part 'engine/html/scene.dart';
|
||||
part 'engine/html/scene_builder.dart';
|
||||
part 'engine/html/shaders/normalized_gradient.dart';
|
||||
part 'engine/html/shaders/shader.dart';
|
||||
part 'engine/html/shaders/shader_builder.dart';
|
||||
part 'engine/html/surface.dart';
|
||||
|
||||
@ -227,8 +227,8 @@ class BitmapCanvas extends EngineCanvas {
|
||||
}
|
||||
|
||||
/// Sets the global paint styles to correspond to [paint].
|
||||
void _setUpPaint(SurfacePaintData paint) {
|
||||
_canvasPool.contextHandle.setUpPaint(paint);
|
||||
void _setUpPaint(SurfacePaintData paint, ui.Rect? shaderBounds) {
|
||||
_canvasPool.contextHandle.setUpPaint(paint, shaderBounds);
|
||||
}
|
||||
|
||||
void _tearDownPaint() {
|
||||
@ -352,7 +352,9 @@ class BitmapCanvas extends EngineCanvas {
|
||||
..lineTo(p2.dx, p2.dy);
|
||||
drawPath(path, paint);
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
ui.Rect? shaderBounds = (paint.shader != null) ?
|
||||
ui.Rect.fromPoints(p1, p2) : null;
|
||||
_setUpPaint(paint, shaderBounds);
|
||||
_canvasPool.strokeLine(p1, p2);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -363,7 +365,9 @@ class BitmapCanvas extends EngineCanvas {
|
||||
if (_useDomForRendering(paint)) {
|
||||
drawRect(_computeScreenBounds(_canvasPool._currentTransform), paint);
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
ui.Rect? shaderBounds = (paint.shader != null) ?
|
||||
_computePictureBounds() : null;
|
||||
_setUpPaint(paint, shaderBounds);
|
||||
_canvasPool.fill();
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -380,7 +384,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)),
|
||||
paint);
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
_setUpPaint(paint, rect);
|
||||
_canvasPool.drawRect(rect, paint.style);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -423,15 +427,15 @@ class BitmapCanvas extends EngineCanvas {
|
||||
math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)),
|
||||
paint);
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
_canvasPool.drawRRect(rrect, paint.style);
|
||||
_setUpPaint(paint, rrect.outerRect);
|
||||
_canvasPool.drawRRect(rrect, paint.style);
|
||||
_tearDownPaint();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) {
|
||||
_setUpPaint(paint);
|
||||
_setUpPaint(paint, outer.outerRect);
|
||||
_canvasPool.drawDRRect(outer, inner, paint.style);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -449,7 +453,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
element.style.borderRadius =
|
||||
'${(rect.width / 2.0)}px / ${(rect.height / 2.0)}px';
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
_setUpPaint(paint, rect);
|
||||
_canvasPool.drawOval(rect, paint.style);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -468,7 +472,8 @@ class BitmapCanvas extends EngineCanvas {
|
||||
paint);
|
||||
element.style.borderRadius = '50%';
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
_setUpPaint(paint, paint.shader != null
|
||||
? ui.Rect.fromCircle(center: c, radius: radius) : null);
|
||||
_canvasPool.drawCircle(c, radius, paint.style);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -490,7 +495,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
}
|
||||
_drawElement(svgElm, ui.Offset(0, 0), paint);
|
||||
} else {
|
||||
_setUpPaint(paint);
|
||||
_setUpPaint(paint, paint.shader != null ? path.getBounds() : null);
|
||||
_canvasPool.drawPath(path, paint.style);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -813,7 +818,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
ctx.font = style.cssFontString;
|
||||
_cachedLastStyle = style;
|
||||
}
|
||||
_setUpPaint(paragraph._paint!.paintData);
|
||||
_setUpPaint(paragraph._paint!.paintData, null);
|
||||
double y = offset.dy + paragraph.alphabeticBaseline;
|
||||
final int len = lines.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
@ -920,7 +925,7 @@ class BitmapCanvas extends EngineCanvas {
|
||||
_drawPointsPaint.strokeWidth = paint.strokeWidth;
|
||||
_drawPointsPaint.maskFilter = paint.maskFilter;
|
||||
|
||||
_setUpPaint(_drawPointsPaint);
|
||||
_setUpPaint(_drawPointsPaint, null);
|
||||
_canvasPool.drawPoints(pointMode, points, paint.strokeWidth! / 2.0);
|
||||
_tearDownPaint();
|
||||
}
|
||||
@ -969,6 +974,11 @@ class BitmapCanvas extends EngineCanvas {
|
||||
math.max(topRight.y, math.max(bottomRight.y, bottomLeft.y))),
|
||||
);
|
||||
}
|
||||
|
||||
/// Computes paint bounds to completely cover picture.
|
||||
ui.Rect _computePictureBounds() {
|
||||
return ui.Rect.fromLTRB(0, 0, _bounds.width, _bounds.height);
|
||||
}
|
||||
}
|
||||
|
||||
String? _stringForBlendMode(ui.BlendMode? blendMode) {
|
||||
|
||||
@ -763,7 +763,7 @@ class ContextStateHandle {
|
||||
/// Sets paint properties on the current canvas.
|
||||
///
|
||||
/// [tearDownPaint] must be called after calling this method.
|
||||
void setUpPaint(SurfacePaintData paint) {
|
||||
void setUpPaint(SurfacePaintData paint, ui.Rect? shaderBounds) {
|
||||
if (assertionsEnabled) {
|
||||
assert(!_debugIsPaintSetUp);
|
||||
_debugIsPaintSetUp = true;
|
||||
@ -778,7 +778,7 @@ class ContextStateHandle {
|
||||
if (paint.shader != null) {
|
||||
final EngineGradient engineShader = paint.shader as EngineGradient;
|
||||
final Object paintStyle =
|
||||
engineShader.createPaintStyle(_canvasPool.context);
|
||||
engineShader.createPaintStyle(_canvasPool.context, shaderBounds);
|
||||
fillStyle = paintStyle;
|
||||
strokeStyle = paintStyle;
|
||||
} else if (paint.color != null) {
|
||||
|
||||
@ -55,7 +55,7 @@ void initWebGl() {
|
||||
}
|
||||
|
||||
void disposeWebGl() {
|
||||
_OffscreenCanvas.dispose();
|
||||
_GlContextCache.dispose();
|
||||
_glRenderer = null;
|
||||
}
|
||||
|
||||
@ -69,6 +69,9 @@ abstract class _GlRenderer {
|
||||
ui.BlendMode blendMode,
|
||||
SurfacePaintData paint);
|
||||
|
||||
Object? drawRect(ui.Rect targetRect, _GlContext gl, _GlProgram glProgram,
|
||||
NormalizedGradient gradient, int widthInPixels, int heightInPixels);
|
||||
|
||||
void drawHairline(html.CanvasRenderingContext2D? _ctx, Float32List positions);
|
||||
}
|
||||
|
||||
@ -77,6 +80,9 @@ abstract class _GlRenderer {
|
||||
/// This class gets instantiated on demand by Vertices constructor. For apps
|
||||
/// that don't use Vertices WebGlRenderer will be removed from release binary.
|
||||
class _WebGlRenderer implements _GlRenderer {
|
||||
|
||||
/// Cached vertex shader reused by [drawVertices] and gradients.
|
||||
static String? _baseVertexShader;
|
||||
@override
|
||||
void drawVertices(
|
||||
html.CanvasRenderingContext2D? context,
|
||||
@ -116,13 +122,13 @@ class _WebGlRenderer implements _GlRenderer {
|
||||
if (widthInPixels == 0 || heightInPixels == 0) {
|
||||
return;
|
||||
}
|
||||
final String vertexShader = _writeVerticesVertexShader();
|
||||
final String vertexShader = writeBaseVertexShader();
|
||||
final String fragmentShader = _writeVerticesFragmentShader();
|
||||
_GlContext gl =
|
||||
_OffscreenCanvas.createGlContext(widthInPixels, heightInPixels)!;
|
||||
_GlContext gl = _GlContextCache.createGlContext(widthInPixels, heightInPixels)!;
|
||||
|
||||
_GlProgram glProgram = gl.useAndCacheProgram(vertexShader, fragmentShader)!;
|
||||
|
||||
Object? transformUniform = gl.getUniformLocation(glProgram.program,
|
||||
Object transformUniform = gl.getUniformLocation(glProgram.program,
|
||||
'u_ctransform');
|
||||
Matrix4 transformAtOffset = transform.clone()
|
||||
..translate(-offsetX, -offsetY);
|
||||
@ -130,10 +136,10 @@ class _WebGlRenderer implements _GlRenderer {
|
||||
|
||||
// Set uniform to scale 0..width/height pixels coordinates to -1..1
|
||||
// clipspace range and flip the Y axis.
|
||||
Object? resolution = gl.getUniformLocation(glProgram.program, 'u_scale');
|
||||
Object resolution = gl.getUniformLocation(glProgram.program, 'u_scale');
|
||||
gl.setUniform4f(resolution, 2.0 / widthInPixels.toDouble(),
|
||||
-2.0 / heightInPixels.toDouble(), 1, 1);
|
||||
Object? shift = gl.getUniformLocation(glProgram.program, 'u_shift');
|
||||
Object shift = gl.getUniformLocation(glProgram.program, 'u_shift');
|
||||
gl.setUniform4f(shift, -1, 1, 0, 0);
|
||||
|
||||
// Setup geometry.
|
||||
@ -141,9 +147,9 @@ class _WebGlRenderer implements _GlRenderer {
|
||||
assert(positionsBuffer != null); // ignore: unnecessary_null_comparison
|
||||
gl.bindArrayBuffer(positionsBuffer);
|
||||
gl.bufferData(positions, gl.kStaticDraw);
|
||||
Object? positionLoc = gl.getAttribLocation(glProgram.program, 'position');
|
||||
Object? positionLoc = gl.getAttributeLocation(glProgram.program, 'position');
|
||||
js_util.callMethod(
|
||||
gl.glContext!, 'vertexAttribPointer', <dynamic>[
|
||||
gl.glContext, 'vertexAttribPointer', <dynamic>[
|
||||
positionLoc, 2, gl.kFloat, false, 0, 0,
|
||||
]);
|
||||
gl.enableVertexAttribArray(0);
|
||||
@ -153,8 +159,8 @@ class _WebGlRenderer implements _GlRenderer {
|
||||
gl.bindArrayBuffer(colorsBuffer);
|
||||
// Buffer kBGRA_8888.
|
||||
gl.bufferData(vertices._colors, gl.kStaticDraw);
|
||||
Object? colorLoc = gl.getAttribLocation(glProgram.program, 'color');
|
||||
js_util.callMethod(gl.glContext!, 'vertexAttribPointer',
|
||||
Object colorLoc = gl.getAttributeLocation(glProgram.program, 'color');
|
||||
js_util.callMethod(gl.glContext, 'vertexAttribPointer',
|
||||
<dynamic>[colorLoc, 4, gl.kUnsignedByte, true, 0, 0]);
|
||||
gl.enableVertexAttribArray(1);
|
||||
gl.clear();
|
||||
@ -167,7 +173,90 @@ class _WebGlRenderer implements _GlRenderer {
|
||||
context.restore();
|
||||
}
|
||||
|
||||
/// Vertex shader transforms pixel space [Vertices.positions] to
|
||||
static final Uint16List _vertexIndicesForRect = Uint16List.fromList(
|
||||
<int>[
|
||||
0, 1, 2, 2, 3, 0
|
||||
]
|
||||
);
|
||||
|
||||
/// Renders a rectangle using given program into an image resource.
|
||||
///
|
||||
/// Browsers that support OffscreenCanvas and the transferToImageBitmap api
|
||||
/// will return ImageBitmap, otherwise will return CanvasElement.
|
||||
Object? drawRect(ui.Rect targetRect, _GlContext gl, _GlProgram glProgram,
|
||||
NormalizedGradient gradient, int widthInPixels, int heightInPixels) {
|
||||
// Setup rectangle coordinates.
|
||||
final double left = targetRect.left;
|
||||
final double top = targetRect.top;
|
||||
final double right = targetRect.right;
|
||||
final double bottom = targetRect.bottom;
|
||||
// Form 2 triangles for rectangle.
|
||||
final Float32List vertices = Float32List(8);
|
||||
vertices[0] = left;
|
||||
vertices[1] = top;
|
||||
vertices[2] = right;
|
||||
vertices[3] = top;
|
||||
vertices[4] = right;
|
||||
vertices[5] = bottom;
|
||||
vertices[6] = left;
|
||||
vertices[7] = bottom;
|
||||
|
||||
Object transformUniform = gl.getUniformLocation(
|
||||
glProgram.program, 'u_ctransform');
|
||||
gl.setUniformMatrix4fv(transformUniform, false, Matrix4.identity().storage);
|
||||
|
||||
// Set uniform to scale 0..width/height pixels coordinates to -1..1
|
||||
// clipspace range and flip the Y axis.
|
||||
Object resolution = gl.getUniformLocation(glProgram.program, 'u_scale');
|
||||
gl.setUniform4f(resolution, 2.0 / widthInPixels.toDouble(),
|
||||
-2.0 / heightInPixels.toDouble(), 1, 1);
|
||||
Object shift = gl.getUniformLocation(glProgram.program, 'u_shift');
|
||||
gl.setUniform4f(shift, -1, 1, 0, 0);
|
||||
|
||||
// Setup geometry.
|
||||
Object positionsBuffer = gl.createBuffer()!;
|
||||
assert(positionsBuffer != null); // ignore: unnecessary_null_comparison
|
||||
gl.bindArrayBuffer(positionsBuffer);
|
||||
gl.bufferData(vertices, gl.kStaticDraw);
|
||||
// Point an attribute to the currently bound vertex buffer object.
|
||||
js_util.callMethod(
|
||||
gl.glContext, 'vertexAttribPointer',
|
||||
<dynamic>[0, 2, gl.kFloat, false, 0, 0]);
|
||||
gl.enableVertexAttribArray(0);
|
||||
|
||||
// Setup color buffer.
|
||||
Object? colorsBuffer = gl.createBuffer();
|
||||
gl.bindArrayBuffer(colorsBuffer);
|
||||
// Buffer kBGRA_8888.
|
||||
final Int32List colors = Int32List.fromList(<int>[
|
||||
0xFF00FF00, 0xFF0000FF, 0xFFFFFF00, 0xFF00FFFF,
|
||||
]);
|
||||
gl.bufferData(colors, gl.kStaticDraw);
|
||||
js_util.callMethod(gl.glContext, 'vertexAttribPointer',
|
||||
<dynamic>[1, 4, gl.kUnsignedByte, true, 0, 0]);
|
||||
gl.enableVertexAttribArray(1);
|
||||
|
||||
Object? indexBuffer = gl.createBuffer();
|
||||
gl.bindElementArrayBuffer(indexBuffer);
|
||||
gl.bufferElementData(_vertexIndicesForRect, gl.kStaticDraw);
|
||||
|
||||
Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution');
|
||||
gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble());
|
||||
|
||||
gl.clear();
|
||||
gl.viewport(0, 0, widthInPixels.toDouble(), heightInPixels.toDouble());
|
||||
|
||||
gl.drawElements(gl.kTriangles, _vertexIndicesForRect.length, gl.kUnsignedShort);
|
||||
|
||||
Object? image = gl.readPatternData();
|
||||
|
||||
gl.bindArrayBuffer(null);
|
||||
gl.bindElementArrayBuffer(null);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/// Creates a vertex shader transforms pixel space [Vertices.positions] to
|
||||
/// final clipSpace -1..1 coordinates with inverted Y Axis.
|
||||
/// #version 300 es
|
||||
/// layout (location=0) in vec4 position;
|
||||
@ -180,18 +269,22 @@ class _WebGlRenderer implements _GlRenderer {
|
||||
/// gl_Position = ((u_ctransform * position) * u_scale) + u_shift;
|
||||
/// v_color = color.zyxw;
|
||||
/// }
|
||||
String _writeVerticesVertexShader() {
|
||||
ShaderBuilder builder = ShaderBuilder(webGLVersion);
|
||||
builder.addIn(ShaderType.kVec4, name: 'position');
|
||||
builder.addIn(ShaderType.kVec4, name: 'color');
|
||||
builder.addUniform(ShaderType.kMat4, name: 'u_ctransform');
|
||||
builder.addUniform(ShaderType.kVec4, name: 'u_scale');
|
||||
builder.addUniform(ShaderType.kVec4, name: 'u_shift');
|
||||
builder.addOut(ShaderType.kVec4, name: 'v_color');
|
||||
ShaderMethod method = builder.addMethod('main');
|
||||
method.addStatement('gl_Position = ((u_ctransform * position) * u_scale) + u_shift;');
|
||||
method.addStatement('v_color = color.zyxw;');
|
||||
return builder.build();
|
||||
static String writeBaseVertexShader() {
|
||||
if (_baseVertexShader == null) {
|
||||
ShaderBuilder builder = ShaderBuilder(webGLVersion);
|
||||
builder.addIn(ShaderType.kVec4, name: 'position');
|
||||
builder.addIn(ShaderType.kVec4, name: 'color');
|
||||
builder.addUniform(ShaderType.kMat4, name: 'u_ctransform');
|
||||
builder.addUniform(ShaderType.kVec4, name: 'u_scale');
|
||||
builder.addUniform(ShaderType.kVec4, name: 'u_shift');
|
||||
builder.addOut(ShaderType.kVec4, name: 'v_color');
|
||||
ShaderMethod method = builder.addMethod('main');
|
||||
method.addStatement(
|
||||
'gl_Position = ((u_ctransform * position) * u_scale) + u_shift;');
|
||||
method.addStatement('v_color = color.zyxw;');
|
||||
_baseVertexShader = builder.build();
|
||||
}
|
||||
return _baseVertexShader!;
|
||||
}
|
||||
|
||||
/// This fragment shader enables Int32List of colors to be passed directly
|
||||
@ -339,30 +432,33 @@ Float32List _convertVertexPositions(ui.VertexMode mode, Float32List positions) {
|
||||
|
||||
/// Compiled and cached gl program.
|
||||
class _GlProgram {
|
||||
final Object? program;
|
||||
final Object program;
|
||||
_GlProgram(this.program);
|
||||
}
|
||||
|
||||
/// JS Interop helper for webgl apis.
|
||||
class _GlContext {
|
||||
final Object? glContext;
|
||||
final Object glContext;
|
||||
final bool isOffscreen;
|
||||
dynamic _kCompileStatus;
|
||||
dynamic _kArrayBuffer;
|
||||
dynamic _kElementArrayBuffer;
|
||||
dynamic _kStaticDraw;
|
||||
dynamic _kFloat;
|
||||
dynamic _kColorBufferBit;
|
||||
dynamic _kTriangles;
|
||||
dynamic _kLinkStatus;
|
||||
dynamic _kUnsignedByte;
|
||||
dynamic _kUnsignedShort;
|
||||
dynamic _kRGBA;
|
||||
|
||||
Object? _canvas;
|
||||
int? _widthInPixels;
|
||||
int? _heightInPixels;
|
||||
static late Map<String, _GlProgram?> _programCache;
|
||||
|
||||
_GlContext.fromOffscreenCanvas(html.OffscreenCanvas canvas)
|
||||
: glContext = canvas.getContext('webgl2', <String, dynamic>{'premultipliedAlpha': false}),
|
||||
: glContext = canvas.getContext('webgl2', <String, dynamic>{'premultipliedAlpha': false})!,
|
||||
isOffscreen = true {
|
||||
_programCache = <String, _GlProgram?>{};
|
||||
_canvas = canvas;
|
||||
@ -370,7 +466,7 @@ class _GlContext {
|
||||
|
||||
_GlContext.fromCanvas(html.CanvasElement canvas, bool useWebGl1)
|
||||
: glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2',
|
||||
<String, dynamic>{'premultipliedAlpha': false}),
|
||||
<String, dynamic>{'premultipliedAlpha': false})!,
|
||||
isOffscreen = false {
|
||||
_programCache = <String, _GlProgram?>{};
|
||||
_canvas = canvas;
|
||||
@ -388,7 +484,7 @@ class _GlContext {
|
||||
// source/destination to draw part of the image data.
|
||||
js_util.callMethod(context, 'drawImage',
|
||||
<dynamic>[_canvas, 0, 0, _widthInPixels, _heightInPixels,
|
||||
left, top, _widthInPixels, _heightInPixels]);
|
||||
left, top, _widthInPixels, _heightInPixels]);
|
||||
}
|
||||
|
||||
_GlProgram? useAndCacheProgram(
|
||||
@ -399,9 +495,9 @@ class _GlContext {
|
||||
// Create and compile shaders.
|
||||
Object vertexShader = compileShader('VERTEX_SHADER', vertexShaderSource);
|
||||
Object fragmentShader =
|
||||
compileShader('FRAGMENT_SHADER', fragmentShaderSource);
|
||||
compileShader('FRAGMENT_SHADER', fragmentShaderSource);
|
||||
// Create a gl program and link shaders.
|
||||
Object? program = createProgram();
|
||||
Object program = createProgram();
|
||||
attachShader(program, vertexShader);
|
||||
attachShader(program, fragmentShader);
|
||||
linkProgram(program);
|
||||
@ -417,57 +513,65 @@ class _GlContext {
|
||||
if (shader == null) {
|
||||
throw Exception(error);
|
||||
}
|
||||
js_util.callMethod(glContext!, 'shaderSource', <dynamic>[shader, source]);
|
||||
js_util.callMethod(glContext!, 'compileShader', <dynamic>[shader]);
|
||||
js_util.callMethod(glContext, 'shaderSource', <dynamic>[shader, source]);
|
||||
js_util.callMethod(glContext, 'compileShader', <dynamic>[shader]);
|
||||
bool shaderStatus = js_util
|
||||
.callMethod(glContext!, 'getShaderParameter', <dynamic>[shader, compileStatus]);
|
||||
.callMethod(glContext, 'getShaderParameter', <dynamic>[shader, compileStatus]);
|
||||
if (!shaderStatus) {
|
||||
throw Exception('Shader compilation failed: ${getShaderInfoLog(shader)}');
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
Object? createProgram() =>
|
||||
js_util.callMethod(glContext!, 'createProgram', const <dynamic>[]);
|
||||
Object createProgram() =>
|
||||
js_util.callMethod(glContext, 'createProgram', const <dynamic>[])!;
|
||||
|
||||
void attachShader(Object? program, Object shader) {
|
||||
js_util.callMethod(glContext!, 'attachShader', <dynamic>[program, shader]);
|
||||
js_util.callMethod(glContext, 'attachShader', <dynamic>[program, shader]);
|
||||
}
|
||||
|
||||
void linkProgram(Object? program) {
|
||||
js_util.callMethod(glContext!, 'linkProgram', <dynamic>[program]);
|
||||
void linkProgram(Object program) {
|
||||
js_util.callMethod(glContext, 'linkProgram', <dynamic>[program]);
|
||||
if (!js_util
|
||||
.callMethod(glContext!, 'getProgramParameter', <dynamic>[program, kLinkStatus])) {
|
||||
.callMethod(glContext, 'getProgramParameter', <dynamic>[program, kLinkStatus])) {
|
||||
throw Exception(getProgramInfoLog(program));
|
||||
}
|
||||
}
|
||||
|
||||
void useProgram(Object? program) {
|
||||
js_util.callMethod(glContext!, 'useProgram', <dynamic>[program]);
|
||||
js_util.callMethod(glContext, 'useProgram', <dynamic>[program]);
|
||||
}
|
||||
|
||||
Object? createBuffer() =>
|
||||
js_util.callMethod(glContext!, 'createBuffer', const <dynamic>[]);
|
||||
js_util.callMethod(glContext, 'createBuffer', const <dynamic>[]);
|
||||
|
||||
void bindArrayBuffer(Object? buffer) {
|
||||
js_util.callMethod(glContext!, 'bindBuffer', <dynamic>[kArrayBuffer, buffer]);
|
||||
js_util.callMethod(glContext, 'bindBuffer', <dynamic>[kArrayBuffer, buffer]);
|
||||
}
|
||||
|
||||
void bindElementArrayBuffer(Object? buffer) {
|
||||
js_util.callMethod(glContext, 'bindBuffer', <dynamic>[kElementArrayBuffer, buffer]);
|
||||
}
|
||||
|
||||
void deleteBuffer(Object buffer) {
|
||||
js_util.callMethod(glContext!, 'deleteBuffer', <dynamic>[buffer]);
|
||||
js_util.callMethod(glContext, 'deleteBuffer', <dynamic>[buffer]);
|
||||
}
|
||||
|
||||
void bufferData(TypedData? data, dynamic type) {
|
||||
js_util.callMethod(glContext!, 'bufferData', <dynamic>[kArrayBuffer, data, type]);
|
||||
js_util.callMethod(glContext, 'bufferData', <dynamic>[kArrayBuffer, data, type]);
|
||||
}
|
||||
|
||||
void bufferElementData(TypedData? data, dynamic type) {
|
||||
js_util.callMethod(glContext, 'bufferData', <dynamic>[kElementArrayBuffer, data, type]);
|
||||
}
|
||||
|
||||
void enableVertexAttribArray(int index) {
|
||||
js_util.callMethod(glContext!, 'enableVertexAttribArray', <dynamic>[index]);
|
||||
js_util.callMethod(glContext, 'enableVertexAttribArray', <dynamic>[index]);
|
||||
}
|
||||
|
||||
/// Clear background.
|
||||
void clear() {
|
||||
js_util.callMethod(glContext!, 'clear', <dynamic>[kColorBufferBit]);
|
||||
js_util.callMethod(glContext, 'clear', <dynamic>[kColorBufferBit]);
|
||||
}
|
||||
|
||||
/// Destroys gl context.
|
||||
@ -476,25 +580,29 @@ class _GlContext {
|
||||
}
|
||||
|
||||
void deleteProgram(Object program) {
|
||||
js_util.callMethod(glContext!, 'deleteProgram', <dynamic>[program]);
|
||||
js_util.callMethod(glContext, 'deleteProgram', <dynamic>[program]);
|
||||
}
|
||||
|
||||
void deleteShader(Object shader) {
|
||||
js_util.callMethod(glContext!, 'deleteShader', <dynamic>[shader]);
|
||||
js_util.callMethod(glContext, 'deleteShader', <dynamic>[shader]);
|
||||
}
|
||||
|
||||
dynamic _getExtension(String extensionName) =>
|
||||
js_util.callMethod(glContext!, 'getExtension', <dynamic>[extensionName]);
|
||||
js_util.callMethod(glContext, 'getExtension', <dynamic>[extensionName]);
|
||||
|
||||
void drawTriangles(int triangleCount, ui.VertexMode vertexMode) {
|
||||
dynamic mode = _triangleTypeFromMode(vertexMode);
|
||||
js_util.callMethod(glContext!, 'drawArrays', <dynamic>[mode, 0, triangleCount]);
|
||||
js_util.callMethod(glContext, 'drawArrays', <dynamic>[mode, 0, triangleCount]);
|
||||
}
|
||||
|
||||
void drawElements(dynamic type, int indexCount, dynamic indexType) {
|
||||
js_util.callMethod(glContext, 'drawElements', <dynamic>[type, indexCount, indexType, 0]);
|
||||
}
|
||||
|
||||
/// Sets affine transformation from normalized device coordinates
|
||||
/// to window coordinates
|
||||
void viewport(double x, double y, double width, double height) {
|
||||
js_util.callMethod(glContext!, 'viewport', <dynamic>[x, y, width, height]);
|
||||
js_util.callMethod(glContext, 'viewport', <dynamic>[x, y, width, height]);
|
||||
}
|
||||
|
||||
dynamic _triangleTypeFromMode(ui.VertexMode mode) {
|
||||
@ -509,183 +617,225 @@ class _GlContext {
|
||||
}
|
||||
|
||||
Object? _createShader(String shaderType) => js_util.callMethod(
|
||||
glContext!, 'createShader', <dynamic>[js_util.getProperty(glContext!, shaderType)]);
|
||||
glContext, 'createShader', <dynamic>[js_util.getProperty(glContext, shaderType)]);
|
||||
|
||||
/// Error state of gl context.
|
||||
dynamic get error => js_util.callMethod(glContext!, 'getError', const <dynamic>[]);
|
||||
dynamic get error => js_util.callMethod(glContext, 'getError', const <dynamic>[]);
|
||||
|
||||
/// Shader compiler error, if this returns [kFalse], to get details use
|
||||
/// [getShaderInfoLog].
|
||||
dynamic get compileStatus =>
|
||||
_kCompileStatus ??= js_util.getProperty(glContext!, 'COMPILE_STATUS');
|
||||
_kCompileStatus ??= js_util.getProperty(glContext, 'COMPILE_STATUS');
|
||||
|
||||
dynamic get kArrayBuffer =>
|
||||
_kArrayBuffer ??= js_util.getProperty(glContext!, 'ARRAY_BUFFER');
|
||||
_kArrayBuffer ??= js_util.getProperty(glContext, 'ARRAY_BUFFER');
|
||||
|
||||
dynamic get kElementArrayBuffer =>
|
||||
_kElementArrayBuffer ??= js_util.getProperty(glContext,
|
||||
'ELEMENT_ARRAY_BUFFER');
|
||||
|
||||
dynamic get kLinkStatus =>
|
||||
_kLinkStatus ??= js_util.getProperty(glContext!, 'LINK_STATUS');
|
||||
_kLinkStatus ??= js_util.getProperty(glContext, 'LINK_STATUS');
|
||||
|
||||
dynamic get kFloat => _kFloat ??= js_util.getProperty(glContext!, 'FLOAT');
|
||||
dynamic get kFloat => _kFloat ??= js_util.getProperty(glContext, 'FLOAT');
|
||||
|
||||
dynamic get kRGBA => _kRGBA ??= js_util.getProperty(glContext!, 'RGBA');
|
||||
dynamic get kRGBA => _kRGBA ??= js_util.getProperty(glContext, 'RGBA');
|
||||
|
||||
dynamic get kUnsignedByte =>
|
||||
_kUnsignedByte ??= js_util.getProperty(glContext!, 'UNSIGNED_BYTE');
|
||||
_kUnsignedByte ??= js_util.getProperty(glContext, 'UNSIGNED_BYTE');
|
||||
|
||||
dynamic get kUnsignedShort =>
|
||||
_kUnsignedShort ??= js_util.getProperty(glContext, 'UNSIGNED_SHORT');
|
||||
|
||||
dynamic get kStaticDraw =>
|
||||
_kStaticDraw ??= js_util.getProperty(glContext!, 'STATIC_DRAW');
|
||||
_kStaticDraw ??= js_util.getProperty(glContext, 'STATIC_DRAW');
|
||||
|
||||
dynamic get kTriangles =>
|
||||
_kTriangles ??= js_util.getProperty(glContext!, 'TRIANGLES');
|
||||
_kTriangles ??= js_util.getProperty(glContext, 'TRIANGLES');
|
||||
|
||||
dynamic get kTriangleFan =>
|
||||
_kTriangles ??= js_util.getProperty(glContext!, 'TRIANGLE_FAN');
|
||||
_kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_FAN');
|
||||
|
||||
dynamic get kTriangleStrip =>
|
||||
_kTriangles ??= js_util.getProperty(glContext!, 'TRIANGLE_STRIP');
|
||||
_kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_STRIP');
|
||||
|
||||
dynamic get kColorBufferBit =>
|
||||
_kColorBufferBit ??= js_util.getProperty(glContext!, 'COLOR_BUFFER_BIT');
|
||||
_kColorBufferBit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT');
|
||||
|
||||
/// Returns reference to uniform in program.
|
||||
Object? getUniformLocation(Object? program, String uniformName) {
|
||||
return js_util
|
||||
.callMethod(glContext!, 'getUniformLocation',
|
||||
<dynamic>[program, uniformName]);
|
||||
Object getUniformLocation(Object program, String uniformName) {
|
||||
Object? res = js_util
|
||||
.callMethod(glContext, 'getUniformLocation', <dynamic>[program, uniformName]);
|
||||
if (res == null) {
|
||||
throw Exception('$uniformName not found');
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns reference to attribute in program.
|
||||
Object? getAttribLocation(Object? program, String uniformName) {
|
||||
/// Returns reference to uniform in program.
|
||||
Object getAttributeLocation(Object program, String attribName) {
|
||||
Object? res = js_util
|
||||
.callMethod(glContext, 'getAttribLocation', <dynamic>[program, attribName]);
|
||||
if (res == null) {
|
||||
throw Exception('$attribName not found');
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets float uniform value.
|
||||
void setUniform1f(Object uniform, double value) {
|
||||
return js_util
|
||||
.callMethod(glContext!, 'getAttribLocation',
|
||||
<dynamic>[program, uniformName]);
|
||||
.callMethod(glContext, 'uniform1f', <dynamic>[uniform, value]);
|
||||
}
|
||||
|
||||
/// Sets vec2 uniform values.
|
||||
void setUniform2f(Object uniform, double value1, double value2) {
|
||||
return js_util
|
||||
.callMethod(glContext!, 'uniform2f', <dynamic>[uniform, value1, value2]);
|
||||
.callMethod(glContext, 'uniform2f', <dynamic>[uniform, value1, value2]);
|
||||
}
|
||||
|
||||
/// Sets vec4 uniform values.
|
||||
void setUniform4f(Object? uniform, double value1, double value2, double value3,
|
||||
void setUniform4f(Object uniform, double value1, double value2, double value3,
|
||||
double value4) {
|
||||
return js_util.callMethod(
|
||||
glContext!, 'uniform4f', <dynamic>[uniform, value1, value2, value3, value4]);
|
||||
glContext, 'uniform4f', <dynamic>[uniform, value1, value2, value3, value4]);
|
||||
}
|
||||
|
||||
/// Sets mat4 uniform values.
|
||||
void setUniformMatrix4fv(Object? uniform, bool transpose, Float32List? value) {
|
||||
void setUniformMatrix4fv(Object uniform, bool transpose, Float32List value) {
|
||||
return js_util.callMethod(
|
||||
glContext!, 'uniformMatrix4fv', <dynamic>[uniform, transpose, value]);
|
||||
glContext, 'uniformMatrix4fv', <dynamic>[uniform, transpose, value]);
|
||||
}
|
||||
|
||||
/// Shader compile error log.
|
||||
dynamic getShaderInfoLog(Object glShader) {
|
||||
return js_util.callMethod(glContext!, 'getShaderInfoLog', <dynamic>[glShader]);
|
||||
return js_util.callMethod(glContext, 'getShaderInfoLog', <dynamic>[glShader]);
|
||||
}
|
||||
|
||||
/// Errors that occurred during failed linking or validation of program
|
||||
/// objects. Typically called after [linkProgram].
|
||||
String? getProgramInfoLog(Object? glProgram) {
|
||||
return js_util.callMethod(glContext!, 'getProgramInfoLog', <dynamic>[glProgram]);
|
||||
String? getProgramInfoLog(Object glProgram) {
|
||||
return js_util.callMethod(glContext, 'getProgramInfoLog', <dynamic>[glProgram]);
|
||||
}
|
||||
|
||||
int? get drawingBufferWidth =>
|
||||
js_util.getProperty(glContext!, 'drawingBufferWidth');
|
||||
js_util.getProperty(glContext, 'drawingBufferWidth');
|
||||
int? get drawingBufferHeight =>
|
||||
js_util.getProperty(glContext!, 'drawingBufferWidth');
|
||||
js_util.getProperty(glContext, 'drawingBufferWidth');
|
||||
|
||||
/// Reads gl contents as image data.
|
||||
///
|
||||
/// Warning: data is read bottom up (flipped).
|
||||
html.ImageData readImageData() {
|
||||
const int kBytesPerPixel = 4;
|
||||
final int bufferWidth = _widthInPixels!;
|
||||
final int bufferHeight = _heightInPixels!;
|
||||
if (browserEngine == BrowserEngine.webkit ||
|
||||
browserEngine == BrowserEngine.firefox) {
|
||||
const int kBytesPerPixel = 4;
|
||||
final int bufferWidth = _widthInPixels!;
|
||||
final int bufferHeight = _heightInPixels!;
|
||||
final Uint8List pixels =
|
||||
Uint8List(bufferWidth * bufferHeight * kBytesPerPixel);
|
||||
js_util.callMethod(glContext!, 'readPixels',
|
||||
js_util.callMethod(glContext, 'readPixels',
|
||||
<dynamic>[0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]);
|
||||
return html.ImageData(
|
||||
Uint8ClampedList.fromList(pixels), bufferWidth, bufferHeight);
|
||||
} else {
|
||||
const int kBytesPerPixel = 4;
|
||||
final int bufferWidth = _widthInPixels!;
|
||||
final int bufferHeight = _heightInPixels!;
|
||||
final Uint8ClampedList pixels =
|
||||
Uint8ClampedList(bufferWidth * bufferHeight * kBytesPerPixel);
|
||||
js_util.callMethod(glContext!, 'readPixels',
|
||||
js_util.callMethod(glContext, 'readPixels',
|
||||
<dynamic>[0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]);
|
||||
return html.ImageData(pixels, bufferWidth, bufferHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns image data in a form that can be used to create Canvas
|
||||
/// context patterns.
|
||||
Object? readPatternData() {
|
||||
// When using OffscreenCanvas and transferToImageBitmap is supported by
|
||||
// browser create ImageBitmap otherwise use more expensive canvas
|
||||
// allocation.
|
||||
if (_canvas != null &&
|
||||
js_util.hasProperty(_canvas!, 'transferToImageBitmap')) {
|
||||
js_util.callMethod(_canvas!, 'getContext', <dynamic>['webgl2']);
|
||||
Object?imageBitmap = js_util.callMethod(_canvas!, 'transferToImageBitmap',
|
||||
<dynamic>[]);
|
||||
return imageBitmap;
|
||||
} else {
|
||||
html.CanvasElement canvas = html.CanvasElement(width: _widthInPixels, height: _heightInPixels);
|
||||
final html.CanvasRenderingContext2D ctx = canvas.context2D;
|
||||
drawImage(ctx, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared Cached OffscreenCanvas for webgl rendering to image.
|
||||
class _OffscreenCanvas {
|
||||
static html.OffscreenCanvas? _canvas;
|
||||
static int _maxPixelWidth = 0;
|
||||
static int _maxPixelHeight = 0;
|
||||
static html.CanvasElement? _glCanvas;
|
||||
static _GlContext? _cachedContext;
|
||||
/// Polyfill for html.OffscreenCanvas that is not supported on some browsers.
|
||||
class _OffScreenCanvas {
|
||||
html.OffscreenCanvas? _canvas;
|
||||
html.CanvasElement? _glCanvas;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
_OffscreenCanvas(int width, int height) {
|
||||
assert(width > 0 && height > 0);
|
||||
if (width > _maxPixelWidth || height > _maxPixelHeight) {
|
||||
// Allocate bigger offscreen canvas.
|
||||
_OffScreenCanvas(this.width, this.height) {
|
||||
if (_OffScreenCanvas.supported) {
|
||||
_canvas = html.OffscreenCanvas(width, height);
|
||||
_maxPixelWidth = width;
|
||||
_maxPixelHeight = height;
|
||||
_cachedContext?.dispose();
|
||||
_cachedContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
static void dispose() {
|
||||
_canvas = null;
|
||||
_maxPixelWidth = 0;
|
||||
_maxPixelHeight = 0;
|
||||
_glCanvas = null;
|
||||
_cachedContext = null;
|
||||
}
|
||||
|
||||
html.OffscreenCanvas? get canvas => _canvas;
|
||||
|
||||
static _GlContext? createGlContext(int widthInPixels, int heightInPixels) {
|
||||
final bool isWebKit = (browserEngine == BrowserEngine.webkit);
|
||||
|
||||
if (_OffscreenCanvas.supported) {
|
||||
final _OffscreenCanvas offScreenCanvas =
|
||||
_OffscreenCanvas(widthInPixels, heightInPixels);
|
||||
_cachedContext ??= _GlContext.fromOffscreenCanvas(offScreenCanvas.canvas!);
|
||||
_cachedContext!.setViewportSize(widthInPixels, heightInPixels);
|
||||
return _cachedContext;
|
||||
} else {
|
||||
// Allocate new canvas element is size is larger.
|
||||
if (widthInPixels > _maxPixelWidth || heightInPixels > _maxPixelHeight) {
|
||||
_glCanvas = html.CanvasElement(
|
||||
width: widthInPixels,
|
||||
height: heightInPixels,
|
||||
);
|
||||
_glCanvas!.className = 'gl-canvas';
|
||||
final double cssWidth = widthInPixels / EngineWindow.browserDevicePixelRatio;
|
||||
final double cssHeight = heightInPixels / EngineWindow.browserDevicePixelRatio;
|
||||
_glCanvas!.style
|
||||
..position = 'absolute'
|
||||
..width = '${cssWidth}px'
|
||||
..height = '${cssHeight}px';
|
||||
_maxPixelWidth = widthInPixels;
|
||||
_maxPixelHeight = heightInPixels;
|
||||
_cachedContext?.dispose();
|
||||
_cachedContext = null;
|
||||
}
|
||||
_cachedContext ??= _GlContext.fromCanvas(_glCanvas!, isWebKit);
|
||||
_cachedContext!.setViewportSize(widthInPixels, heightInPixels);
|
||||
return _cachedContext;
|
||||
_glCanvas = html.CanvasElement(
|
||||
width: width,
|
||||
height: height,
|
||||
);
|
||||
_glCanvas!.className = 'gl-canvas';
|
||||
final double cssWidth = width / EngineWindow.browserDevicePixelRatio;
|
||||
final double cssHeight = height / EngineWindow.browserDevicePixelRatio;
|
||||
_glCanvas!.style
|
||||
..position = 'absolute'
|
||||
..width = '${cssWidth}px'
|
||||
..height = '${cssHeight}px';
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_canvas = null;
|
||||
_glCanvas = null;
|
||||
}
|
||||
|
||||
/// Feature detects OffscreenCanvas.
|
||||
static bool get supported =>
|
||||
js_util.hasProperty(html.window, 'OffscreenCanvas');
|
||||
}
|
||||
|
||||
/// Creates gl context from cached OffscreenCanvas for webgl rendering to image.
|
||||
class _GlContextCache {
|
||||
static int _maxPixelWidth = 0;
|
||||
static int _maxPixelHeight = 0;
|
||||
static _GlContext? _cachedContext;
|
||||
static _OffScreenCanvas? _offScreenCanvas;
|
||||
|
||||
static void dispose() {
|
||||
_maxPixelWidth = 0;
|
||||
_maxPixelHeight = 0;
|
||||
_cachedContext = null;
|
||||
_offScreenCanvas?.dispose();
|
||||
}
|
||||
|
||||
static _GlContext? createGlContext(int widthInPixels, int heightInPixels) {
|
||||
if (widthInPixels > _maxPixelWidth || heightInPixels > _maxPixelHeight) {
|
||||
_cachedContext?.dispose();
|
||||
_cachedContext = null;
|
||||
_offScreenCanvas = null;
|
||||
_maxPixelWidth = math.max(_maxPixelWidth, widthInPixels);
|
||||
_maxPixelHeight = math.max(_maxPixelHeight, widthInPixels);
|
||||
}
|
||||
_offScreenCanvas ??= _OffScreenCanvas(widthInPixels, heightInPixels);
|
||||
if (_OffScreenCanvas.supported) {
|
||||
_cachedContext ??=
|
||||
_GlContext.fromOffscreenCanvas(_offScreenCanvas!._canvas!);
|
||||
} else {
|
||||
_cachedContext ??= _GlContext.fromCanvas(_offScreenCanvas!._glCanvas!,
|
||||
webGLVersion == 1);
|
||||
}
|
||||
_cachedContext!.setViewportSize(widthInPixels, heightInPixels);
|
||||
return _cachedContext;
|
||||
}
|
||||
}
|
||||
|
||||
162
lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart
Normal file
162
lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart
Normal file
@ -0,0 +1,162 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.10
|
||||
part of engine;
|
||||
|
||||
/// 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 {
|
||||
NormalizedGradient._(this.thresholdCount, this._thresholds, this._scale,
|
||||
this._bias);
|
||||
|
||||
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;
|
||||
bool addFirst = stops[0] != 0.0;
|
||||
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) {
|
||||
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 (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 (double stop in stops) {
|
||||
thresholds[thresholdIndex++] = stop;
|
||||
}
|
||||
if (addLast) {
|
||||
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.
|
||||
int lastColorIndex = 4 * (normalizedCount - 1);
|
||||
for (int i = 0; i < lastColorIndex; i++) {
|
||||
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++) {
|
||||
double t = thresholds[i];
|
||||
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);
|
||||
}
|
||||
|
||||
/// Sets uniforms for threshold, bias and scale for program.
|
||||
void setupUniforms(_GlContext gl, _GlProgram glProgram) {
|
||||
for (int i = 0; i < thresholdCount; i++) {
|
||||
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]);
|
||||
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) {
|
||||
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) {
|
||||
String biasSource = '${biasName}_${start}';
|
||||
method.addStatement('${biasName} = ${biasSource};');
|
||||
String scaleSource = '${scaleName}_${start}';
|
||||
method.addStatement('${scaleName} = ${scaleSource};');
|
||||
} else {
|
||||
// Add probe check.
|
||||
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];
|
||||
}
|
||||
@ -10,7 +10,8 @@ abstract class EngineGradient implements ui.Gradient {
|
||||
EngineGradient._();
|
||||
|
||||
/// Creates a fill style to be used in painting.
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx);
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
|
||||
ui.Rect? shaderBounds);
|
||||
}
|
||||
|
||||
class GradientSweep extends EngineGradient {
|
||||
@ -22,14 +23,111 @@ class GradientSweep extends EngineGradient {
|
||||
assert(startAngle != null), // ignore: unnecessary_null_comparison
|
||||
assert(endAngle != null), // ignore: unnecessary_null_comparison
|
||||
assert(startAngle < endAngle),
|
||||
assert(matrix4 == null || _matrix4IsValid(matrix4)),
|
||||
super._() {
|
||||
_validateColorStops(colors, colorStops);
|
||||
}
|
||||
|
||||
@override
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx) {
|
||||
throw UnimplementedError();
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
|
||||
ui.Rect? shaderBounds) {
|
||||
assert(shaderBounds != null);
|
||||
int widthInPixels = shaderBounds!.right.ceil();
|
||||
int heightInPixels = shaderBounds.bottom.ceil();
|
||||
assert(widthInPixels > 0 && heightInPixels > 0);
|
||||
|
||||
initWebGl();
|
||||
// Render gradient into a bitmap and create a canvas pattern.
|
||||
_OffScreenCanvas offScreenCanvas =
|
||||
_OffScreenCanvas(widthInPixels, heightInPixels);
|
||||
_GlContext gl = _OffScreenCanvas.supported
|
||||
? _GlContext.fromOffscreenCanvas(offScreenCanvas._canvas!)
|
||||
: _GlContext.fromCanvas(offScreenCanvas._glCanvas!,
|
||||
webGLVersion == 1);
|
||||
gl.setViewportSize(widthInPixels, heightInPixels);
|
||||
|
||||
NormalizedGradient normalizedGradient = NormalizedGradient(
|
||||
colors, stops: colorStops);
|
||||
|
||||
_GlProgram glProgram = gl.useAndCacheProgram(
|
||||
_WebGlRenderer.writeBaseVertexShader(),
|
||||
_createSweepFragmentShader(normalizedGradient, tileMode))!;
|
||||
|
||||
Object tileOffset = gl.getUniformLocation(glProgram.program, 'u_tile_offset');
|
||||
double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
|
||||
double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height);
|
||||
gl.setUniform2f(tileOffset,
|
||||
shaderBounds.left + 2 * (shaderBounds.width * (centerX - 0.5)),
|
||||
-shaderBounds.top - 2 * (shaderBounds.height * (centerY - 0.5)));
|
||||
Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range');
|
||||
gl.setUniform2f(angleRange, startAngle, endAngle);
|
||||
normalizedGradient.setupUniforms(gl, glProgram);
|
||||
if (matrix4 != null) {
|
||||
Object gradientMatrix = gl.getUniformLocation(
|
||||
glProgram.program, 'm_gradient');
|
||||
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4!);
|
||||
}
|
||||
|
||||
Object? imageBitmap = _glRenderer!.drawRect(shaderBounds, gl,
|
||||
glProgram, normalizedGradient, widthInPixels, heightInPixels);
|
||||
|
||||
return ctx!.createPattern(imageBitmap!, 'no-repeat')!;
|
||||
}
|
||||
|
||||
String _createSweepFragmentShader(NormalizedGradient gradient,
|
||||
ui.TileMode tileMode) {
|
||||
ShaderBuilder builder = ShaderBuilder.fragment(webGLVersion);
|
||||
builder.floatPrecision = ShaderPrecision.kMedium;
|
||||
builder.addIn(ShaderType.kVec4, name: 'v_color');
|
||||
builder.addUniform(ShaderType.kVec2, name: 'u_resolution');
|
||||
builder.addUniform(ShaderType.kVec2, name: 'u_tile_offset');
|
||||
builder.addUniform(ShaderType.kVec2, name: 'angle_range');
|
||||
builder.addUniform(ShaderType.kMat4, name: 'm_gradient');
|
||||
ShaderDeclaration fragColor = builder.fragmentColor;
|
||||
ShaderMethod method = builder.addMethod('main');
|
||||
// Sweep gradient
|
||||
method.addStatement(
|
||||
'vec2 center = 0.5 * (u_resolution + u_tile_offset);');
|
||||
method.addStatement(
|
||||
'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;');
|
||||
method.addStatement(
|
||||
'float angle = atan(-localCoord.y, -localCoord.x) + ${math.pi};');
|
||||
method.addStatement(
|
||||
'float sweep = angle_range.y - angle_range.x;');
|
||||
method.addStatement(
|
||||
'angle = (angle - angle_range.x) / sweep;');
|
||||
method.addStatement(''
|
||||
'float st = angle;');
|
||||
|
||||
method.addStatement('vec4 bias;');
|
||||
method.addStatement('vec4 scale;');
|
||||
// Write uniforms for each threshold, bias and scale.
|
||||
for (int i = 0; i < (gradient.thresholdCount - 1) ~/ 4 + 1; i++) {
|
||||
builder.addUniform(ShaderType.kVec4, name: 'threshold_${i}');
|
||||
}
|
||||
for (int i = 0; i < gradient.thresholdCount; i++) {
|
||||
builder.addUniform(ShaderType.kVec4, name: 'bias_$i');
|
||||
builder.addUniform(ShaderType.kVec4, name: 'scale_$i');
|
||||
}
|
||||
String probeName = 'st';
|
||||
switch (tileMode) {
|
||||
case ui.TileMode.clamp:
|
||||
break;
|
||||
case ui.TileMode.repeated:
|
||||
method.addStatement('float tiled_st = fract(st);');
|
||||
probeName = 'tiled_st';
|
||||
break;
|
||||
case ui.TileMode.mirror:
|
||||
method.addStatement('float t_1 = (st - 1.0);');
|
||||
method.addStatement('float tiled_st = abs((t_1 - 2.0 * floor(t_1 * 0.5)) - 1.0);');
|
||||
probeName = 'tiled_st';
|
||||
break;
|
||||
}
|
||||
_writeUnrolledBinarySearch(method, 0, gradient.thresholdCount - 1,
|
||||
probe: probeName, sourcePrefix: 'threshold',
|
||||
biasName: 'bias', scaleName: 'scale');
|
||||
method.addStatement('${fragColor.name} = ${probeName} * scale + bias;');
|
||||
String shader = builder.build();
|
||||
return shader;
|
||||
}
|
||||
|
||||
final ui.Offset center;
|
||||
@ -68,7 +166,8 @@ class GradientLinear extends EngineGradient {
|
||||
final _FastMatrix64? matrix4;
|
||||
|
||||
@override
|
||||
html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx) {
|
||||
html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx,
|
||||
ui.Rect? shaderBounds) {
|
||||
_FastMatrix64? matrix4 = this.matrix4;
|
||||
html.CanvasGradient gradient;
|
||||
if (matrix4 != null) {
|
||||
@ -115,7 +214,8 @@ class GradientRadial extends EngineGradient {
|
||||
final Float32List? matrix4;
|
||||
|
||||
@override
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx) {
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
|
||||
ui.Rect? shaderBounds) {
|
||||
if (!experimentalUseSkia) {
|
||||
if (tileMode != ui.TileMode.clamp) {
|
||||
throw UnimplementedError(
|
||||
@ -154,7 +254,8 @@ class GradientConical extends EngineGradient {
|
||||
final Float32List? matrix4;
|
||||
|
||||
@override
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx) {
|
||||
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
|
||||
ui.Rect? shaderBounds) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ class ShaderBuilder {
|
||||
ShaderDeclaration? _fragmentColorDeclaration;
|
||||
|
||||
ShaderBuilder(this.version) : isWebGl2 = version == WebGLVersion.webgl2,
|
||||
_isFragmentShader = false;
|
||||
_isFragmentShader = false;
|
||||
|
||||
ShaderBuilder.fragment(this.version) :
|
||||
isWebGl2 = version == WebGLVersion.webgl2,
|
||||
@ -238,9 +238,23 @@ class ShaderMethod {
|
||||
final String returnType = 'void';
|
||||
final String name;
|
||||
final List<String> _statements = [];
|
||||
int _indentLevel = 1;
|
||||
|
||||
void indent() {
|
||||
++_indentLevel;
|
||||
}
|
||||
|
||||
void unindent() {
|
||||
assert(_indentLevel != 1);
|
||||
--_indentLevel;
|
||||
}
|
||||
|
||||
void addStatement(String statement) {
|
||||
_statements.add(statement);
|
||||
if (assertionsEnabled) {
|
||||
_statements.add(' ' * _indentLevel + statement);
|
||||
} else {
|
||||
_statements.add(statement);
|
||||
}
|
||||
}
|
||||
|
||||
void write(StringBuffer buffer) {
|
||||
|
||||
@ -507,6 +507,12 @@ class _FastMatrix64 {
|
||||
transformedX = matrix[12] + (matrix[0] * x) + (matrix[4] * y);
|
||||
transformedY = matrix[13] + (matrix[1] * x) + (matrix[5] * y);
|
||||
}
|
||||
|
||||
String debugToString() =>
|
||||
'${matrix[0].toStringAsFixed(3)}, ${matrix[4].toStringAsFixed(3)}, ${matrix[8].toStringAsFixed(3)}, ${matrix[12].toStringAsFixed(3)}\n'
|
||||
'${matrix[1].toStringAsFixed(3)}, ${matrix[5].toStringAsFixed(3)}, ${matrix[9].toStringAsFixed(3)}, ${matrix[13].toStringAsFixed(3)}\n'
|
||||
'${matrix[2].toStringAsFixed(3)}, ${matrix[6].toStringAsFixed(3)}, ${matrix[10].toStringAsFixed(3)}, ${matrix[14].toStringAsFixed(3)}\n'
|
||||
'${matrix[3].toStringAsFixed(3)}, ${matrix[7].toStringAsFixed(3)}, ${matrix[11].toStringAsFixed(3)}, ${matrix[15].toStringAsFixed(3)}\n';
|
||||
}
|
||||
|
||||
/// Roughly the inverse of [ui.Shadow.convertRadiusToSigma].
|
||||
|
||||
@ -0,0 +1,125 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.6
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui hide window;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('Shader Normalized Gradient', () {
|
||||
test('3 stop at start', () {
|
||||
NormalizedGradient gradient = NormalizedGradient(<ui.Color>[
|
||||
ui.Color(0xFF000000), ui.Color(0xFFFF7f3f)
|
||||
], stops: <double>[0.0, 0.5]);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.25);
|
||||
assert(res == 0xFF7f3f1f);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 0.7);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
});
|
||||
|
||||
test('3 stop at end', () {
|
||||
NormalizedGradient gradient = NormalizedGradient(<ui.Color>[
|
||||
ui.Color(0xFF000000), ui.Color(0xFFFF7f3f)
|
||||
], stops: <double>[0.5, 1.0]);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.25);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.75);
|
||||
assert(res == 0xFF7f3f1f);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
});
|
||||
|
||||
test('4 stop', () {
|
||||
NormalizedGradient gradient = NormalizedGradient(<ui.Color>[
|
||||
ui.Color(0xFF000000), ui.Color(0xFFFF7f3f)
|
||||
], stops: <double>[0.25, 0.5]);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.25);
|
||||
assert(res == 0xFF000000);
|
||||
res = _computeColorAt(gradient, 0.4);
|
||||
assert(res == 0xFF994c25);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 0.75);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFF7f3f);
|
||||
});
|
||||
|
||||
test('5 stop', () {
|
||||
NormalizedGradient gradient = NormalizedGradient(<ui.Color>[
|
||||
ui.Color(0x10000000), ui.Color(0x20FF0000),
|
||||
ui.Color(0x4000FF00), ui.Color(0x800000FF),
|
||||
ui.Color(0xFFFFFFFF)
|
||||
], stops: <double>[0.0, 0.1, 0.2, 0.5, 1.0]);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0x10000000);
|
||||
res = _computeColorAt(gradient, 0.05);
|
||||
assert(res == 0x187f0000);
|
||||
res = _computeColorAt(gradient, 0.1);
|
||||
assert(res == 0x20ff0000);
|
||||
res = _computeColorAt(gradient, 0.15);
|
||||
assert(res == 0x307f7f00);
|
||||
res = _computeColorAt(gradient, 0.2);
|
||||
assert(res == 0x4000ff00);
|
||||
res = _computeColorAt(gradient, 0.4);
|
||||
assert(res == 0x6a0054a9);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0x800000fe);
|
||||
res = _computeColorAt(gradient, 0.9);
|
||||
assert(res == 0xe5ccccff);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xffffffff);
|
||||
});
|
||||
|
||||
test('2 stops at ends', () {
|
||||
NormalizedGradient gradient = NormalizedGradient(<ui.Color>[
|
||||
ui.Color(0x00000000), ui.Color(0xFFFFFFFF)
|
||||
]);
|
||||
int res = _computeColorAt(gradient, 0.0);
|
||||
assert(res == 0);
|
||||
res = _computeColorAt(gradient, 1.0);
|
||||
assert(res == 0xFFFFFFFF);
|
||||
res = _computeColorAt(gradient, 0.5);
|
||||
assert(res == 0x7f7f7f7f);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
int _computeColorAt(NormalizedGradient gradient, double t) {
|
||||
int i = 0;
|
||||
while (t > gradient.thresholdAt(i + 1)) {
|
||||
++i;
|
||||
}
|
||||
double r = t * gradient.scaleAt(i * 4) + gradient.biasAt(i * 4);
|
||||
double g = t * gradient.scaleAt(i * 4 + 1) + gradient.biasAt(i * 4 + 1);
|
||||
double b = t * gradient.scaleAt(i * 4 + 2) + gradient.biasAt(i * 4 + 2);
|
||||
double a = t * gradient.scaleAt(i * 4 + 3) + gradient.biasAt(i * 4 + 3);
|
||||
int val = 0;
|
||||
val |= (a * 0xFF).toInt() & 0xFF;
|
||||
val<<=8;
|
||||
val |= (r * 0xFF).toInt() & 0xFF;
|
||||
val<<=8;
|
||||
val |= (g * 0xFF).toInt() & 0xFF;
|
||||
val<<=8;
|
||||
val |= (b * 0xFF).toInt() & 0xFF;
|
||||
return val;
|
||||
}
|
||||
@ -201,7 +201,7 @@ void testMain() {
|
||||
'precision mediump float;\n'
|
||||
'uniform float ${variable.name};\n'
|
||||
'void main() {\n'
|
||||
'f1 = 5.0;\n'
|
||||
' f1 = 5.0;\n'
|
||||
'}\n');
|
||||
});
|
||||
});
|
||||
|
||||
318
lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart
Normal file
318
lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart
Normal file
@ -0,0 +1,318 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.6
|
||||
import 'dart:html' as html;
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import 'scuba.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() async {
|
||||
const double screenWidth = 600.0;
|
||||
const double screenHeight = 800.0;
|
||||
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
|
||||
|
||||
// Commit a recording canvas to a bitmap, and compare with the expected
|
||||
Future<void> _checkScreenshot(RecordingCanvas rc, String fileName,
|
||||
{Rect region = const Rect.fromLTWH(0, 0, 500, 240),
|
||||
double maxDiffRatePercent = 0.0, bool write: false}) async {
|
||||
final EngineCanvas engineCanvas = BitmapCanvas(screenRect);
|
||||
|
||||
rc.endRecording();
|
||||
rc.apply(engineCanvas, screenRect);
|
||||
|
||||
// Wrap in <flt-scene> so that our CSS selectors kick in.
|
||||
final html.Element sceneElement = html.Element.tag('flt-scene');
|
||||
try {
|
||||
sceneElement.append(engineCanvas.rootElement);
|
||||
html.document.body.append(sceneElement);
|
||||
await matchGoldenFile('$fileName.png',
|
||||
region: region, maxDiffRatePercent: maxDiffRatePercent, write: write);
|
||||
} finally {
|
||||
// The page is reused across tests, so remove the element after taking the
|
||||
// Scuba screenshot.
|
||||
sceneElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
setUp(() async {
|
||||
debugEmulateFlutterTesterEnvironment = true;
|
||||
});
|
||||
|
||||
setUpStableTestFonts();
|
||||
|
||||
test('Paints sweep gradient rectangles', () async {
|
||||
final RecordingCanvas canvas =
|
||||
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final Paint borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = Color(0xFF000000);
|
||||
|
||||
List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),];
|
||||
List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
EngineGradient sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
0, 360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
EngineGradient sweepGradientRotated = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
0, 360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawRect(rectBounds,
|
||||
Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with shifted center and rotation.
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawRect(rectBounds,
|
||||
Paint()..shader = engineGradientToShader(sweepGradientRotated, Rect.fromLTWH(rectBounds.center.dx, rectBounds.top, rectBounds.width / 2, rectBounds.height)));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with start/endangle.
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawRect(rectBounds,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.repeated,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
canvas.drawRect(rectBounds,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode mirror
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.mirror,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
canvas.drawRect(rectBounds,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await _checkScreenshot(canvas, 'sweep_gradient_rect');
|
||||
});
|
||||
|
||||
test('Paints sweep gradient ovals', () async {
|
||||
final RecordingCanvas canvas =
|
||||
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final Paint borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = Color(0xFF000000);
|
||||
|
||||
List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),];
|
||||
List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
EngineGradient sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
0, 360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
EngineGradient sweepGradientRotated = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
0, 360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
canvas.drawOval(rectBounds,
|
||||
Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with shifted center and rotation.
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawOval(rectBounds,
|
||||
Paint()..shader = engineGradientToShader(sweepGradientRotated, Rect.fromLTWH(rectBounds.center.dx, rectBounds.top, rectBounds.width / 2, rectBounds.height)));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with start/endangle.
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
canvas.drawOval(rectBounds,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.repeated,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
canvas.drawOval(rectBounds,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode mirror
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.mirror,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
canvas.drawOval(rectBounds,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await _checkScreenshot(canvas, 'sweep_gradient_oval');
|
||||
});
|
||||
|
||||
test('Paints sweep gradient paths', () async {
|
||||
final RecordingCanvas canvas =
|
||||
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
|
||||
canvas.save();
|
||||
|
||||
final Paint borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = Color(0xFF000000);
|
||||
|
||||
List<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),];
|
||||
List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
|
||||
|
||||
EngineGradient sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
0, 360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
EngineGradient sweepGradientRotated = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
0, 360.0 / 180.0 * math.pi,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
const double kBoxWidth = 150;
|
||||
const double kBoxHeight = 80;
|
||||
// Gradient with default center.
|
||||
Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
|
||||
Path path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(path,
|
||||
Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with shifted center and rotation.
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(path,
|
||||
Paint()..shader = engineGradientToShader(sweepGradientRotated, Rect.fromLTWH(rectBounds.center.dx, rectBounds.top, rectBounds.width / 2, rectBounds.height)));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Gradient with start/endangle.
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.clamp,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(path,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode repeat
|
||||
rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.repeated,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(path,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
// Tile mode mirror
|
||||
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
|
||||
sweepGradient = GradientSweep(Offset(0.5, 0.5),
|
||||
colors, stops, TileMode.mirror,
|
||||
math.pi / 6, 3 * math.pi / 4,
|
||||
Matrix4.rotationZ(math.pi / 6.0).storage);
|
||||
path = samplePathFromRect(rectBounds);
|
||||
canvas.drawPath(path,
|
||||
new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds));
|
||||
canvas.drawRect(rectBounds, borderPaint);
|
||||
|
||||
canvas.restore();
|
||||
await _checkScreenshot(canvas, 'sweep_gradient_path');
|
||||
});
|
||||
}
|
||||
|
||||
Shader engineGradientToShader(GradientSweep gradient, Rect rect) {
|
||||
return Gradient.sweep(
|
||||
Offset(rect.left + gradient.center.dx * rect.width,
|
||||
rect.top + gradient.center.dy * rect.height),
|
||||
gradient.colors, gradient.colorStops, gradient.tileMode,
|
||||
gradient.startAngle,
|
||||
gradient.endAngle,
|
||||
gradient.matrix4 == null ? null :
|
||||
Float64List.fromList(gradient.matrix4),
|
||||
);
|
||||
}
|
||||
|
||||
Path samplePathFromRect(Rect rectBounds) =>
|
||||
Path()
|
||||
..moveTo(rectBounds.center.dx, rectBounds.top)
|
||||
..lineTo(rectBounds.left, rectBounds.bottom)
|
||||
..quadraticBezierTo(rectBounds.center.dx + 20, rectBounds.bottom - 40,
|
||||
rectBounds.right, rectBounds.bottom)
|
||||
..close();
|
||||
Loading…
x
Reference in New Issue
Block a user