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 |