Chromium Code Reviews| Index: third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp |
| diff --git a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp |
| index 3d352b67a928bbbc0d3d3414aab1d80bae729430..249887bf5d64d0da15c365c408839eb8d0be8019 100644 |
| --- a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp |
| +++ b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp |
| @@ -5,11 +5,13 @@ |
| #include "modules/webgl/WebGL2RenderingContextBase.h" |
| #include "bindings/modules/v8/WebGLAny.h" |
| +#include "core/dom/DOMException.h" |
| #include "core/frame/ImageBitmap.h" |
| #include "core/html/HTMLCanvasElement.h" |
| #include "core/html/HTMLImageElement.h" |
| #include "core/html/HTMLVideoElement.h" |
| #include "core/html/ImageData.h" |
| +#include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "modules/webgl/WebGLActiveInfo.h" |
| #include "modules/webgl/WebGLBuffer.h" |
| @@ -40,6 +42,8 @@ GLsync syncObjectOrZero(const WebGLSync* object) { |
| return object ? object->object() : nullptr; |
| } |
| +// TODO(kainino): Change outByteLength to GLuint and change the associated |
| +// range checking (and all uses) - overflow becomes possible in cases below |
| bool validateSubSourceAndGetData(DOMArrayBufferView* view, |
| GLuint subOffset, |
| GLuint subLength, |
| @@ -148,6 +152,79 @@ const GLenum kCompressedTextureFormatsETC2EAC[] = { |
| GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, |
| }; |
| +class WebGLGetBufferSubDataAsyncCallback |
|
esprehn
2016/10/18 01:44:16
This should be garbage collected, and use a HeapHa
Kai Ninomiya
2016/10/19 18:17:49
Done.
|
| + : public RefCounted<WebGLGetBufferSubDataAsyncCallback> { |
| + public: |
| + WebGLGetBufferSubDataAsyncCallback( |
| + WebGL2RenderingContextBase* context, |
| + ScriptPromiseResolver* promiseResolver, |
| + void* shmReadbackResultData, |
| + GLuint commandsIssuedQueryID, |
| + DOMArrayBufferView* destinationArrayBufferView, |
| + void* destinationDataPtr, |
| + long long destinationByteLength) |
| + : context(context), |
| + promiseResolver(promiseResolver), |
| + shmReadbackResultData(shmReadbackResultData), |
| + commandsIssuedQueryID(commandsIssuedQueryID), |
| + destinationArrayBufferView(destinationArrayBufferView), |
| + destinationDataPtr(destinationDataPtr), |
| + destinationByteLength(destinationByteLength) { |
| + DCHECK(shmReadbackResultData); |
| + DCHECK(destinationDataPtr); |
| + } |
| + |
| + void destroy() { |
| + DCHECK(shmReadbackResultData); |
| + context->contextGL()->FreeSharedMemory(shmReadbackResultData); |
| + shmReadbackResultData = nullptr; |
| + DOMException* exception = |
| + DOMException::create(InvalidStateError, "Context lost or destroyed"); |
| + this->promiseResolver->reject(exception); |
| + } |
| + |
| + void resolve() { |
| + if (!this->context || !this->shmReadbackResultData) { |
| + DOMException* exception = |
| + DOMException::create(InvalidStateError, "Context lost or destroyed"); |
| + this->promiseResolver->reject(exception); |
| + return; |
| + } |
| + if (this->destinationArrayBufferView->buffer()->isNeutered()) { |
| + DOMException* exception = DOMException::create( |
| + InvalidStateError, "ArrayBufferView became invalid asynchronously"); |
| + this->promiseResolver->reject(exception); |
| + return; |
| + } |
| + memcpy(this->destinationDataPtr, this->shmReadbackResultData, |
| + this->destinationByteLength); |
| + // TODO(kainino): What would happen if the DOM was suspended when the |
| + // promise became resolved? Could another JS task happen between the memcpy |
| + // and the promise resolution task, which would see the wrong data? |
| + this->promiseResolver->resolve(this->destinationArrayBufferView); |
|
esprehn
2016/10/18 01:44:16
remove this-> and use m_
Kai Ninomiya
2016/10/19 17:54:10
Done.
|
| + |
| + context->contextGL()->DeleteQueriesEXT(1, &commandsIssuedQueryID); |
| + this->destroy(); |
| + this->context->unregisterGetBufferSubDataAsyncCallback(this); |
| + } |
| + |
| + private: |
| + WeakPersistent<WebGL2RenderingContextBase> context; |
| + Persistent<ScriptPromiseResolver> promiseResolver; |
| + |
| + // Pointer to shared memory where the gpu readback result is stored. |
| + void* shmReadbackResultData; |
|
esprehn
2016/10/18 01:44:16
m_ for all the private fields
Kai Ninomiya
2016/10/19 17:54:10
Done.
|
| + // ID of the GL query used to call this callback. |
| + GLuint commandsIssuedQueryID; |
| + |
| + // ArrayBufferView returned from the promise. |
| + Persistent<DOMArrayBufferView> destinationArrayBufferView; |
| + // Pointer into the offset into destinationArrayBufferView. |
| + void* destinationDataPtr; |
| + // Size in bytes of the copy operation being performed. |
| + long long destinationByteLength; |
| +}; |
| + |
| WebGL2RenderingContextBase::WebGL2RenderingContextBase( |
| HTMLCanvasElement* passedCanvas, |
| std::unique_ptr<WebGraphicsContext3DProvider> contextProvider, |
| @@ -187,6 +264,15 @@ WebGL2RenderingContextBase::~WebGL2RenderingContextBase() { |
| m_currentTransformFeedbackPrimitivesWrittenQuery = nullptr; |
| } |
| +void WebGL2RenderingContextBase::destroyContext() { |
| + for (auto& callback : m_getBufferSubDataAsyncCallbacks) { |
| + callback->destroy(); |
| + } |
| + m_getBufferSubDataAsyncCallbacks.clear(); |
| + |
| + WebGLRenderingContextBase::destroyContext(); |
| +} |
| + |
| void WebGL2RenderingContextBase::initializeNewContext() { |
| ASSERT(!isContextLost()); |
| ASSERT(drawingBuffer()); |
| @@ -357,38 +443,104 @@ void WebGL2RenderingContextBase::getBufferSubData(GLenum target, |
| DOMArrayBufferView* dstData, |
| GLuint dstOffset, |
| GLuint length) { |
| - const char* funcName = "getBufferSubData"; |
| - if (isContextLost()) |
| - return; |
| - if (!validateValueFitNonNegInt32(funcName, "srcByteOffset", srcByteOffset)) { |
| + WebGLBuffer* sourceBuffer = nullptr; |
| + void* destinationDataPtr = nullptr; |
| + long long destinationByteLength = 0; |
| + const char* message = validateGetBufferSubData( |
| + __FUNCTION__, target, srcByteOffset, dstData, dstOffset, length, |
| + &sourceBuffer, &destinationDataPtr, &destinationByteLength); |
| + if (message) { |
|
esprehn
2016/10/18 01:44:16
You drop the message on the floor here, should you
Kai Ninomiya
2016/10/19 17:54:10
Yeah, it's an already specified API and the spec d
|
| return; |
| } |
| - WebGLBuffer* buffer = validateBufferDataTarget(funcName, target); |
| - if (!buffer) |
| - return; |
| - void* subBaseAddress = nullptr; |
| - long long subByteLength = 0; |
| - if (!validateSubSourceAndGetData(dstData, dstOffset, length, &subBaseAddress, |
| - &subByteLength)) { |
| - synthesizeGLError(GL_INVALID_VALUE, funcName, "buffer overflow"); |
| - return; |
| - } |
| - if (subByteLength == 0) { |
| + |
| + // If the length of the copy is zero, this is a no-op. |
| + if (!destinationByteLength) { |
| return; |
| } |
| void* mappedData = |
| contextGL()->MapBufferRange(target, static_cast<GLintptr>(srcByteOffset), |
| - subByteLength, GL_MAP_READ_BIT); |
| + destinationByteLength, GL_MAP_READ_BIT); |
| if (!mappedData) |
| return; |
| - memcpy(subBaseAddress, mappedData, subByteLength); |
| + memcpy(destinationDataPtr, mappedData, destinationByteLength); |
| contextGL()->UnmapBuffer(target); |
| } |
| +ScriptPromise WebGL2RenderingContextBase::getBufferSubDataAsync( |
| + ScriptState* scriptState, |
| + GLenum target, |
| + GLintptr srcByteOffset, |
| + DOMArrayBufferView* dstData, |
| + GLuint dstOffset, |
| + GLuint length) { |
| + ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| + ScriptPromise promise = resolver->promise(); |
| + |
| + WebGLBuffer* sourceBuffer = nullptr; |
| + void* destinationDataPtr = nullptr; |
| + long long destinationByteLength = 0; |
| + const char* message = validateGetBufferSubData( |
| + __FUNCTION__, target, srcByteOffset, dstData, dstOffset, length, |
| + &sourceBuffer, &destinationDataPtr, &destinationByteLength); |
| + if (message) { |
| + DOMException* exception = DOMException::create(InvalidStateError, message); |
| + resolver->reject(exception); |
| + return promise; |
| + } |
| + |
| + message = validateGetBufferSubDataBounds( |
| + __FUNCTION__, sourceBuffer, srcByteOffset, destinationByteLength); |
| + if (message) { |
| + DOMException* exception = DOMException::create(InvalidStateError, message); |
| + resolver->reject(exception); |
| + return promise; |
| + } |
| + |
| + // If the length of the copy is zero, this is a no-op. |
| + if (!destinationByteLength) { |
| + resolver->resolve(dstData); |
| + return promise; |
| + } |
| + |
| + GLuint queryID; |
| + contextGL()->GenQueriesEXT(1, &queryID); |
| + contextGL()->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, queryID); |
| + void* mappedData = contextGL()->GetBufferSubDataAsyncCHROMIUM( |
| + target, srcByteOffset, destinationByteLength); |
| + contextGL()->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM); |
| + if (!mappedData) { |
| + DOMException* exception = |
| + DOMException::create(InvalidStateError, "Out of memory"); |
| + resolver->reject(exception); |
| + return promise; |
| + } |
| + |
| + auto callbackObject = adoptRef(new WebGLGetBufferSubDataAsyncCallback( |
| + this, resolver, mappedData, queryID, dstData, destinationDataPtr, |
| + destinationByteLength)); |
| + registerGetBufferSubDataAsyncCallback(callbackObject.get()); |
| + auto callback = |
| + WTF::bind(&WebGLGetBufferSubDataAsyncCallback::resolve, callbackObject); |
| + drawingBuffer()->contextProvider()->signalQuery( |
| + queryID, convertToBaseCallback(std::move(callback))); |
| + |
| + return promise; |
| +} |
| + |
| +void WebGL2RenderingContextBase::registerGetBufferSubDataAsyncCallback( |
| + WebGLGetBufferSubDataAsyncCallback* callback) { |
| + m_getBufferSubDataAsyncCallbacks.insert(callback); |
| +} |
| + |
| +void WebGL2RenderingContextBase::unregisterGetBufferSubDataAsyncCallback( |
| + WebGLGetBufferSubDataAsyncCallback* callback) { |
| + m_getBufferSubDataAsyncCallbacks.erase(callback); |
| +} |
| + |
| void WebGL2RenderingContextBase::blitFramebuffer(GLint srcX0, |
| GLint srcY0, |
| GLint srcX1, |
| @@ -4419,6 +4571,63 @@ bool WebGL2RenderingContextBase::validateBufferDataUsage( |
| } |
| } |
| +const char* WebGL2RenderingContextBase::validateGetBufferSubData( |
| + const char* functionName, |
| + GLenum target, |
| + GLintptr sourceByteOffset, |
| + DOMArrayBufferView* destinationArrayBufferView, |
| + GLuint destinationOffset, |
| + GLuint length, |
| + WebGLBuffer** outSourceBuffer, |
| + void** outDestinationDataPtr, |
| + long long* outDestinationByteLength) { |
| + if (isContextLost()) { |
| + return "Context lost"; |
| + } |
| + |
| + if (!validateValueFitNonNegInt32(functionName, "srcByteOffset", |
| + sourceByteOffset)) { |
| + return "Invalid value: srcByteOffset"; |
| + } |
| + |
| + if (target == GL_TRANSFORM_FEEDBACK_BUFFER && m_transformFeedbackBinding) { |
| + synthesizeGLError(GL_INVALID_OPERATION, functionName, |
| + "targeted transform feedback buffer is bound"); |
| + return "Invalid operation: targeted transform feedback buffer is bound"; |
| + } |
| + |
| + WebGLBuffer* sourceBuffer = validateBufferDataTarget(functionName, target); |
| + if (!sourceBuffer) { |
| + return "Invalid operation: no buffer bound to target"; |
| + } |
| + *outSourceBuffer = sourceBuffer; |
| + |
| + if (!validateSubSourceAndGetData( |
| + destinationArrayBufferView, destinationOffset, length, |
| + outDestinationDataPtr, outDestinationByteLength)) { |
| + synthesizeGLError(GL_INVALID_VALUE, functionName, "overflow of dstData"); |
| + return "Invalid value: overflow of dstData"; |
| + } |
| + |
| + return nullptr; |
| +} |
| + |
| +const char* WebGL2RenderingContextBase::validateGetBufferSubDataBounds( |
| + const char* functionName, |
| + WebGLBuffer* sourceBuffer, |
| + GLintptr srcByteOffset, |
| + long long subByteLength) { |
| + CheckedNumeric<long long> srcEnd = srcByteOffset; |
| + srcEnd += subByteLength; |
| + if (!srcEnd.IsValid() || srcEnd.ValueOrDie() > sourceBuffer->getSize()) { |
| + synthesizeGLError(GL_INVALID_VALUE, functionName, |
| + "overflow of bound buffer"); |
| + return "Invalid value: overflow of bound buffer"; |
| + } |
| + |
| + return nullptr; |
| +} |
| + |
| void WebGL2RenderingContextBase::removeBoundBuffer(WebGLBuffer* buffer) { |
| if (m_boundCopyReadBuffer == buffer) |
| m_boundCopyReadBuffer = nullptr; |