This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
[web] Add basic color per vertex drawVertices API support #13066
Merged
Merged
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 c754ee6
add vertex mode
ferhatb 10e056e
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb 55c0794
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb 28a645f
add test
ferhatb 39983c9
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb fad22cb
add vertex mode to GLContext
ferhatb ede8610
Move viewport transformation into shader
ferhatb d3aa143
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb b8b06ee
Update golden revision
ferhatb 1f86ec9
Add comments on shaders
ferhatb 832679e
add webgl1 support for webkit
ferhatb e374430
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb d00f608
dartfmt
ferhatb 3a2f80f
Update tests and fix hairline rendering
ferhatb 95ce6c2
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb 9349a0e
Added TODO for transform/drawImage
ferhatb d2ec77c
Implement maxDiffRate parameter for screenshot tests
ferhatb 0cbb64b
Merge remote-tracking branch 'upstream/master' into draw_vertices2
ferhatb 98a3eea
Update golden_tester with maxDiffRate parameter
ferhatb f4d0ab1
Rename maxRate
ferhatb 41525cb
Add assert for positions.length
ferhatb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
| }'''; | ||
| // 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 | ||
|
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( | ||
|
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); | ||
|
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(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
ferhatb marked this conversation as resolved.
Outdated
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 { | ||
|
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(); | ||
|
|
@@ -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; | ||
|
|
@@ -902,3 +1097,162 @@ String _cssTransformAtOffset( | |
| return matrix4ToCssTransform( | ||
| transformWithOffset(transform, ui.Offset(offsetX, offsetY))); | ||
| } | ||
|
|
||
| /// JS Interop helper for webgl apis. | ||
| class _GlContext { | ||
|
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]); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.