| Index: third_party/WebKit/Source/core/html/canvas/CanvasAsyncBlobCreator.cpp
|
| diff --git a/third_party/WebKit/Source/core/html/canvas/CanvasAsyncBlobCreator.cpp b/third_party/WebKit/Source/core/html/canvas/CanvasAsyncBlobCreator.cpp
|
| index d0c082add4c2fb6da7454e845a3c9bd48435ef30..2e1415f3d71a7ff11178f96465dce446042c4aa1 100644
|
| --- a/third_party/WebKit/Source/core/html/canvas/CanvasAsyncBlobCreator.cpp
|
| +++ b/third_party/WebKit/Source/core/html/canvas/CanvasAsyncBlobCreator.cpp
|
| @@ -5,7 +5,6 @@
|
| #include "CanvasAsyncBlobCreator.h"
|
|
|
| #include "core/fileapi/Blob.h"
|
| -#include "platform/Task.h"
|
| #include "platform/ThreadSafeFunctional.h"
|
| #include "platform/graphics/ImageBuffer.h"
|
| #include "platform/heap/Handle.h"
|
| @@ -28,6 +27,21 @@ const double SlackBeforeDeadline = 0.001; // a small slack period between deadli
|
| const int NumChannelsPng = 4;
|
| const int LongTaskImageSizeThreshold = 1000 * 1000; // The max image size we expect to encode in 14ms on Linux in PNG format
|
|
|
| +// The encoding task is highly likely to switch from idle task to alternative
|
| +// code path when the startTimeoutDelay is set to be below 150ms. As we want the
|
| +// majority of encoding tasks to take the usual async idle task, we set a
|
| +// lenient limit -- 200ms here.
|
| +const double IdleTaskStartTimeoutDelay = 200.0;
|
| +// We should be more lenient on completion timeout delay to ensure that the
|
| +// switch from idle to main thread only happens to a minority of toBlob calls
|
| +#if !OS(ANDROID)
|
| +// Png image encoding on 4k by 4k canvas on Mac HDD takes 5.7+ seconds
|
| +const double IdleTaskCompleteTimeoutDelay = 6700.0;
|
| +#else
|
| +// Png image encoding on 4k by 4k canvas on Android One takes 9.0+ seconds
|
| +const double IdleTaskCompleteTimeoutDelay = 10000.0;
|
| +#endif
|
| +
|
| bool isDeadlineNearOrPassed(double deadlineSeconds)
|
| {
|
| return (deadlineSeconds - SlackBeforeDeadline - monotonicallyIncreasingTime() <= 0);
|
| @@ -50,6 +64,7 @@ CanvasAsyncBlobCreator::CanvasAsyncBlobCreator(PassRefPtr<DOMUint8ClampedArray>
|
| ASSERT(m_data->length() == (unsigned) (size.height() * size.width() * 4));
|
| m_encodedImage = adoptPtr(new Vector<unsigned char>());
|
| m_pixelRowStride = size.width() * NumChannelsPng;
|
| + m_idleTaskStatus = IdleTaskStatus::Default;
|
| m_numRowsCompleted = 0;
|
| }
|
|
|
| @@ -59,18 +74,23 @@ CanvasAsyncBlobCreator::~CanvasAsyncBlobCreator()
|
|
|
| void CanvasAsyncBlobCreator::scheduleAsyncBlobCreation(bool canUseIdlePeriodScheduling, double quality)
|
| {
|
| - // TODO: async blob creation should be supported in worker_pool threads as well. but right now blink does not have that
|
| ASSERT(isMainThread());
|
|
|
| // Make self-reference to keep this object alive until the final task completes
|
| m_selfRef = this;
|
|
|
| - // At the time being, progressive encoding is only applicable to png image format,
|
| - // and thus idle tasks scheduling can only be applied to png image format.
|
| - // TODO(xlai): Progressive encoding on jpeg and webp image formats (crbug.com/571398, crbug.com/571399)
|
| if (canUseIdlePeriodScheduling) {
|
| + // At the time being, progressive encoding is only applicable to png image format,
|
| + // and thus idle tasks scheduling can only be applied to png image format.
|
| + // TODO(xlai): Progressive encoding on jpeg and webp image formats (crbug.com/571398, crbug.com/571399)
|
| ASSERT(m_mimeType == "image/png");
|
| - Platform::current()->mainThread()->scheduler()->postIdleTask(BLINK_FROM_HERE, bind<double>(&CanvasAsyncBlobCreator::initiatePngEncoding, this));
|
| + m_idleTaskStatus = IdleTaskStatus::IdleTaskNotStarted;
|
| + m_alternativeSelfRef = this;
|
| +
|
| + this->scheduleInitiatePngEncoding();
|
| + // We post the below task to check if the above idle task isn't late.
|
| + // There's no risk of concurrency as both tasks are on main thread.
|
| + this->postDelayedTaskToMainThread(BLINK_FROM_HERE, new Task(bind(&CanvasAsyncBlobCreator::idleTaskStartTimeoutEvent, this, quality)), IdleTaskStartTimeoutDelay);
|
| } else if (m_mimeType == "image/jpeg") {
|
| Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&CanvasAsyncBlobCreator::initiateJpegEncoding, this, quality));
|
| } else {
|
| @@ -84,40 +104,55 @@ void CanvasAsyncBlobCreator::initiateJpegEncoding(const double& quality)
|
| m_jpegEncoderState = JPEGImageEncoderState::create(m_size, quality, m_encodedImage.get());
|
| if (!m_jpegEncoderState) {
|
| Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&BlobCallback::handleEvent, m_callback, nullptr));
|
| - m_selfRef.clear();
|
| + clearSelfReference();
|
| return;
|
| }
|
| BackgroundTaskRunner::TaskSize taskSize = (m_size.height() * m_size.width() >= LongTaskImageSizeThreshold) ? BackgroundTaskRunner::TaskSizeLongRunningTask : BackgroundTaskRunner::TaskSizeShortRunningTask;
|
| BackgroundTaskRunner::postOnBackgroundThread(BLINK_FROM_HERE, threadSafeBind(&CanvasAsyncBlobCreator::encodeImageOnEncoderThread, AllowCrossThreadAccess(this), quality), taskSize);
|
| }
|
|
|
| +void CanvasAsyncBlobCreator::scheduleInitiatePngEncoding()
|
| +{
|
| + Platform::current()->mainThread()->scheduler()->postIdleTask(BLINK_FROM_HERE, bind<double>(&CanvasAsyncBlobCreator::initiatePngEncoding, this));
|
| +}
|
| +
|
| void CanvasAsyncBlobCreator::initiatePngEncoding(double deadlineSeconds)
|
| {
|
| ASSERT(isMainThread());
|
| - m_pngEncoderState = PNGImageEncoderState::create(m_size, m_encodedImage.get());
|
| - if (!m_pngEncoderState) {
|
| - Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&BlobCallback::handleEvent, m_callback, nullptr));
|
| - m_selfRef.clear();
|
| + if (m_idleTaskStatus == IdleTaskStatus::IdleTaskSwitchedToMainThreadTask) {
|
| + // Clear the selfRef as the encoding task has already been carried out
|
| + // on alternative code path.
|
| + clearSelfReference();
|
| return;
|
| }
|
|
|
| - CanvasAsyncBlobCreator::idleEncodeRowsPng(deadlineSeconds);
|
| -}
|
| + ASSERT(m_idleTaskStatus == IdleTaskStatus::IdleTaskNotStarted);
|
| + m_idleTaskStatus = IdleTaskStatus::IdleTaskStarted;
|
|
|
| -void CanvasAsyncBlobCreator::scheduleIdleEncodeRowsPng()
|
| -{
|
| - ASSERT(isMainThread());
|
| - Platform::current()->currentThread()->scheduler()->postIdleTask(BLINK_FROM_HERE, WTF::bind<double>(&CanvasAsyncBlobCreator::idleEncodeRowsPng, this));
|
| + if (!initializePngStruct()) {
|
| + m_idleTaskStatus = IdleTaskStatus::IdleTaskFailed;
|
| + // Clears the selfRef as the idle task has failed.
|
| + clearSelfReference();
|
| + return;
|
| + }
|
| + this->idleEncodeRowsPng(deadlineSeconds);
|
| }
|
|
|
| void CanvasAsyncBlobCreator::idleEncodeRowsPng(double deadlineSeconds)
|
| {
|
| ASSERT(isMainThread());
|
| + if (m_idleTaskStatus == IdleTaskStatus::IdleTaskSwitchedToMainThreadTask) {
|
| + // Clear the selfRef as the encoding task has already been carried out
|
| + // on alternative code path.
|
| + clearSelfReference();
|
| + return;
|
| + }
|
| +
|
| unsigned char* inputPixels = m_data->data() + m_pixelRowStride * m_numRowsCompleted;
|
| for (int y = m_numRowsCompleted; y < m_size.height(); ++y) {
|
| if (isDeadlineNearOrPassed(deadlineSeconds)) {
|
| m_numRowsCompleted = y;
|
| - CanvasAsyncBlobCreator::scheduleIdleEncodeRowsPng();
|
| + Platform::current()->currentThread()->scheduler()->postIdleTask(BLINK_FROM_HERE, bind<double>(&CanvasAsyncBlobCreator::idleEncodeRowsPng, this));
|
| return;
|
| }
|
| PNGImageEncoder::writeOneRowToPng(inputPixels, m_pngEncoderState.get());
|
| @@ -126,11 +161,33 @@ void CanvasAsyncBlobCreator::idleEncodeRowsPng(double deadlineSeconds)
|
| m_numRowsCompleted = m_size.height();
|
| PNGImageEncoder::finalizePng(m_pngEncoderState.get());
|
|
|
| + m_idleTaskStatus = IdleTaskStatus::IdleTaskCompleted;
|
| +
|
| if (isDeadlineNearOrPassed(deadlineSeconds)) {
|
| Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&CanvasAsyncBlobCreator::createBlobAndCall, this));
|
| } else {
|
| this->createBlobAndCall();
|
| }
|
| +
|
| + // Clears the selfRef as the idle task has come to a completion.
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&CanvasAsyncBlobCreator::clearSelfReference, this));
|
| +}
|
| +
|
| +void CanvasAsyncBlobCreator::encodeRowsPngOnMainThread()
|
| +{
|
| + ASSERT(m_idleTaskStatus == IdleTaskStatus::IdleTaskSwitchedToMainThreadTask);
|
| +
|
| + // Continue encoding from the last completed row
|
| + unsigned char* inputPixels = m_data->data() + m_pixelRowStride * m_numRowsCompleted;
|
| + for (int y = m_numRowsCompleted; y < m_size.height(); ++y) {
|
| + PNGImageEncoder::writeOneRowToPng(inputPixels, m_pngEncoderState.get());
|
| + inputPixels += m_pixelRowStride;
|
| + }
|
| + PNGImageEncoder::finalizePng(m_pngEncoderState.get());
|
| + this->createBlobAndCall();
|
| +
|
| + // Clears alternative selfRef as the alternative code path has completed.
|
| + this->clearAlternativeSelfReference();
|
| }
|
|
|
| void CanvasAsyncBlobCreator::createBlobAndCall()
|
| @@ -138,20 +195,43 @@ void CanvasAsyncBlobCreator::createBlobAndCall()
|
| ASSERT(isMainThread());
|
| Blob* resultBlob = Blob::create(m_encodedImage->data(), m_encodedImage->size(), m_mimeType);
|
| Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&BlobCallback::handleEvent, m_callback, resultBlob));
|
| - clearSelfReference(); // self-destruct once job is done.
|
| +}
|
| +
|
| +void CanvasAsyncBlobCreator::createNullptrAndCall()
|
| +{
|
| + ASSERT(isMainThread());
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&BlobCallback::handleEvent, m_callback, nullptr));
|
| }
|
|
|
| void CanvasAsyncBlobCreator::encodeImageOnEncoderThread(double quality)
|
| {
|
| ASSERT(!isMainThread());
|
|
|
| + bool encodeSuccess;
|
| if (m_mimeType == "image/jpeg") {
|
| JPEGImageEncoder::encodeWithPreInitializedState(m_jpegEncoderState.get(), m_data->data());
|
| - } else if (!ImageDataBuffer(m_size, m_data->data()).encodeImage(m_mimeType, quality, m_encodedImage.get())) {
|
| - scheduleCreateNullptrAndCallOnMainThread();
|
| + encodeSuccess = true;
|
| + } else {
|
| + encodeSuccess = ImageDataBuffer(m_size, m_data->data()).encodeImage(m_mimeType, quality, m_encodedImage.get());
|
| + }
|
| +
|
| + if (encodeSuccess) {
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&CanvasAsyncBlobCreator::createBlobAndCall, AllowCrossThreadAccess(this)));
|
| + } else {
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&BlobCallback::handleEvent, m_callback.get(), nullptr));
|
| }
|
|
|
| - scheduleCreateBlobAndCallOnMainThread();
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&CanvasAsyncBlobCreator::clearSelfReference, AllowCrossThreadAccess(this)));
|
| +}
|
| +
|
| +bool CanvasAsyncBlobCreator::initializePngStruct()
|
| +{
|
| + m_pngEncoderState = PNGImageEncoderState::create(m_size, m_encodedImage.get());
|
| + if (!m_pngEncoderState) {
|
| + this->createNullptrAndCall();
|
| + return false;
|
| + }
|
| + return true;
|
| }
|
|
|
| void CanvasAsyncBlobCreator::clearSelfReference()
|
| @@ -162,17 +242,55 @@ void CanvasAsyncBlobCreator::clearSelfReference()
|
| m_selfRef.clear();
|
| }
|
|
|
| -void CanvasAsyncBlobCreator::scheduleCreateBlobAndCallOnMainThread()
|
| +void CanvasAsyncBlobCreator::clearAlternativeSelfReference()
|
| {
|
| - ASSERT(!isMainThread());
|
| - Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&CanvasAsyncBlobCreator::createBlobAndCall, AllowCrossThreadAccess(this)));
|
| + ASSERT(isMainThread());
|
| + m_alternativeSelfRef.clear();
|
| }
|
|
|
| -void CanvasAsyncBlobCreator::scheduleCreateNullptrAndCallOnMainThread()
|
| +void CanvasAsyncBlobCreator::idleTaskStartTimeoutEvent(double quality)
|
| {
|
| - ASSERT(!isMainThread());
|
| - Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&BlobCallback::handleEvent, m_callback.get(), nullptr));
|
| - Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&CanvasAsyncBlobCreator::clearSelfReference, AllowCrossThreadAccess(this)));
|
| + if (m_idleTaskStatus == IdleTaskStatus::IdleTaskStarted) {
|
| + // Even if the task started quickly, we still want to ensure completion
|
| + this->postDelayedTaskToMainThread(BLINK_FROM_HERE, new Task(bind(&CanvasAsyncBlobCreator::idleTaskCompleteTimeoutEvent, this)), IdleTaskCompleteTimeoutDelay);
|
| + } else if (m_idleTaskStatus == IdleTaskStatus::IdleTaskNotStarted) {
|
| + // If the idle task does not start after a delay threshold, we will
|
| + // force it to happen on main thread (even though it may cause more
|
| + // janks) to prevent toBlob being postponed forever in extreme cases.
|
| + m_idleTaskStatus = IdleTaskStatus::IdleTaskSwitchedToMainThreadTask;
|
| + signalTaskSwitchInStartTimeoutEventForTesting();
|
| + if (initializePngStruct()) {
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&CanvasAsyncBlobCreator::encodeRowsPngOnMainThread, this));
|
| + } else {
|
| + this->clearAlternativeSelfReference(); // As it fails on alternative path
|
| + }
|
| + } else {
|
| + // No need to hold the alternativeSelfRef as the alternative code path
|
| + // will not occur for IdleTaskFailed and IdleTaskCompleted
|
| + this->clearAlternativeSelfReference();
|
| + }
|
| +
|
| +}
|
| +
|
| +void CanvasAsyncBlobCreator::idleTaskCompleteTimeoutEvent()
|
| +{
|
| + ASSERT(m_idleTaskStatus != IdleTaskStatus::IdleTaskNotStarted);
|
| +
|
| + if (m_idleTaskStatus == IdleTaskStatus::IdleTaskStarted) {
|
| + // It has taken too long to complete for the idle task.
|
| + m_idleTaskStatus = IdleTaskStatus::IdleTaskSwitchedToMainThreadTask;
|
| + signalTaskSwitchInCompleteTimeoutEventForTesting();
|
| + Platform::current()->mainThread()->taskRunner()->postTask(BLINK_FROM_HERE, bind(&CanvasAsyncBlobCreator::encodeRowsPngOnMainThread, this));
|
| + } else {
|
| + // No need to hold the alternativeSelfRef as the alternative code path
|
| + // will not occur for IdleTaskFailed and IdleTaskCompleted
|
| + this->clearAlternativeSelfReference();
|
| + }
|
| +}
|
| +
|
| +void CanvasAsyncBlobCreator::postDelayedTaskToMainThread(const WebTraceLocation& location, Task* task, double delayMs)
|
| +{
|
| + Platform::current()->mainThread()->taskRunner()->postDelayedTask(location, task, delayMs);
|
| }
|
|
|
| } // namespace blink
|
|
|