Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
115a44d
[web] Implement drawVertices for BlendMode.src and hairline rendering
ferhatb Oct 4, 2019
c754ee6
add vertex mode
ferhatb Oct 4, 2019
10e056e
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 7, 2019
55c0794
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 7, 2019
28a645f
add test
ferhatb Oct 7, 2019
39983c9
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 9, 2019
fad22cb
add vertex mode to GLContext
ferhatb Oct 9, 2019
ede8610
Move viewport transformation into shader
ferhatb Oct 10, 2019
d3aa143
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 10, 2019
b8b06ee
Update golden revision
ferhatb Oct 10, 2019
1f86ec9
Add comments on shaders
ferhatb Oct 10, 2019
832679e
add webgl1 support for webkit
ferhatb Oct 11, 2019
e374430
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 11, 2019
d00f608
dartfmt
ferhatb Oct 11, 2019
3a2f80f
Update tests and fix hairline rendering
ferhatb Oct 11, 2019
95ce6c2
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 11, 2019
9349a0e
Added TODO for transform/drawImage
ferhatb Oct 11, 2019
d2ec77c
Implement maxDiffRate parameter for screenshot tests
ferhatb Oct 14, 2019
0cbb64b
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb Oct 14, 2019
98a3eea
Update golden_tester with maxDiffRate parameter
ferhatb Oct 14, 2019
f4d0ab1
Rename maxRate
ferhatb Oct 14, 2019
41525cb
Add assert for positions.length
ferhatb Oct 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/web_ui/dev/goldens_lock.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
repository: https://github.com/flutter/goldens.git
revision: 7efcec3e8b0bbb6748a992b23a0a89300aa323c7
revision: 686dd320f6cce6da9a7a43e3ec9c0147f39eb19d
358 changes: 356 additions & 2 deletions lib/web_ui/lib/src/engine/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,201 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking {
picture.recordingCanvas.apply(this);
}

// Vertex shader transforms pixel space [Vertices.positions] to
// final clipSpace -1..1 coordinates with inverted Y Axis.
static const _vertexShaderTriangle = '''
#version 300 es
layout (location=0) in vec4 position;
layout (location=1) in vec4 color;
uniform vec4 u_scale;
uniform vec4 u_shift;

out vec4 vColor;
void main() {
gl_Position = (position * u_scale) + u_shift;
vColor = color;
Comment thread
ferhatb marked this conversation as resolved.
Outdated
}''';
// This fragment shader enables Int32List of colors to be passed directly
// to gl context buffer for rendering by decoding RGBA8888.
static const _fragmentShaderTriangle = '''
#version 300 es
Comment thread
ferhatb marked this conversation as resolved.
precision highp float;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vec4(vColor[2], vColor[1], vColor[0], vColor[3]);
}''';

/// Draws vertices on a gl context.
///
/// If both colors and textures is specified in paint data,
/// for [BlendMode.source] we skip colors and use textures,
/// for [BlendMode.dst] we only use colors and ignore textures.
/// We also skip paint shader when no texture is specified.
///
/// If no colors or textures are specified, stroke hairlines with
/// [Paint.color].
///
/// If colors is specified, convert colors to premultiplied (alpha) colors
/// and use a SkTriColorShader to render.
@override
void drawVertices(
Comment thread
ferhatb marked this conversation as resolved.
ui.Vertices vertices, ui.BlendMode blendMode, ui.PaintData paint) {
// TODO(flutter_web): Implement shaders for [Paint.shader] and
// blendMode. https://github.com/flutter/flutter/issues/40096
assert(paint.shader == null,
'Linear/Radial/SweepGradient and ImageShader not supported yet');
assert(blendMode == ui.BlendMode.srcOver);
final Int32List colors = vertices.colors;
final ui.VertexMode mode = vertices.mode;
if (colors == null) {
final Float32List positions = mode == ui.VertexMode.triangles
? vertices.positions
: _convertVertexPositions(mode, vertices.positions);
// Draw hairline for vertices if no vertex colors are specified.
_drawHairline(positions, paint.color ?? ui.Color(0xFF000000));
return;
}

final html.CanvasElement glCanvas = html.CanvasElement(
width: _widthInBitmapPixels,
height: _heightInBitmapPixels,
);

glCanvas.style
..position = 'absolute'
..width = _canvas.style.width
..height = _canvas.style.height;
glCanvas.className = 'gl-canvas';

_children.add(glCanvas);
rootElement.append(glCanvas);
Comment thread
ferhatb marked this conversation as resolved.

_GlContext gl = _GlContext(glCanvas);

// Create and compile shaders.
Object vertexShader =
gl.compileShader('VERTEX_SHADER', _vertexShaderTriangle);
Object fragmentShader =
gl.compileShader('FRAGMENT_SHADER', _fragmentShaderTriangle);
// Create a gl program and link shaders.
Object program = gl.createProgram();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shader compilation per draw call is going to be really really expensive. Can these programs be cached?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes very expensive. Next step is to implement blendMode which will move this to a web OffscreenCanvas and get image data instead of creating a canvas. Once it is decoupled we can cache/share program and even warm up.

gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

// Set uniform to scale 0..width/height pixels coordinates to -1..1
// clipspace range and flip the Y axis.
Object resolution = gl.getUniformLocation(program, 'u_scale');
gl.setUniform4f(resolution, 2.0 / _widthInBitmapPixels.toDouble(),
-2.0 / _heightInBitmapPixels.toDouble(), 1, 1);
Object shift = gl.getUniformLocation(program, 'u_shift');
gl.setUniform4f(shift, -1, 1, 0, 0);

// Setup geometry.
Object positionsBuffer = gl.createBuffer();
assert(positionsBuffer != null);
gl.bindArrayBuffer(positionsBuffer);
final Float32List positions = vertices.positions;
gl.bufferData(positions, gl.kStaticDraw);
js_util.callMethod(
gl.glContext, 'vertexAttribPointer', [0, 2, gl.kFloat, false, 0, 0]);
gl.enableVertexAttribArray(0);

// Setup color buffer.
Object colorsBuffer = gl.createBuffer();
gl.bindArrayBuffer(colorsBuffer);
// Buffer kBGRA_8888.
gl.bufferData(colors, gl.kStaticDraw);

js_util.callMethod(gl.glContext, 'vertexAttribPointer',
[1, 4, gl.kUnsignedByte, true, 0, 0]);
gl.enableVertexAttribArray(1);
gl.clear();
final int vertexCount = positions.length ~/ 2;
gl.drawTriangles(vertexCount, mode);
}

void _drawHairline(Float32List positions, ui.Color color) {
assert(positions != null);
html.CanvasRenderingContext2D _ctx = ctx;
save();
final int pointCount = positions.length ~/ 2;
_setFillAndStrokeStyle('', color.toCssString());
_ctx.lineWidth = 1.0;
int triIndex = 0;
_ctx.beginPath();
for (int i = 0, len = pointCount * 2; i < len; i += 2) {
final double dx = positions[i];
final double dy = positions[i + 1];
if (triIndex & 3 == 0) {
Comment thread
ferhatb marked this conversation as resolved.
Outdated
Comment thread
ferhatb marked this conversation as resolved.
Outdated
_ctx.moveTo(dx, dy);
} else {
_ctx.lineTo(dx, dy);
}
if (triIndex & 3 == 2) {
_ctx.closePath();
_ctx.stroke();
triIndex = 0;
} else {
triIndex++;
}
}
restore();
}

// Converts from [VertexMode] triangleFan and triangleStrip to triangles.
Float32List _convertVertexPositions(
ui.VertexMode mode, Float32List positions) {
assert(mode != ui.VertexMode.triangles);
if (mode == ui.VertexMode.triangleFan) {
final int coordinateCount = positions.length ~/ 2;
final int triangleCount = coordinateCount - 2;
final Float32List triangleList = Float32List(triangleCount * 3 * 2);
double centerX = positions[0];
double centerY = positions[1];
int destIndex = 0;
int positionIndex = 2;
for (int triangleIndex = 0;
triangleIndex < triangleCount;
triangleIndex++, positionIndex += 2) {
triangleList[destIndex++] = centerX;
triangleList[destIndex++] = centerY;
triangleList[destIndex++] = positions[positionIndex];
triangleList[destIndex++] = positions[positionIndex + 1];
triangleList[destIndex++] = positions[positionIndex + 2];
triangleList[destIndex++] = positions[positionIndex + 3];
}
return triangleList;
} else {
Comment thread
ferhatb marked this conversation as resolved.
// Set of connected triangles. Each triangle shares 2 last vertices.
final int vertexCount = positions.length ~/ 2;
int triangleCount = vertexCount - 2;
double x0 = positions[0];
double y0 = positions[1];
double x1 = positions[2];
double y1 = positions[3];
final Float32List triangleList = Float32List(triangleCount * 3 * 2);
int destIndex = 0;
for (int i = 0, positionIndex = 4; i < triangleCount; i++) {
final double x2 = positions[positionIndex++];
final double y2 = positions[positionIndex++];
triangleList[destIndex++] = x0;
triangleList[destIndex++] = y0;
triangleList[destIndex++] = x1;
triangleList[destIndex++] = y1;
triangleList[destIndex++] = x2;
triangleList[destIndex++] = y2;
x0 = x1;
y0 = y1;
x1 = x2;
y1 = y2;
}
return triangleList;
}
}

/// 'Runs' the given [path] by applying all of its commands to the canvas.
void _runPath(ui.Path path) {
ctx.beginPath();
Expand Down Expand Up @@ -700,8 +895,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking {
break;
case PathCommandTypes.rRect:
final RRectCommand rrectCommand = command;
_RRectToCanvasRenderer(ctx).render(rrectCommand.rrect,
startNewPath: false);
_RRectToCanvasRenderer(ctx)
.render(rrectCommand.rrect, startNewPath: false);
break;
case PathCommandTypes.rect:
final RectCommand rectCommand = command;
Expand Down Expand Up @@ -902,3 +1097,162 @@ String _cssTransformAtOffset(
return matrix4ToCssTransform(
transformWithOffset(transform, ui.Offset(offsetX, offsetY)));
}

/// JS Interop helper for webgl apis.
class _GlContext {
Comment thread
ds84182 marked this conversation as resolved.
final Object glContext;
dynamic _kCompileStatus;
dynamic _kArrayBuffer;
dynamic _kStaticDraw;
dynamic _kFloat;
dynamic _kColorBufferBit;
dynamic _kTriangles;
dynamic _kLinkStatus;
dynamic _kUnsignedByte;

_GlContext(html.CanvasElement canvas)
: glContext = canvas.getContext('webgl2');

Object compileShader(String shaderType, String source) {
Object shader = _createShader(shaderType);
js_util.callMethod(glContext, 'shaderSource', [shader, source]);
js_util.callMethod(glContext, 'compileShader', [shader]);
bool shaderStatus = js_util
.callMethod(glContext, 'getShaderParameter', [shader, compileStatus]);
if (!shaderStatus) {
throw Exception('Shader compilation failed: ${getShaderInfoLog(shader)}');
}
return shader;
}

Object createProgram() =>
js_util.callMethod(glContext, 'createProgram', const []);

void attachShader(Object program, Object shader) {
js_util.callMethod(glContext, 'attachShader', [program, shader]);
}

void linkProgram(Object program) {
js_util.callMethod(glContext, 'linkProgram', [program]);
if (!js_util
.callMethod(glContext, 'getProgramParameter', [program, kLinkStatus])) {
throw Exception(getProgramInfoLog(program));
}
}

void useProgram(Object program) {
js_util.callMethod(glContext, 'useProgram', [program]);
}

Object createBuffer() =>
js_util.callMethod(glContext, 'createBuffer', const []);

void bindArrayBuffer(Object buffer) {
js_util.callMethod(glContext, 'bindBuffer', [kArrayBuffer, buffer]);
}

void bufferData(TypedData data, dynamic type) {
js_util.callMethod(glContext, 'bufferData', [kArrayBuffer, data, type]);
}

void enableVertexAttribArray(int index) {
js_util.callMethod(glContext, 'enableVertexAttribArray', [index]);
}

/// Clear background.
void clear() {
js_util.callMethod(glContext, 'clear', [kColorBufferBit]);
}

void drawTriangles(int triangleCount, ui.VertexMode vertexMode) {
dynamic mode = _triangleTypeFromMode(vertexMode);
js_util.callMethod(glContext, 'drawArrays', [mode, 0, triangleCount]);
}

/// 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', [x, y, width, height]);
}

dynamic _triangleTypeFromMode(ui.VertexMode mode) {
switch (mode) {
case ui.VertexMode.triangles:
return kTriangles;
break;
case ui.VertexMode.triangleFan:
return kTriangleFan;
break;
case ui.VertexMode.triangleStrip:
return kTriangleStrip;
break;
}
}

Object _createShader(String shaderType) => js_util.callMethod(
glContext, 'createShader', [js_util.getProperty(glContext, shaderType)]);

/// Error state of gl context.
dynamic get error => js_util.callMethod(glContext, 'getError', const []);

/// Shader compiler error, if this returns [kFalse], to get details use
/// [getShaderInfoLog].
dynamic get compileStatus =>
_kCompileStatus ??= js_util.getProperty(glContext, 'COMPILE_STATUS');

dynamic get kArrayBuffer =>
_kArrayBuffer ??= js_util.getProperty(glContext, 'ARRAY_BUFFER');

dynamic get kLinkStatus =>
_kLinkStatus ??= js_util.getProperty(glContext, 'LINK_STATUS');

dynamic get kFloat => _kFloat ??= js_util.getProperty(glContext, 'FLOAT');

dynamic get kUnsignedByte =>
_kUnsignedByte ??= js_util.getProperty(glContext, 'UNSIGNED_BYTE');

dynamic get kStaticDraw =>
_kStaticDraw ??= js_util.getProperty(glContext, 'STATIC_DRAW');

dynamic get kTriangles =>
_kTriangles ??= js_util.getProperty(glContext, 'TRIANGLES');

dynamic get kTriangleFan =>
_kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_FAN');

dynamic get kTriangleStrip =>
_kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_STRIP');

dynamic get kColorBufferBit =>
_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', [program, uniformName]);
}

/// Sets vec2 uniform values.
void setUniform2f(Object uniform, double value1, double value2) {
return js_util
.callMethod(glContext, 'uniform2f', [uniform, value1, value2]);
}

/// Sets vec4 uniform values.
void setUniform4f(Object uniform, double value1, double value2, double value3,
double value4) {
return js_util.callMethod(
glContext, 'uniform4f', [uniform, value1, value2, value3, value4]);
}

/// Shader compile error log.
dynamic getShaderInfoLog(Object glShader) {
return js_util.callMethod(glContext, 'getShaderInfoLog', [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', [glProgram]);
}
}
Loading