Title: [126747] trunk/Source/WebCore
Revision
126747
Author
[email protected]
Date
2012-08-27 04:38:44 -0700 (Mon, 27 Aug 2012)

Log Message

Web Inspector: [WebGL] Implement serializing WebGL state and replaying it later
https://bugs.webkit.org/show_bug.cgi?id=94933

Patch by Andrey Adaikin <[email protected]> on 2012-08-27
Reviewed by Pavel Feldman.

This will allow us to save the WebGL state at any arbitrary point in order to replay a trace log later on another canvas.
Main points of the change:
- Allow every Resource and Call instances be serialized to a Replayable, and resurrected back during a replay on another canvas.

- Before executing an original WebGL call and saving it to a trace log, first serialize all the Resources that will be involved in
  this call, if they are not yet serialized. The latter part is implemented with a Cache instance, checking that we convert a Resource
  to a Replayable only once: the first time it was used in the trace log being collected. We do not need to serialize the subsequent
  changes to this Resource's state (if any), since they will be saved in the trace log being collected and replayed automatically.

- Some information about the WebGL state may be requested in the runtime (e.g. with the gl.getParameter method). We use this wherever
  possible instead of collecting Calls in a Resource log. Otherwise, we have to collect WebGL calls (like gl.texImage2D) in order to
  replay them later.

- Later the Replayable objects hierarchy (the TraceLog) may be serialized further for cross-device transmission, for example.

* inspector/InjectedScriptWebGLModuleSource.js:
(.):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (126746 => 126747)


--- trunk/Source/WebCore/ChangeLog	2012-08-27 11:32:46 UTC (rev 126746)
+++ trunk/Source/WebCore/ChangeLog	2012-08-27 11:38:44 UTC (rev 126747)
@@ -1,3 +1,128 @@
+2012-08-27  Andrey Adaikin  <[email protected]>
+
+        Web Inspector: [WebGL] Implement serializing WebGL state and replaying it later
+        https://bugs.webkit.org/show_bug.cgi?id=94933
+
+        Reviewed by Pavel Feldman.
+
+        This will allow us to save the WebGL state at any arbitrary point in order to replay a trace log later on another canvas.
+        Main points of the change:
+        - Allow every Resource and Call instances be serialized to a Replayable, and resurrected back during a replay on another canvas.
+
+        - Before executing an original WebGL call and saving it to a trace log, first serialize all the Resources that will be involved in
+          this call, if they are not yet serialized. The latter part is implemented with a Cache instance, checking that we convert a Resource
+          to a Replayable only once: the first time it was used in the trace log being collected. We do not need to serialize the subsequent
+          changes to this Resource's state (if any), since they will be saved in the trace log being collected and replayed automatically.
+
+        - Some information about the WebGL state may be requested in the runtime (e.g. with the gl.getParameter method). We use this wherever
+          possible instead of collecting Calls in a Resource log. Otherwise, we have to collect WebGL calls (like gl.texImage2D) in order to
+          replay them later.
+
+        - Later the Replayable objects hierarchy (the TraceLog) may be serialized further for cross-device transmission, for example.
+        
+
+        * inspector/InjectedScriptWebGLModuleSource.js:
+        (.):
+
+2012-08-27  Sheriff Bot  <[email protected]>
+
+        Unreviewed, rolling out r126694 and r126731.
+        http://trac.webkit.org/changeset/126694
+        http://trac.webkit.org/changeset/126731
+        https://bugs.webkit.org/show_bug.cgi?id=95069
+
+        Causes all pages to be rendered as a completely black page in
+        Qt WebKit2 (Requested by carewolf on #webkit).
+
+        * platform/graphics/GraphicsContext3D.h:
+        * platform/graphics/texmap/TextureMapperGL.cpp:
+        (SharedGLData):
+        (WebCore::TextureMapperGLData::SharedGLData::getCurrentGLContext):
+        (WebCore::TextureMapperGLData::SharedGLData::currentSharedGLData):
+        (WebCore::TextureMapperGLData::SharedGLData::SharedGLData):
+        (WebCore::TextureMapperGLData::sharedGLData):
+        (WebCore::TextureMapperGLData::TextureMapperGLData):
+        (TextureMapperGLData):
+        (WebCore::scissorClip):
+        (WebCore::TextureMapperGL::ClipStack::apply):
+        (WebCore::TextureMapperGLData::initializeStencil):
+        (WebCore::TextureMapperGL::TextureMapperGL):
+        (WebCore::TextureMapperGL::beginPainting):
+        (WebCore::TextureMapperGL::endPainting):
+        (WebCore::TextureMapperGL::drawQuad):
+        (WebCore::TextureMapperGL::drawBorder):
+        (WebCore):
+        (WebCore::TextureMapperGL::drawTextureRectangleARB):
+        (WebCore::TextureMapperGL::drawTexture):
+        (WebCore::viewportMatrix):
+        (WebCore::TextureMapperGL::drawTextureWithAntialiasing):
+        (WebCore::TextureMapperGL::drawTexturedQuadWithProgram):
+        (WebCore::BitmapTextureGL::didReset):
+        (WebCore::BitmapTextureGL::updateContents):
+        (WebCore::TextureMapperGL::drawFiltered):
+        (WebCore::BitmapTextureGL::initializeStencil):
+        (WebCore::BitmapTextureGL::clearIfNeeded):
+        (WebCore::BitmapTextureGL::createFboIfNeeded):
+        (WebCore::BitmapTextureGL::bind):
+        (WebCore::BitmapTextureGL::~BitmapTextureGL):
+        (WebCore::TextureMapperGL::bindDefaultSurface):
+        (WebCore::TextureMapperGL::beginScissorClip):
+        (WebCore::TextureMapperGL::beginClip):
+        (WebCore::TextureMapperGL::endClip):
+        (WebCore::TextureMapperGL::createTexture):
+        * platform/graphics/texmap/TextureMapperGL.h:
+        (TextureMapperGL):
+        (ClipStack):
+        (WebCore::BitmapTextureGL::textureTarget):
+        (BitmapTextureGL):
+        (WebCore::BitmapTextureGL::BitmapTextureGL):
+        * platform/graphics/texmap/TextureMapperShaderManager.cpp:
+        (WebCore):
+        (WebCore::TextureMapperShaderManager::getShaderProgram):
+        (WebCore::TextureMapperShaderProgram::TextureMapperShaderProgram):
+        (WebCore::TextureMapperShaderProgram::initializeProgram):
+        (WebCore::TextureMapperShaderProgram::getUniformLocation):
+        (WebCore::TextureMapperShaderProgram::~TextureMapperShaderProgram):
+        (WebCore::TextureMapperShaderProgramSimple::TextureMapperShaderProgramSimple):
+        (WebCore::TextureMapperShaderProgramSolidColor::TextureMapperShaderProgramSolidColor):
+        (WebCore::TextureMapperShaderProgramRectSimple::TextureMapperShaderProgramRectSimple):
+        (WebCore::TextureMapperShaderProgramOpacityAndMask::TextureMapperShaderProgramOpacityAndMask):
+        (WebCore::TextureMapperShaderProgramRectOpacityAndMask::TextureMapperShaderProgramRectOpacityAndMask):
+        (WebCore::TextureMapperShaderProgramAntialiasingNoMask::TextureMapperShaderProgramAntialiasingNoMask):
+        (WebCore::TextureMapperShaderManager::TextureMapperShaderManager):
+        (WebCore::StandardFilterProgram::~StandardFilterProgram):
+        (WebCore::StandardFilterProgram::StandardFilterProgram):
+        (WebCore::StandardFilterProgram::create):
+        (WebCore::StandardFilterProgram::prepare):
+        (WebCore::TextureMapperShaderManager::getShaderForFilter):
+        * platform/graphics/texmap/TextureMapperShaderManager.h:
+        (WebCore::TextureMapperShaderProgram::id):
+        (WebCore::TextureMapperShaderProgram::vertexAttrib):
+        (TextureMapperShaderProgram):
+        (WebCore::TextureMapperShaderProgram::matrixLocation):
+        (WebCore::TextureMapperShaderProgram::flipLocation):
+        (WebCore::TextureMapperShaderProgram::textureSizeLocation):
+        (WebCore::TextureMapperShaderProgram::sourceTextureLocation):
+        (WebCore::TextureMapperShaderProgram::maskTextureLocation):
+        (WebCore::TextureMapperShaderProgram::opacityLocation):
+        (WebCore::TextureMapperShaderProgram::isValidUniformLocation):
+        (StandardFilterProgram):
+        (WebCore::StandardFilterProgram::vertexAttrib):
+        (WebCore::StandardFilterProgram::texCoordAttrib):
+        (WebCore::StandardFilterProgram::textureUniform):
+        (WebCore::TextureMapperShaderProgramSimple::create):
+        (TextureMapperShaderProgramSimple):
+        (WebCore::TextureMapperShaderProgramRectSimple::create):
+        (WebCore::TextureMapperShaderProgramOpacityAndMask::create):
+        (WebCore::TextureMapperShaderProgramRectOpacityAndMask::create):
+        (WebCore::TextureMapperShaderProgramSolidColor::create):
+        (WebCore::TextureMapperShaderProgramSolidColor::colorLocation):
+        (TextureMapperShaderProgramSolidColor):
+        (WebCore::TextureMapperShaderProgramAntialiasingNoMask::create):
+        (WebCore::TextureMapperShaderProgramAntialiasingNoMask::expandedQuadVerticesInTextureCoordinatesLocation):
+        (WebCore::TextureMapperShaderProgramAntialiasingNoMask::expandedQuadEdgesInScreenSpaceLocation):
+        (TextureMapperShaderProgramAntialiasingNoMask):
+
 2012-08-27  Ilya Tikhonovsky  <[email protected]>
 
         Web Inspector: HeapSnapshot: filter weak links in markQueriableHeapObjects and two other minor fixes.

Modified: trunk/Source/WebCore/inspector/InjectedScriptWebGLModuleSource.js (126746 => 126747)


--- trunk/Source/WebCore/inspector/InjectedScriptWebGLModuleSource.js	2012-08-27 11:32:46 UTC (rev 126746)
+++ trunk/Source/WebCore/inspector/InjectedScriptWebGLModuleSource.js	2012-08-27 11:38:44 UTC (rev 126747)
@@ -245,6 +245,49 @@
             if (!Resource.forObject(this._args[i]))
                 this._args[i] = TypeUtils.clone(this._args[i]);
         }
+    },
+
+    /**
+     * @param {Cache} cache
+     * @return {ReplayableCall}
+     */
+    toReplayable: function(cache)
+    {
+        this.freeze();
+        var thisObject = /** @type {ReplayableResource} */ Resource.toReplayable(this._thisObject, cache);
+        var result = Resource.toReplayable(this._result, cache);
+        var args = this._args.map(function(obj) {
+            return Resource.toReplayable(obj, cache);
+        });
+        return new ReplayableCall(thisObject, this._functionName, args, result);
+    },
+
+    /**
+     * @param {ReplayableCall} replayableCall
+     * @param {Cache} cache
+     * @return {Call}
+     */
+    replay: function(replayableCall, cache)
+    {
+        var replayObject = ReplayableResource.replay(replayableCall.resource(), cache);
+        var replayFunction = replayObject[replayableCall.functionName()];
+        console.assert(typeof replayFunction === "function", "Expected a function to replay");
+        var replayArgs = replayableCall.args().map(function(obj) {
+            return ReplayableResource.replay(obj, cache);
+        });
+        var replayResult = replayFunction.apply(replayObject, replayArgs);
+        if (replayableCall.result() instanceof ReplayableResource) {
+            var resource = replayableCall.result().replay(cache);
+            if (!resource.wrappedObject())
+                resource.setWrappedObject(replayResult);
+        }
+
+        this._thisObject = replayObject;
+        this._functionName = replayableCall.functionName();
+        this._args = replayArgs;
+        this._result = replayResult;
+        this._freezed = true;
+        return this;
     }
 }
 
@@ -302,7 +345,8 @@
      */
     replay: function(cache)
     {
-        // FIXME: Do the replay.
+        var call = Object.create(Call.prototype);
+        return call.replay(this, cache);
     }
 }
 
@@ -338,6 +382,17 @@
     return null;
 }
 
+/**
+ * @param {Resource|*} obj
+ * @param {Cache} cache
+ * @return {ReplayableResource|*}
+ */
+Resource.toReplayable = function(obj, cache)
+{
+    var resource = Resource.forObject(obj);
+    return resource ? resource.toReplayable(cache) : obj;
+}
+
 Resource.prototype = {
     /**
      * @return {number}
@@ -400,6 +455,66 @@
     },
 
     /**
+     * @param {Cache} cache
+     * @return {ReplayableResource}
+     */
+    toReplayable: function(cache)
+    {
+        var result = cache.get(this._id);
+        if (result)
+            return result;
+        var data = {
+            id: this._id
+        };
+        result = new ReplayableResource(this, data);
+        cache.put(this._id, result); // Put into the cache early to avoid loops.
+        data.calls = this._calls.map(function(call) {
+            return call.toReplayable(cache);
+        });
+        this._populateReplayableData(data, cache);
+        return result;
+    },
+
+    /**
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _populateReplayableData: function(data, cache)
+    {
+        // Do nothing. Should be overridden by subclasses.
+    },
+
+    /**
+     * @param {Object} data
+     * @param {Cache} cache
+     * @return {Resource}
+     */
+    replay: function(data, cache)
+    {
+        var resource = cache.get(data.id);
+        if (resource)
+            return resource;
+        this._id = data.id;
+        this._resourceManager = null;
+        this._calls = [];
+        this._wrappedObject = null;
+        cache.put(data.id, this); // Put into the cache early to avoid loops.
+        this._doReplayCalls(data, cache);
+        console.assert(this._wrappedObject, "Resource should be reconstructed!");
+        return this;
+    },
+
+    /**
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _doReplayCalls: function(data, cache)
+    {
+        for (var i = 0, n = data.calls.length; i < n; ++i)
+            this._calls.push(data.calls[i].replay(cache));
+    },
+
+    /**
      * @param {Call} call
      */
     pushCall: function(call)
@@ -424,6 +539,8 @@
  */
 function ReplayableResource(originalResource, data)
 {
+    this._proto = originalResource.__proto__;
+    this._data = data;
 }
 
 ReplayableResource.prototype = {
@@ -433,11 +550,24 @@
      */
     replay: function(cache)
     {
-        // FIXME: Do the replay.
+        var result = Object.create(this._proto);
+        result = result.replay(this._data, cache)
+        console.assert(result.__proto__ === this._proto, "Wrong type of a replay result");
+        return result;
     }
 }
 
 /**
+ * @param {ReplayableResource|*} obj
+ * @param {Cache} cache
+ * @return {*}
+ */
+ReplayableResource.replay = function(obj, cache)
+{
+    return (obj instanceof ReplayableResource) ? obj.replay(cache).wrappedObject() : obj;
+}
+
+/**
  * @constructor
  * @extends {Resource}
  */
@@ -449,6 +579,69 @@
 
 WebGLBoundResource.prototype = {
     /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _populateReplayableData: function(data, cache)
+    {
+        var state = this._state;
+        data.state = {};
+        Object.keys(state).forEach(function(parameter) {
+            data.state[parameter] = Resource.toReplayable(state[parameter], cache);
+        });
+    },
+
+    /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _doReplayCalls: function(data, cache)
+    {
+        var state = {};
+        Object.keys(data.state).forEach(function(parameter) {
+            state[parameter] = ReplayableResource.replay(data.state[parameter], cache);
+        });
+        this._state = state;
+
+        var gl = this._replayContextResource(data, cache).wrappedObject();
+
+        var bindingsData = {
+            TEXTURE_2D: ["bindTexture", "TEXTURE_BINDING_2D"],
+            TEXTURE_CUBE_MAP: ["bindTexture", "TEXTURE_BINDING_CUBE_MAP"],
+            ARRAY_BUFFER: ["bindBuffer", "ARRAY_BUFFER_BINDING"],
+            ELEMENT_ARRAY_BUFFER: ["bindBuffer", "ELEMENT_ARRAY_BUFFER_BINDING"],
+            FRAMEBUFFER: ["bindFramebuffer", "FRAMEBUFFER_BINDING"],
+            RENDERBUFFER: ["bindRenderbuffer", "RENDERBUFFER_BINDING"]
+        };
+        var originalBindings = {};
+        Object.keys(bindingsData).forEach(function(bindingTarget) {
+            var bindingParameter = bindingsData[bindingTarget][1];
+            originalBindings[bindingTarget] = gl.getParameter(gl[bindingParameter]);
+        });
+
+        Resource.prototype._doReplayCalls.call(this, data, cache);
+
+        Object.keys(bindingsData).forEach(function(bindingTarget) {
+            var bindMethodName = bindingsData[bindingTarget][0];
+            gl[bindMethodName].call(gl, gl[bindingTarget], originalBindings[bindingTarget]);
+        });
+    },
+
+    _replayContextResource: function(data, cache)
+    {
+        var calls = data.calls;
+        for (var i = 0, n = calls.length; i < n; ++i) {
+            var resource = ReplayableResource.replay(calls[i].resource(), cache);
+            var contextResource = WebGLRenderingContextResource.forObject(resource);
+            if (contextResource)
+                return contextResource;
+        }
+        return null;
+    },
+
+    /**
      * @param {number} target
      * @param {string} bindMethodName
      */
@@ -473,7 +666,31 @@
 }
 
 WebGLTextureResource.prototype = {
-    /** @inheritDoc */
+    /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _doReplayCalls: function(data, cache)
+    {
+        var gl = this._replayContextResource(data, cache).wrappedObject();
+
+        var state = {};
+        WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
+            state[parameter] = gl.getParameter(gl[parameter]);
+        });
+
+        WebGLBoundResource.prototype._doReplayCalls.call(this, data, cache);
+
+        WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
+            gl.pixelStorei(gl[parameter], state[parameter]);
+        });
+    },
+
+    /**
+     * @override
+     * @param {Call} call
+     */
     pushCall: function(call)
     {
         var gl = call.resource().wrappedObject();
@@ -536,8 +753,111 @@
     Resource.call(this, wrappedObject);
 }
 
+/**
+ * @type {Object.<number,string>}
+ */
+WebGLProgramResource.UniformMethodNames = null;
+
 WebGLProgramResource.prototype = {
-    /** @inheritDoc */
+    /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _populateReplayableData: function(data, cache)
+    {
+        var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
+        var program = this.wrappedObject();
+
+        var uniforms = [];
+        var uniformsCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
+        for (var i = 0; i < uniformsCount; ++i) {
+            var activeInfo = gl.getActiveUniform(program, i);
+            if (!activeInfo)
+                continue;
+            var uniformLocation = gl.getUniformLocation(program, activeInfo.name);
+            if (!uniformLocation)
+                continue;
+            var value = gl.getUniform(program, uniformLocation);
+            uniforms.push({
+                name: activeInfo.name,
+                type: activeInfo.type,
+                value: value
+            });
+        }
+        data.uniforms = uniforms;
+    },
+
+    /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _doReplayCalls: function(data, cache)
+    {
+        Resource.prototype._doReplayCalls.call(this, data, cache);
+        var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
+        var program = this.wrappedObject();
+
+        var originalProgram = gl.getParameter(gl.CURRENT_PROGRAM);
+        var currentProgram = originalProgram;
+        
+        data.uniforms.forEach(function(uniform) {
+            var uniformLocation = gl.getUniformLocation(program, uniform.name);
+            if (!uniformLocation)
+                return;
+            if (currentProgram !== program) {
+                currentProgram = program;
+                gl.useProgram(program);
+            }
+            var methodName = this._uniformMethodNameByType(gl, uniform.type);
+            if (methodName.indexOf("Matrix") === -1)
+                gl[methodName].call(gl, uniformLocation, uniform.value);
+            else
+                gl[methodName].call(gl, uniformLocation, false, uniform.value);
+        }.bind(this));
+
+        if (currentProgram !== originalProgram)
+            gl.useProgram(originalProgram);
+    },
+
+    /**
+     * @param {WebGLRenderingContext} gl
+     * @param {number} type
+     * @return {string}
+     */
+    _uniformMethodNameByType: function(gl, type)
+    {
+        var uniformMethodNames = WebGLProgramResource.UniformMethodNames;
+        if (!uniformMethodNames) {
+            uniformMethodNames = {};
+            uniformMethodNames[gl.FLOAT] = "uniform1f";
+            uniformMethodNames[gl.FLOAT_VEC2] = "uniform2fv";
+            uniformMethodNames[gl.FLOAT_VEC3] = "uniform3fv";
+            uniformMethodNames[gl.FLOAT_VEC4] = "uniform4fv";
+            uniformMethodNames[gl.INT] = "uniform1i";
+            uniformMethodNames[gl.BOOL] = "uniform1i";
+            uniformMethodNames[gl.SAMPLER_2D] = "uniform1i";
+            uniformMethodNames[gl.SAMPLER_CUBE] = "uniform1i";
+            uniformMethodNames[gl.INT_VEC2] = "uniform2iv";
+            uniformMethodNames[gl.BOOL_VEC2] = "uniform2iv";
+            uniformMethodNames[gl.INT_VEC3] = "uniform3iv";
+            uniformMethodNames[gl.BOOL_VEC3] = "uniform3iv";
+            uniformMethodNames[gl.INT_VEC4] = "uniform4iv";
+            uniformMethodNames[gl.BOOL_VEC4] = "uniform4iv";
+            uniformMethodNames[gl.FLOAT_MAT2] = "uniformMatrix2fv";
+            uniformMethodNames[gl.FLOAT_MAT3] = "uniformMatrix3fv";
+            uniformMethodNames[gl.FLOAT_MAT4] = "uniformMatrix4fv";
+            WebGLProgramResource.UniformMethodNames = uniformMethodNames;
+        }
+        console.assert(uniformMethodNames[type], "Unknown uniform type " + type);
+        return uniformMethodNames[type];
+    },
+
+    /**
+     * @override
+     * @param {Call} call
+     */
     pushCall: function(call)
     {
         // FIXME: remove any older calls that no longer contribute to the resource state.
@@ -558,7 +878,10 @@
 }
 
 WebGLShaderResource.prototype = {
-    /** @inheritDoc */
+    /**
+     * @override
+     * @param {Call} call
+     */
     pushCall: function(call)
     {
         // FIXME: remove any older calls that no longer contribute to the resource state.
@@ -579,7 +902,10 @@
 }
 
 WebGLBufferResource.prototype = {
-    /** @inheritDoc */
+    /**
+     * @override
+     * @param {Call} call
+     */
     pushCall: function(call)
     {
         // FIXME: remove any older calls that no longer contribute to the resource state.
@@ -600,7 +926,10 @@
 }
 
 WebGLFramebufferResource.prototype = {
-    /** @inheritDoc */
+    /**
+     * @override
+     * @param {Call} call
+     */
     pushCall: function(call)
     {
         // FIXME: remove any older calls that no longer contribute to the resource state.
@@ -620,7 +949,10 @@
 }
 
 WebGLRenderbufferResource.prototype = {
-    /** @inheritDoc */
+    /**
+     * @override
+     * @param {Call} call
+     */
     pushCall: function(call)
     {
         // FIXME: remove any older calls that no longer contribute to the resource state.
@@ -634,17 +966,35 @@
  * @constructor
  * @extends {Resource}
  * @param {WebGLRenderingContext} glContext
+ * @param {Function} replayContextCallback
  */
-function WebGLRenderingContextResource(glContext)
+function WebGLRenderingContextResource(glContext, replayContextCallback)
 {
     Resource.call(this, glContext);
     this._proxyObject = null;
+    this._replayContextCallback = replayContextCallback;
 }
 
 /**
  * @const
  * @type {Array.<string>}
  */
+WebGLRenderingContextResource.GLCapabilities = [
+    "BLEND",
+    "CULL_FACE",
+    "DEPTH_TEST",
+    "DITHER",
+    "POLYGON_OFFSET_FILL",
+    "SAMPLE_ALPHA_TO_COVERAGE",
+    "SAMPLE_COVERAGE",
+    "SCISSOR_TEST",
+    "STENCIL_TEST"
+];
+
+/**
+ * @const
+ * @type {Array.<string>}
+ */
 WebGLRenderingContextResource.PixelStoreParameters = [
     "PACK_ALIGNMENT",
     "UNPACK_ALIGNMENT",
@@ -654,6 +1004,62 @@
 ];
 
 /**
+ * @const
+ * @type {Array.<string>}
+ */
+WebGLRenderingContextResource.StateParameters = [
+    "ACTIVE_TEXTURE",
+    "ARRAY_BUFFER_BINDING",
+    "BLEND_COLOR",
+    "BLEND_DST_ALPHA",
+    "BLEND_DST_RGB",
+    "BLEND_EQUATION_ALPHA",
+    "BLEND_EQUATION_RGB",
+    "BLEND_SRC_ALPHA",
+    "BLEND_SRC_RGB",
+    "COLOR_CLEAR_VALUE",
+    "COLOR_WRITEMASK",
+    "CULL_FACE_MODE",
+    "CURRENT_PROGRAM",
+    "DEPTH_CLEAR_VALUE",
+    "DEPTH_FUNC",
+    "DEPTH_RANGE",
+    "DEPTH_WRITEMASK",
+    "ELEMENT_ARRAY_BUFFER_BINDING",
+    "FRAMEBUFFER_BINDING",
+    "FRONT_FACE",
+    "GENERATE_MIPMAP_HINT",
+    "LINE_WIDTH",
+    "PACK_ALIGNMENT",
+    "POLYGON_OFFSET_FACTOR",
+    "POLYGON_OFFSET_UNITS",
+    "RENDERBUFFER_BINDING",
+    "SAMPLE_COVERAGE_INVERT",
+    "SAMPLE_COVERAGE_VALUE",
+    "SCISSOR_BOX",
+    "STENCIL_BACK_FAIL",
+    "STENCIL_BACK_FUNC",
+    "STENCIL_BACK_PASS_DEPTH_FAIL",
+    "STENCIL_BACK_PASS_DEPTH_PASS",
+    "STENCIL_BACK_REF",
+    "STENCIL_BACK_VALUE_MASK",
+    "STENCIL_BACK_WRITEMASK",
+    "STENCIL_CLEAR_VALUE",
+    "STENCIL_FAIL",
+    "STENCIL_FUNC",
+    "STENCIL_PASS_DEPTH_FAIL",
+    "STENCIL_PASS_DEPTH_PASS",
+    "STENCIL_REF",
+    "STENCIL_VALUE_MASK",
+    "STENCIL_WRITEMASK",
+    "UNPACK_ALIGNMENT",
+    "UNPACK_COLORSPACE_CONVERSION_WEBGL",
+    "UNPACK_FLIP_Y_WEBGL",
+    "UNPACK_PREMULTIPLY_ALPHA_WEBGL",
+    "VIEWPORT"
+];
+
+/**
  * @param {*} obj
  * @return {WebGLRenderingContextResource}
  */
@@ -681,6 +1087,153 @@
     },
 
     /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _populateReplayableData: function(data, cache)
+    {
+        var gl = this.wrappedObject();
+        data.replayContextCallback = this._replayContextCallback;
+
+        // FIXME: Save the getError() status and restore it after taking the GL state snapshot.
+
+        // Take a full GL state snapshot.
+        var glState = {};
+        WebGLRenderingContextResource.GLCapabilities.forEach(function(parameter) {
+            glState[parameter] = gl.isEnabled(gl[parameter]);
+        });
+        WebGLRenderingContextResource.StateParameters.forEach(function(parameter) {
+            glState[parameter] = Resource.toReplayable(gl.getParameter(gl[parameter]), cache);
+            // FIXME: Call while(gl.getError() != gl.NO_ERROR) {...} to check if a particular parameter is supported.
+        });
+
+        // VERTEX_ATTRIB_ARRAYS
+        var maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+        var vertexAttribParameters = ["VERTEX_ATTRIB_ARRAY_BUFFER_BINDING", "VERTEX_ATTRIB_ARRAY_ENABLED", "VERTEX_ATTRIB_ARRAY_SIZE", "VERTEX_ATTRIB_ARRAY_STRIDE", "VERTEX_ATTRIB_ARRAY_TYPE", "VERTEX_ATTRIB_ARRAY_NORMALIZED", "CURRENT_VERTEX_ATTRIB"];
+        var vertexAttribStates = [];
+        for (var i = 0; i < maxVertexAttribs; ++i) {
+            var state = {};
+            vertexAttribParameters.forEach(function(attribParameter) {
+                state[attribParameter] = Resource.toReplayable(gl.getVertexAttrib(i, gl[attribParameter]), cache);
+            });
+            state.VERTEX_ATTRIB_ARRAY_POINTER = gl.getVertexAttribOffset(i, gl.VERTEX_ATTRIB_ARRAY_POINTER);
+            vertexAttribStates.push(state);
+        }
+        glState.vertexAttribStates = vertexAttribStates;
+
+        // TEXTURES
+        var currentTextureBinding = gl.getParameter(gl.ACTIVE_TEXTURE);
+        var maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
+        var textureBindings = [];
+        for (var i = 0; i < maxTextureImageUnits; ++i) {
+            gl.activeTexture(gl.TEXTURE0 + i);
+            var state = {
+                TEXTURE_2D: Resource.toReplayable(gl.getParameter(gl.TEXTURE_BINDING_2D), cache),
+                TEXTURE_CUBE_MAP: Resource.toReplayable(gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP), cache)
+            };
+            textureBindings.push(state);
+        }
+        glState.textureBindings = textureBindings;
+        gl.activeTexture(currentTextureBinding);
+
+        data.glState = glState;
+    },
+
+    /**
+     * @override
+     * @param {Object} data
+     * @param {Cache} cache
+     */
+    _doReplayCalls: function(data, cache)
+    {
+        this._proxyObject = null;
+        this._replayContextCallback = data.replayContextCallback;
+
+        var context = this._replayContextCallback();
+        var contextResource = Resource.forObject(context);
+        var gl = contextResource ? contextResource.wrappedObject() : context;
+        this.setWrappedObject(gl);
+
+        var glState = data.glState;
+        gl.bindFramebuffer(gl.FRAMEBUFFER, ReplayableResource.replay(glState.FRAMEBUFFER_BINDING, cache));
+        gl.bindRenderbuffer(gl.RENDERBUFFER, ReplayableResource.replay(glState.RENDERBUFFER_BINDING, cache));
+
+        // Enable or disable server-side GL capabilities.
+        WebGLRenderingContextResource.GLCapabilities.forEach(function(parameter) {
+            console.assert(parameter in glState);
+            if (glState[parameter])
+                gl.enable(gl[parameter]);
+            else
+                gl.disable(gl[parameter]);
+        });
+
+        gl.blendColor(glState.BLEND_COLOR[0], glState.BLEND_COLOR[1], glState.BLEND_COLOR[2], glState.BLEND_COLOR[3]);
+        gl.blendEquationSeparate(glState.BLEND_EQUATION_RGB, glState.BLEND_EQUATION_ALPHA);
+        gl.blendFuncSeparate(glState.BLEND_SRC_RGB, glState.BLEND_DST_RGB, glState.BLEND_SRC_ALPHA, glState.BLEND_DST_ALPHA);
+        gl.clearColor(glState.COLOR_CLEAR_VALUE[0], glState.COLOR_CLEAR_VALUE[1], glState.COLOR_CLEAR_VALUE[2], glState.COLOR_CLEAR_VALUE[3]);
+        gl.clearDepth(glState.DEPTH_CLEAR_VALUE);
+        gl.clearStencil(glState.STENCIL_CLEAR_VALUE);
+        gl.colorMask(glState.COLOR_WRITEMASK[0], glState.COLOR_WRITEMASK[1], glState.COLOR_WRITEMASK[2], glState.COLOR_WRITEMASK[3]);
+        gl.cullFace(glState.CULL_FACE_MODE);
+        gl.depthFunc(glState.DEPTH_FUNC);
+        gl.depthMask(glState.DEPTH_WRITEMASK);
+        gl.depthRange(glState.DEPTH_RANGE[0], glState.DEPTH_RANGE[1]);
+        gl.frontFace(glState.FRONT_FACE);
+        gl.hint(gl.GENERATE_MIPMAP_HINT, glState.GENERATE_MIPMAP_HINT);
+        gl.lineWidth(glState.LINE_WIDTH);
+
+        WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
+            gl.pixelStorei(gl[parameter], glState[parameter]);
+        });
+
+        gl.polygonOffset(glState.POLYGON_OFFSET_FACTOR, glState.POLYGON_OFFSET_UNITS);
+        gl.sampleCoverage(glState.SAMPLE_COVERAGE_VALUE, glState.SAMPLE_COVERAGE_INVERT);
+        gl.stencilFuncSeparate(gl.FRONT, glState.STENCIL_FUNC, glState.STENCIL_REF, glState.STENCIL_VALUE_MASK);
+        gl.stencilFuncSeparate(gl.BACK, glState.STENCIL_BACK_FUNC, glState.STENCIL_BACK_REF, glState.STENCIL_BACK_VALUE_MASK);
+        gl.stencilOpSeparate(gl.FRONT, glState.STENCIL_FAIL, glState.STENCIL_PASS_DEPTH_FAIL, glState.STENCIL_PASS_DEPTH_PASS);
+        gl.stencilOpSeparate(gl.BACK, glState.STENCIL_BACK_FAIL, glState.STENCIL_BACK_PASS_DEPTH_FAIL, glState.STENCIL_BACK_PASS_DEPTH_PASS);
+        gl.stencilMaskSeparate(gl.FRONT, glState.STENCIL_WRITEMASK);
+        gl.stencilMaskSeparate(gl.BACK, glState.STENCIL_BACK_WRITEMASK);
+
+        gl.scissor(glState.SCISSOR_BOX[0], glState.SCISSOR_BOX[1], glState.SCISSOR_BOX[2], glState.SCISSOR_BOX[3]);
+        gl.viewport(glState.VIEWPORT[0], glState.VIEWPORT[1], glState.VIEWPORT[2], glState.VIEWPORT[3]);
+
+        gl.useProgram(ReplayableResource.replay(glState.CURRENT_PROGRAM, cache));
+
+        // VERTEX_ATTRIB_ARRAYS
+        var maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+        for (var i = 0; i < maxVertexAttribs; ++i) {
+            var state = glState.vertexAttribStates[i] || {};
+            if (state.VERTEX_ATTRIB_ARRAY_ENABLED)
+                gl.enableVertexAttribArray(i);
+            else
+                gl.disableVertexAttribArray(i);
+            if (state.CURRENT_VERTEX_ATTRIB)
+                gl.vertexAttrib4fv(i, state.CURRENT_VERTEX_ATTRIB);
+            var buffer = ReplayableResource.replay(state.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, cache);
+            if (buffer) {
+                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+                gl.vertexAttribPointer(i, state.VERTEX_ATTRIB_ARRAY_SIZE, state.VERTEX_ATTRIB_ARRAY_TYPE, state.VERTEX_ATTRIB_ARRAY_NORMALIZED, state.VERTEX_ATTRIB_ARRAY_STRIDE, state.VERTEX_ATTRIB_ARRAY_POINTER);
+            }
+        }
+        gl.bindBuffer(gl.ARRAY_BUFFER, ReplayableResource.replay(glState.ARRAY_BUFFER_BINDING, cache));
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ReplayableResource.replay(glState.ELEMENT_ARRAY_BUFFER_BINDING, cache));
+
+        // TEXTURES
+        var maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
+        for (var i = 0; i < maxTextureImageUnits; ++i) {
+            gl.activeTexture(gl.TEXTURE0 + i);
+            var state = glState.textureBindings[i] || {};
+            gl.bindTexture(gl.TEXTURE_2D, ReplayableResource.replay(state.TEXTURE_2D, cache));
+            gl.bindTexture(gl.TEXTURE_CUBE_MAP, ReplayableResource.replay(state.TEXTURE_CUBE_MAP, cache));
+        }
+        gl.activeTexture(glState.ACTIVE_TEXTURE);
+
+        return Resource.prototype._doReplayCalls.call(this, data, cache);
+    },
+
+    /**
      * @param {Object|number} target
      * @return {Resource}
      */
@@ -693,39 +1246,39 @@
         var bindingTarget;
         var bindMethodName;
         switch (target) {
-            case gl.ARRAY_BUFFER:
-                bindingTarget = gl.ARRAY_BUFFER_BINDING;
-                bindMethodName = "bindBuffer";
-                break;
-            case gl.ELEMENT_ARRAY_BUFFER:
-                bindingTarget = gl.ELEMENT_ARRAY_BUFFER_BINDING;
-                bindMethodName = "bindBuffer";
-                break;
-            case gl.TEXTURE_2D:
-                bindingTarget = gl.TEXTURE_BINDING_2D;
-                bindMethodName = "bindTexture";
-                break;
-            case gl.TEXTURE_CUBE_MAP:
-            case gl.TEXTURE_CUBE_MAP_POSITIVE_X:
-            case gl.TEXTURE_CUBE_MAP_NEGATIVE_X:
-            case gl.TEXTURE_CUBE_MAP_POSITIVE_Y:
-            case gl.TEXTURE_CUBE_MAP_NEGATIVE_Y:
-            case gl.TEXTURE_CUBE_MAP_POSITIVE_Z:
-            case gl.TEXTURE_CUBE_MAP_NEGATIVE_Z:
-                bindingTarget = gl.TEXTURE_BINDING_CUBE_MAP;
-                bindMethodName = "bindTexture";
-                break;
-            case gl.FRAMEBUFFER:
-                bindingTarget = gl.FRAMEBUFFER_BINDING;
-                bindMethodName = "bindFramebuffer";
-                break;
-            case gl.RENDERBUFFER:
-                bindingTarget = gl.RENDERBUFFER_BINDING;
-                bindMethodName = "bindRenderbuffer";
-                break;
-            default:
-                console.error("ASSERT_NOT_REACHED: unknown binding target " + target);
-                return null;
+        case gl.ARRAY_BUFFER:
+            bindingTarget = gl.ARRAY_BUFFER_BINDING;
+            bindMethodName = "bindBuffer";
+            break;
+        case gl.ELEMENT_ARRAY_BUFFER:
+            bindingTarget = gl.ELEMENT_ARRAY_BUFFER_BINDING;
+            bindMethodName = "bindBuffer";
+            break;
+        case gl.TEXTURE_2D:
+            bindingTarget = gl.TEXTURE_BINDING_2D;
+            bindMethodName = "bindTexture";
+            break;
+        case gl.TEXTURE_CUBE_MAP:
+        case gl.TEXTURE_CUBE_MAP_POSITIVE_X:
+        case gl.TEXTURE_CUBE_MAP_NEGATIVE_X:
+        case gl.TEXTURE_CUBE_MAP_POSITIVE_Y:
+        case gl.TEXTURE_CUBE_MAP_NEGATIVE_Y:
+        case gl.TEXTURE_CUBE_MAP_POSITIVE_Z:
+        case gl.TEXTURE_CUBE_MAP_NEGATIVE_Z:
+            bindingTarget = gl.TEXTURE_BINDING_CUBE_MAP;
+            bindMethodName = "bindTexture";
+            break;
+        case gl.FRAMEBUFFER:
+            bindingTarget = gl.FRAMEBUFFER_BINDING;
+            bindMethodName = "bindFramebuffer";
+            break;
+        case gl.RENDERBUFFER:
+            bindingTarget = gl.RENDERBUFFER_BINDING;
+            bindMethodName = "bindRenderbuffer";
+            break;
+        default:
+            console.error("ASSERT_NOT_REACHED: unknown binding target " + target);
+            return null;
         }
         resource = Resource.forObject(gl.getParameter(bindingTarget));
         if (resource)
@@ -965,7 +1518,7 @@
      */
     captureResource: function(resource)
     {
-        // FIXME: Capture current resource state to start the replay from.
+        resource.toReplayable(this._replayablesCache);
     },
 
     /**
@@ -973,7 +1526,12 @@
      */
     addCall: function(call)
     {
-        // FIXME: Convert the call to a ReplayableCall and push it.
+        var res = Resource.forObject(call.result());
+        if (res)
+            this.captureResource(res);
+        var size = this._replayablesCache.size();
+        this._replayableCalls.push(call.toReplayable(this._replayablesCache));
+        console.assert(this._replayablesCache.size() === size, "Internal error: We should have captured all the resources already by this time.");
     }
 }
 
@@ -1162,7 +1720,7 @@
      */
     wrapWebGLContext: function(glContext)
     {
-        var resource = Resource.forObject(glContext) || new WebGLRenderingContextResource(glContext);
+        var resource = Resource.forObject(glContext) || new WebGLRenderingContextResource(glContext, this._constructReplayContext.bind(this, glContext));
         this._manager.registerResource(resource);
         var proxy = resource.proxyObject();
         return proxy;
@@ -1227,6 +1785,7 @@
             return "";
         }
         // Return current screenshot.
+        // FIXME: Support replaying several canvases simultaneously.
         return this._replayContext.canvas.toDataURL();
     },
 
@@ -1236,6 +1795,26 @@
     _makeTraceLogId: function()
     {
         return "{\"injectedScriptId\":" + injectedScriptId + ",\"traceLogId\":" + (++this._lastTraceLogId) + "}";
+    },
+
+    /**
+     * @param {WebGLRenderingContext} originalGlContext
+     * @return {WebGLRenderingContext}
+     */
+    _constructReplayContext: function(originalGlContext)
+    {
+        var replayContext = originalGlContext["__replayContext"];
+        if (!replayContext) {
+            var canvas = originalGlContext.canvas.cloneNode(true);
+            // FIXME: Pass original context id instead of "experimental-webgl".
+            // FIXME: Pass original ContextAttributes to the getContext() method.
+            replayContext = canvas.getContext("experimental-webgl");
+            originalGlContext["__replayContext"] = replayContext;
+            this._replayContext = replayContext;
+        } else {
+            // FIXME: Reset the replay GL state and clear the canvas.
+        }
+        return replayContext;
     }
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to