phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
461 lines (408 loc) • 16.1 kB
JavaScript
var WebGLTextureUnitsWrapper = require('../../../../src/renderer/webgl/wrappers/WebGLTextureUnitsWrapper');
function createMockRenderer (maxTextures)
{
var glTexture = {};
var gl = {
TEXTURE_2D: 0x0DE1,
RGBA: 0x1908,
UNSIGNED_BYTE: 0x1401,
createTexture: vi.fn(function () { return glTexture; }),
bindTexture: vi.fn(),
texImage2D: vi.fn()
};
return {
gl: gl,
maxTextures: maxTextures !== undefined ? maxTextures : 4,
glWrapper: {
updateBindingsActiveTexture: vi.fn()
}
};
}
function createMockTexture (id)
{
return {
webGLTexture: { id: id || 'texture' }
};
}
describe('WebGLTextureUnitsWrapper', function ()
{
var renderer;
var wrapper;
beforeEach(function ()
{
renderer = createMockRenderer(4);
wrapper = new WebGLTextureUnitsWrapper(renderer);
});
describe('constructor', function ()
{
it('should store the renderer reference', function ()
{
expect(wrapper.renderer).toBe(renderer);
});
it('should initialize units as an array', function ()
{
expect(Array.isArray(wrapper.units)).toBe(true);
});
it('should initialize unitIndices as an array', function ()
{
expect(Array.isArray(wrapper.unitIndices)).toBe(true);
});
it('should call init during construction', function ()
{
expect(renderer.gl.createTexture).toHaveBeenCalled();
});
});
describe('init', function ()
{
it('should set units array length to maxTextures', function ()
{
expect(wrapper.units.length).toBe(renderer.maxTextures);
});
it('should set unitIndices array length to maxTextures', function ()
{
expect(wrapper.unitIndices.length).toBe(renderer.maxTextures);
});
it('should populate unitIndices with sequential indices', function ()
{
for (var i = 0; i < renderer.maxTextures; i++)
{
expect(wrapper.unitIndices[i]).toBe(i);
}
});
it('should set all units to undefined', function ()
{
for (var i = 0; i < renderer.maxTextures; i++)
{
expect(wrapper.units[i]).toBeUndefined();
}
});
it('should call gl.createTexture once', function ()
{
expect(renderer.gl.createTexture).toHaveBeenCalledTimes(1);
});
it('should call gl.bindTexture for each texture unit', function ()
{
expect(renderer.gl.bindTexture).toHaveBeenCalledTimes(renderer.maxTextures);
});
it('should call gl.texImage2D once', function ()
{
expect(renderer.gl.texImage2D).toHaveBeenCalledTimes(1);
});
it('should call updateBindingsActiveTexture for each unit during init', function ()
{
expect(renderer.glWrapper.updateBindingsActiveTexture).toHaveBeenCalledTimes(renderer.maxTextures);
});
it('should reset units and unitIndices when called again', function ()
{
wrapper.units[0] = createMockTexture('old');
renderer.gl.createTexture.mockClear();
renderer.gl.bindTexture.mockClear();
renderer.gl.texImage2D.mockClear();
renderer.glWrapper.updateBindingsActiveTexture.mockClear();
wrapper.init();
expect(wrapper.units.length).toBe(renderer.maxTextures);
expect(wrapper.unitIndices.length).toBe(renderer.maxTextures);
expect(wrapper.units[0]).toBeUndefined();
});
it('should work with maxTextures of 1', function ()
{
var r = createMockRenderer(1);
var w = new WebGLTextureUnitsWrapper(r);
expect(w.units.length).toBe(1);
expect(w.unitIndices.length).toBe(1);
expect(w.unitIndices[0]).toBe(0);
});
it('should work with maxTextures of 8', function ()
{
var r = createMockRenderer(8);
var w = new WebGLTextureUnitsWrapper(r);
expect(w.units.length).toBe(8);
expect(w.unitIndices.length).toBe(8);
for (var i = 0; i < 8; i++)
{
expect(w.unitIndices[i]).toBe(i);
}
});
});
describe('bind', function ()
{
beforeEach(function ()
{
// Set units to undefined so binding new textures triggers needsBind
for (var i = 0; i < renderer.maxTextures; i++)
{
wrapper.units[i] = undefined;
}
renderer.gl.bindTexture.mockClear();
renderer.glWrapper.updateBindingsActiveTexture.mockClear();
});
it('should update the unit to the given texture', function ()
{
var texture = createMockTexture('tex0');
wrapper.bind(texture, 0);
expect(wrapper.units[0]).toBe(texture);
});
it('should call gl.bindTexture with the texture webGLTexture', function ()
{
var texture = createMockTexture('tex1');
wrapper.bind(texture, 1);
expect(renderer.gl.bindTexture).toHaveBeenCalledWith(renderer.gl.TEXTURE_2D, texture.webGLTexture);
});
it('should call gl.bindTexture with null when texture is null', function ()
{
wrapper.units[0] = createMockTexture('existing');
renderer.gl.bindTexture.mockClear();
wrapper.bind(null, 0);
expect(renderer.gl.bindTexture).toHaveBeenCalledWith(renderer.gl.TEXTURE_2D, null);
});
it('should set unit to null when binding null', function ()
{
wrapper.units[0] = createMockTexture('existing');
wrapper.bind(null, 0);
expect(wrapper.units[0]).toBeNull();
});
it('should not call gl.bindTexture when same texture is already bound and force is false', function ()
{
var texture = createMockTexture('same');
wrapper.units[2] = texture;
renderer.gl.bindTexture.mockClear();
wrapper.bind(texture, 2, false);
expect(renderer.gl.bindTexture).not.toHaveBeenCalled();
});
it('should call gl.bindTexture when same texture is already bound but force is true', function ()
{
var texture = createMockTexture('forced');
wrapper.units[1] = texture;
renderer.gl.bindTexture.mockClear();
wrapper.bind(texture, 1, true);
expect(renderer.gl.bindTexture).toHaveBeenCalledWith(renderer.gl.TEXTURE_2D, texture.webGLTexture);
});
it('should call updateBindingsActiveTexture when binding a new texture', function ()
{
var texture = createMockTexture('new');
wrapper.units[0] = undefined;
wrapper.bind(texture, 0);
expect(renderer.glWrapper.updateBindingsActiveTexture).toHaveBeenCalled();
});
it('should call updateBindingsActiveTexture with the correct unit', function ()
{
var texture = createMockTexture('new');
wrapper.bind(texture, 2);
var lastCall = renderer.glWrapper.updateBindingsActiveTexture.mock.calls[
renderer.glWrapper.updateBindingsActiveTexture.mock.calls.length - 1
];
expect(lastCall[0].bindings.activeTexture).toBe(2);
});
it('should call updateBindingsActiveTexture when forceActive is not false', function ()
{
var texture = createMockTexture('tex');
wrapper.units[0] = texture;
renderer.glWrapper.updateBindingsActiveTexture.mockClear();
// same texture, no force, but forceActive defaults to true
wrapper.bind(texture, 0, false, true);
expect(renderer.glWrapper.updateBindingsActiveTexture).toHaveBeenCalled();
});
it('should not call updateBindingsActiveTexture when texture is same, no force, and forceActive is false', function ()
{
var texture = createMockTexture('tex');
wrapper.units[0] = texture;
renderer.glWrapper.updateBindingsActiveTexture.mockClear();
wrapper.bind(texture, 0, false, false);
expect(renderer.glWrapper.updateBindingsActiveTexture).not.toHaveBeenCalled();
});
it('should bind different textures to different units independently', function ()
{
var tex0 = createMockTexture('t0');
var tex1 = createMockTexture('t1');
wrapper.bind(tex0, 0);
wrapper.bind(tex1, 1);
expect(wrapper.units[0]).toBe(tex0);
expect(wrapper.units[1]).toBe(tex1);
});
});
describe('bindUnits', function ()
{
beforeEach(function ()
{
for (var i = 0; i < renderer.maxTextures; i++)
{
wrapper.units[i] = undefined;
}
renderer.gl.bindTexture.mockClear();
renderer.glWrapper.updateBindingsActiveTexture.mockClear();
});
it('should bind each provided texture to its corresponding unit', function ()
{
var tex0 = createMockTexture('t0');
var tex1 = createMockTexture('t1');
wrapper.bindUnits([ tex0, tex1 ]);
expect(wrapper.units[0]).toBe(tex0);
expect(wrapper.units[1]).toBe(tex1);
});
it('should skip undefined entries', function ()
{
var tex0 = createMockTexture('t0');
wrapper.bindUnits([ tex0, undefined, undefined, undefined ]);
expect(wrapper.units[0]).toBe(tex0);
expect(wrapper.units[1]).toBeUndefined();
expect(wrapper.units[2]).toBeUndefined();
});
it('should bind null values to unbind a unit', function ()
{
var tex0 = createMockTexture('t0');
wrapper.units[0] = tex0;
wrapper.bindUnits([ null ]);
expect(wrapper.units[0]).toBeNull();
});
it('should respect maxTextures and ignore extra entries', function ()
{
var textures = [];
for (var i = 0; i <= renderer.maxTextures; i++)
{
textures.push(createMockTexture('t' + i));
}
renderer.gl.bindTexture.mockClear();
wrapper.bindUnits(textures);
// Only maxTextures number of binds should occur (some units may already be bound)
expect(renderer.gl.bindTexture.mock.calls.length).toBeLessThanOrEqual(renderer.maxTextures);
});
it('should use force flag when provided', function ()
{
var tex0 = createMockTexture('t0');
wrapper.units[0] = tex0;
renderer.gl.bindTexture.mockClear();
wrapper.bindUnits([ tex0 ], true);
expect(renderer.gl.bindTexture).toHaveBeenCalled();
});
it('should handle an empty array without errors', function ()
{
expect(function ()
{
wrapper.bindUnits([]);
}).not.toThrow();
});
it('should bind textures to all units when array fills maxTextures', function ()
{
var textures = [];
for (var i = 0; i < renderer.maxTextures; i++)
{
textures.push(createMockTexture('t' + i));
}
wrapper.bindUnits(textures);
for (var j = 0; j < renderer.maxTextures; j++)
{
expect(wrapper.units[j]).toBe(textures[j]);
}
});
});
describe('unbindTexture', function ()
{
it('should unbind the texture from the unit it is bound to', function ()
{
var texture = createMockTexture('target');
wrapper.units[1] = texture;
wrapper.unbindTexture(texture);
expect(wrapper.units[1]).toBeNull();
});
it('should unbind the texture from multiple units if bound to several', function ()
{
var texture = createMockTexture('multi');
wrapper.units[0] = texture;
wrapper.units[2] = texture;
wrapper.unbindTexture(texture);
expect(wrapper.units[0]).toBeNull();
expect(wrapper.units[2]).toBeNull();
});
it('should not affect units bound to other textures', function ()
{
var target = createMockTexture('target');
var other = createMockTexture('other');
wrapper.units[0] = target;
wrapper.units[1] = other;
wrapper.unbindTexture(target);
expect(wrapper.units[1]).toBe(other);
});
it('should not affect units that are null', function ()
{
var texture = createMockTexture('t');
wrapper.units[0] = null;
wrapper.units[1] = texture;
wrapper.unbindTexture(texture);
expect(wrapper.units[0]).toBeNull();
expect(wrapper.units[1]).toBeNull();
});
it('should do nothing when texture is not bound to any unit', function ()
{
var texture = createMockTexture('unbound');
var other = createMockTexture('other');
wrapper.units[0] = other;
wrapper.unbindTexture(texture);
expect(wrapper.units[0]).toBe(other);
});
it('should call gl.bindTexture with null for each matching unit', function ()
{
var texture = createMockTexture('t');
wrapper.units[0] = texture;
wrapper.units[3] = texture;
renderer.gl.bindTexture.mockClear();
wrapper.unbindTexture(texture);
var nullBindCalls = renderer.gl.bindTexture.mock.calls.filter(function (call)
{
return call[1] === null;
});
expect(nullBindCalls.length).toBe(2);
});
});
describe('unbindAllUnits', function ()
{
it('should set all units to null', function ()
{
var tex0 = createMockTexture('t0');
var tex1 = createMockTexture('t1');
wrapper.units[0] = tex0;
wrapper.units[1] = tex1;
wrapper.unbindAllUnits();
for (var i = 0; i < wrapper.units.length; i++)
{
expect(wrapper.units[i]).toBeNull();
}
});
it('should call gl.bindTexture with null for every unit', function ()
{
renderer.gl.bindTexture.mockClear();
wrapper.unbindAllUnits();
expect(renderer.gl.bindTexture).toHaveBeenCalledTimes(wrapper.units.length);
renderer.gl.bindTexture.mock.calls.forEach(function (call)
{
expect(call[1]).toBeNull();
});
});
it('should work when all units are already null', function ()
{
for (var i = 0; i < renderer.maxTextures; i++)
{
wrapper.units[i] = null;
}
expect(function ()
{
wrapper.unbindAllUnits();
}).not.toThrow();
for (var j = 0; j < renderer.maxTextures; j++)
{
expect(wrapper.units[j]).toBeNull();
}
});
it('should work when units are a mix of textures, null, and undefined', function ()
{
var texture = createMockTexture('mixed');
wrapper.units[0] = texture;
wrapper.units[1] = null;
wrapper.units[2] = undefined;
wrapper.unbindAllUnits();
for (var i = 0; i < 3; i++)
{
expect(wrapper.units[i]).toBeNull();
}
});
});
});