Skip to content

Commit

Permalink
Improving canvas 2D performance by switching graphics rendering pipel…
Browse files Browse the repository at this point in the history
…ine.

1. Functionality to switch from the accelerated (GPU) graphics pipeline
to the recording graphics pipeline.
2. Simple heuristic to determine when the pipeline switch would increase
performance based on how the canvas was used previously.
3. Automatically switch pipeline to increase performance based on the
heuristic if the enable-canvas-2d-dynamic-pipeline-mode-switching flag is enabled.

BUG=606687, 606686, 606685
TBR=grt@chromium.org

Review-Url: https://codereview.chromium.org/2141793002
Cr-Commit-Position: refs/heads/master@{#406585}
  • Loading branch information
sebastienlc authored and Commit bot committed Jul 20, 2016
1 parent b2db88d commit 98739b0
Show file tree
Hide file tree
Showing 20 changed files with 225 additions and 2 deletions.
6 changes: 6 additions & 0 deletions chrome/app/generated_resources.grd
Original file line number Diff line number Diff line change
Expand Up @@ -5425,6 +5425,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_FLAGS_DISPLAY_LIST_2D_CANVAS_DESCRIPTION" desc="Description of the 'Enable display list 2D canvas' lab.">
Enables the use of display lists to record 2D canvas commands. This allows 2D canvas rasterization to be performed on separate thread.
</message>
<message name="IDS_FLAGS_ENABLE_2D_CANVAS_DYNAMIC_RENDERING_MODE_SWITCHING_NAME" desc="Enable canvas 2D dynamic pipeline switching mode.">
Enable 2D canvas dynamic rendering mode switching.
</message>
<message name="IDS_FLAGS_ENABLE_2D_CANVAS_DYNAMIC_RENDERING_MODE_SWITCHING_DESCRIPTION" desc="Description of 'Enable canvas 2D dynamic pipeline switching mode'">
Enables dynamic graphics rendering pipeline switching in the 2D canvas to optimize performance based on the types of draw operations being invoked.
</message>
<message name="IDS_FLAGS_EXPERIMENTAL_EXTENSION_APIS_NAME" desc="Name of the 'Experimental Extension APIs' lab.">
Experimental Extension APIs
</message>
Expand Down
4 changes: 4 additions & 0 deletions chrome/browser/about_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,10 @@ const FeatureEntry kFeatureEntries[] = {
IDS_FLAGS_DISPLAY_LIST_2D_CANVAS_DESCRIPTION, kOsAll,
ENABLE_DISABLE_VALUE_TYPE(switches::kEnableDisplayList2dCanvas,
switches::kDisableDisplayList2dCanvas)},
{"enable-canvas-2d-dynamic-rendering-mode-switching",
IDS_FLAGS_ENABLE_2D_CANVAS_DYNAMIC_RENDERING_MODE_SWITCHING_NAME,
IDS_FLAGS_ENABLE_2D_CANVAS_DYNAMIC_RENDERING_MODE_SWITCHING_DESCRIPTION, kOsAll,
SINGLE_VALUE_TYPE(switches::kEnableCanvas2dDynamicRenderingModeSwitching)},
{"composited-layer-borders", IDS_FLAGS_COMPOSITED_LAYER_BORDERS,
IDS_FLAGS_COMPOSITED_LAYER_BORDERS_DESCRIPTION, kOsAll,
SINGLE_VALUE_TYPE(cc::switches::kShowCompositedLayerBorders)},
Expand Down
1 change: 1 addition & 0 deletions chrome/browser/chromeos/login/chrome_restart_request.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ void DeriveCommandLine(const GURL& start_url,
::switches::kDisableDisplayList2dCanvas,
::switches::kEnableDisplayList2dCanvas,
::switches::kForceDisplayList2dCanvas,
::switches::kEnableCanvas2dDynamicRenderingModeSwitching,
::switches::kDisableGpuSandbox,
::switches::kEnableDistanceFieldText,
::switches::kEnableGpuAsyncWorkerContext,
Expand Down
1 change: 1 addition & 0 deletions content/browser/renderer_host/render_process_host_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
switches::kExplicitlyAllowedPorts,
switches::kForceDeviceScaleFactor,
switches::kForceDisplayList2dCanvas,
switches::kEnableCanvas2dDynamicRenderingModeSwitching,
switches::kForceOverlayFullscreenVideo,
switches::kFullMemoryCrashReport,
switches::kInertVisualViewport,
Expand Down
4 changes: 4 additions & 0 deletions content/child/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ void SetRuntimeFeaturesDefaultsAndUpdateFromArgs(
if (command_line.HasSwitch(switches::kForceDisplayList2dCanvas))
WebRuntimeFeatures::forceDisplayList2dCanvas(true);

if (command_line.HasSwitch(
switches::kEnableCanvas2dDynamicRenderingModeSwitching))
WebRuntimeFeatures::enableCanvas2dDynamicRenderingModeSwitching(true);

if (command_line.HasSwitch(switches::kEnableWebGLDraftExtensions))
WebRuntimeFeatures::enableWebGLDraftExtensions(true);

Expand Down
5 changes: 5 additions & 0 deletions content/public/common/content_switches.cc
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,11 @@ const char kEnableDisplayList2dCanvas[] = "enable-display-list-2d-canvas";
const char kForceDisplayList2dCanvas[] = "force-display-list-2d-canvas";
const char kDisableDisplayList2dCanvas[] = "disable-display-list-2d-canvas";

// Enables dynamic rendering pipeline switching to optimize the
// performance of 2d canvas
const char kEnableCanvas2dDynamicRenderingModeSwitching[] =
"enable-canvas-2d-dynamic-rendering-mode-switching";

// Enable experimental canvas features, e.g. canvas 2D context attributes
const char kEnableExperimentalCanvasFeatures[] =
"enable-experimental-canvas-features";
Expand Down
1 change: 1 addition & 0 deletions content/public/common/content_switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ CONTENT_EXPORT extern const char kEnablePreferCompositingToLCDText[];
CONTENT_EXPORT extern const char kEnableBlinkFeatures[];
CONTENT_EXPORT extern const char kEnableBrowserSideNavigation[];
CONTENT_EXPORT extern const char kEnableDisplayList2dCanvas[];
CONTENT_EXPORT extern const char kEnableCanvas2dDynamicRenderingModeSwitching[];
CONTENT_EXPORT extern const char kEnableDistanceFieldText[];
CONTENT_EXPORT extern const char kEnableExperimentalCanvasFeatures[];
CONTENT_EXPORT extern const char kEnableExperimentalWebPlatformFeatures[];
Expand Down
28 changes: 28 additions & 0 deletions third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ inline HTMLCanvasElement::HTMLCanvasElement(Document& document)
, m_originClean(true)
, m_didFailToCreateImageBuffer(false)
, m_imageBufferIsClear(false)
, m_numFramesSinceLastRenderingModeSwitch(0)
, m_pendingRenderingModeSwitch(false)
{
CanvasMetrics::countCanvasContextUsage(CanvasMetrics::CanvasCreated);
}
Expand Down Expand Up @@ -316,6 +318,32 @@ void HTMLCanvasElement::didFinalizeFrame()
ro->invalidatePaintRectangle(mappedDirtyRect);
}
m_dirtyRect = FloatRect();

m_numFramesSinceLastRenderingModeSwitch++;
if (RuntimeEnabledFeatures::enableCanvas2dDynamicRenderingModeSwitchingEnabled()
&& !RuntimeEnabledFeatures::canvas2dFixedRenderingModeEnabled()) {
if (m_context->is2d() && buffer()->isAccelerated()
&& m_numFramesSinceLastRenderingModeSwitch >= ExpensiveCanvasHeuristicParameters::MinFramesBeforeSwitch
&& !m_pendingRenderingModeSwitch) {
if (!m_context->isAccelerationOptimalForCanvasContent()) {
// The switch must be done asynchronously in order to avoid switching during the paint invalidation step.
Platform::current()->currentThread()->getWebTaskRunner()->postTask(
BLINK_FROM_HERE,
WTF::bind([](WeakPtr<ImageBuffer> buffer) {
if (buffer) {
buffer->disableAcceleration();
}
},
m_imageBuffer->m_weakPtrFactory.createWeakPtr()));
m_numFramesSinceLastRenderingModeSwitch = 0;
m_pendingRenderingModeSwitch = true;
}
}
}

if (m_pendingRenderingModeSwitch && !buffer()->isAccelerated()) {
m_pendingRenderingModeSwitch = false;
}
}

void HTMLCanvasElement::didDisableAcceleration()
Expand Down
3 changes: 3 additions & 0 deletions third_party/WebKit/Source/core/html/HTMLCanvasElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ class CORE_EXPORT HTMLCanvasElement final : public HTMLElement, public ContextLi

// Used for OffscreenCanvas that controls this HTML canvas element
std::unique_ptr<CanvasSurfaceLayerBridge> m_surfaceLayerBridge;

int m_numFramesSinceLastRenderingModeSwitch;
bool m_pendingRenderingModeSwitch;
};

} // namespace blink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class CORE_EXPORT CanvasRenderingContext : public GarbageCollectedFinalized<Canv
virtual void styleDidChange(const ComputedStyle* oldStyle, const ComputedStyle& newStyle) { }
virtual std::pair<Element*, String> getControlAndIdIfHitRegionExists(const LayoutPoint& location) { NOTREACHED(); return std::make_pair(nullptr, String()); }
virtual String getIdFromControl(const Element* element) { return String(); }
virtual bool isAccelerationOptimalForCanvasContent() const { return true; }

// WebGL-specific interface
virtual bool is3d() const { return false; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1068,4 +1068,33 @@ unsigned CanvasRenderingContext2D::hitRegionsCount() const
return 0;
}

bool CanvasRenderingContext2D::isAccelerationOptimalForCanvasContent() const
{
// Simple heuristic to determine if the GPU accelerated pipeline should be
// used to maximize performance of 2D canvas based on past usage.

int numDrawPathCalls =
m_usageCounters.numDrawCalls[StrokePath] +
m_usageCounters.numDrawCalls[FillPath] +
m_usageCounters.numDrawCalls[FillText] +
m_usageCounters.numDrawCalls[StrokeText] +
m_usageCounters.numDrawCalls[FillRect] +
m_usageCounters.numDrawCalls[StrokeRect];

double acceleratedCost =
numDrawPathCalls * ExpensiveCanvasHeuristicParameters::AcceleratedDrawPathApproximateCost +
m_usageCounters.numGetImageDataCalls * ExpensiveCanvasHeuristicParameters::AcceleratedGetImageDataApproximateCost+
m_usageCounters.numDrawCalls[DrawImage] * ExpensiveCanvasHeuristicParameters::AcceleratedDrawImageApproximateCost;

double recordingCost =
numDrawPathCalls * ExpensiveCanvasHeuristicParameters::RecordingDrawPathApproximateCost +
m_usageCounters.numGetImageDataCalls * ExpensiveCanvasHeuristicParameters::UnacceleratedGetImageDataApproximateCost +
m_usageCounters.numDrawCalls[DrawImage] * ExpensiveCanvasHeuristicParameters::RecordingDrawImageApproximateCost;

if (recordingCost * ExpensiveCanvasHeuristicParameters::AcceleratedHeuristicBias < acceleratedCost) {
return false;
}
return true;
}

} // namespace blink
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ class MODULES_EXPORT CanvasRenderingContext2D final : public CanvasRenderingCont

void validateStateStack() final;

bool isAccelerationOptimalForCanvasContent() const;

private:
friend class CanvasRenderingContext2DAutoRestoreSkCanvas;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,4 +801,56 @@ TEST_F(CanvasRenderingContext2DTest, GetImageDataDisablesAcceleration)
RuntimeEnabledFeatures::setCanvas2dFixedRenderingModeEnabled(savedFixedRenderingMode);
}

TEST_F(CanvasRenderingContext2DTest, IsAccelerationOptimalForCanvasContentHeuristic)
{
createContext(NonOpaque);

std::unique_ptr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface = wrapUnique(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 10), NonOpaque));
canvasElement().createImageBufferUsingSurfaceForTesting(std::move(fakeAccelerateSurface));

NonThrowableExceptionState exceptionState;

CanvasRenderingContext2D* context = context2d();
EXPECT_TRUE(context->isAccelerationOptimalForCanvasContent());

context->fillRect(10, 10, 100, 100);
EXPECT_FALSE(context->isAccelerationOptimalForCanvasContent());

context->drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 1, 1, 0, 0, 10, 10, exceptionState);
EXPECT_TRUE(context->isAccelerationOptimalForCanvasContent());

int numReps = 100;
for (int i = 0; i < numReps; i++) {
context->fillRect(10, 10, 100, 100);
}
EXPECT_FALSE(context->isAccelerationOptimalForCanvasContent());
}

TEST_F(CanvasRenderingContext2DTest, DisableAcceleration)
{
createContext(NonOpaque);

std::unique_ptr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface = wrapUnique(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 10), NonOpaque));
canvasElement().createImageBufferUsingSurfaceForTesting(std::move(fakeAccelerateSurface));
CanvasRenderingContext2D* context = context2d();

// 800 = 10 * 10 * 4 * 2 where 10*10 is canvas size, 4 is num of bytes per pixel per buffer,
// and 2 is an estimate of num of gpu buffers required
EXPECT_EQ(800, getCurrentGPUMemoryUsage());
EXPECT_EQ(800, getGlobalGPUMemoryUsage());
EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount());

context->fillRect(10, 10, 100, 100);
EXPECT_TRUE(canvasElement().buffer()->isAccelerated());

canvasElement().buffer()->disableAcceleration();
EXPECT_FALSE(canvasElement().buffer()->isAccelerated());

context->fillRect(10, 10, 100, 100);

EXPECT_EQ(0, getCurrentGPUMemoryUsage());
EXPECT_EQ(0, getGlobalGPUMemoryUsage());
EXPECT_EQ(0u, getGlobalAcceleratedImageBufferCount());
}

} // namespace blink
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ BackspaceDefaultHandler status=test
BroadcastChannel status=stable
CacheIgnoreSearchOption status=experimental
Canvas2dFixedRenderingMode status=test
EnableCanvas2dDynamicRenderingModeSwitching status=experimental
Canvas2dImageChromium status=experimental
ClientHints status=stable
CompositedSelectionUpdate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,47 @@ enum {

}; // enum


// Constants and Coefficients for 2D Canvas Dynamic Rendering Mode Switching
// =========================================================================

// Approximate relative costs of different types of operations for the
// accelerated rendering pipeline and the recording rendering pipeline.
// These costs were estimated experimentally based on the performance
// of each pipeline in the animometer benchmark. Multiple factors influence
// the true cost of each type of operation, including:
// - The hardware configuration
// - Version of the project
// - Additional details about the operation:
// - Subtype of operation (png vs svg image, arc vs line...)
// - Scale
// - Associated effects (patterns, gradients...)
// The coefficients are equal to 1/n, where n is equal to the number of calls
// of a type that can be performed in each frame while maintaining
// 50 frames per second. They were estimated using the animometer benchmark.

const double AcceleratedDrawPathApproximateCost = 0.004;
const double AcceleratedGetImageDataApproximateCost = 0.1;
const double AcceleratedDrawImageApproximateCost = 0.002;

const double RecordingDrawPathApproximateCost = 0.0014;
const double UnacceleratedGetImageDataApproximateCost = 0.001; // This cost is for non-display-list mode after fallback.
const double RecordingDrawImageApproximateCost = 0.004;

// Coefficient used in the isAccelerationOptimalForCanvasContent
// heuristic to create a bias in the output.
// If set to a value greater than 1, it creates a bias towards suggesting acceleration.
// If set to a value smaller than 1, it creates a bias towards not suggesting acceleration
// For example, if its value is 1.5, then disabling gpu acceleration will only be suggested if
// recordingCost * 1.5 < acceleratedCost.
const double AcceleratedHeuristicBias = 1.5;

// Minimum number of frames that need to be rendered
// before the rendering pipeline may be switched. Having this set
// to more than 1 increases the sample size of usage data before a
// decision is made, improving the accuracy of heuristics.
const int MinFramesBeforeSwitch = 3;

} // namespace ExpensiveCanvasHeuristicParameters

} // namespace blink
Expand Down
35 changes: 34 additions & 1 deletion third_party/WebKit/Source/platform/graphics/ImageBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "platform/graphics/ExpensiveCanvasHeuristicParameters.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/ImageBufferClient.h"
#include "platform/graphics/RecordingImageBufferSurface.h"
#include "platform/graphics/StaticBitmapImage.h"
#include "platform/graphics/UnacceleratedImageBufferSurface.h"
#include "platform/graphics/gpu/DrawingBuffer.h"
Expand Down Expand Up @@ -80,7 +81,8 @@ std::unique_ptr<ImageBuffer> ImageBuffer::create(const IntSize& size, OpacityMod
}

ImageBuffer::ImageBuffer(std::unique_ptr<ImageBufferSurface> surface)
: m_snapshotState(InitialSnapshotState)
: m_weakPtrFactory(this)
, m_snapshotState(InitialSnapshotState)
, m_surface(std::move(surface))
, m_client(0)
, m_gpuMemoryUsage(0)
Expand Down Expand Up @@ -400,6 +402,37 @@ void ImageBuffer::updateGPUMemoryUsage() const
}
}

class UnacceleratedSurfaceFactory : public RecordingImageBufferFallbackSurfaceFactory {
public:
virtual std::unique_ptr<ImageBufferSurface> createSurface(const IntSize& size, OpacityMode opacityMode)
{
return wrapUnique(new UnacceleratedImageBufferSurface(size, opacityMode));
}

virtual ~UnacceleratedSurfaceFactory() { }
};

void ImageBuffer::disableAcceleration()
{
if (!isAccelerated()) {
return;
}

// Get current frame.
SkImage* image = m_surface->newImageSnapshot(PreferNoAcceleration, SnapshotReasonPaint).get();

// Create and configure a recording (unaccelerated) surface.
std::unique_ptr<RecordingImageBufferFallbackSurfaceFactory> surfaceFactory = wrapUnique(new UnacceleratedSurfaceFactory());
std::unique_ptr<ImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(m_surface->size(), std::move(surfaceFactory), m_surface->getOpacityMode()));
surface->canvas()->drawImage(image, 0, 0);
surface->setImageBuffer(this);

// Replace the current surface with the new surface.
m_surface = std::move(surface);

didDisableAcceleration();
}

bool ImageDataBuffer::encodeImage(const String& mimeType, const double& quality, Vector<unsigned char>* encodedImage) const
{
if (mimeType == "image/jpeg") {
Expand Down
4 changes: 4 additions & 0 deletions third_party/WebKit/Source/platform/graphics/ImageBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ class PLATFORM_EXPORT ImageBuffer {
static unsigned getGlobalAcceleratedImageBufferCount() { return s_globalAcceleratedImageBufferCount; }
intptr_t getGPUMemoryUsage() { return m_gpuMemoryUsage; }

void disableAcceleration();

WeakPtrFactory<ImageBuffer> m_weakPtrFactory;

protected:
ImageBuffer(std::unique_ptr<ImageBufferSurface>);

Expand Down
5 changes: 5 additions & 0 deletions third_party/WebKit/Source/web/WebRuntimeFeatures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ void WebRuntimeFeatures::enableDisplayList2dCanvas(bool enable)
RuntimeEnabledFeatures::setDisplayList2dCanvasEnabled(enable);
}

void WebRuntimeFeatures::enableCanvas2dDynamicRenderingModeSwitching(bool enable)
{
RuntimeEnabledFeatures::setEnableCanvas2dDynamicRenderingModeSwitchingEnabled(enable);
}

void WebRuntimeFeatures::enableDoNotUnlockSharedBuffer(bool enable)
{
RuntimeEnabledFeatures::setDoNotUnlockSharedBufferEnabled(enable);
Expand Down
2 changes: 1 addition & 1 deletion third_party/WebKit/public/web/WebRuntimeFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class WebRuntimeFeatures {
BLINK_EXPORT static void enableXSLT(bool);
BLINK_EXPORT static void forceOverlayFullscreenVideo(bool);
BLINK_EXPORT static void enableAutoplayMutedVideos(bool);

BLINK_EXPORT static void enableCanvas2dDynamicRenderingModeSwitching(bool);
private:
WebRuntimeFeatures();
};
Expand Down
2 changes: 2 additions & 0 deletions tools/metrics/histograms/histograms.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81996,6 +81996,8 @@ To add a new entry, add it with any value and run test to compute valid value.
<int value="773919225" label="disable-office-editing-component-extension"/>
<int value="779086132" label="enable-data-reduction-proxy-alt"/>
<int value="782167080" label="enable-new-qp-input-view"/>
<int value="805567148"
label="enable-canvas-2d-dynamic-rendering-mode-switching"/>
<int value="807734471" label="tab-management-experiment-type-disabled"/>
<int value="811374216" label="disable-new-bookmark-apps"/>
<int value="820650704" label="disable-ntp-popular-sites"/>
Expand Down

0 comments on commit 98739b0

Please sign in to comment.