import { Color } from "../../Source/Cesium.js"; import { PixelFormat } from "../../Source/Cesium.js"; import { PrimitiveType } from "../../Source/Cesium.js"; import { WebGLConstants } from "../../Source/Cesium.js"; import { Buffer } from "../../Source/Cesium.js"; import { BufferUsage } from "../../Source/Cesium.js"; import { ClearCommand } from "../../Source/Cesium.js"; import { ContextLimits } from "../../Source/Cesium.js"; import { CubeMap } from "../../Source/Cesium.js"; import { DrawCommand } from "../../Source/Cesium.js"; import { Framebuffer } from "../../Source/Cesium.js"; import { PixelDatatype } from "../../Source/Cesium.js"; import { Renderbuffer } from "../../Source/Cesium.js"; import { RenderbufferFormat } from "../../Source/Cesium.js"; import { RenderState } from "../../Source/Cesium.js"; import { ShaderProgram } from "../../Source/Cesium.js"; import { Texture } from "../../Source/Cesium.js"; import { VertexArray } from "../../Source/Cesium.js"; import createContext from "../createContext.js"; describe( "Renderer/Framebuffer", function () { var context; var sp; var va; var framebuffer; beforeAll(function () { context = createContext(); }); afterAll(function () { context.destroyForSpecs(); }); afterEach(function () { sp = sp && sp.destroy(); va = va && va.destroy(); framebuffer = framebuffer && framebuffer.destroy(); }); it("has a color texture attachment", function () { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], }); expect(framebuffer.getColorTexture(0)).toBeDefined(); }); it("has a color renderbuffer attachment", function () { framebuffer = new Framebuffer({ context: context, colorRenderbuffers: [ new Renderbuffer({ context: context, format: RenderbufferFormat.RGBA4, }), ], }); expect(framebuffer.getColorRenderbuffer(0)).toBeDefined(); }); it("has a depth texture attachment", function () { if (context.depthTexture) { framebuffer = new Framebuffer({ context: context, depthTexture: new Texture({ context: context, width: 1, height: 1, pixelFormat: PixelFormat.DEPTH_COMPONENT, pixelDatatype: PixelDatatype.UNSIGNED_SHORT, }), }); expect(framebuffer.depthTexture).toBeDefined(); } }); it("has a depth renderbuffer attachment", function () { framebuffer = new Framebuffer({ context: context, depthRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.DEPTH_COMPONENT16, }), }); expect(framebuffer.depthRenderbuffer).toBeDefined(); }); it("has a stencil renderbuffer attachment", function () { framebuffer = new Framebuffer({ context: context, stencilRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.STENCIL_INDEX8, }), }); expect(framebuffer.stencilRenderbuffer).toBeDefined(); }); it("has a depth-stencil texture attachment", function () { if (context.depthTexture) { framebuffer = new Framebuffer({ context: context, depthStencilTexture: new Texture({ context: context, width: 1, height: 1, pixelFormat: PixelFormat.DEPTH_STENCIL, pixelDatatype: PixelDatatype.UNSIGNED_INT_24_8, }), }); expect(framebuffer.depthStencilTexture).toBeDefined(); } }); it("has a depth-stencil renderbuffer attachment", function () { framebuffer = new Framebuffer({ context: context, depthStencilRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.DEPTH_STENCIL, }), }); expect(framebuffer.depthStencilRenderbuffer).toBeDefined(); }); it("has a depth attachment", function () { framebuffer = new Framebuffer({ context: context, }); expect(framebuffer.hasDepthAttachment).toEqual(false); framebuffer.destroy(); framebuffer = new Framebuffer({ context: context, depthRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.DEPTH_COMPONENT16, }), }); expect(framebuffer.hasDepthAttachment).toEqual(true); }); it("clears a color attachment", function () { // 1 of 3. Clear default color buffer to black. ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); // 2 of 3. Clear framebuffer color attachment to green. var colorTexture = new Texture({ context: context, width: 1, height: 1, }); framebuffer = new Framebuffer({ context: context, colorTextures: [colorTexture], }); var clearCommand = new ClearCommand({ color: new Color(0.0, 1.0, 0.0, 1.0), framebuffer: framebuffer, }); clearCommand.execute(context); // 3 of 3. Render green to default color buffer by reading from previous color attachment var fs = "uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, vec2(0.0)); }"; var uniformMap = { u_texture: function () { return colorTexture; }, }; expect({ context: context, fragmentShader: fs, uniformMap: uniformMap, }).contextToRender([0, 255, 0, 255]); }); it("clears a cube map face color attachment", function () { var cubeMap = new CubeMap({ context: context, width: 1, height: 1, }); // 1 of 3. Clear default color buffer to black. ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); // 2 of 3. Clear framebuffer color attachment to green. framebuffer = new Framebuffer({ context: context, colorTextures: [cubeMap.positiveX], destroyAttachments: false, }); var clearCommand = new ClearCommand({ color: new Color(0.0, 1.0, 0.0, 1.0), framebuffer: framebuffer, }); clearCommand.execute(context); // 3 of 3. Render green to default color buffer by reading from previous color attachment var fs = "uniform samplerCube u_cubeMap; void main() { gl_FragColor = textureCube(u_cubeMap, vec3(1.0, 0.0, 0.0)); }"; var uniformMap = { u_cubeMap: function () { return cubeMap; }, }; expect({ context: context, fragmentShader: fs, uniformMap: uniformMap, }).contextToRender([0, 255, 0, 255]); cubeMap = cubeMap.destroy(); }); function renderColorTexture(framebuffer, color, expectedColor) { var vs = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs = "uniform vec4 color; void main() { gl_FragColor = color; }"; sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { position: 0, }, }); va = new VertexArray({ context: context, attributes: [ { index: 0, vertexBuffer: Buffer.createVertexBuffer({ context: context, typedArray: new Float32Array([0, 0, 0, 1]), usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 4, }, ], }); var uniformMap = { color: function () { return color; }, }; var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, uniformMap: uniformMap, framebuffer: framebuffer, }); command.execute(context); expect({ context: context, framebuffer: framebuffer, }).toReadPixels(expectedColor); } it("draws to a color attachment", function () { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], }); renderColorTexture(framebuffer, new Color(0.0, 1.0, 0.0, 1.0), [ 0, 255, 0, 255, ]); }); it("draws to a floating-point color attachment", function () { if (!context.colorBufferFloat) { return; } framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, pixelDatatype: PixelDatatype.FLOAT, }), ], }); renderColorTexture(framebuffer, new Color(0.5, 1.5, 2.0, 1.0), [ 0.5, 1.5, 2.0, 1.0, ]); }); it("draws to a half floating-point color attachment", function () { if (!context.colorBufferHalfFloat) { return; } framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, pixelDatatype: PixelDatatype.HALF_FLOAT, }), ], }); renderColorTexture(framebuffer, new Color(0.5, 1.5, 2.0, 1.0), [ 14336, 15872, 16384, 15360, ]); }); function renderDepthAttachment(framebuffer, texture) { ClearCommand.ALL.execute(context); var framebufferClear = new ClearCommand({ depth: 1.0, framebuffer: framebuffer, }); framebufferClear.execute(context); // 1 of 3. Render green point into color attachment. var vs = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs = "void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); }"; sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { position: 0, }, }); va = new VertexArray({ context: context, attributes: [ { index: 0, vertexBuffer: Buffer.createVertexBuffer({ context: context, typedArray: new Float32Array([0, 0, 0, 1]), usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 4, }, ], }); var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, framebuffer: framebuffer, renderState: RenderState.fromCache({ depthTest: { enabled: true, }, }), }); command.execute(context); // 2 of 3. Verify default color buffer is still black. expect(context).toReadPixels([0, 0, 0, 255]); // 3 of 3. Render green to default color buffer by reading from previous color attachment var vs2 = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs2 = "uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, vec2(0.0)).rrrr; }"; var sp2 = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs2, fragmentShaderSource: fs2, attributeLocations: { position: 0, }, }); var uniformMap = { u_texture: function () { return texture; }, }; command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp2, vertexArray: va, uniformMap: uniformMap, }); command.execute(context); sp2 = sp2.destroy(); return context.readPixels(); } it("draws to a depth texture attachment", function () { if (context.depthTexture) { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], depthTexture: new Texture({ context: context, width: 1, height: 1, pixelFormat: PixelFormat.DEPTH_COMPONENT, pixelDatatype: PixelDatatype.UNSIGNED_SHORT, }), }); if (framebuffer.status === WebGLConstants.FRAMEBUFFER_COMPLETE) { expect( renderDepthAttachment(framebuffer, framebuffer.depthTexture) ).toEqualEpsilon([128, 128, 128, 255], 1); } } }); it("draws to a depth-stencil texture attachment", function () { if (context.depthTexture) { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], depthStencilTexture: new Texture({ context: context, width: 1, height: 1, pixelFormat: PixelFormat.DEPTH_STENCIL, pixelDatatype: PixelDatatype.UNSIGNED_INT_24_8, }), }); if (framebuffer.status === WebGLConstants.FRAMEBUFFER_COMPLETE) { expect( renderDepthAttachment(framebuffer, framebuffer.depthStencilTexture) ).toEqualEpsilon([128, 128, 128, 255], 1); } } }); it("draws with a depth attachment", function () { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], depthRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.DEPTH_COMPONENT16, width: 1, height: 1, }), }); var vs = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs = "void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }"; sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { position: 0, }, }); va = new VertexArray({ context: context, attributes: [ { index: 0, vertexBuffer: Buffer.createVertexBuffer({ context: context, typedArray: new Float32Array([0, 0, 0, 1]), usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 4, }, ], }); // 1 of 3. Clear framebuffer var clearCommand = new ClearCommand({ color: new Color(0.0, 0.0, 0.0, 0.0), depth: 1.0, framebuffer: framebuffer, }); clearCommand.execute(context); expect({ context: context, framebuffer: framebuffer, }).toReadPixels([0, 0, 0, 0]); // 2 of 3. Does not pass depth test var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, framebuffer: framebuffer, renderState: RenderState.fromCache({ depthTest: { enabled: true, func: WebGLConstants.NEVER, }, }), }); command.execute(context); expect({ context: context, framebuffer: framebuffer, }).toReadPixels([0, 0, 0, 0]); // 3 of 3. Passes depth test command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, framebuffer: framebuffer, renderState: RenderState.fromCache({ depthTest: { enabled: true, func: WebGLConstants.ALWAYS, }, }), }); command.execute(context); expect({ context: context, framebuffer: framebuffer, }).toReadPixels([255, 255, 255, 255]); }); it("draws with multiple render targets", function () { if (!context.drawBuffers) { return; } var source = new Uint8Array(4); var colorTexture0 = new Texture({ context: context, source: { arrayBufferView: source, width: 1, height: 1, }, }); var colorTexture1 = new Texture({ context: context, source: { arrayBufferView: source, width: 1, height: 1, }, }); framebuffer = new Framebuffer({ context: context, colorTextures: [colorTexture0, colorTexture1], }); // 1 of 5. Clear default color buffer to black. ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); // 2 of 5. Render red point into color attachment 0 and green point to color attachment 1. var vs = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs = "#extension GL_EXT_draw_buffers : enable \n void main() { gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0); gl_FragData[1] = vec4(0.0, 1.0, 0.0, 1.0); }"; sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { position: 0, }, }); va = new VertexArray({ context: context, attributes: [ { index: 0, vertexBuffer: Buffer.createVertexBuffer({ context: context, typedArray: new Float32Array([0, 0, 0, 1]), usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 4, }, ], }); var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, framebuffer: framebuffer, }); command.execute(context); // 3 of 5. Verify default color buffer is still black. expect(context).toReadPixels([0, 0, 0, 255]); // 4 of 5. Render yellow to default color buffer by reading from previous color attachments var vs2 = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs2 = "uniform sampler2D u_texture0; uniform sampler2D u_texture1; void main() { gl_FragColor = texture2D(u_texture0, vec2(0.0)) + texture2D(u_texture1, vec2(0.0)); }"; var sp2 = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs2, fragmentShaderSource: fs2, attributeLocations: { position: 0, }, }); var uniformMap = { u_texture0: function () { return colorTexture0; }, u_texture1: function () { return colorTexture1; }, }; command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp2, vertexArray: va, uniformMap: uniformMap, }); command.execute(context); expect(context).toReadPixels([255, 255, 0, 255]); // 5 of 5. Verify clearing multiple color attachments var clearCommand = new ClearCommand({ color: new Color(0.0, 0.0, 0.0, 0.0), framebuffer: framebuffer, }); clearCommand.execute(context); command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp2, vertexArray: va, }); command.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); sp2 = sp2.destroy(); }); it("gets the status of a complete framebuffer", function () { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], depthRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.DEPTH_COMPONENT16, width: 1, height: 1, }), }); expect(framebuffer.status).toEqual(WebGLConstants.FRAMEBUFFER_COMPLETE); }); it("gets the status of a incomplete framebuffer", function () { if (!!window.webglStub) { return; } framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], depthRenderbuffer: new Renderbuffer({ context: context, format: RenderbufferFormat.RGBA4, width: 2, height: 2, }), }); expect(framebuffer.status).not.toEqual( WebGLConstants.FRAMEBUFFER_COMPLETE ); }); it("destroys", function () { var f = new Framebuffer({ context: context, }); expect(f.isDestroyed()).toEqual(false); f.destroy(); expect(f.isDestroyed()).toEqual(true); }); it("throws when created with color texture and color renderbuffer attachments", function () { expect(function () { framebuffer = new Framebuffer({ context: context, colorTextures: "not undefined", colorRenderbuffers: "not undefined", }); }).toThrowDeveloperError(); }); it("throws when created with depth texture and depth renderbuffer attachments", function () { expect(function () { framebuffer = new Framebuffer({ context: context, depthTexture: "not undefined", depthRenderbuffer: "not undefined", }); }).toThrowDeveloperError(); }); it("throws when created with depth-stencil texture and depth-stencil renderbuffer attachments", function () { expect(function () { framebuffer = new Framebuffer({ context: context, depthStencilTexture: "not undefined", depthStencilRenderbuffer: "not undefined", }); }).toThrowDeveloperError(); }); it("throws when created with depth and depth-stencil attachments", function () { expect(function () { framebuffer = new Framebuffer({ context: context, depthRenderbuffer: "not undefined", depthStencilRenderbuffer: "not undefined", }); }).toThrowDeveloperError(); }); it("throws when created with stencil and depth-stencil attachments", function () { expect(function () { framebuffer = new Framebuffer({ context: context, stencilRenderbuffer: "not undefined", depthStencilRenderbuffer: "not undefined", }); }).toThrowDeveloperError(); }); it("throws when created with depth and stencil attachments", function () { expect(function () { framebuffer = new Framebuffer({ context: context, depthRenderbuffer: "not undefined", stencilRenderbuffer: "not undefined", }); }).toThrowDeveloperError(); }); it("throws when created with a color texture with a non-color pixel format", function () { if (context.depthTexture) { expect(function () { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, pixelFormat: PixelFormat.DEPTH_COMPONENT, pixelDatatype: PixelDatatype.UNSIGNED_SHORT, }), ], }); }).toThrowDeveloperError(); } }); it("throws when created with a depth texture without a DEPTH_COMPONENT pixel format", function () { expect(function () { framebuffer = new Framebuffer({ context: context, depthTexture: new Texture({ context: context, width: 1, height: 1, }), }); }).toThrowDeveloperError(); }); it("throws when created with a depth-stencil texture without a DEPTH_STENCIL pixel format", function () { expect(function () { framebuffer = new Framebuffer({ context: context, depthStencilTexture: new Texture({ context: context, width: 1, height: 1, }), }); }).toThrowDeveloperError(); }); it("throws when the depth test is enabled without an appropriate attachment", function () { framebuffer = new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, }), ], }); var vs = "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }"; var fs = "void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }"; sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { position: 0, }, }); va = new VertexArray({ context: context, attributes: [ { index: 0, vertexBuffer: Buffer.createVertexBuffer({ context: context, typedArray: new Float32Array([0, 0, 0, 1]), usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 4, }, ], }); expect(function () { var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, framebuffer: framebuffer, renderState: RenderState.fromCache({ depthTest: { enabled: true, }, }), }); command.execute(context); }).toThrowDeveloperError(); }); it("throws when the number of color texture exceeds the number color attachments supported", function () { expect(function () { return new Framebuffer({ context: context, colorTextures: new Array(ContextLimits.maximumColorAttachments + 1), }); }).toThrowDeveloperError(); }); it("throws when the number of color renderbuffers exceeds the number color attachments supported", function () { expect(function () { return new Framebuffer({ context: context, colorRenderbuffers: new Array( ContextLimits.maximumColorAttachments + 1 ), }); }).toThrowDeveloperError(); }); it("throws when the index to getColorTexture is out of bounds", function () { framebuffer = new Framebuffer({ context: context, }); expect(function () { framebuffer.getColorTexture(); }).toThrowDeveloperError(); expect(function () { framebuffer.getColorTexture(-1); }).toThrowDeveloperError(); expect(function () { framebuffer.getColorTexture(ContextLimits.maximumColorAttachments + 1); }).toThrowDeveloperError(); }); it("throws when the index to getColorRenderbuffer is out of bounds", function () { framebuffer = new Framebuffer({ context: context, }); expect(function () { framebuffer.getColorRenderbuffer(); }).toThrowDeveloperError(); expect(function () { framebuffer.getColorRenderbuffer(-1); }).toThrowDeveloperError(); expect(function () { framebuffer.getColorRenderbuffer( ContextLimits.maximumColorAttachments + 1 ); }).toThrowDeveloperError(); }); it("throws when a color attachment has a floating-point datatype but the EXT_color_buffer_float extension is not supported", function () { if (context.colorBufferFloat) { return; } expect(function () { return new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, pixelDatatype: PixelDatatype.FLOAT, }), ], }); }).toThrowDeveloperError(); }); it("throws when a color attachment has a half floating-point datatype but the EXT_color_buffer_half_float extension is not supported", function () { if (context.colorBufferHalfFloat) { return; } expect(function () { return new Framebuffer({ context: context, colorTextures: [ new Texture({ context: context, width: 1, height: 1, pixelDatatype: PixelDatatype.HALF_FLOAT, }), ], }); }).toThrowDeveloperError(); }); it("fails to destroy", function () { var f = new Framebuffer({ context: context, }); f.destroy(); expect(function () { f.destroy(); }).toThrowDeveloperError(); }); it("throws when there is no context", function () { expect(function () { return new Framebuffer(); }).toThrowDeveloperError(); }); }, "WebGL" );