Reuse the same temporary canvas for creating shader images (flutter/engine#32864)

This commit is contained in:
Jackson Gardner 2022-04-27 14:14:01 -07:00 committed by GitHub
parent 264868b89b
commit af7fb1d68b
3 changed files with 149 additions and 70 deletions

View File

@ -22,6 +22,27 @@ import 'vertex_shaders.dart';
const double kFltEpsilon = 1.19209290E-07; // == 1 / (2 ^ 23)
const double kFltEpsilonSquared = 1.19209290E-07 * 1.19209290E-07;
class SharedCanvas {
OffScreenCanvas? _canvas;
bool _checkedOut = false;
GlContext checkOutContext(int width, int height) {
assert(!_checkedOut);
_checkedOut = true;
if(_canvas == null) {
_canvas = OffScreenCanvas(width, height);
} else {
_canvas!.resize(width, height);
}
return GlContext(_canvas!);
}
void checkInContext() {
assert(_checkedOut);
_checkedOut = false;
}
}
SharedCanvas _sharedCanvas = SharedCanvas();
abstract class EngineGradient implements ui.Gradient {
/// Hidden constructor to prevent subclassing.
EngineGradient._();
@ -58,9 +79,7 @@ class GradientSweep extends EngineGradient {
initWebGl();
// Render gradient into a bitmap and create a canvas pattern.
final OffScreenCanvas offScreenCanvas =
OffScreenCanvas(widthInPixels, heightInPixels);
final GlContext gl = GlContext(offScreenCanvas);
final GlContext gl = _sharedCanvas.checkOutContext(widthInPixels, heightInPixels);
gl.setViewportSize(widthInPixels, heightInPixels);
final NormalizedGradient normalizedGradient =
@ -82,23 +101,27 @@ class GradientSweep extends EngineGradient {
final Object gradientMatrix =
gl.getUniformLocation(glProgram.program, 'm_gradient');
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage);
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels);
} else {
return glRenderer!.drawRect(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels)!;
}
final Object result = () {
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels);
} else {
return glRenderer!.drawRect(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels)!;
}
}();
_sharedCanvas.checkInContext();
return result;
}
@override
@ -222,9 +245,7 @@ class GradientLinear extends EngineGradient {
assert(widthInPixels > 0 && heightInPixels > 0);
initWebGl();
// Render gradient into a bitmap and create a canvas pattern.
final OffScreenCanvas offScreenCanvas =
OffScreenCanvas(widthInPixels, heightInPixels);
final GlContext gl = GlContext(offScreenCanvas);
final GlContext gl = _sharedCanvas.checkOutContext(widthInPixels, heightInPixels);
gl.setViewportSize(widthInPixels, heightInPixels);
final NormalizedGradient normalizedGradient =
@ -314,27 +335,31 @@ class GradientLinear extends EngineGradient {
final Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution');
gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble());
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width,
shaderBounds.height) /* !! shaderBounds */,
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels,
);
} else {
return glRenderer!.drawRect(
ui.Rect.fromLTWH(0, 0, shaderBounds.width,
shaderBounds.height) /* !! shaderBounds */,
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels,
)!;
}
final Object result = () {
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width,
shaderBounds.height) /* !! shaderBounds */,
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels,
);
} else {
return glRenderer!.drawRect(
ui.Rect.fromLTWH(0, 0, shaderBounds.width,
shaderBounds.height) /* !! shaderBounds */,
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels,
)!;
}
}();
_sharedCanvas.checkInContext();
return result;
}
/// Creates a linear gradient with tiling repeat or mirror.
@ -490,9 +515,7 @@ class GradientRadial extends EngineGradient {
initWebGl();
// Render gradient into a bitmap and create a canvas pattern.
final OffScreenCanvas offScreenCanvas =
OffScreenCanvas(widthInPixels, heightInPixels);
final GlContext gl = GlContext(offScreenCanvas);
final GlContext gl = _sharedCanvas.checkOutContext(widthInPixels, heightInPixels);
gl.setViewportSize(widthInPixels, heightInPixels);
final NormalizedGradient normalizedGradient =
@ -519,23 +542,27 @@ class GradientRadial extends EngineGradient {
gl.setUniformMatrix4fv(gradientMatrix, false,
matrix4 == null ? Matrix4.identity().storage : matrix4!);
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels);
} else {
return glRenderer!.drawRect(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels)!;
}
final Object result = () {
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels);
} else {
return glRenderer!.drawRect(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
gl,
glProgram,
normalizedGradient,
widthInPixels,
heightInPixels)!;
}
}();
_sharedCanvas.checkInContext();
return result;
}
/// Creates a radial gradient with tiling repeat or mirror.

View File

@ -966,12 +966,31 @@ class OffScreenCanvas {
height: height,
);
canvasElement!.className = 'gl-canvas';
final double cssWidth = width / EnginePlatformDispatcher.browserDevicePixelRatio;
final double cssHeight = height / EnginePlatformDispatcher.browserDevicePixelRatio;
canvasElement!.style
..position = 'absolute'
..width = '${cssWidth}px'
..height = '${cssHeight}px';
_updateCanvasCssSize(canvasElement!);
}
}
void _updateCanvasCssSize(html.CanvasElement element) {
final double cssWidth = width / EnginePlatformDispatcher.browserDevicePixelRatio;
final double cssHeight = height / EnginePlatformDispatcher.browserDevicePixelRatio;
element.style
..position = 'absolute'
..width = '${cssWidth}px'
..height = '${cssHeight}px';
}
void resize(int requestedWidth, int requestedHeight) {
if(requestedWidth != width && requestedHeight != height) {
width = requestedWidth;
height = requestedHeight;
if(offScreenCanvas != null) {
offScreenCanvas!.width = requestedWidth;
offScreenCanvas!.height = requestedHeight;
} else if (canvasElement != null) {
canvasElement!.width = requestedWidth;
canvasElement!.height = requestedHeight;
_updateCanvasCssSize(canvasElement!);
}
}
}

View File

@ -5,6 +5,7 @@
import 'dart:html' as html;
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:web_gl';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
@ -390,6 +391,38 @@ Future<void> testMain() async {
canvas.apply(engineCanvas, screenRect);
});
test('Creating lots of gradients doesn\'t create too many webgl contexts',
() async {
final html.CanvasElement sideCanvas =
html.CanvasElement(width: 5, height: 5);
final RenderingContext? context =
sideCanvas.getContext('webgl') as RenderingContext?;
expect(context, isNotNull);
final EngineCanvas engineCanvas =
BitmapCanvas(const Rect.fromLTRB(0, 0, 100, 100), RenderStrategy());
for (double x = 0; x < 100; x += 10) {
for (double y = 0; y < 100; y += 10) {
const List<Color> colors = <Color>[
Color(0xFFFF0000),
Color(0xFF0000FF),
];
final GradientLinear linearGradient = GradientLinear(
const Offset(0, 0),
const Offset(10, 10),
colors,
null,
TileMode.clamp,
Matrix4.identity().storage);
engineCanvas.drawRect(Rect.fromLTWH(x, y, 10, 10),
SurfacePaintData()..shader = linearGradient);
}
}
expect(context!.isContextLost(), isFalse);
}, skip: isFirefox);
test('Paints clamped, rotated and shifted linear gradient', () async {
final RecordingCanvas canvas =
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));