Device and Pipeline WebGL API

This commit is contained in:
Felipe Alfonso 2017-01-09 19:22:38 -03:00
parent 71cee10a55
commit 3eb78fdfea
2 changed files with 519 additions and 0 deletions

View file

@ -0,0 +1,185 @@
var PHASER_ASSERT = function (condition, message) {
if (!(condition)) {
console.error('Assertion Failed: ', message);
}
}
// Device works as the WebGL backend. It's the API
// that interacts directly with WebGL calls. It's
// design to reduce the amount state changes and
// redundant gl calls.
var Device = function (gl) {
this.gl = gl;
this.canvas = gl.canvas;
this.pipelineState = null;
}
Device.prototype.clearPipelineState = function () {
var gl = this.gl;
var canvas = gl.canvas;
if (this.pipelineState === null) {
this.pipelineState = null;
gl.useProgram(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.disable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.STENCIL_TEST);
gl.viewport(0, 0, canvas.width, canvas.height);
}
};
Device.prototype.setPipelineState = function (pipelineState) {
var vertexInput, inputLayout, elements, elementsCount,
viewport, blendState, vertexBuffer, indexBuffer,
depthStencilState, gl;
PHASER_ASSERT(pipelineState != null, 'Invalid pipeline state.');
if (this.pipelineState === pipelineState)
return;
this.pipelineState = pipelineState;
gl = this.gl;
vertexInput = pipelineState.vertexInput;
inputLayout = vertexInput.inputLayout;
elements = inputLayout.elements;
elementsCount = inputLayout.elementsCount;
viewport = pipelineState.viewport;
blendState = pipelineState.blendState;
vertexBuffer = vertexInput.vertexBuffer;
indexBuffer = vertexInput.indexBuffer;
depthStencilState = pipelineState.depthStencilState;
// Bind Shader Pipeline
gl.useProgram(vertexInput.shaderPipeline.programHandle);
// Set Viewport
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
gl.depthRange(viewport.zNear, viewport.zFar);
// Bind buffers
if (vertexBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer.bufferHandle);
if (indexBuffer)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.bufferHandle);
// Bind Elements
for (var index = 0; index < elementsCount; ++index) {
var element = elements[index];
var elementLocation = element.location;
gl.enableVertexAttribArray(elementLocation);
gl.vertexAttribPointer(
elementLocation,
element.size,
element.format,
gl.FALSE,
element.stride,
element.offset
);
}
// Bind blend state
if (blendState && blendState.enabled) {
gl.enable(gl.BLEND);
gl.logicOp(blendState.logicOperator);
gl.blendEquationSeparate(blendState.rgbEquation, blendState.alphaEquation);
gl.blendFuncSeparate(blendState.sourceRgb, blendState.destinationRgb, blendState.sourceAlpha, blendState.destinationAlpha);
} else {
gl.disable(gl.BLEND);
}
// Bind depth state
if (depthStencilState && depthStencilState.depthEnabled) {
gl.enable(gl.DEPTH_TEST);
gl.depthMask(depthStencilState.depthWriteMask);
gl.depthFunc(depthStencilState.depthFunction);
} else {
gl.disable(gl.DEPTH_TEST);
}
// Bind stencil state
if (depthStencilState && depthStencilState.stencilEnabled) {
gl.enable(gl.STENCIL_TEST);
gl.stencilOp(
depthStencilState.stencilFail,
depthStencilState.stencilZFail,
depthStencilState.stencilZPass
);
gl.stencilFunc(
depthStencilState.stencilFunction,
1, 0xFF
);
} else {
gl.disable(gl.STENCIL_TEST);
}
};
Device.prototype.setTexture2D = function (texture2D) {
var gl = this.gl;
gl.activeTexture(gl.TEXTURE0 + texture2D.unit);
gl.bindTexture(gl.TEXTURE_2D, texture2D.textureHandle);
};
Device.prototype.setTextureCube = function (textureCube) {
var gl = this.gl;
gl.activeTexture(gl.TEXTURE0 + textureCube.unit);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, textureCube.textureHandle);
};
Device.prototype.updateVertexBuffer = function () {
var gl = this.gl;
var vertexBuffer = this.pipelineState.vertexInput.vertexBuffer;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertexBuffer.bufferSize, vertexBuffer.bufferHost);
};
Device.prototype.updateIndexBuffer = function () {
var gl = this.gl;
var indexBuffer = this.pipelineState.vertexInput.indexBuffer;
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, indexBuffer.bufferSize, indexBuffer.bufferHost);
};
Device.prototype.updateConstantBuffer = function () {
var elements = this.pipelineState.constantBuffer.elements;
var elementsCount = this.pipelineState.constantBuffer.elementsCount;
for (var index = 0; index < elementsCount; ++index) {
elements[index].update();
}
};
Device.prototype.updateSubVertexBuffer = function (offset, byteSize) {
var gl = this.gl;
var vertexBuffer = this.pipelineState.vertexInput.vertexBuffer;
gl.bufferSubData(gl.ARRAY_BUFFER, offset, byteSize, vertexBuffer.bufferHost);
};
Device.prototype.updateSubIndexBuffer = function (offset, byteSize) {
var gl = this.gl;
var indexBuffer = this.pipelineState.vertexInput.indexBuffer;
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, byteSize, indexBuffer.bufferHost);
};
Device.prototype.drawIndexed = function (indexCount, startIndexLocation) {
var gl = this.gl;
var vertexInput = this.pipelineState.vertexInput;
gl.drawElements(vertexInput.primitiveTopology, indexCount, vertexInput.indexBuffer.format, startIndexLocation);
};
Device.prototype.draw = function (vertexCount, startVertexLocation) {
var gl = this.gl;
var vertexInput = this.pipelineState.vertexInput;
gl.drawArrays(vertexInput.primitiveTopology, startVertexLocation, vertexCount);
};
Device.prototype.setClearColor = function (red, green, blue, alpha) {
this.gl.clearColor(red, green, blue, alpha);
};
Device.prototype.setClearDepth = function (depth) {
this.gl.clearDepth(depth);
};
Device.prototype.setClearStencil = function (stencil) {
this.gl.clearStencil(stencil);
};
Device.prototype.clearColorBuffer = function () {
var gl = this.gl;
gl.clear(gl.COLOR_BUFFER_BIT);
};
Device.prototype.clearDepthStencilBuffer = function () {
var gl = this.gl;
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
};
Device.prototype.clearAllBuffers = function () {
var gl = this.gl;
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT);
};

View file

@ -0,0 +1,334 @@
var CompileShader = function (gl, shaderType, shaderSource) {
var shader = null;
PHASER_ASSERT(shaderType === gl.VERTEX_SHADER || shaderType === gl.FRAGMENT_SHADER, "Invalid Shader Type");
shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
PHASER_ASSERT(gl.getShaderParameter(shader, gl.COMPILE_STATUS), "Failed shader compilation. Error: \n" + gl.getShaderInfoLog(shader));
return shader;
}
// Used by the constant buffer. Needed to simulate
// UBO
var ConstantElement = function (gl, shaderPipeline, descriptor) {
this.location = gl.getUniformLocation(shaderPipeline.programHandle, descriptor.name);
this.transpose = descriptor.transpose ? true : false;
this.dataReference = descriptor.dataReference;
if (!ArrayBuffer.isView(this.dataReference)) {
console.error('Constant data reference must be a typed array.');
return;
}
switch (descriptor.type) {
case PipelineState.TYPE_FLOAT:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform1fv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_INT:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform1iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_BOOL:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform1iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_FLOAT_VEC2:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform2fv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_INT_VEC2:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform2iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_BOOL_VEC2:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform2iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_FLOAT_VEC3:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform3fv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_INT_VEC3:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform3iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_BOOL_VEC3:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform3iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_FLOAT_VEC4:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform4fv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_INT_VEC4:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform4iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_BOOL_VEC4:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform4iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_MAT2:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniformMatrix2fv(this.location, this.transpose, this.dataReference);
};
break;
case PipelineState.TYPE_MAT3:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniformMatrix3fv(this.location, this.transpose, this.dataReference);
};
break;
case PipelineState.TYPE_MAT4:
PHASER_ASSERT((this.dataReference instanceof Float32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniformMatrix4fv(this.location, this.transpose, this.dataReference);
};
break;
case PipelineState.TYPE_SAMPLER_2D:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform1iv(this.location, this.dataReference);
};
break;
case PipelineState.TYPE_SAMPLER_CUBE:
PHASER_ASSERT((this.dataReference instanceof Int32Array), 'Invalid type for constant refenrece data');
this.update = function () {
gl.uniform1iv(this.location, this.dataReference);
};
break;
}
}
ConstantElement.TYPE_FLOAT = 0;
ConstantElement.TYPE_INT = 1;
ConstantElement.TYPE_BOOL = 2;
ConstantElement.TYPE_FLOAT_VEC2 = 3;
ConstantElement.TYPE_INT_VEC2 = 4;
ConstantElement.TYPE_BOOL_VEC2 = 5;
ConstantElement.TYPE_FLOAT_VEC3 = 6;
ConstantElement.TYPE_INT_VEC3 = 7;
ConstantElement.TYPE_BOOL_VEC3 = 8;
ConstantElement.TYPE_FLOAT_VEC4 = 9;
ConstantElement.TYPE_INT_VEC4 = 10;
ConstantElement.TYPE_BOOL_VEC4 = 11;
ConstantElement.TYPE_MAT2 = 12;
ConstantElement.TYPE_MAT3 = 13;
ConstantElement.TYPE_MAT4 = 14;
ConstantElement.TYPE_SAMPLER_2D = 15;
ConstantElement.TYPE_SAMPLER_CUBE = 16;
// Describes a Constant buffer. This will try to
// simulate UBO.
var ConstantBuffer = function (gl, shaderPipeline, elementsDescArray) {
this.elements = [];
for (var index = 0, length = elementsDescArray.length; index < length; ++index) {
this.elements.push(new ConstantElement(gl, shaderPipeline, elementsDescArray[index]));
}
this.elementsCount = this.elements.length;
}
// Describes a Viewport
var Viewport = function (gl, descriptor) {
this.x = descriptor.x;
this.y = descriptor.y;
this.width = descriptor.width;
this.height = descriptor.height;
this.zNear = descriptor.zNear;
this.zFar = descriptor.zFar;
}
// Describes a Shader Pipeline. This means a compiled and linked
// list of shaders
var ShaderPipeline = function (gl, descriptor) {
var program = gl.createProgram();
var vertexShader = CompileShader(gl, gl.VERTEX_SHADER, descriptor.vertexShader);
var fragmentShader = CompileShader(gl, gl.FRAGMENT_SHADER, descriptor.fragmentShader);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.validateProgram(program);
PHASER_ASSERT(gl.getProgramParameter(program, gl.LINK_STATUS), "Failed to link program. Error: \n" + gl.getProgramInfoLog(program));
this.vertexShader = vertexShader;
this.fragmentShader = fragmentShader;
this.programHandle = program;
}
// Describes a 2D texture. It'll contain it's own texture unit and dimensions
var Texture2D = function (gl, descriptor) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, descriptor.wrap_mode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, descriptor.wrap_mode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, descriptor.filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, descriptor.filter);
if (!ArrayBuffer.isView(descriptor.pixels)) {
gl.texImage2D(
gl.TEXTURE_2D,
descriptor.mipLevels,
descriptor.internalFormat,
descriptor.format,
descriptor.type,
descriptor.pixels
);
} else {
gl.texImage2D(
gl.TEXTURE_2D,
descriptor.mipLevels,
descriptor.internalFormat,
descriptor.width,
descriptor.height,
descriptor.border,
descriptor.format,
descriptor.type,
descriptor.pixels
);
}
this.textureHandle = texture;
this.unit = descriptor.unit;
this.width = descriptor.width;
this.height = descriptor.height;
}
// Describes a Vertex Buffer. It'll be used by the Vertex Input
var VertexBuffer = function (gl, descriptor) {
var buffer = gl.createBuffer();
var bufferSize = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
if (descriptor.data && descriptor.initialCopy) {
gl.bufferData(gl.ARRAY_BUFFER, descriptor.data, descriptor.usage);
bufferSize = descriptor.data.byteLength;
} else {
gl.bufferData(gl.ARRAY_BUFFER, descriptor.bufferSize, descriptor.usage);
bufferSize = descriptor.bufferSize;
}
this.bufferHandle = buffer;
this.bufferSize = bufferSize;
this.bufferHost = descriptor.bufferHost;
}
// Describes a Index Buffer. It'll be used by the Vertex Input
var IndexBuffer = function (gl, descriptor) {
var buffer = gl.createBuffer();
var bufferSize = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
if (descriptor.data && descriptor.initialCopy) {
gl.bufferData(gl.ARRAY_BUFFER, descriptor.data, descriptor.usage);
bufferSize = descriptor.data.byteLength;
} else {
gl.bufferData(gl.ARRAY_BUFFER, descriptor.bufferSize, descriptor.usage);
bufferSize = descriptor.bufferSize;
}
this.bufferHandle = buffer;
this.bufferSize = bufferSize;
this.bufferHost = descriptor.bufferHost;
this.indexFormat = descriptor.indexFormat;
}
// Describes a Blend State. Functions, operations and equations.
var BlendState = function (gl, descriptor) {
this.enabled = descriptor.enabled;
this.logicOperation = descriptor.logicOperation;
this.rgbEquation = descriptor.rgbEquation;
this.sourceRgb = descriptor.sourceRgb;
this.destinationRgb = descriptor.destinationRgb;
this.alphaEquation = descriptor.alphaEquation;
this.sourceAlpha = descriptor.sourceAlpha;
this.destinationAlpha = descriptor.destinationAlpha;
}
// Describes a Depth-Stencil State. Functions and operations
var DepthStencilState = function (gl, descriptor) {
this.stencilEnabled = descriptor.stencilEnabled;
this.depthEnabled = descriptor.depthEnabled;
this.depthFunction = descriptor.depthFunction;
this.depthWriteMask = descriptor.depthWriteMask;
this.stencilFunction = descriptor.stencilFunction;
this.stencilFail = descriptor.stencilFail;
this.stencilZFail = descriptor.stencilZFail;
this.stencilZPass = descriptor.stencilZPass;
}
// Describes an Element Layout. Used by the Vertex Input.
// An element layout is how vertex attributes is display in GPU memory
var ElementLayout = function (gl, shaderPipeline, descriptor) {
this.location = gl.getAttribLocation(shaderPipeline.programHandle, descriptor.name);
this.size = descriptor.size;
this.offset = descriptor.offset;
this.stride = descriptor.stride;
this.format = descriptor.format;
}
// Describes a Input Layout. Input layout is a collection of element layout.
var InputLayout = function (gl, shaderPipeline, elementsDescArray) {
this.elements = [];
for (var index = 0, length = elementsDescArray.length; index < length; ++index) {
this.elements.push(new ElementLayout(gl, shaderPipeline, elementsDescArray[index]));
}
this.elementsCount = this.elements.length;
}
// Describes a Render Target. A render target has depth-stencil and color buffers
var RenderTarget = function (gl, descriptor) {}
// Describes a Vertex Input set. This is what WebGL will use to
// define how it handles vertex data. Vertex Input internally describes
// a primitive topology, vertex buffer, index buffer, input layout and
// a shader pipeline.
var VertexInput = function (gl, descriptor) {
var vertexBufferDescriptor = descriptor.vertexBuffer;
var indexBufferDescriptor = descriptor.indexBuffer;
var shaderPipelineDescriptor = descriptor.shaderPipeline;
var inputLayoutDescriptor = descriptor.inputLayout;
this.primitiveTopology = descriptor.primitiveTopology;
this.vertexBuffer = vertexBufferDescriptor ? new VertexBuffer(gl, vertexBufferDescriptor) : null;
this.indexBuffer = indexBufferDescriptor ? new IndexBuffer(gl, indexBufferDescriptor) : null;
this.shaderPipeline = shaderPipelineDescriptor ? new ShaderPipeline(gl, shaderPipelineDescriptor) : null;
this.inputLayout = inputLayoutDescriptor ? new InputLayout(gl, this.shaderPipeline, inputLayoutDescriptor) : null;
}
// Describes a Pipeline State. This is uploaded once to the GPU with
// vertex input, viewport, blend and depth-stencil states.
// Pipeline states are fixed states. This will reduce the need to sync
// GL states with GPU. This would give the potential of future optimizations
// like designing a pararllel renderer using a stateless render queue by simply
// passing a state-stamp with the draw call commands.
var PipelineState = function (gl, descriptor) {
var vertexInputDescriptor = descriptor.vertexInput;
var viewportDescriptor = descriptor.viewport;
var blendStateDescriptor = descriptor.blendState;
var depthStencilStateDescriptor = descriptor.depthStencilState;
var constantBufferDescriptor = descriptor.constantBuffer;
this.vertexInput = vertexInputDescriptor ? new VertexInput(gl, vertexInputDescriptor) : null;
this.viewport = viewportDescriptor ? new Viewport(gl, viewportDescriptor) : null;
this.blendState = blendStateDescriptor ? new BlendState(gl, blendStateDescriptor) : null;
this.depthStencilState = depthStencilStateDescriptor ? new DepthStencilState(gl, depthStencilStateDescriptor) : null;
this.constantBuffer = constantBufferDescriptor ? new ConstantBuffer(gl, this.vertexInput.shaderPipeline, constantBufferDescriptor) : null;
}