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;
}
}