未验证 提交 060f108c 编写于 作者: F Ferhat 提交者: GitHub

Add web ImageShader support for drawVertices. (#25729)

上级 7676dbc9
repository: https://github.com/flutter/goldens.git
revision: d885b270d90e031fdda2efd640eb5d6b6fbc9ce5
revision: 449cb88e9ffed446e4e7df9beb73814e5fb11ead
......@@ -940,12 +940,13 @@ class BitmapCanvas extends EngineCanvas {
// blendMode. https://github.com/flutter/flutter/issues/40096
// Move rendering to OffscreenCanvas so that transform is preserved
// as well.
assert(paint.shader == null,
'Linear/Radial/SweepGradient and ImageShader not supported yet');
assert(paint.shader == null || paint.shader is ImageShader,
'Linear/Radial/SweepGradient not supported yet');
final Int32List? colors = vertices._colors;
final ui.VertexMode mode = vertices._mode;
html.CanvasRenderingContext2D? ctx = _canvasPool.context;
if (colors == null && paint.style != ui.PaintingStyle.fill) {
if (colors == null && paint.style != ui.PaintingStyle.fill &&
paint.shader == null) {
final Float32List positions = mode == ui.VertexMode.triangles
? vertices._positions
: _convertVertexPositions(mode, vertices._positions);
......
......@@ -321,6 +321,7 @@ class RecordingCanvas {
void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
final double paintSpread = math.max(_getPaintSpread(paint), 1.0);
final PaintDrawLine command = PaintDrawLine(p1, p2, paint.paintData);
// TODO(yjbanov): This can be optimized. Currently we create a box around
......@@ -344,6 +345,7 @@ class RecordingCanvas {
void drawPaint(SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
renderStrategy.hasArbitraryPaint = true;
_didDraw = true;
final PaintDrawPaint command = PaintDrawPaint(paint.paintData);
......@@ -353,6 +355,7 @@ class RecordingCanvas {
void drawRect(ui.Rect rect, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
if (paint.shader != null) {
renderStrategy.hasArbitraryPaint = true;
}
......@@ -369,6 +372,7 @@ class RecordingCanvas {
void drawRRect(ui.RRect rrect, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
if (paint.shader != null || !rrect.webOnlyUniformRadii) {
renderStrategy.hasArbitraryPaint = true;
}
......@@ -385,6 +389,7 @@ class RecordingCanvas {
void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
// Check the inner bounds are contained within the outer bounds
// see: https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkCanvas.cpp?l=1787-1789
ui.Rect innerRect = inner.outerRect;
......@@ -443,6 +448,7 @@ class RecordingCanvas {
void drawOval(ui.Rect rect, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
renderStrategy.hasArbitraryPaint = true;
_didDraw = true;
final double paintSpread = _getPaintSpread(paint);
......@@ -457,6 +463,7 @@ class RecordingCanvas {
void drawCircle(ui.Offset c, double radius, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
renderStrategy.hasArbitraryPaint = true;
_didDraw = true;
final double paintSpread = _getPaintSpread(paint);
......@@ -474,6 +481,7 @@ class RecordingCanvas {
void drawPath(ui.Path path, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
if (paint.shader == null) {
// For Rect/RoundedRect paths use drawRect/drawRRect code paths for
// DomCanvas optimization.
......@@ -510,6 +518,7 @@ class RecordingCanvas {
void drawImage(ui.Image image, ui.Offset offset, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
renderStrategy.hasArbitraryPaint = true;
renderStrategy.hasImageElements = true;
_didDraw = true;
......@@ -547,6 +556,7 @@ class RecordingCanvas {
void drawImageRect(
ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaint paint) {
assert(!_recordingEnded);
assert(paint.shader == null || paint.shader is! ImageShader, 'ImageShader not supported yet');
renderStrategy.hasArbitraryPaint = true;
renderStrategy.hasImageElements = true;
_didDraw = true;
......
......@@ -49,6 +49,10 @@ class SurfaceVertices implements ui.Vertices {
}
}
/// Lazily initializes web gl.
///
/// Used to treeshake WebGlRenderer when user doesn't create Vertices object
/// to use the API.
void initWebGl() {
_glRenderer ??= _WebGlRenderer();
}
......@@ -85,6 +89,45 @@ class _WebGlRenderer implements _GlRenderer {
/// Cached vertex shader reused by [drawVertices] and gradients.
static String? _baseVertexShader;
static String? _textureVertexShader;
static void _setupVertexTransforms(_GlContext gl, _GlProgram glProgram,
double offsetX, double offsetY, double widthInPixels,
double heightInPixels, Matrix4 transform) {
Object transformUniform = gl.getUniformLocation(glProgram.program,
'u_ctransform');
Matrix4 transformAtOffset = transform.clone()
..translate(-offsetX, -offsetY);
gl.setUniformMatrix4fv(transformUniform, false, transformAtOffset.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);
}
static void _setupTextureScalar(_GlContext gl, _GlProgram glProgram,
double sx, double sy) {
Object scalar = gl.getUniformLocation(glProgram.program, 'u_texscale');
gl.setUniform2f(scalar, sx, sy);
}
static dynamic _tileModeToGlWrapping(_GlContext gl, ui.TileMode tileMode) {
switch(tileMode) {
case ui.TileMode.clamp:
return gl.kClampToEdge;
case ui.TileMode.decal:
return gl.kClampToEdge;
case ui.TileMode.mirror:
return gl.kMirroredRepeat;
case ui.TileMode.repeated:
return gl.kRepeat;
}
}
@override
void drawVertices(
html.CanvasRenderingContext2D? context,
......@@ -94,6 +137,7 @@ class _WebGlRenderer implements _GlRenderer {
SurfaceVertices vertices,
ui.BlendMode blendMode,
SurfacePaintData paint) {
// Compute bounds of vertices.
final Float32List positions = vertices._positions;
ui.Rect bounds = _computeVerticesBounds(positions, transform);
......@@ -124,60 +168,137 @@ class _WebGlRenderer implements _GlRenderer {
if (widthInPixels == 0 || heightInPixels == 0) {
return;
}
final String vertexShader = writeBaseVertexShader();
final String fragmentShader = _writeVerticesFragmentShader();
final bool isWebGl2 = webGLVersion == WebGLVersion.webgl2;
final ImageShader? imageShader = paint.shader == null ? null
: paint.shader as ImageShader;
final String vertexShader = imageShader == null
? writeBaseVertexShader() : writeTextureVertexShader();
final String fragmentShader = imageShader == null
? _writeVerticesFragmentShader()
: _writeVerticesTextureFragmentShader(isWebGl2,
imageShader.tileModeX, imageShader.tileModeY);
_GlContext gl = _GlContextCache.createGlContext(widthInPixels, heightInPixels)!;
_GlProgram glProgram = gl.useAndCacheProgram(vertexShader, fragmentShader);
_GlProgram glProgram = gl.cacheProgram(vertexShader, fragmentShader);
gl.useProgram(glProgram);
Object transformUniform = gl.getUniformLocation(glProgram.program,
'u_ctransform');
Matrix4 transformAtOffset = transform.clone()
..translate(-offsetX, -offsetY);
gl.setUniformMatrix4fv(transformUniform, false, transformAtOffset.storage);
Object? positionAttributeLocation =
gl.getAttributeLocation(glProgram.program, 'position');
// 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);
_setupVertexTransforms(gl, glProgram, offsetX, offsetY,
widthInPixels.toDouble(), heightInPixels.toDouble(),
transform);
if (imageShader != null) {
/// To map from vertex position to texture coordinate in 0..1 range,
/// we setup scalar to be used in vertex shader.
_setupTextureScalar(gl, glProgram, 1.0 / imageShader._image.width.toDouble(),
1.0 / imageShader._image.height.toDouble());
}
// Setup geometry.
//
// Create buffer for vertex coordinates.
Object positionsBuffer = gl.createBuffer()!;
assert(positionsBuffer != null); // ignore: unnecessary_null_comparison
Object? vao;
if (imageShader != null) {
if (isWebGl2) {
// Create a vertex array object.
vao = gl.createVertexArray();
// Set vertex array object as active one.
gl.bindVertexArray(vao!);
}
}
// Turn on position attribute.
gl.enableVertexAttribArray(positionAttributeLocation);
// Bind buffer as position buffer and transfer data.
gl.bindArrayBuffer(positionsBuffer);
gl.bufferData(positions, gl.kStaticDraw);
Object? positionLoc = gl.getAttributeLocation(glProgram.program, 'position');
// Setup data format for attribute.
js_util.callMethod(
gl.glContext, 'vertexAttribPointer', <dynamic>[
positionLoc, 2, gl.kFloat, false, 0, 0,
positionAttributeLocation, 2, gl.kFloat, false, 0, 0,
]);
gl.enableVertexAttribArray(0);
// Setup color buffer.
Object? colorsBuffer = gl.createBuffer();
gl.bindArrayBuffer(colorsBuffer);
final int vertexCount = positions.length ~/ 2;
// Buffer kBGRA_8888.
if (vertices._colors == null) {
final ui.Color color = paint.color ?? ui.Color(0xFF000000);
Uint32List vertexColors = Uint32List(vertexCount);
for (int i = 0; i < vertexCount; i++) {
vertexColors[i] = color.value;
Object? texture;
if (imageShader == null) {
// Setup color buffer.
Object? colorsBuffer = gl.createBuffer();
gl.bindArrayBuffer(colorsBuffer);
// Buffer kBGRA_8888.
if (vertices._colors == null) {
final ui.Color color = paint.color ?? ui.Color(0xFF000000);
Uint32List vertexColors = Uint32List(vertexCount);
for (int i = 0; i < vertexCount; i++) {
vertexColors[i] = color.value;
}
gl.bufferData(vertexColors, gl.kStaticDraw);
} else {
gl.bufferData(vertices._colors, gl.kStaticDraw);
}
gl.bufferData(vertexColors, gl.kStaticDraw);
Object colorLoc = gl.getAttributeLocation(glProgram.program, 'color');
js_util.callMethod(gl.glContext, 'vertexAttribPointer',
<dynamic>[colorLoc, 4, gl.kUnsignedByte, true, 0, 0]);
gl.enableVertexAttribArray(colorLoc);
} else {
gl.bufferData(vertices._colors, gl.kStaticDraw);
// Copy image it to the texture.
texture = gl.createTexture();
// Texture units are a global array of references to the textures.
// By setting activeTexture, we associate the bound texture to a unit.
// Every time we call a texture function such as texImage2D with a target
// like TEXTURE_2D, it looks up texture by using the currently active
// unit.
// In our case we have a single texture unit 0.
gl.activeTexture(gl.kTexture0);
gl.bindTexture(gl.kTexture2D, texture);
gl.texImage2D(
gl.kTexture2D,
0,
gl.kRGBA,
gl.kRGBA,
gl.kUnsignedByte,
imageShader._image.imgElement
);
if (isWebGl2) {
// Texture REPEAT and MIRROR is only supported in WebGL 2, for
// WebGL 1.0 we let shader compute correct uv coordinates.
gl.texParameteri(gl.kTexture2D, gl.kTextureWrapS,
_tileModeToGlWrapping(gl, imageShader.tileModeX));
gl.texParameteri(gl.kTexture2D, gl.kTextureWrapT,
_tileModeToGlWrapping(gl, imageShader.tileModeY));
// Mipmapping saves your texture in different resolutions
// so the graphics card can choose which resolution is optimal
// without artifacts.
gl.generateMipmap(gl.kTexture2D);
} else {
// For webgl1, if a texture is not mipmap complete, then the return
// value of a texel fetch is (0, 0, 0, 1), so we have to set
// minifying function to filter.
// See https://www.khronos.org/registry/webgl/specs/1.0.0/#5.13.8.
gl.texParameteri(gl.kTexture2D, gl.kTextureWrapS,
gl.kClampToEdge);
gl.texParameteri(gl.kTexture2D, gl.kTextureWrapT,
gl.kClampToEdge);
gl.texParameteri(gl.kTexture2D, gl.kTextureMinFilter, gl.kLinear);
}
}
Object colorLoc = gl.getAttributeLocation(glProgram.program, 'color');
js_util.callMethod(gl.glContext, 'vertexAttribPointer',
<dynamic>[colorLoc, 4, gl.kUnsignedByte, true, 0, 0]);
gl.enableVertexAttribArray(1);
// Finally render triangles.
gl.clear();
final Uint16List ?indices = vertices._indices;
if (indices == null) {
gl.drawTriangles(vertexCount, vertices._mode);
......@@ -191,6 +312,10 @@ class _WebGlRenderer implements _GlRenderer {
gl.drawElements(gl.kTriangles, indices.length, gl.kUnsignedShort);
}
if (vao != null) {
gl.unbindVertexArray();
}
context!.save();
context.resetTransform();
gl.drawImage(context, offsetX, offsetY);
......@@ -331,6 +456,26 @@ class _WebGlRenderer implements _GlRenderer {
return _baseVertexShader!;
}
static String writeTextureVertexShader() {
if (_textureVertexShader == null) {
ShaderBuilder builder = ShaderBuilder(webGLVersion);
builder.addIn(ShaderType.kVec4, name: 'position');
builder.addUniform(ShaderType.kMat4, name: 'u_ctransform');
builder.addUniform(ShaderType.kVec4, name: 'u_scale');
builder.addUniform(ShaderType.kVec2, name: 'u_texscale');
builder.addUniform(ShaderType.kVec4, name: 'u_shift');
builder.addOut(ShaderType.kVec2, name: 'v_texcoord');
ShaderMethod method = builder.addMethod('main');
method.addStatement(
'gl_Position = ((u_ctransform * position) * u_scale) + u_shift;');
method.addStatement(
'v_texcoord = vec2(position.x * u_texscale.x, '
'(position.y * u_texscale.y));');
_textureVertexShader = builder.build();
}
return _textureVertexShader!;
}
/// This fragment shader enables Int32List of colors to be passed directly
/// to gl context buffer for rendering by decoding RGBA8888.
/// #version 300 es
......@@ -349,6 +494,33 @@ class _WebGlRenderer implements _GlRenderer {
return builder.build();
}
String _writeVerticesTextureFragmentShader(bool isWebGl2,
ui.TileMode? tileModeX, ui.TileMode? tileModeY) {
ShaderBuilder builder = ShaderBuilder.fragment(webGLVersion);
builder.floatPrecision = ShaderPrecision.kMedium;
builder.addIn(ShaderType.kVec2, name:'v_texcoord');
builder.addUniform(ShaderType.kSampler2D, name: 'u_texture');
ShaderMethod method = builder.addMethod('main');
if (isWebGl2
|| tileModeX == null || tileModeY == null
|| (tileModeX == ui.TileMode.clamp && tileModeY == ui.TileMode.clamp)) {
method.addStatement('${builder.fragmentColor.name} = '
'${builder.texture2DFunction}(u_texture, v_texcoord);');
} else {
// Repeat and mirror are not supported for webgl1. Write code to
// adjust texture coordinate.
//
// This will write u and v floats, clamp/repeat and mirror the value and
// pass it to sampler.
method.addTileStatements('v_texcoord.x', 'u', tileModeX);
method.addTileStatements('v_texcoord.y', 'v', tileModeY);
method.addStatement('vec2 uv = vec2(u, v);');
method.addStatement('${builder.fragmentColor.name} = '
'${builder.texture2DFunction}(u_texture, uv);');
}
return builder.build();
}
@override
void drawHairline(html.CanvasRenderingContext2D? _ctx, Float32List positions) {
assert(positions != null); // ignore: unnecessary_null_comparison
......@@ -490,11 +662,20 @@ class _GlContext {
dynamic _kStaticDraw;
dynamic _kFloat;
dynamic _kColorBufferBit;
dynamic _kTexture2D;
dynamic _kTextureWrapS;
dynamic _kTextureWrapT;
dynamic _kRepeat;
dynamic _kClampToEdge;
dynamic _kMirroredRepeat;
dynamic _kTriangles;
dynamic _kLinkStatus;
dynamic _kUnsignedByte;
dynamic _kUnsignedShort;
dynamic _kRGBA;
dynamic _kLinear;
dynamic _kTextureMinFilter;
int? _kTexture0;
Object? _canvas;
int? _widthInPixels;
......@@ -531,7 +712,7 @@ class _GlContext {
left, top, _widthInPixels, _heightInPixels]);
}
_GlProgram useAndCacheProgram(
_GlProgram cacheProgram(
String vertexShaderSource, String fragmentShaderSource) {
String cacheKey = '$vertexShaderSource||$fragmentShaderSource';
_GlProgram? cachedProgram = _programCache[cacheKey];
......@@ -547,7 +728,6 @@ class _GlContext {
linkProgram(program);
cachedProgram = _GlProgram(program);
_programCache[cacheKey] = cachedProgram;
useProgram(program);
}
return cachedProgram;
}
......@@ -582,8 +762,8 @@ class _GlContext {
}
}
void useProgram(Object? program) {
js_util.callMethod(glContext, 'useProgram', <dynamic>[program]);
void useProgram(_GlProgram program) {
js_util.callMethod(glContext, 'useProgram', <dynamic>[program.program]);
}
Object? createBuffer() =>
......@@ -593,10 +773,55 @@ class _GlContext {
js_util.callMethod(glContext, 'bindBuffer', <dynamic>[kArrayBuffer, buffer]);
}
Object? createVertexArray() =>
js_util.callMethod(glContext, 'createVertexArray', const <dynamic>[]);
void bindVertexArray(Object vertexObjectArray) {
js_util.callMethod(glContext, 'bindVertexArray',
<dynamic>[vertexObjectArray]);
}
void unbindVertexArray() {
js_util.callMethod(glContext, 'bindVertexArray',
<dynamic>[null]);
}
void bindElementArrayBuffer(Object? buffer) {
js_util.callMethod(glContext, 'bindBuffer', <dynamic>[kElementArrayBuffer, buffer]);
}
Object? createTexture() =>
js_util.callMethod(glContext, 'createTexture', const <dynamic>[]);
void generateMipmap(dynamic target) =>
js_util.callMethod(glContext, 'generateMipmap', <dynamic>[target]);
void bindTexture(dynamic target, Object? buffer) {
js_util.callMethod(glContext, 'bindTexture', <dynamic>[target, buffer]);
}
void activeTexture(int textureUnit) {
js_util.callMethod(glContext, 'activeTexture', <dynamic>[textureUnit]);
}
void texImage2D(dynamic target, int level, dynamic internalFormat,
dynamic format, dynamic dataType,
dynamic pixels, {int? width, int? height, int border = 0}) {
if (width == null) {
js_util.callMethod(glContext, 'texImage2D', <dynamic>[
target, level, internalFormat, format, dataType, pixels]);
} else {
js_util.callMethod(glContext, 'texImage2D', <dynamic>[
target, level, internalFormat, width, height, border, format, dataType,
pixels]);
}
}
void texParameteri(dynamic target, dynamic parameterName, dynamic value) {
js_util.callMethod(glContext, 'texParameteri', <dynamic>[
target, parameterName, value]);
}
void deleteBuffer(Object buffer) {
js_util.callMethod(glContext, 'deleteBuffer', <dynamic>[buffer]);
}
......@@ -609,7 +834,7 @@ class _GlContext {
js_util.callMethod(glContext, 'bufferData', <dynamic>[kElementArrayBuffer, data, type]);
}
void enableVertexAttribArray(int index) {
void enableVertexAttribArray(dynamic index) {
js_util.callMethod(glContext, 'enableVertexAttribArray', <dynamic>[index]);
}
......@@ -706,6 +931,34 @@ class _GlContext {
dynamic get kColorBufferBit =>
_kColorBufferBit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT');
dynamic get kTexture2D =>
_kTexture2D ??= js_util.getProperty(glContext, 'TEXTURE_2D');
int get kTexture0 =>
_kTexture0 ??= js_util.getProperty(glContext, 'TEXTURE0') as int;
dynamic get kTextureWrapS =>
_kTextureWrapS ??= js_util.getProperty(glContext, 'TEXTURE_WRAP_S');
dynamic get kTextureWrapT =>
_kTextureWrapT ??= js_util.getProperty(glContext, 'TEXTURE_WRAP_T');
dynamic get kRepeat =>
_kRepeat ??= js_util.getProperty(glContext, 'REPEAT');
dynamic get kClampToEdge =>
_kClampToEdge ??= js_util.getProperty(glContext, 'CLAMP_TO_EDGE');
dynamic get kMirroredRepeat =>
_kMirroredRepeat ??= js_util.getProperty(glContext, 'MIRRORED_REPEAT');
dynamic get kLinear =>
_kLinear ??= js_util.getProperty(glContext, 'LINEAR');
dynamic get kTextureMinFilter =>
_kTextureMinFilter ??= js_util.getProperty(glContext,
'TEXTURE_MIN_FILTER');
/// Returns reference to uniform in program.
Object getUniformLocation(Object program, String uniformName) {
Object? res = js_util
......
......@@ -51,9 +51,10 @@ class GradientSweep extends EngineGradient {
NormalizedGradient normalizedGradient =
NormalizedGradient(colors, stops: colorStops);
_GlProgram glProgram = gl.useAndCacheProgram(
_GlProgram glProgram = gl.cacheProgram(
_WebGlRenderer.writeBaseVertexShader(),
_createSweepFragmentShader(normalizedGradient, tileMode));
gl.useProgram(glProgram);
Object tileOffset =
gl.getUniformLocation(glProgram.program, 'u_tile_offset');
......@@ -134,6 +135,18 @@ class GradientSweep extends EngineGradient {
final Float32List? matrix4;
}
class ImageShader implements ui.ImageShader {
ImageShader(ui.Image image, this.tileModeX, this.tileModeY, this.matrix4, this.filterQuality)
: _image = image as HtmlImage;
final ui.TileMode tileModeX;
final ui.TileMode tileModeY;
final Float64List matrix4;
final ui.FilterQuality? filterQuality;
final HtmlImage _image;
}
class GradientLinear extends EngineGradient {
GradientLinear(
this.from,
......@@ -221,9 +234,10 @@ class GradientLinear extends EngineGradient {
NormalizedGradient normalizedGradient =
NormalizedGradient(colors, stops: colorStops);
_GlProgram glProgram = gl.useAndCacheProgram(
_GlProgram glProgram = gl.cacheProgram(
_WebGlRenderer.writeBaseVertexShader(),
_createLinearFragmentShader(normalizedGradient, tileMode));
gl.useProgram(glProgram);
// Setup from/to uniforms.
//
......@@ -491,10 +505,11 @@ class GradientRadial extends EngineGradient {
NormalizedGradient normalizedGradient =
NormalizedGradient(colors, stops: colorStops);
_GlProgram glProgram = gl.useAndCacheProgram(
_GlProgram glProgram = gl.cacheProgram(
_WebGlRenderer.writeBaseVertexShader(),
_createRadialFragmentShader(
normalizedGradient, shaderBounds, tileMode));
gl.useProgram(glProgram);
Object tileOffset =
gl.getUniformLocation(glProgram.program, 'u_tile_offset');
......
......@@ -229,6 +229,8 @@ class ShaderBuilder {
String _precisionToString(int precision) => precision == ShaderPrecision.kLow
? 'lowp'
: precision == ShaderPrecision.kMedium ? 'mediump' : 'highp';
String get texture2DFunction => isWebGl2 ? 'texture' : 'texture2D';
}
class ShaderMethod {
......@@ -256,6 +258,36 @@ class ShaderMethod {
}
}
/// Adds statements to compute tiling in 0..1 coordinate space.
///
/// For clamp we simply assign source value to destination.
///
/// For repeat, we use fractional part of source value.
/// float destination = fract(source);
///
/// For mirror, we repeat every 2 units, by scaling and measuring distance
/// from floor.
/// float destination = 1.0 - source;
/// destination = abs((destination - 2.0 * floor(destination * 0.5)) - 1.0);
void addTileStatements(String source, String destination,
ui.TileMode tileMode) {
switch(tileMode) {
case ui.TileMode.repeated:
addStatement('float $destination = fract($source);');
break;
case ui.TileMode.mirror:
addStatement('float $destination = ($source - 1.0);');
addStatement(
'$destination = '
'abs(($destination - 2.0 * floor($destination * 0.5)) - 1.0);');
break;
case ui.TileMode.clamp:
case ui.TileMode.decal:
addStatement('float $destination = $source;');
break;
}
}
void write(StringBuffer buffer) {
buffer.writeln('$returnType $name() {');
for (String statement in _statements) {
......
......@@ -693,12 +693,9 @@ class Shadow {
class ImageShader extends Shader {
factory ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4, {
FilterQuality? filterQuality,
}) {
if (engine.useCanvasKit) {
return engine.CkImageShader(image, tmx, tmy, matrix4, filterQuality);
}
throw UnsupportedError('ImageShader not implemented for web platform.');
}
}) => engine.useCanvasKit
? engine.CkImageShader(image, tmx, tmy, matrix4, filterQuality)
: engine.ImageShader(image, tmx, tmy, matrix4, filterQuality);
}
class ImmutableBuffer {
......
......@@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:html' as html;
import 'dart:typed_data';
import 'dart:js_util' as js_util;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/ui.dart' hide TextStyle;
import 'package:ui/ui.dart' hide TextStyle, ImageShader;
import 'package:ui/src/engine.dart';
import 'package:web_engine_tester/golden_tester.dart';
......@@ -24,9 +26,10 @@ void testMain() async {
// 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, 500),
bool write = false}) async {
final EngineCanvas engineCanvas = BitmapCanvas(screenRect,
RenderStrategy());
double maxDiffRatePercent = 0.0,
bool write = false}) async {
final EngineCanvas engineCanvas =
BitmapCanvas(screenRect, RenderStrategy());
rc.endRecording();
rc.apply(engineCanvas, screenRect);
......@@ -39,7 +42,7 @@ void testMain() async {
'$fileName.png',
region: region,
write: write,
maxDiffRatePercent: 0.0,
maxDiffRatePercent: maxDiffRatePercent,
);
} finally {
// The page is reused across tests, so remove the element after taking the
......@@ -56,179 +59,353 @@ void testMain() async {
await webOnlyFontCollection.ensureFontsLoaded();
});
Future<void> _testVertices(String fileName, Vertices vertices,
BlendMode blendMode,
Paint paint, {bool write: false}) async {
Future<void> _testVertices(
String fileName, Vertices vertices, BlendMode blendMode, Paint paint,
{bool write: false}) async {
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
rc.drawVertices(vertices as SurfaceVertices, blendMode,
paint as SurfacePaint);
rc.drawVertices(
vertices as SurfaceVertices, blendMode, paint as SurfacePaint);
await _checkScreenshot(rc, fileName, write: write);
}
test('Should draw green hairline triangles when colors array is null.',
() async {
final Vertices vertices = Vertices.raw(VertexMode.triangles,
() async {
final Vertices vertices = Vertices.raw(
VertexMode.triangles,
Float32List.fromList([
20.0, 20.0, 220.0, 10.0, 110.0, 220.0,
220.0, 320.0, 20.0, 310.0, 200.0, 420.0
20.0,
20.0,
220.0,
10.0,
110.0,
220.0,
220.0,
320.0,
20.0,
310.0,
200.0,
420.0
]));
await _testVertices(
'draw_vertices_hairline_triangle',
vertices,
BlendMode.srcOver,
Paint()..color = Color.fromARGB(255, 0, 128, 0));
await _testVertices('draw_vertices_hairline_triangle', vertices,
BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0));
});
test('Should draw black hairline triangles when colors array is null'
' and Paint() has no color.',
() async {
test(
'Should draw black hairline triangles when colors array is null'
' and Paint() has no color.', () async {
// ignore: unused_local_variable
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF]);
final Vertices vertices = Vertices.raw(VertexMode.triangles,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF
]);
final Vertices vertices = Vertices.raw(
VertexMode.triangles,
Float32List.fromList([
20.0, 20.0, 220.0, 10.0, 110.0, 220.0,
220.0, 320.0, 20.0, 310.0, 200.0, 420.0
20.0,
20.0,
220.0,
10.0,
110.0,
220.0,
220.0,
320.0,
20.0,
310.0,
200.0,
420.0
]));
await _testVertices(
'draw_vertices_hairline_triangle_black',
vertices,
BlendMode.srcOver,
Paint());
await _testVertices('draw_vertices_hairline_triangle_black', vertices,
BlendMode.srcOver, Paint());
});
/// Regression test for https://github.com/flutter/flutter/issues/71442.
test('Should draw filled triangles when colors array is null'
' and Paint() has color.',
() async {
// ignore: unused_local_variable
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF]);
final Vertices vertices = Vertices.raw(VertexMode.triangles,
Float32List.fromList([
20.0, 20.0, 220.0, 10.0, 110.0, 220.0,
220.0, 320.0, 20.0, 310.0, 200.0, 420.0
]));
await _testVertices(
'draw_vertices_triangle_green_filled',
vertices,
BlendMode.srcOver,
Paint()
..style = PaintingStyle.fill
..color = const Color(0xFF00FF00)
);
});
test('Should draw hairline triangleFan.',
() async {
final Vertices vertices = Vertices.raw(VertexMode.triangleFan,
test(
'Should draw filled triangles when colors array is null'
' and Paint() has color.', () async {
// ignore: unused_local_variable
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF
]);
final Vertices vertices = Vertices.raw(
VertexMode.triangles,
Float32List.fromList([
150.0, 150.0, 20.0, 10.0, 80.0, 20.0,
220.0, 15.0, 280.0, 30.0, 300.0, 420.0
20.0,
20.0,
220.0,
10.0,
110.0,
220.0,
220.0,
320.0,
20.0,
310.0,
200.0,
420.0
]));
await _testVertices(
'draw_vertices_hairline_triangle_fan',
'draw_vertices_triangle_green_filled',
vertices,
BlendMode.srcOver,
Paint()..color = Color.fromARGB(255, 0, 128, 0));
Paint()
..style = PaintingStyle.fill
..color = const Color(0xFF00FF00));
});
test('Should draw hairline triangleStrip.',
() async {
final Vertices vertices = Vertices.raw(VertexMode.triangleStrip,
test('Should draw hairline triangleFan.', () async {
final Vertices vertices = Vertices.raw(
VertexMode.triangleFan,
Float32List.fromList([
20.0, 20.0, 220.0, 10.0, 110.0, 220.0,
220.0, 320.0, 20.0, 310.0, 200.0, 420.0
150.0,
150.0,
20.0,
10.0,
80.0,
20.0,
220.0,
15.0,
280.0,
30.0,
300.0,
420.0
]));
await _testVertices(
'draw_vertices_hairline_triangle_strip',
vertices,
BlendMode.srcOver,
Paint()..color = Color.fromARGB(255, 0, 128, 0));
await _testVertices('draw_vertices_hairline_triangle_fan', vertices,
BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0));
});
test('Should draw triangles with colors.',
() async {
test('Should draw hairline triangleStrip.', () async {
final Vertices vertices = Vertices.raw(
VertexMode.triangleStrip,
Float32List.fromList([
20.0,
20.0,
220.0,
10.0,
110.0,
220.0,
220.0,
320.0,
20.0,
310.0,
200.0,
420.0
]));
await _testVertices('draw_vertices_hairline_triangle_strip', vertices,
BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0));
});
test('Should draw triangles with colors.', () async {
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF]);
final Vertices vertices = Vertices.raw(VertexMode.triangles,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF
]);
final Vertices vertices = Vertices.raw(
VertexMode.triangles,
Float32List.fromList([
150.0, 150.0, 20.0, 10.0, 80.0, 20.0,
220.0, 15.0, 280.0, 30.0, 300.0, 420.0
]), colors: colors);
150.0,
150.0,
20.0,
10.0,
80.0,
20.0,
220.0,
15.0,
280.0,
30.0,
300.0,
420.0
]),
colors: colors);
await _testVertices(
'draw_vertices_triangles',
vertices,
BlendMode.srcOver,
await _testVertices('draw_vertices_triangles', vertices, BlendMode.srcOver,
Paint()..color = Color.fromARGB(255, 0, 128, 0));
});
test('Should draw triangles with colors and indices.', () async {
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF0000FF]);
final Uint16List indices = Uint16List.fromList(<int>[
0, 1, 2, 3, 4, 0
]);
final Int32List colors = Int32List.fromList(
<int>[0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFF0000, 0xFF0000FF]);
final Uint16List indices = Uint16List.fromList(<int>[0, 1, 2, 3, 4, 0]);
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
final Vertices vertices = Vertices.raw(VertexMode.triangles,
final Vertices vertices = Vertices.raw(
VertexMode.triangles,
Float32List.fromList([
210.0, 150.0, 30.0, 110.0, 80.0, 30.0,
220.0, 15.0, 280.0, 30.0,
]), colors: colors,
210.0,
150.0,
30.0,
110.0,
80.0,
30.0,
220.0,
15.0,
280.0,
30.0,
]),
colors: colors,
indices: indices);
rc.drawVertices(vertices as SurfaceVertices, BlendMode.srcOver,
SurfacePaint());
rc.drawVertices(
vertices as SurfaceVertices, BlendMode.srcOver, SurfacePaint());
await _checkScreenshot(rc, 'draw_vertices_triangles_indexed');
});
test('Should draw triangleFan with colors.',
() async {
test('Should draw triangleFan with colors.', () async {
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF]);
final Vertices vertices = Vertices.raw(VertexMode.triangleFan,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF
]);
final Vertices vertices = Vertices.raw(
VertexMode.triangleFan,
Float32List.fromList([
150.0, 150.0, 20.0, 10.0, 80.0, 20.0,
220.0, 15.0, 280.0, 30.0, 300.0, 420.0
]), colors: colors);
150.0,
150.0,
20.0,
10.0,
80.0,
20.0,
220.0,
15.0,
280.0,
30.0,
300.0,
420.0
]),
colors: colors);
await _testVertices(
'draw_vertices_triangle_fan',
vertices,
BlendMode.srcOver,
Paint()..color = Color.fromARGB(255, 0, 128, 0));
await _testVertices('draw_vertices_triangle_fan', vertices,
BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0));
});
test('Should draw triangleStrip with colors.',
() async {
test('Should draw triangleStrip with colors.', () async {
final Int32List colors = Int32List.fromList(<int>[
0xFFFF0000, 0xFF00FF00, 0xFF0000FF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF]);
final Vertices vertices = Vertices.raw(VertexMode.triangleStrip,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF,
0xFFFF0000,
0xFF00FF00,
0xFF0000FF
]);
final Vertices vertices = Vertices.raw(
VertexMode.triangleStrip,
Float32List.fromList([
20.0, 20.0, 220.0, 10.0, 110.0, 220.0,
220.0, 320.0, 20.0, 310.0, 200.0, 420.0
]), colors: colors);
await _testVertices(
'draw_vertices_triangle_strip',
vertices,
BlendMode.srcOver,
Paint()..color = Color.fromARGB(255, 0, 128, 0));
20.0,
20.0,
220.0,
10.0,
110.0,
220.0,
220.0,
320.0,
20.0,
310.0,
200.0,
420.0
]),
colors: colors);
await _testVertices('draw_vertices_triangle_strip', vertices,
BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0));
});
Future<void> testTexture(TileMode tileMode, String filename) async {
final Uint16List indices = Uint16List.fromList(<int>[0, 1, 2, 3, 4, 0]);
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
final Vertices vertices = Vertices.raw(
VertexMode.triangles,
Float32List.fromList([
210.0,
150.0,
0.0,
0.0,
80.0,
30.0,
220.0,
15.0,
280.0,
30.0,
]),
indices: indices);
Float32List matrix4 = Matrix4.identity().storage;
final HtmlImage img = await createTestImage();
final SurfacePaint paint = SurfacePaint();
final ImageShader imgShader = ImageShader(img, tileMode, tileMode,
Float64List.fromList(matrix4), FilterQuality.high);
paint.shader = imgShader;
rc.drawVertices(vertices as SurfaceVertices, BlendMode.srcOver, paint);
await _checkScreenshot(rc, filename, maxDiffRatePercent: 1.0);
}
test('Should draw triangle with texture and indices', () async {
await testTexture(TileMode.clamp, 'draw_vertices_texture');
});
test('Should draw triangle with texture and indices', () async {
await testTexture(TileMode.mirror, 'draw_vertices_texture_mirror');
});
test('Should draw triangle with texture and indices', () async {
await testTexture(TileMode.repeated, 'draw_vertices_texture_repeated');
});
}
Future<HtmlImage> createTestImage({int width = 50, int height = 40}) {
html.CanvasElement canvas =
new html.CanvasElement(width: width, height: height);
html.CanvasRenderingContext2D ctx = canvas.context2D;
ctx.fillStyle = '#E04040';
ctx.fillRect(0, 0, width / 3, height);
ctx.fill();
ctx.fillStyle = '#40E080';
ctx.fillRect(width / 3, 0, width / 3, height);
ctx.fill();
ctx.fillStyle = '#2040E0';
ctx.fillRect(2 * width / 3, 0, width / 3, height);
ctx.fill();
html.ImageElement imageElement = html.ImageElement();
Completer<HtmlImage> completer = Completer();
imageElement.onLoad.listen((event) {
completer.complete(HtmlImage(imageElement, width, height));
});
imageElement.src = js_util.callMethod(canvas, 'toDataURL', <dynamic>[]);
return completer.future;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册