phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
559 lines (442 loc) • 19.2 kB
JavaScript
vi.mock('../../../src/const', function ()
{
return {
BlendModes: {
SKIP_CHECK: -1,
NORMAL: 0
}
};
});
var ContainerWebGLRenderer = require('../../../src/gameobjects/container/ContainerWebGLRenderer');
function makeTransformMatrix ()
{
return {
loadIdentity: vi.fn(),
multiply: vi.fn(),
translate: vi.fn(),
rotate: vi.fn(),
scale: vi.fn(),
applyITRS: vi.fn()
};
}
function makeCamera ()
{
return {
addToRenderList: vi.fn()
};
}
function makeContext (blendMode)
{
var ctx = {
camera: makeCamera(),
blendMode: blendMode !== undefined ? blendMode : 0,
setBlendMode: vi.fn(),
use: vi.fn(),
release: vi.fn(),
getClone: vi.fn()
};
ctx.getClone.mockReturnValue(ctx);
return ctx;
}
function makeContainer (children, overrides)
{
var base = {
list: children || [],
localTransform: makeTransformMatrix(),
blendMode: -1,
alpha: 1,
scrollFactorX: 1,
scrollFactorY: 1,
x: 0,
y: 0,
rotation: 0,
scaleX: 1,
scaleY: 1
};
if (overrides)
{
for (var key in overrides)
{
base[key] = overrides[key];
}
}
return base;
}
function makeChild (overrides)
{
var child = {
willRender: vi.fn().mockReturnValue(true),
alphaTopLeft: 1,
alphaTopRight: 1,
alphaBottomLeft: 1,
alphaBottomRight: 1,
scrollFactorX: 1,
scrollFactorY: 1,
blendMode: 0,
setAlpha: vi.fn(),
setScrollFactor: vi.fn(),
renderWebGLStep: vi.fn()
};
if (overrides)
{
for (var key in overrides)
{
child[key] = overrides[key];
}
}
return child;
}
describe('ContainerWebGLRenderer', function ()
{
it('should be a function', function ()
{
expect(typeof ContainerWebGLRenderer).toBe('function');
});
describe('camera and early exit', function ()
{
it('should always call camera.addToRenderList with the container', function ()
{
var ctx = makeContext();
var container = makeContainer([]);
var renderer = {};
ContainerWebGLRenderer(renderer, container, ctx, null, 0, [], 0);
expect(ctx.camera.addToRenderList).toHaveBeenCalledWith(container);
});
it('should return early when the container has no children', function ()
{
var ctx = makeContext();
var container = makeContainer([]);
var renderer = {};
ContainerWebGLRenderer(renderer, container, ctx, null, 0, [], 0);
expect(container.localTransform.applyITRS).not.toHaveBeenCalled();
expect(container.localTransform.loadIdentity).not.toHaveBeenCalled();
});
it('should call camera.addToRenderList even when there are no children', function ()
{
var ctx = makeContext();
var container = makeContainer([]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.camera.addToRenderList).toHaveBeenCalledTimes(1);
});
});
describe('transform matrix without parentMatrix', function ()
{
it('should call applyITRS when no parentMatrix is provided', function ()
{
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ], { x: 10, y: 20, rotation: 0.5, scaleX: 2, scaleY: 3 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(container.localTransform.applyITRS).toHaveBeenCalledWith(10, 20, 0.5, 2, 3);
});
it('should not call loadIdentity or multiply when no parentMatrix is provided', function ()
{
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(container.localTransform.loadIdentity).not.toHaveBeenCalled();
expect(container.localTransform.multiply).not.toHaveBeenCalled();
});
});
describe('transform matrix with parentMatrix', function ()
{
it('should call loadIdentity, multiply, translate, rotate, scale when parentMatrix is provided', function ()
{
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ], { x: 5, y: 10, rotation: 1, scaleX: 2, scaleY: 2 });
var parentMatrix = {};
ContainerWebGLRenderer({}, container, ctx, parentMatrix, 0, [], 0);
var tm = container.localTransform;
expect(tm.loadIdentity).toHaveBeenCalledTimes(1);
expect(tm.multiply).toHaveBeenCalledWith(parentMatrix);
expect(tm.translate).toHaveBeenCalledWith(5, 10);
expect(tm.rotate).toHaveBeenCalledWith(1);
expect(tm.scale).toHaveBeenCalledWith(2, 2);
});
it('should not call applyITRS when parentMatrix is provided', function ()
{
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ]);
var parentMatrix = {};
ContainerWebGLRenderer({}, container, ctx, parentMatrix, 0, [], 0);
expect(container.localTransform.applyITRS).not.toHaveBeenCalled();
});
});
describe('child willRender filtering', function ()
{
it('should skip children where willRender returns false', function ()
{
var ctx = makeContext();
var child = makeChild({ willRender: vi.fn().mockReturnValue(false) });
var container = makeContainer([ child ]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(child.renderWebGLStep).not.toHaveBeenCalled();
expect(child.setAlpha).not.toHaveBeenCalled();
});
it('should pass the camera to child.willRender', function ()
{
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(child.willRender).toHaveBeenCalledWith(ctx.camera);
});
it('should render children where willRender returns true', function ()
{
var ctx = makeContext();
var child = makeChild({ willRender: vi.fn().mockReturnValue(true) });
var container = makeContainer([ child ]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(child.renderWebGLStep).toHaveBeenCalledTimes(1);
});
});
describe('child alpha handling with alphaTopLeft defined', function ()
{
it('should multiply each corner alpha by container alpha before rendering', function ()
{
var ctx = makeContext();
var child = makeChild({
alphaTopLeft: 0.5,
alphaTopRight: 0.6,
alphaBottomLeft: 0.7,
alphaBottomRight: 0.8
});
var container = makeContainer([ child ], { alpha: 0.5 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
var setAlphaCalls = child.setAlpha.mock.calls;
// First call: multiplied values
expect(setAlphaCalls[0][0]).toBeCloseTo(0.25);
expect(setAlphaCalls[0][1]).toBeCloseTo(0.30);
expect(setAlphaCalls[0][2]).toBeCloseTo(0.35);
expect(setAlphaCalls[0][3]).toBeCloseTo(0.40);
});
it('should restore original corner alpha values after rendering', function ()
{
var ctx = makeContext();
var child = makeChild({
alphaTopLeft: 0.5,
alphaTopRight: 0.6,
alphaBottomLeft: 0.7,
alphaBottomRight: 0.8
});
var container = makeContainer([ child ], { alpha: 0.5 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
var setAlphaCalls = child.setAlpha.mock.calls;
// Second call: restored original values
expect(setAlphaCalls[1][0]).toBeCloseTo(0.5);
expect(setAlphaCalls[1][1]).toBeCloseTo(0.6);
expect(setAlphaCalls[1][2]).toBeCloseTo(0.7);
expect(setAlphaCalls[1][3]).toBeCloseTo(0.8);
});
});
describe('child alpha handling without alphaTopLeft (uniform alpha)', function ()
{
it('should use child.alpha for all corners when alphaTopLeft is undefined', function ()
{
var ctx = makeContext();
var child = makeChild();
// Remove alphaTopLeft so the else branch is taken
delete child.alphaTopLeft;
child.alpha = 0.4;
var container = makeContainer([ child ], { alpha: 0.5 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
var setAlphaCalls = child.setAlpha.mock.calls;
// All four corners should be 0.4 * 0.5 = 0.2
expect(setAlphaCalls[0][0]).toBeCloseTo(0.2);
expect(setAlphaCalls[0][1]).toBeCloseTo(0.2);
expect(setAlphaCalls[0][2]).toBeCloseTo(0.2);
expect(setAlphaCalls[0][3]).toBeCloseTo(0.2);
});
it('should restore uniform alpha value after rendering', function ()
{
var ctx = makeContext();
var child = makeChild();
delete child.alphaTopLeft;
child.alpha = 0.4;
var container = makeContainer([ child ], { alpha: 0.5 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
var setAlphaCalls = child.setAlpha.mock.calls;
// Restored values should all be the original uniform alpha
expect(setAlphaCalls[1][0]).toBeCloseTo(0.4);
expect(setAlphaCalls[1][1]).toBeCloseTo(0.4);
expect(setAlphaCalls[1][2]).toBeCloseTo(0.4);
expect(setAlphaCalls[1][3]).toBeCloseTo(0.4);
});
});
describe('child scroll factor handling', function ()
{
it('should multiply child scrollFactor by container scrollFactor before rendering', function ()
{
var ctx = makeContext();
var child = makeChild({ scrollFactorX: 0.5, scrollFactorY: 0.25 });
var container = makeContainer([ child ], { scrollFactorX: 2, scrollFactorY: 4 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
var calls = child.setScrollFactor.mock.calls;
// First call: multiplied
expect(calls[0][0]).toBeCloseTo(1.0);
expect(calls[0][1]).toBeCloseTo(1.0);
});
it('should restore original child scrollFactor values after rendering', function ()
{
var ctx = makeContext();
var child = makeChild({ scrollFactorX: 0.5, scrollFactorY: 0.25 });
var container = makeContainer([ child ], { scrollFactorX: 2, scrollFactorY: 4 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
var calls = child.setScrollFactor.mock.calls;
// Second call: restored originals
expect(calls[1][0]).toBeCloseTo(0.5);
expect(calls[1][1]).toBeCloseTo(0.25);
});
});
describe('renderWebGLStep call', function ()
{
it('should call child.renderWebGLStep with correct arguments', function ()
{
var renderer = {};
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ]);
ContainerWebGLRenderer(renderer, container, ctx, null, 0, [], 0);
expect(child.renderWebGLStep).toHaveBeenCalledTimes(1);
var callArgs = child.renderWebGLStep.mock.calls[0];
expect(callArgs[0]).toBe(renderer);
expect(callArgs[1]).toBe(child);
expect(callArgs[2]).toBe(ctx); // currentContext
expect(callArgs[3]).toBe(container.localTransform); // transformMatrix
expect(callArgs[4]).toBeUndefined();
expect(callArgs[5]).toBe(container.list); // children array
expect(callArgs[6]).toBe(0); // index
});
it('should pass correct child index when multiple children exist', function ()
{
var renderer = {};
var ctx = makeContext();
var child0 = makeChild();
var child1 = makeChild();
var child2 = makeChild();
var container = makeContainer([ child0, child1, child2 ]);
ContainerWebGLRenderer(renderer, container, ctx, null, 0, [], 0);
expect(child0.renderWebGLStep.mock.calls[0][6]).toBe(0);
expect(child1.renderWebGLStep.mock.calls[0][6]).toBe(1);
expect(child2.renderWebGLStep.mock.calls[0][6]).toBe(2);
});
});
describe('blend mode: container has no blend mode (SKIP_CHECK = -1)', function ()
{
it('should clone context and set blend mode to 0 when container blendMode is -1 and context blendMode is not 0', function ()
{
var clonedCtx = makeContext(0);
var ctx = makeContext(1);
ctx.getClone.mockReturnValue(clonedCtx);
var child = makeChild({ blendMode: 0 });
var container = makeContainer([ child ], { blendMode: -1 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.getClone).toHaveBeenCalled();
expect(clonedCtx.setBlendMode).toHaveBeenCalledWith(0);
expect(clonedCtx.use).toHaveBeenCalled();
});
it('should not clone context when container blendMode is -1 and context blendMode is already 0', function ()
{
var ctx = makeContext(0);
var child = makeChild({ blendMode: 0 });
var container = makeContainer([ child ], { blendMode: -1 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
// getClone should not be called for the baseContext normalisation
// (child.blendMode === currentContext.blendMode so no child clone either)
expect(ctx.getClone).not.toHaveBeenCalled();
});
it('should clone context for a child whose blendMode differs from currentContext blendMode when container has no blend mode', function ()
{
var childCtx = makeContext(2);
var ctx = makeContext(0);
ctx.getClone.mockReturnValue(childCtx);
var child = makeChild({ blendMode: 2 });
var container = makeContainer([ child ], { blendMode: -1 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.getClone).toHaveBeenCalled();
expect(childCtx.setBlendMode).toHaveBeenCalledWith(2);
expect(childCtx.use).toHaveBeenCalled();
});
it('should not clone context for child when container has its own blend mode', function ()
{
var ctx = makeContext(0);
var child = makeChild({ blendMode: 2 });
var container = makeContainer([ child ], { blendMode: 1 }); // container has a real blend mode
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.getClone).not.toHaveBeenCalled();
});
it('should not clone context for child when child blendMode is SKIP_CHECK (-1)', function ()
{
var ctx = makeContext(0);
var child = makeChild({ blendMode: -1 }); // SKIP_CHECK
var container = makeContainer([ child ], { blendMode: -1 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.getClone).not.toHaveBeenCalled();
});
});
describe('context release', function ()
{
it('should release currentContext when it differs from the original drawingContext', function ()
{
var clonedCtx = makeContext(2);
clonedCtx.getClone = vi.fn().mockReturnValue(clonedCtx);
var ctx = makeContext(0);
ctx.getClone.mockReturnValue(clonedCtx);
var child = makeChild({ blendMode: 2 });
var container = makeContainer([ child ], { blendMode: -1 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(clonedCtx.release).toHaveBeenCalledTimes(1);
});
it('should not call release when currentContext is the same as drawingContext', function ()
{
var ctx = makeContext(0);
var child = makeChild({ blendMode: 0 });
var container = makeContainer([ child ], { blendMode: -1 });
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.release).not.toHaveBeenCalled();
});
it('should not call release when container has no children', function ()
{
var ctx = makeContext(0);
var container = makeContainer([]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(ctx.release).not.toHaveBeenCalled();
});
});
describe('multiple children', function ()
{
it('should call renderWebGLStep for every child that passes willRender', function ()
{
var ctx = makeContext();
var children = [ makeChild(), makeChild(), makeChild() ];
var container = makeContainer(children);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
children.forEach(function (child)
{
expect(child.renderWebGLStep).toHaveBeenCalledTimes(1);
});
});
it('should skip only children where willRender returns false among many children', function ()
{
var ctx = makeContext();
var visibleChild = makeChild();
var hiddenChild = makeChild({ willRender: vi.fn().mockReturnValue(false) });
var container = makeContainer([ visibleChild, hiddenChild ]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(visibleChild.renderWebGLStep).toHaveBeenCalledTimes(1);
expect(hiddenChild.renderWebGLStep).not.toHaveBeenCalled();
});
it('should call setAlpha and setScrollFactor twice per rendered child (set + restore)', function ()
{
var ctx = makeContext();
var child = makeChild();
var container = makeContainer([ child ]);
ContainerWebGLRenderer({}, container, ctx, null, 0, [], 0);
expect(child.setAlpha).toHaveBeenCalledTimes(2);
expect(child.setScrollFactor).toHaveBeenCalledTimes(2);
});
});
});