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

Added compute pass #2993

Merged
merged 7 commits into from
Sep 8, 2015
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
120 changes: 120 additions & 0 deletions Source/Renderer/ComputeCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*global define*/
define([
'../Core/defaultValue',
'../Core/PrimitiveType',
'../Scene/Pass'
], function(
defaultValue,
PrimitiveType,
Pass) {
"use strict";

/**
* Represents a command to the renderer for GPU Compute (using old-school GPGPU).
*
* @private
*/
var ComputeCommand = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);

/**
* The vertex array. If none is provided, a viewport quad will be used.
*
* @type {VertexArray}
* @default undefined
*/
this.vertexArray = options.vertexArray;

/**
* The fragment shader source. The default vertex shader is ViewportQuadVS.
*
* @type {ShaderSource}
* @default undefined
*/
this.fragmentShaderSource = options.fragmentShaderSource;

/**
* The shader program to apply.
*
* @type {ShaderProgram}
* @default undefined
*/
this.shaderProgram = options.shaderProgram;

/**
* An object with functions whose names match the uniforms in the shader program
* and return values to set those uniforms.
*
* @type {Object}
* @default undefined
*/
this.uniformMap = options.uniformMap;

/**
* Texture to use for offscreen rendering.
*
* @type {Texture}
* @default undefined
*/
this.outputTexture = options.outputTexture;

/**
* Function that is called immediately before the ComputeCommand is executed. Used to
* update any renderer resources. Takes the ComputeCommand as its single argument.
*
* @type {Function}
* @default undefined
*/
this.preExecute = options.preExecute;

/**
* Function that is called after the ComputeCommand is executed. Takes the output
* texture as its single argument.
*
* @type {Function}
* @default undefined
*/
this.postExecute = options.postExecute;

/**
* Whether the renderer resources will persist beyond this call. If not, they
* will be destroyed after completion.
*
* @type {Boolean}
* @default false
*/
this.persists = defaultValue(options.persists, false);

/**
* The pass when to render. Always compute pass.
*
* @type {Pass}
* @default Pass.COMPUTE;
*/
this.pass = Pass.COMPUTE;
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't have to do anything now, but if we have separate lists for each pass in a future PR as we discussed, it will most likely mean that we don't need this member any more.


/**
* The object who created this command. This is useful for debugging command
* execution; it allows us to see who created a command when we only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {Object}
* @default undefined
*
* @see Scene#debugCommandFilter
*/
this.owner = options.owner;
};

/**
* Executes the compute command.
*
* @param {Context} context The context that processes the compute command.
*/
ComputeCommand.prototype.execute = function(computeEngine) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps remove this, and just have the scene do:

computeEngine.execute(computeCommand);

What do you think?

We could probably do the same with the other commands, but we need to think through it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about this as well. Either way works for me.

One downside is the naming would be inconsistent with the other commands: context.draw(drawCommand, ...) and context.clear(clearCommand, ...). These could be tweaked though.

Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't have to be in this PR, but I think we can remove those execute functions too; I was never happy with them and they just kinda happened organically when we went from each primitive calling context.draw in its update to instead returning commands in their update.

computeEngine.execute(this);
};

return ComputeCommand;
});
158 changes: 158 additions & 0 deletions Source/Renderer/ComputeEngine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*global define*/
define([
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Geometry',
'../Core/GeometryAttribute',
'../Core/PrimitiveType',
'../Shaders/ViewportQuadVS',
'./BufferUsage',
'./ClearCommand',
'./DrawCommand',
'./Framebuffer',
'./RenderState',
'./ShaderProgram'
], function(
BoundingRectangle,
Color,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Geometry,
GeometryAttribute,
PrimitiveType,
ViewportQuadVS,
BufferUsage,
ClearCommand,
DrawCommand,
Framebuffer,
RenderState,
ShaderProgram) {
"use strict";

/**
* @private
*/
var ComputeEngine = function(context) {
this._context = context;
};

var renderStateScratch;
var drawCommandScratch = new DrawCommand({
primitiveType : PrimitiveType.TRIANGLES
});
var clearCommandScratch = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0)
});

function createFramebuffer(context, outputTexture) {
return new Framebuffer({
context : context,
colorTextures : [outputTexture],
destroyAttachments : false
});
}

function createViewportQuadShader(context, fragmentShaderSource) {
return ShaderProgram.fromCache({
context : context,
vertexShaderSource : ViewportQuadVS,
fragmentShaderSource : fragmentShaderSource,
attributeLocations : {
position : 0,
textureCoordinates : 1
}
});
}

function createRenderState(width, height) {
if ((!defined(renderStateScratch)) ||
(renderStateScratch.viewport.width !== width) ||
(renderStateScratch.viewport.height !== height)) {

renderStateScratch = RenderState.fromCache({
viewport : new BoundingRectangle(0, 0, width, height)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just use a default render state since the pass state with override width and height when it is undefined in the render state?

The way we have it now, we could end up with a ton of cached render states when the user resizes the canvas.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now PassState is a little inflexible with that. Inside applyViewport if the RenderState doesn't have a viewport the PassState will use the Context's drawing buffer width and height.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll think of some other way to do this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, right. This is probably fine. Right now we would really just have two render states: one for imagery layers and another for the sun.

});
}
return renderStateScratch;
}

ComputeEngine.prototype.execute = function(computeCommand) {
//>>includeStart('debug', pragmas.debug);
if (!defined(computeCommand)) {
throw new DeveloperError('computeCommand is required.');
}
//>>includeEnd('debug');

// This may modify the command's resources, so do error checking afterwards
if (defined(computeCommand.preExecute)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is before the potential DeveloperErrors below because it might modify the command, right? Let's add a comment since so in the future we don't think "why not put all all DeveloperErrors at the top together?"

computeCommand.preExecute(computeCommand);
}

//>>includeStart('debug', pragmas.debug);
if (!defined(computeCommand.fragmentShaderSource) && !defined(computeCommand.shaderProgram)) {
throw new DeveloperError('computeCommand.fragmentShaderSource or computeCommand.shaderProgram is required.');
}

if (!defined(computeCommand.outputTexture)) {
throw new DeveloperError('computeCommand.outputTexture is required.');
}
//>>includeEnd('debug');

var outputTexture = computeCommand.outputTexture;
var width = outputTexture.width;
var height = outputTexture.height;

var context = this._context;
var vertexArray = defined(computeCommand.vertexArray) ? computeCommand.vertexArray : context.getViewportQuadVertexArray();
var shaderProgram = defined(computeCommand.shaderProgram) ? computeCommand.shaderProgram : createViewportQuadShader(context, computeCommand.fragmentShaderSource);
var framebuffer = createFramebuffer(context, outputTexture);
var renderState = createRenderState(width, height);
var uniformMap = computeCommand.uniformMap;

var clearCommand = clearCommandScratch;
clearCommand.framebuffer = framebuffer;
clearCommand.renderState = renderState;
clearCommand.execute(context);

var drawCommand = drawCommandScratch;
drawCommand.vertexArray = vertexArray;
drawCommand.renderState = renderState;
drawCommand.shaderProgram = shaderProgram;
drawCommand.uniformMap = uniformMap;
drawCommand.framebuffer = framebuffer;
drawCommand.execute(context);

framebuffer.destroy();

if (!computeCommand.persists) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this will change with pooling, but we need to think through the semantics of persists a bit more since, for example, a persistence compute command never empties the shader cache...maybe it is OK for now.

shaderProgram.destroy();
if (defined(computeCommand.vertexArray)) {
vertexArray.destroy();
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove empty whitespace.

if (defined(computeCommand.postExecute)) {
computeCommand.postExecute(outputTexture);
}
};

ComputeEngine.prototype.isDestroyed = function() {
return false;
};

ComputeEngine.prototype.destroy = function() {
return destroyObject(this);
};

return ComputeEngine;
});
13 changes: 7 additions & 6 deletions Source/Renderer/Context.js
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ define([
textureCoordinates : 1
};

Context.prototype.createViewportQuadCommand = function(fragmentShaderSource, overrides) {
Context.prototype.getViewportQuadVertexArray = function() {
// Per-context cache for viewport quads
var vertexArray = this.cache.viewportQuad_vertexArray;

Expand Down Expand Up @@ -955,21 +955,22 @@ define([
vertexArray = VertexArray.fromGeometry({
context : this,
geometry : geometry,
attributeLocations : {
position : 0,
textureCoordinates : 1
},
attributeLocations : viewportQuadAttributeLocations,
bufferUsage : BufferUsage.STATIC_DRAW,
interleave : true
});

this.cache.viewportQuad_vertexArray = vertexArray;
}

return vertexArray;
};

Context.prototype.createViewportQuadCommand = function(fragmentShaderSource, overrides) {
overrides = defaultValue(overrides, defaultValue.EMPTY_OBJECT);

return new DrawCommand({
vertexArray : vertexArray,
vertexArray : this.getViewportQuadVertexArray(),
primitiveType : PrimitiveType.TRIANGLES,
renderState : overrides.renderState,
shaderProgram : ShaderProgram.fromCache({
Expand Down
3 changes: 2 additions & 1 deletion Source/Renderer/VertexArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ define([
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* }); * var attributes = [
* });
* var attributes = [
* {
* index : 0,
* vertexBuffer : positionBuffer,
Expand Down
2 changes: 1 addition & 1 deletion Source/Scene/Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -1953,7 +1953,7 @@ define([
};

/**
* View an rectangle on an ellipsoid or map.
* View a rectangle on an ellipsoid or map.
*
* @param {Rectangle} rectangle The rectangle to view.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to view.
Expand Down
4 changes: 2 additions & 2 deletions Source/Scene/GlobeSurfaceTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ define([
}
};

GlobeSurfaceTile.processStateMachine = function(tile, context, terrainProvider, imageryLayerCollection) {
GlobeSurfaceTile.processStateMachine = function(tile, context, commandList, terrainProvider, imageryLayerCollection) {
var surfaceTile = tile.data;
if (!defined(surfaceTile)) {
surfaceTile = tile.data = new GlobeSurfaceTile();
Expand Down Expand Up @@ -345,7 +345,7 @@ define([
}
}

var thisTileDoneLoading = tileImagery.processStateMachine(tile, context);
var thisTileDoneLoading = tileImagery.processStateMachine(tile, context, commandList);
isDoneLoading = isDoneLoading && thisTileDoneLoading;

// The imagery is renderable as soon as we have any renderable imagery for this region.
Expand Down
4 changes: 2 additions & 2 deletions Source/Scene/GlobeSurfaceTileProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,8 @@ define([
*
* @exception {DeveloperError} <code>loadTile</code> must not be called before the tile provider is ready.
*/
GlobeSurfaceTileProvider.prototype.loadTile = function(context, frameState, tile) {
GlobeSurfaceTile.processStateMachine(tile, context, this._terrainProvider, this._imageryLayers);
GlobeSurfaceTileProvider.prototype.loadTile = function(context, frameState, commandList, tile) {
GlobeSurfaceTile.processStateMachine(tile, context, commandList, this._terrainProvider, this._imageryLayers);
};

var boundingSphereScratch = new BoundingSphere();
Expand Down
4 changes: 2 additions & 2 deletions Source/Scene/Imagery.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ define([
return this.referenceCount;
};

Imagery.prototype.processStateMachine = function(context) {
Imagery.prototype.processStateMachine = function(context, commandList) {
if (this.state === ImageryState.UNLOADED) {
this.state = ImageryState.TRANSITIONING;
this.imageryLayer._requestImagery(this);
Expand All @@ -93,7 +93,7 @@ define([

if (this.state === ImageryState.TEXTURE_LOADED) {
this.state = ImageryState.TRANSITIONING;
this.imageryLayer._reprojectTexture(context, this);
this.imageryLayer._reprojectTexture(context, commandList, this);
}
};

Expand Down
Loading