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