UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

201 lines (198 loc) 8.66 kB
import { Debug } from '../../core/debug.js'; import { BUFFER_GPUDYNAMIC, PRIMITIVE_POINTS } from './constants.js'; import { VertexBuffer } from './vertex-buffer.js'; import { DebugGraphics } from './debug-graphics.js'; import { Shader } from './shader.js'; import { ShaderDefinitionUtils } from './shader-definition-utils.js'; /** * @import { GraphicsDevice } from './graphics-device.js' */ /** * This object allows you to configure and use the transform feedback feature (WebGL2 only). How to * use: * * 1. First, check that you're on WebGL2, by looking at the `app.graphicsDevice.isWebGL2`` value. * 2. Define the outputs in your vertex shader. The syntax is `out vec3 out_vertex_position`, * note that there must be out_ in the name. You can then simply assign values to these outputs in * VS. The order and size of shader outputs must match the output buffer layout. * 3. Create the shader using `TransformFeedback.createShader(device, vsCode, yourShaderName)`. * 4. Create/acquire the input vertex buffer. Can be any VertexBuffer, either manually created, or * from a Mesh. * 5. Create the TransformFeedback object: `const tf = new TransformFeedback(inputBuffer)`. This * object will internally create an output buffer. * 6. Run the shader: `tf.process(shader)`. Shader will take the input buffer, process it and write * to the output buffer, then the input/output buffers will be automatically swapped, so you'll * immediately see the result. * * ```javascript * // *** shader asset *** * attribute vec3 vertex_position; * attribute vec3 vertex_normal; * attribute vec2 vertex_texCoord0; * out vec3 out_vertex_position; * out vec3 out_vertex_normal; * out vec2 out_vertex_texCoord0; * void main(void) { * // read position and normal, write new position (push away) * out_vertex_position = vertex_position + vertex_normal * 0.01; * // pass other attributes unchanged * out_vertex_normal = vertex_normal; * out_vertex_texCoord0 = vertex_texCoord0; * } * ``` * * ```javascript * // *** script asset *** * var TransformExample = pc.createScript('transformExample'); * * // attribute that references shader asset and material * TransformExample.attributes.add('shaderCode', { type: 'asset', assetType: 'shader' }); * TransformExample.attributes.add('material', { type: 'asset', assetType: 'material' }); * * TransformExample.prototype.initialize = function() { * const device = this.app.graphicsDevice; * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, new pc.TorusGeometry({ tubeRadius: 0.01, ringRadius: 3 })); * const meshInstance = new pc.MeshInstance(mesh, this.material.resource); * const entity = new pc.Entity(); * entity.addComponent('render', { * type: 'asset', * meshInstances: [meshInstance] * }); * app.root.addChild(entity); * * // if webgl2 is not supported, transform-feedback is not available * if (!device.isWebGL2) return; * const inputBuffer = mesh.vertexBuffer; * this.tf = new pc.TransformFeedback(inputBuffer); * this.shader = pc.TransformFeedback.createShader(device, this.shaderCode.resource, "tfMoveUp"); * }; * * TransformExample.prototype.update = function(dt) { * if (!this.app.graphicsDevice.isWebGL2) return; * this.tf.process(this.shader); * }; * ``` * * @category Graphics */ class TransformFeedback { /** * Create a new TransformFeedback instance. * * @param {VertexBuffer} inputBuffer - The input vertex buffer. * @param {VertexBuffer} [outputBuffer] - The optional output buffer. * If not specified, a buffer with parameters matching the input buffer will be created. * @param {number} [usage] - The optional usage type of the output vertex buffer. Can be: * * - {@link BUFFER_STATIC} * - {@link BUFFER_DYNAMIC} * - {@link BUFFER_STREAM} * - {@link BUFFER_GPUDYNAMIC} * * Defaults to {@link BUFFER_GPUDYNAMIC} (which is recommended for continuous update). */ constructor(inputBuffer, outputBuffer, usage = BUFFER_GPUDYNAMIC){ if (outputBuffer !== undefined && !(outputBuffer instanceof VertexBuffer)) { Debug.deprecated('Such a constructor that takes the second parameter usage is deprecated.'); usage = outputBuffer; outputBuffer = undefined; } this.device = inputBuffer.device; const gl = this.device.gl; const outVB = outputBuffer ?? inputBuffer; Debug.assert(outVB.format.interleaved || outVB.format.elements.length <= 1, outputBuffer ? 'Output vertex buffer used by TransformFeedback needs to be interleaved.' : 'Input vertex buffer used by TransformFeedback needs to be interleaved.'); if (usage === BUFFER_GPUDYNAMIC && outVB.usage !== usage) { // have to recreate input buffer with other usage gl.bindBuffer(gl.ARRAY_BUFFER, outVB.impl.bufferId); gl.bufferData(gl.ARRAY_BUFFER, outVB.storage, gl.DYNAMIC_COPY); } this._inputBuffer = inputBuffer; this._destroyOutputBuffer = !outputBuffer; this._outputBuffer = outputBuffer ?? new VertexBuffer(inputBuffer.device, inputBuffer.format, inputBuffer.numVertices, { usage: usage, data: inputBuffer.storage }); } /** * Creates a transform feedback ready vertex shader from code. * * @param {GraphicsDevice} graphicsDevice - The graphics device used by the renderer. * @param {string} vertexCode - Vertex shader code. Should contain output variables starting with "out_" or feedbackVaryings. * @param {string} name - Unique name for caching the shader. * @param {string[]} [feedbackVaryings] - A list of shader output variable names that will be captured. * @returns {Shader} A shader to use in the process() function. */ static createShader(graphicsDevice, vertexCode, name, feedbackVaryings) { return new Shader(graphicsDevice, ShaderDefinitionUtils.createDefinition(graphicsDevice, { name, vertexCode, feedbackVaryings, useTransformFeedback: true, fragmentCode: 'void main(void) {gl_FragColor = vec4(0.0);}' })); } /** * Destroys the transform feedback helper object. */ destroy() { if (this._destroyOutputBuffer) { this._outputBuffer.destroy(); } } /** * Runs the specified shader on the input buffer, writes results into the new buffer, then * optionally swaps input/output. * * @param {Shader} shader - A vertex shader to run. Should be created with * {@link TransformFeedback.createShader}. * @param {boolean} [swap] - Swap input/output buffer data. Useful for continuous buffer * processing. Default is true. */ process(shader, swap = true) { const device = this.device; DebugGraphics.pushGpuMarker(device, 'TransformFeedback'); const oldRt = device.getRenderTarget(); device.setRenderTarget(null); device.updateBegin(); device.setVertexBuffer(this._inputBuffer); device.setRaster(false); device.setTransformFeedbackBuffer(this._outputBuffer); device.setShader(shader); device.draw({ type: PRIMITIVE_POINTS, base: 0, baseVertex: 0, count: this._inputBuffer.numVertices, indexed: false }); device.setTransformFeedbackBuffer(null); device.setRaster(true); device.updateEnd(); device.setRenderTarget(oldRt); DebugGraphics.popGpuMarker(device); // swap buffers if (swap) { Debug.call(()=>{ if (this._inputBuffer.format !== this._outputBuffer.format) { Debug.warnOnce('Trying to swap buffers with different formats.'); } }); let tmp = this._inputBuffer.impl.bufferId; this._inputBuffer.impl.bufferId = this._outputBuffer.impl.bufferId; this._outputBuffer.impl.bufferId = tmp; // swap VAO tmp = this._inputBuffer.impl.vao; this._inputBuffer.impl.vao = this._outputBuffer.impl.vao; this._outputBuffer.impl.vao = tmp; } } /** * The current input buffer. * * @type {VertexBuffer} */ get inputBuffer() { return this._inputBuffer; } /** * The current output buffer. * * @type {VertexBuffer} */ get outputBuffer() { return this._outputBuffer; } } export { TransformFeedback };