UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

480 lines (414 loc) 18 kB
var ShaderProgramFactory = require('../../../src/renderer/webgl/ShaderProgramFactory'); describe('ShaderProgramFactory', function () { var mockRenderer; var factory; beforeEach(function () { mockRenderer = { createProgram: function (vertexSource, fragmentSource) { return { vertexSource: vertexSource, fragmentSource: fragmentSource }; } }; factory = new ShaderProgramFactory(mockRenderer); }); describe('constructor', function () { it('should store the renderer reference', function () { expect(factory.renderer).toBe(mockRenderer); }); it('should initialise programs as an empty object', function () { expect(factory.programs).toBeDefined(); expect(Object.keys(factory.programs).length).toBe(0); }); }); describe('has', function () { it('should return false when no programs are cached', function () { expect(factory.has('someKey')).toBe(false); }); it('should return true when a program has been cached under the given key', function () { factory.programs['someKey'] = { dummy: true }; expect(factory.has('someKey')).toBe(true); }); it('should return false for a key that does not exist even when other keys do', function () { factory.programs['otherKey'] = { dummy: true }; expect(factory.has('someKey')).toBe(false); }); it('should return false when the key is an empty string and cache is empty', function () { expect(factory.has('')).toBe(false); }); }); describe('getKey', function () { it('should return the base name when no additions or features are provided', function () { var base = { name: 'myShader', vertexShader: '', fragmentShader: '' }; expect(factory.getKey(base)).toBe('myShader'); }); it('should return the base name when additions and features are empty arrays', function () { var base = { name: 'myShader', vertexShader: '', fragmentShader: '' }; expect(factory.getKey(base, [], [])).toBe('myShader'); }); it('should append addition names separated by underscores', function () { var base = { name: 'base', vertexShader: '', fragmentShader: '' }; var additions = [ { name: 'addA', disable: false, additions: {} }, { name: 'addB', disable: false, additions: {} } ]; var key = factory.getKey(base, additions); expect(key).toBe('base__addA_addB'); }); it('should skip disabled additions in the key', function () { var base = { name: 'base', vertexShader: '', fragmentShader: '' }; var additions = [ { name: 'addA', disable: false, additions: {} }, { name: 'addB', disable: true, additions: {} }, { name: 'addC', disable: false, additions: {} } ]; var key = factory.getKey(base, additions); expect(key).toBe('base__addA_addC'); }); it('should append sorted feature keys after a double underscore separator', function () { var base = { name: 'base', vertexShader: '', fragmentShader: '' }; var features = [ 'zebra', 'apple', 'mango' ]; var key = factory.getKey(base, undefined, features); expect(key).toBe('base__apple_mango_zebra'); }); it('should sort features alphabetically regardless of input order', function () { var base = { name: 'base', vertexShader: '', fragmentShader: '' }; var featuresA = [ 'z', 'a', 'm' ]; var featuresB = [ 'a', 'm', 'z' ]; expect(factory.getKey(base, undefined, featuresA)).toBe(factory.getKey(base, undefined, featuresB)); }); it('should combine additions and features in the key', function () { var base = { name: 'base', vertexShader: '', fragmentShader: '' }; var additions = [ { name: 'addA', disable: false, additions: {} } ]; var features = [ 'featureB', 'featureA' ]; var key = factory.getKey(base, additions, features); expect(key).toBe('base__addA__featureA_featureB'); }); it('should return only the base name when all additions are disabled', function () { var base = { name: 'base', vertexShader: '', fragmentShader: '' }; var additions = [ { name: 'addA', disable: true, additions: {} }, { name: 'addB', disable: true, additions: {} } ]; var key = factory.getKey(base, additions); expect(key).toBe('base_'); }); }); describe('getShaderProgram', function () { it('should compile and return a new program when none is cached', function () { var base = { name: 'myShader', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; var program = factory.getShaderProgram(base); expect(program).toBeDefined(); }); it('should cache the compiled program under the derived key', function () { var base = { name: 'myShader', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; factory.getShaderProgram(base); var key = factory.getKey(base); expect(factory.has(key)).toBe(true); }); it('should return the cached program on subsequent calls with the same config', function () { var base = { name: 'myShader', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; var programA = factory.getShaderProgram(base); var programB = factory.getShaderProgram(base); expect(programA).toBe(programB); }); it('should not call createProgram again if the program is already cached', function () { var callCount = 0; mockRenderer.createProgram = function (v, f) { callCount++; return { vertexSource: v, fragmentSource: f }; }; var base = { name: 'myShader', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; factory.getShaderProgram(base); factory.getShaderProgram(base); expect(callCount).toBe(1); }); it('should return different programs for different base shader names', function () { var baseA = { name: 'shaderA', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; var baseB = { name: 'shaderB', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; var programA = factory.getShaderProgram(baseA); var programB = factory.getShaderProgram(baseB); expect(programA).not.toBe(programB); }); }); describe('createShaderProgram', function () { it('should store the compiled program in the programs cache under the given name', function () { var base = { name: 'test', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; factory.createShaderProgram('test', base); expect(factory.programs['test']).toBeDefined(); }); it('should return the compiled program', function () { var base = { name: 'test', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; var result = factory.createShaderProgram('test', base); expect(result).toBe(factory.programs['test']); }); it('should strip carriage return characters from vertex and fragment source', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: 'void\r main\r(){}', fragmentShader: 'void\r frag\r(){}' }; factory.createShaderProgram('test', base); expect(captured.vertex.indexOf('\r')).toBe(-1); expect(captured.fragment.indexOf('\r')).toBe(-1); expect(captured.vertex).toBe('void main(){}'); expect(captured.fragment).toBe('void frag(){}'); }); it('should replace a pragma template in vertex source with addition content', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(mySlot)\nvoid main(){}', fragmentShader: 'void main(){}' }; var additions = [ { name: 'myAddition', disable: false, additions: { mySlot: 'float x = 1.0;' } } ]; factory.createShaderProgram('test', base, additions); expect(captured.vertex).toContain('float x = 1.0;'); expect(captured.vertex).not.toContain('#pragma phaserTemplate(mySlot)'); }); it('should replace a pragma template in fragment source with addition content', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: 'void main(){}', fragmentShader: '#pragma phaserTemplate(mySlot)\nvoid main(){}' }; var additions = [ { name: 'myAddition', disable: false, additions: { mySlot: 'float y = 2.0;' } } ]; factory.createShaderProgram('test', base, additions); expect(captured.fragment).toContain('float y = 2.0;'); expect(captured.fragment).not.toContain('#pragma phaserTemplate(mySlot)'); }); it('should concatenate multiple additions for the same slot', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(slot)\nvoid main(){}', fragmentShader: 'void main(){}' }; var additions = [ { name: 'a1', disable: false, additions: { slot: 'float a = 1.0;' } }, { name: 'a2', disable: false, additions: { slot: 'float b = 2.0;' } } ]; factory.createShaderProgram('test', base, additions); expect(captured.vertex).toContain('float a = 1.0;'); expect(captured.vertex).toContain('float b = 2.0;'); }); it('should skip disabled additions when building shader source', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(slot)\nvoid main(){}', fragmentShader: 'void main(){}' }; var additions = [ { name: 'enabled', disable: false, additions: { slot: 'float a = 1.0;' } }, { name: 'disabled', disable: true, additions: { slot: 'float b = 99.0;' } } ]; factory.createShaderProgram('test', base, additions); expect(captured.vertex).toContain('float a = 1.0;'); expect(captured.vertex).not.toContain('float b = 99.0;'); }); it('should insert feature defines into the features pragma slot', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(features)\nvoid main(){}', fragmentShader: '#pragma phaserTemplate(features)\nvoid main(){}' }; factory.createShaderProgram('test', base, undefined, [ 'myFeature', 'another' ]); expect(captured.vertex).toContain('#define FEATURE_MYFEATURE'); expect(captured.vertex).toContain('#define FEATURE_ANOTHER'); expect(captured.fragment).toContain('#define FEATURE_MYFEATURE'); expect(captured.fragment).toContain('#define FEATURE_ANOTHER'); }); it('should convert feature names to upper case for defines', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(features)\nvoid main(){}', fragmentShader: 'void main(){}' }; factory.createShaderProgram('test', base, undefined, [ 'mixedCase' ]); expect(captured.vertex).toContain('#define FEATURE_MIXEDCASE'); }); it('should replace non-alphanumeric characters in feature names with underscores', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(features)\nvoid main(){}', fragmentShader: 'void main(){}' }; factory.createShaderProgram('test', base, undefined, [ 'my-feature.flag' ]); expect(captured.vertex).toContain('#define FEATURE_MY_FEATURE_FLAG'); }); it('should insert shader name defines via the shaderName pragma', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(shaderName)\nvoid main(){}', fragmentShader: '#pragma phaserTemplate(shaderName)\nvoid main(){}' }; factory.createShaderProgram('myProgram', base); expect(captured.vertex).toContain('#define SHADER_NAME myProgram__VERTEX'); expect(captured.fragment).toContain('#define SHADER_NAME myProgram__FRAGMENT'); }); it('should remove any remaining pragma template directives', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(unused)\nvoid main(){}', fragmentShader: '#pragma phaserTemplate(alsoUnused)\nvoid main(){}' }; factory.createShaderProgram('test', base); expect(captured.vertex).not.toContain('#pragma phaserTemplate'); expect(captured.fragment).not.toContain('#pragma phaserTemplate'); }); it('should handle undefined additions without throwing', function () { var base = { name: 'test', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; expect(function () { factory.createShaderProgram('test', base, undefined, undefined); }).not.toThrow(); }); it('should handle an empty additions array without throwing', function () { var base = { name: 'test', vertexShader: 'void main(){}', fragmentShader: 'void main(){}' }; expect(function () { factory.createShaderProgram('test', base, [], []); }).not.toThrow(); }); it('should strip carriage returns from addition source strings', function () { var captured = {}; mockRenderer.createProgram = function (v, f) { captured.vertex = v; captured.fragment = f; return {}; }; var base = { name: 'test', vertexShader: '#pragma phaserTemplate(slot)\nvoid main(){}', fragmentShader: 'void main(){}' }; var additions = [ { name: 'a', disable: false, additions: { slot: 'float\r x\r = 1.0;' } } ]; factory.createShaderProgram('test', base, additions); expect(captured.vertex.indexOf('\r')).toBe(-1); }); }); });