diff --git a/CHANGES.md b/CHANGES.md
index 8067f5b703ba..eacb3f72820c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -43,6 +43,7 @@ var geometry = BoxGeometry.createGeometry(box);
* Added the ability to specify a `minimumTerrainLevel` and `maximumTerrainLevel` when constructing an `ImageryLayer`. The layer will only be shown for terrain tiles within the specified range.
* Added `Math.setRandomNumberSeed` and `Math.nextRandomNumber` for generating repeatable random numbers.
* Added `Color.fromRandom` to generate random and partially random colors.
+* Added `Scene.debugShowFrustums` and `Scene.debugFrustumStatistics` for rendering debugging.
* Improved geometry batching performance by moving work to a web worker.
* Improved `WallGeometry` to follow the curvature of the earth.
* Fixed broken surface rendering in Columbus View when using the `EllipsoidTerrainProvider`.
diff --git a/Source/Renderer/DrawCommand.js b/Source/Renderer/DrawCommand.js
index 8807f54dbbfb..52e1cdb03aa6 100644
--- a/Source/Renderer/DrawCommand.js
+++ b/Source/Renderer/DrawCommand.js
@@ -163,6 +163,13 @@ define(function() {
* @see DrawCommand#boundingVolume
*/
this.debugShowBoundingVolume = false;
+
+ /**
+ * @private
+ *
+ * Used to implement {@see Scene.debugShowFrustums}.
+ */
+ this.debugOverlappingFrustums = 0;
};
/**
diff --git a/Source/Renderer/ShaderProgram.js b/Source/Renderer/ShaderProgram.js
index df1d51f23e8b..a76390b5cae9 100644
--- a/Source/Renderer/ShaderProgram.js
+++ b/Source/Renderer/ShaderProgram.js
@@ -2280,6 +2280,28 @@ define([
this._samplerUniforms = uniforms.samplerUniforms;
this._automaticUniforms = partitionedUniforms.automaticUniforms;
this._manualUniforms = partitionedUniforms.manualUniforms;
+
+ /**
+ * GLSL source for the shader program's vertex shader. This is the version of
+ * the source provided when the shader program was created, not the final
+ * source provided to WebGL, which includes Cesium bulit-ins.
+ *
+ * @type {String}
+ *
+ * @readonly
+ */
+ this.vertexShaderSource = vertexShaderSource;
+
+ /**
+ * GLSL source for the shader program's fragment shader. This is the version of
+ * the source provided when the shader program was created, not the final
+ * source provided to WebGL, which includes Cesium bulit-ins.
+ *
+ * @type {String}
+ *
+ * @readonly
+ */
+ this.fragmentShaderSource = fragmentShaderSource;
};
function extractShaderVersion(source) {
diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js
index cc9b0c65ab92..736a520fdf9f 100644
--- a/Source/Scene/Scene.js
+++ b/Source/Scene/Scene.js
@@ -242,6 +242,42 @@ define([
*/
this.debugCommandFilter = undefined;
+ /**
+ * This property is for debugging only; it is not for production use.
+ *
+ * When true
, commands are shaded based on the frustums they
+ * overlap. Commands in the closest frustum are tinted red, commands in
+ * the next closest are green, and commands in the farthest frustum are
+ * blue. If a command overlaps more than one frustum, the color components
+ * are combined, e.g., a command overlapping the first two frustums is tinted
+ * yellow.
+ *
+ *
+ * @type Boolean
+ *
+ * @default false
+ */
+ this.debugShowFrustums = false;
+
+ /**
+ * This property is for debugging only; it is not for production use.
+ *
+ * When {@see Scene.debugShowFrustums} is true
, this contains
+ * properties with statistics about the number of command execute per frustum.
+ * totalCommands
is the total number of commands executed, ignoring
+ * overlap. commandsInFrustums
is an array with the number of times
+ * commands are executed redundantly, e.g., how many commands overlap two or
+ * three frustums.
+ *
+ *
+ * @type Object
+ *
+ * @default undefined
+ *
+ * @readonly
+ */
+ this.debugFrustumStatistics = undefined;
+
this._debugSphere = undefined;
// initial guess at frustums.
@@ -377,8 +413,13 @@ define([
}
function insertIntoBin(scene, command, distance) {
+ if (scene.debugShowFrustums) {
+ command.debugOverlappingFrustums = 0;
+ }
+
var frustumCommandsList = scene._frustumCommandsList;
var length = frustumCommandsList.length;
+
for (var i = 0; i < length; ++i) {
var frustumCommands = frustumCommandsList[i];
var curNear = frustumCommands.near;
@@ -395,25 +436,20 @@ define([
// PERFORMANCE_IDEA: sort bins
frustumCommands.commands[frustumCommands.index++] = command;
- if (command.executeInClosestFrustum) {
- break;
+ if (scene.debugShowFrustums) {
+ command.debugOverlappingFrustums |= (1 << i);
}
- }
- }
-
- function insertIntoAllBins(scene, command) {
- var frustumCommandsList = scene._frustumCommandsList;
- var length = frustumCommandsList.length;
- for (var i = 0; i < length; ++i) {
- var frustumCommands = frustumCommandsList[i];
-
- // PERFORMANCE_IDEA: sort bins
- frustumCommands.commands[frustumCommands.index++] = command;
if (command.executeInClosestFrustum) {
break;
}
}
+
+ if (scene.debugShowFrustums) {
+ var cf = scene.debugFrustumStatistics.commandsInFrustums;
+ cf[command.debugOverlappingFrustums] = defined(cf[command.debugOverlappingFrustums]) ? cf[command.debugOverlappingFrustums] + 1 : 1;
+ ++scene.debugFrustumStatistics.totalCommands;
+ }
}
var scratchCullingVolume = new CullingVolume();
@@ -427,9 +463,16 @@ define([
var direction = camera.getDirectionWC();
var position = camera.getPositionWC();
+ if (scene.debugShowFrustums) {
+ scene.debugFrustumStatistics = {
+ totalCommands : 0,
+ commandsInFrustums : {}
+ };
+ }
+
var frustumCommandsList = scene._frustumCommandsList;
- var frustumsLength = frustumCommandsList.length;
- for (var n = 0; n < frustumsLength; ++n) {
+ var numberOfFrustums = frustumCommandsList.length;
+ for (var n = 0; n < numberOfFrustums; ++n) {
frustumCommandsList[n].index = 0;
}
@@ -468,15 +511,16 @@ define([
distances = transformedBV.getPlaneDistances(position, direction, distances);
near = Math.min(near, distances.start);
far = Math.max(far, distances.stop);
-
- insertIntoBin(scene, command, distances);
} else {
// Clear commands don't need a bounding volume - just add the clear to all frustums.
// If another command has no bounding volume, though, we need to use the camera's
// worst-case near and far planes to avoid clipping something important.
+ distances.start = camera.frustum.near;
+ distances.stop = camera.frustum.far;
undefBV = !(command instanceof ClearCommand);
- insertIntoAllBins(scene, command);
}
+
+ insertIntoBin(scene, command, distances);
}
}
@@ -495,19 +539,65 @@ define([
// last frame, else compute the new frustums and sort them by frustum again.
var farToNearRatio = scene.farToNearRatio;
var numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
- if (near !== Number.MAX_VALUE && (numFrustums !== frustumsLength || (frustumCommandsList.length !== 0 &&
- (near < frustumCommandsList[0].near || far > frustumCommandsList[frustumsLength - 1].far)))) {
+ if (near !== Number.MAX_VALUE && (numFrustums !== numberOfFrustums || (frustumCommandsList.length !== 0 &&
+ (near < frustumCommandsList[0].near || far > frustumCommandsList[numberOfFrustums - 1].far)))) {
updateFrustums(near, far, farToNearRatio, numFrustums, frustumCommandsList);
createPotentiallyVisibleSet(scene, listName);
}
}
+ function createFrustumDebugFragmentShaderSource(command) {
+ var fragmentShaderSource = command.shaderProgram.fragmentShaderSource;
+ var renamedFS = fragmentShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_frustumDebug_main()');
+
+ // Support up to three frustums. If a command overlaps all
+ // three, it's code is not changed.
+ var r = (command.debugOverlappingFrustums & (1 << 0)) ? '1.0' : '0.0';
+ var g = (command.debugOverlappingFrustums & (1 << 1)) ? '1.0' : '0.0';
+ var b = (command.debugOverlappingFrustums & (1 << 2)) ? '1.0' : '0.0';
+
+ var pickMain =
+ 'void main() \n' +
+ '{ \n' +
+ ' czm_frustumDebug_main(); \n' +
+ ' gl_FragColor.rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n' +
+ '}';
+
+ return renamedFS + '\n' + pickMain;
+ }
+
+ function executeFrustumDebugCommand(command, context, passState) {
+ if (defined(command.shaderProgram)) {
+ // Replace shader for frustum visualization
+ var sp = command.shaderProgram;
+ var attributeLocations = {};
+ var attributes = sp.getVertexAttributes();
+ for (var a in attributes) {
+ if (attributes.hasOwnProperty(a)) {
+ attributeLocations[a] = attributes[a].index;
+ }
+ }
+
+ command.shaderProgram = context.getShaderCache().getShaderProgram(
+ sp.vertexShaderSource, createFrustumDebugFragmentShaderSource(command), attributeLocations);
+
+ command.execute(context, passState);
+
+ command.shaderProgram.release();
+ command.shaderProgram = sp;
+ }
+ }
+
function executeCommand(command, scene, context, passState) {
if ((defined(scene.debugCommandFilter)) && !scene.debugCommandFilter(command)) {
return;
}
- command.execute(context, passState);
+ if (!scene.debugShowFrustums) {
+ command.execute(context, passState);
+ } else {
+ executeFrustumDebugCommand(command, context, passState);
+ }
if (command.debugShowBoundingVolume && (defined(command.boundingVolume))) {
// Debug code to draw bounding volume for command. Not optimized!
diff --git a/Specs/Renderer/ShaderProgramSpec.js b/Specs/Renderer/ShaderProgramSpec.js
index 13ecb779436c..e88040e06ce3 100644
--- a/Specs/Renderer/ShaderProgramSpec.js
+++ b/Specs/Renderer/ShaderProgramSpec.js
@@ -40,6 +40,15 @@ defineSuite([
destroyContext(context);
});
+ it('has vertex and fragment shader source', function() {
+ var vs = 'void main() { gl_Position = vec4(1.0); }';
+ var fs = 'void main() { gl_FragColor = vec4(1.0); }';
+ sp = context.createShaderProgram(vs, fs);
+
+ expect(sp.vertexShaderSource).toEqual(vs);
+ expect(sp.fragmentShaderSource).toEqual(fs);
+ });
+
it('has a position vertex attribute', function() {
var vs = 'attribute vec4 position; void main() { gl_Position = position; }';
var fs = 'void main() { gl_FragColor = vec4(1.0); }';
diff --git a/Specs/Scene/MultifrustumSpec.js b/Specs/Scene/MultifrustumSpec.js
index ecdd69d77c2c..d7420557a63f 100644
--- a/Specs/Scene/MultifrustumSpec.js
+++ b/Specs/Scene/MultifrustumSpec.js
@@ -182,6 +182,20 @@ defineSuite([
expect(context.readPixels()).toEqual([255, 255, 255, 255]);
});
+ it('renders primitive in last frustum with debugShowFrustums', function() {
+ createBillboards();
+ var color = new Color(1.0, 1.0, 1.0, 0.0);
+ billboard0.setColor(color);
+ billboard1.setColor(color);
+
+ scene.debugShowFrustums = true;
+ scene.initializeFrame();
+ scene.render();
+ expect(context.readPixels()).toEqual([0, 0, 255, 255]);
+ expect(scene.debugFrustumStatistics.totalCommands).toEqual(3);
+ expect(scene.debugFrustumStatistics.commandsInFrustums).toEqual({ 1 : 1, 2 : 1, 4 : 1});
+ });
+
function createPrimitive(bounded, closestFrustum) {
bounded = defaultValue(bounded, true);
closestFrustum = defaultValue(closestFrustum, false);
@@ -196,12 +210,12 @@ defineSuite([
var that = this;
this._um = {
- u_color : function() {
- return that.color;
- },
- u_model : function() {
- return that._modelMatrix;
- }
+ u_color : function() {
+ return that.color;
+ },
+ u_model : function() {
+ return that._modelMatrix;
+ }
};
};