Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frustum debugging #1073

Merged
merged 16 commits into from
Aug 27, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
7 changes: 7 additions & 0 deletions Source/Renderer/DrawCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ define(function() {
* @see DrawCommand#boundingVolume
*/
this.debugShowBoundingVolume = false;

/**
* @private
*
* Used to implement {@see Scene.debugShowFrustums}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This broke the doc build because the @ private attribute needs to come after any comments. I fixed it in #1080 since I was already fixing other doc related issues with my pull and that PR should come in tomorrow anyway.

*/
this.debugOverlappingFrustums = 0;
};

/**
Expand Down
22 changes: 22 additions & 0 deletions Source/Renderer/ShaderProgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
132 changes: 111 additions & 21 deletions Source/Scene/Scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,42 @@ define([
*/
this.debugCommandFilter = undefined;

/**
* This property is for debugging only; it is not for production use.
* <p>
* When <code>true</code>, 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.
* </p>
*
* @type Boolean
*
* @default false
*/
this.debugShowFrustums = false;

/**
* This property is for debugging only; it is not for production use.
* <p>
* When {@see Scene.debugShowFrustums} is <code>true</code>, this contains
* properties with statistics about the number of command execute per frustum.
* <code>totalCommands</code> is the total number of commands executed, ignoring
* overlap. <code>commandsInFrustums</code> is an array with the number of times
* commands are executed redundantly, e.g., how many commands overlap two or
* three frustums.
* </p>
*
* @type Object
*
* @default undefined
*
* @readonly
*/
this.debugFrustumStatistics = undefined;

this._debugSphere = undefined;

// initial guess at frustums.
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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!
Expand Down
9 changes: 9 additions & 0 deletions Specs/Renderer/ShaderProgramSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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); }';
Expand Down
26 changes: 20 additions & 6 deletions Specs/Scene/MultifrustumSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
};
};

Expand Down