| /* | |
| * Copyright (C) 2025 Mozilla. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' | |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| * POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| const NUM_PARTICLES = 8000; | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x111111); | |
| const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); | |
| const innerHeight = 1080; | |
| const innerWidth = 1920; | |
| camera.position.z = 500; | |
| const canvas = { | |
| addEventListener() {}, | |
| style: {}, | |
| getContext(kind) { | |
| return { | |
| getExtension(extension) { | |
| if (extension == 'EXT_blend_minmax') { | |
| return {MIN_EXT: 32775, MAX_EXT: 32776} | |
| } | |
| if (extension == 'OES_vertex_array_object') { | |
| return { | |
| createVertexArrayOES() { return 1 }, | |
| bindVertexArrayOES() {}, | |
| deleteVertexArrayOES() {}, | |
| } | |
| } | |
| }, | |
| createTexture() {}, | |
| bindTexture() {}, | |
| texImage2D() {}, | |
| texImage3D() {}, | |
| texParameteri() {}, | |
| uniform1i() {}, | |
| uniform1f() {}, | |
| uniform2f() {}, | |
| uniform3f() {}, | |
| uniform4f() {}, | |
| clearColor() {}, | |
| clear() {}, | |
| clearDepth() {}, | |
| clearStencil() {}, | |
| enable() {}, | |
| disable() {}, | |
| depthFunc() {}, | |
| depthMask() {}, | |
| frontFace() {}, | |
| cullFace() {}, | |
| getContextAttributes() {}, | |
| createBuffer() {}, | |
| createFramebuffer() {}, | |
| bindBuffer() {}, | |
| bufferData() {}, | |
| createProgram() {}, | |
| attachShader() {}, | |
| linkProgram() {}, | |
| useProgram() {}, | |
| getAttribLocation() {}, | |
| getUniformLocation() {}, | |
| createShader() {}, | |
| shaderSource() {}, | |
| compileShader() {}, | |
| getShaderParameter() {}, | |
| getProgramInfoLog() { return "" }, | |
| getShaderInfoLog() { return "" }, | |
| getProgramParameter() {}, | |
| deleteShader() {}, | |
| colorMask() {}, | |
| stencilMask() {}, | |
| createVertexArray() {}, | |
| bindVertexArray() {}, | |
| drawElements() {}, | |
| lineWidth() {}, | |
| drawArrays() {}, | |
| viewport() {}, | |
| getParameter(param) { | |
| if (param == 34930) { return 16 } | |
| if (param == 35660) { return 16 } | |
| if (param == 3379) { return 8192 } | |
| if (param == 36347) { return 1024 } | |
| if (param == 36348) { return 32 } | |
| if (param == 36349) { return 1024 } | |
| if (param == 35661) { return 80 } | |
| if (param == 7938) { return "WebGL 2.0" } | |
| if (param == 3088) { return [0,0,1024,480] } | |
| if (param == 2978) { return [0,0,1024,480] } | |
| }, | |
| MAX_TEXTURE_IMAGE_UNITS: 34930, | |
| MAX_VERTEX_TEXTURE_IMAGE_UNITS: 35660, | |
| MAX_TEXTURE_SIZE: 3379, | |
| MAX_VERTEX_UNIFORM_VECTORS: 36347, | |
| MAX_VARYING_VECTORS: 36348, | |
| MAX_FRAGMENT_UNIFORM_VECTORS: 36349, | |
| MAX_COMBINED_TEXTURE_IMAGE_UNITS: 35661, | |
| VERSION: 7938, | |
| SCISSOR_BOX: 3088, | |
| VIEWPORT: 2978 | |
| } | |
| } | |
| } | |
| const renderer = new THREE.WebGLRenderer({ | |
| antialias: false, | |
| canvas, | |
| powerPreference: 'low-power', | |
| precision: 'lowp' | |
| }); | |
| renderer.setSize(innerWidth, innerHeight); | |
| const createGeometryParticle = (size) => { | |
| const visibleHeight = 2 * Math.tan(75 * Math.PI/360) * 500; | |
| const radius = (size / innerHeight) * visibleHeight / 2; | |
| // Main circle | |
| const geometry = new THREE.CircleGeometry(radius, 32); | |
| const material = new THREE.MeshBasicMaterial({ | |
| color: 0xffffff, | |
| depthTest: false | |
| }); | |
| const circle = new THREE.Mesh(geometry, material); | |
| const posArray = geometry.attributes.position.array; | |
| const outlineVertices = []; | |
| for (let i = 3; i < posArray.length; i += 3) { | |
| outlineVertices.push( | |
| new THREE.Vector3( | |
| posArray[i], | |
| posArray[i + 1], | |
| posArray[i + 2] | |
| ) | |
| ); | |
| } | |
| const outlineGeometry = new THREE.BufferGeometry().setFromPoints(outlineVertices); | |
| const outline = new THREE.LineLoop( | |
| outlineGeometry, | |
| new THREE.LineBasicMaterial({ color: 0x000000, depthTest: false }) | |
| ); | |
| const group = new THREE.Group(); | |
| group.add(circle); | |
| group.add(outline); | |
| return group; | |
| }; | |
| // Initialize particles | |
| var initialized = false; | |
| const particles = []; | |
| function initialize() { | |
| for(let i = 0; i < NUM_PARTICLES; i++) { | |
| const size = 10 + Math.random() * 80; | |
| const particle = createGeometryParticle(size); | |
| // Random initial position | |
| const visibleWidth = 2 * Math.tan(75 * Math.PI/360) * 500 * camera.aspect; | |
| particle.position.set( | |
| THREE.MathUtils.randFloatSpread(visibleWidth), | |
| THREE.MathUtils.randFloatSpread(visibleWidth/camera.aspect), | |
| 0 | |
| ); | |
| // Velocity storage | |
| particle.velocity = new THREE.Vector2( | |
| (Math.random() - 0.5) * 8, | |
| (Math.random() - 0.5) * 8 | |
| ); | |
| scene.add(particle); | |
| particles.push(particle); | |
| } | |
| initialized = true; | |
| } | |
| // Metrics and animation | |
| const visibleWidth = 2 * Math.tan(75 * Math.PI/360) * 500 * camera.aspect; | |
| const visibleHeight = visibleWidth / camera.aspect; | |
| function animate() { | |
| particles.forEach(particle => { | |
| particle.position.x += particle.velocity.x; | |
| particle.position.y += particle.velocity.y; | |
| // Boundary checks | |
| if(Math.abs(particle.position.x) > visibleWidth/2) | |
| particle.velocity.x *= -1; | |
| if(Math.abs(particle.position.y) > visibleHeight/2) | |
| particle.velocity.y *= -1; | |
| }); | |
| renderer.render(scene, camera); | |
| } | |
| class Benchmark { | |
| runIteration() { | |
| if (!initialized) { | |
| initialize(); | |
| } | |
| animate(); | |
| } | |
| } |