UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

419 lines (312 loc) • 11.5 kB
import { getTypeByteSize } from "../codegen/glsl/getTypeByteSize.js"; import { ParticleDataTypes } from "../nodes/ParticleDataTypes.js"; import { genAttributeInputName } from "../codegen/glsl/genAttributeInputName.js"; import { genAttributeOutputName } from "../codegen/glsl/genAttributeOutputName.js"; import { getShaderErrors, WRITE_WHITE_FRAGMENT_SHADER } from "./GLSLSimulationShader.js"; export class TransformFeedback { constructor() { /** * * @type {String} * @private */ this.__source = null; /** * * @type {WebGLProgram} * @private */ this.__program = null; /** * Uniform location pointers from GL context. Order matches order of uniform specification array * @type {number[]} * @private */ this.__uniform_locations = []; /** * * @type {ParticleAttributeSpecification[]} */ this.attributes = []; /** * How much space does a single vertex require * @type {number} * @private */ this.__attribute_byte_size = 0; /** * * @type {ParticleAttributeSpecification[]} */ this.uniforms = []; /** * * @type {FunctionModuleRegistry} * @private */ this.__function_registry = null; /** * * @type {WebGLTransformFeedback} * @private */ this.__transform_feedback = null; /** * * @type {WebGL2RenderingContext} * @private */ this.__context = null; /** * * @type {number} * @private */ this.__output_buffer_handle = null; /** * * @type {number} * @private */ this.__output_buffer_size = 0; } /** * * @return {String} */ getSourceCode() { return this.__source; } /** * @param {string} source */ setSourceCode(source) { if (this.isCompiled()) { throw new Error("Can't assign new source to a compiled program"); } this.__source = source; } /** * * @param {ParticleAttributeSpecification[]} attributes */ setAttributes(attributes) { this.attributes = attributes; } /** * * @param {ParticleAttributeSpecification[]} attributes */ setUniforms(attributes) { this.uniforms = attributes; } __updateAttributeByteSize() { let offset = 0; const attributes = this.attributes; const attribute_count = attributes.length; for (let i = 0; i < attribute_count; i++) { const attribute = attributes[i]; offset += getTypeByteSize(attribute.type); } this.__attribute_byte_size = offset; } /** * * @param {EmitterAttributeData} attribute_source * @private */ __prepareOutputBuffer(attribute_source) { const min_size = attribute_source.data.getSize(); if (this.__output_buffer_size < min_size) { this.__output_buffer_size = min_size; /** * * @type {WebGLRenderingContext} */ const gl = this.__context; gl.bindBuffer(gl.ARRAY_BUFFER, this.__output_buffer_handle); gl.bufferData(gl.ARRAY_BUFFER, min_size * 4, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); } } /** * * @param {Array} uniform_values * @param {EmitterAttributeData} attributeSource */ execute(uniform_values, attributeSource) { const gl = this.__context; gl.useProgram(this.__program); // write uniforms const uniform_locations = this.__uniform_locations; const uniform_count = uniform_locations.length; for (let i = 0; i < uniform_count; i++) { const location = uniform_locations[i]; const uniform_spec = this.uniforms[i]; const uniformValue = uniform_values[i]; switch (uniform_spec.type) { case ParticleDataTypes.Float: gl.uniform1f(location, uniformValue); break; case ParticleDataTypes.Matrix4: gl.uniformMatrix4fv(location, false, uniformValue) break; default: throw new Error(`Unsupported uniform type`); } } // bind attributes const attributes = this.attributes; const attribute_count = attributes.length; let offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, attributeSource.data.gl_buffer_f32); //flush data to buffer gl.bufferData(gl.ARRAY_BUFFER, attributeSource.data.data_f32, gl.DYNAMIC_DRAW); for (let i = 0; i < attribute_count; i++) { const attribute = attributes[i]; const size = getTypeByteSize(attribute.type); const componentCount = attribute.computeComponentCount(); gl.enableVertexAttribArray(i); gl.vertexAttribPointer(i, componentCount, gl.FLOAT, false, this.__attribute_byte_size, offset); offset += size; } gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.__transform_feedback); this.__prepareOutputBuffer(attributeSource); // bind target buffer gl.bindBufferRange(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.__output_buffer_handle, 0, attributeSource.count * this.__attribute_byte_size); // gl.enable(gl.RASTERIZER_DISCARD); gl.beginTransformFeedback(gl.POINTS); gl.drawArrays(gl.POINTS, 0, attributeSource.count); gl.endTransformFeedback(); gl.disable(gl.RASTERIZER_DISCARD); // Unbind the transform feedback buffer so subsequent attempts gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null); // unbind transform feedback gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); } /** * * @param {string} name * @return {number} */ getUniformIndexByName(name) { for (let i = 0; i < this.uniforms.length; i++) { const u = this.uniforms[i]; if (u.name === name) { return i; } } return -1; } /** * * @private */ __buildUniformPointers() { const gl = this.__context; const program = this.__program; // obtain uniform pointers const active_uniform_count = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); for (let i = 0; i < active_uniform_count; i++) { /** * * @type {WebGLActiveInfo} */ const uniform = gl.getActiveUniform(program, i); const name = uniform.name; const uniform_index = this.getUniformIndexByName(name); if (uniform_index === -1) { throw new Error(`Uniform '${name}' not found`); } this.__uniform_locations[uniform_index] = gl.getUniformLocation(program, name); } } /** * GL context uses it's own internal buffers that are not shared with JS context, because of this - it is necessary to copy data out * @param {EmitterAttributeData} target */ readOutput(target) { /** * * @type {WebGL2RenderingContext} */ const gl = this.__context; gl.bindBuffer(gl.ARRAY_BUFFER, this.__output_buffer_handle); gl.getBufferSubData(gl.ARRAY_BUFFER, 0, target.data.data_f32); } isCompiled() { return this.__program !== null; } dispose() { if (this.isCompiled()) { const gl = this.__context; gl.deleteBuffer(this.__output_buffer_handle); gl.deleteProgram(this.__program); this.__program = null; this.__context = null; this.__outpub_buffer_handle = null; } } /** * * @param {WebGL2RenderingContext} gl */ compile(gl) { if (this.isCompiled()) { throw new Error('Already compiled, must call .dispose first before reusing this transform'); } if (this.__source === null) { throw new Error('No shader source code, shader must be built before being compiled'); } this.__updateAttributeByteSize(); const vertex_shader = gl.createShader(gl.VERTEX_SHADER); const fragment_shader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(vertex_shader, this.__source); gl.shaderSource(fragment_shader, WRITE_WHITE_FRAGMENT_SHADER); // Compile vertex shader gl.compileShader(vertex_shader); if (!gl.getShaderParameter(vertex_shader, gl.COMPILE_STATUS)) { const error_message = getShaderErrors(gl, vertex_shader, 'VERTEX'); // cleanup gl.deleteShader(vertex_shader); throw new Error(error_message); } // Compile fragment shader gl.compileShader(fragment_shader); if (!gl.getShaderParameter(fragment_shader, gl.COMPILE_STATUS)) { const error_message = getShaderErrors(gl, fragment_shader, 'FRAGMENT'); // cleanup gl.deleteShader(vertex_shader); gl.deleteShader(fragment_shader); throw new Error(error_message); } const program = gl.createProgram(); gl.attachShader(program, vertex_shader); gl.attachShader(program, fragment_shader); // clean up allocated shaders, they are now part of the program and can be removed gl.deleteShader(vertex_shader); gl.deleteShader(fragment_shader); // bind attribute inputs for (let i = 0; i < this.attributes.length; i++) { const attribute = this.attributes[i]; const input_name = genAttributeInputName(attribute); gl.bindAttribLocation(program, i, input_name); } // bind outputs const output_names = this.attributes.map(genAttributeOutputName); // We use interleaved attributes for cache coherence gl.transformFeedbackVaryings(program, output_names, gl.INTERLEAVED_ATTRIBS); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { const error_message = "Shader program failed to link" + gl.getProgramInfoLog(program); gl.deleteProgram(program); throw new Error(error_message); } this.__context = gl; this.__program = program; this.__buildUniformPointers(gl); this.__output_buffer_handle = gl.createBuffer(); this.__transform_feedback = gl.createTransformFeedback(); } }