diff --git a/libs/filamentjs/CMakeLists.txt b/libs/filamentjs/CMakeLists.txt index 58d5055355..24d1cbfe90 100644 --- a/libs/filamentjs/CMakeLists.txt +++ b/libs/filamentjs/CMakeLists.txt @@ -16,6 +16,7 @@ set(CPP_SRC set(COPTS "${COPTS} -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0") set(LOPTS "${LOPTS} --bind") +set(LOPTS "${LOPTS} -s FILESYSTEM=0") set(LOPTS "${LOPTS} -s ALLOW_MEMORY_GROWTH=1") set(LOPTS "${LOPTS} -s MODULARIZE_INSTANCE=1") set(LOPTS "${LOPTS} -s EXPORT_NAME=Filament") diff --git a/libs/filamentjs/tests/test.py b/libs/filamentjs/tests/test.py index f21e0c0051..58dfaf254e 100755 --- a/libs/filamentjs/tests/test.py +++ b/libs/filamentjs/tests/test.py @@ -1,20 +1,23 @@ #!/usr/bin/env python3 import glob -import http.server import os import shutil -import socketserver import sys from pathlib import Path +from twisted.web.server import Site, GzipEncoderFactory +from twisted.web.static import File +from twisted.web.resource import Resource, EncodingResourceWrapper +from twisted.internet import reactor + from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from watchdog.events import LoggingEventHandler SCRIPT_DIR = Path(os.path.dirname(os.path.abspath(__file__))) -ROOT_DIR = Path(os.path.realpath(SCRIPT_DIR / "../../../")) +ROOT_DIR = Path(os.path.realpath(SCRIPT_DIR / "../../..")) BUILD_DIR = ROOT_DIR / "out/cmake-webgl-release" TOOLS_DIR = ROOT_DIR / "out/cmake-release/tools" SERVE_DIR = BUILD_DIR / "libs/filamentjs" @@ -22,26 +25,15 @@ GLMATRIX_DIR = ROOT_DIR / "third_party/gl-matrix" CURR_DIR = Path(os.path.realpath('.')) PORT = 8000 -global server - # Restart the script if the JavaScript or HTML change. class OnDirectoryChanged(FileSystemEventHandler): def on_modified(self, event): - global server if event.event_type != 'modified': return - server.shutdown() print(f'{event.src_path} was modified\nRestarting...') - os.chdir(CURR_DIR) os.execl(sys.executable, *([sys.executable]+sys.argv)) -print("Starting observer...") -handler = OnDirectoryChanged() -observer = Observer() -observer.schedule(handler, path=str(CURR_DIR), recursive=True) -observer.start() - # Copy test assets into the server folder. print("Copying assets...") @@ -99,26 +91,20 @@ shutil.copy(parquet_filamesh, SERVE_DIR) for tex in parquet_textures: shutil.copy(tex, SERVE_DIR) -# Associate wasm files with the correct MIME type. - -Handler = http.server.SimpleHTTPRequestHandler -Handler.extensions_map.update({ - '.wasm': 'application/wasm', -}) - # Serve all files in the server folder. print(f"Serving {SERVE_DIR}...") print(f" http://localhost:{PORT}/test_redball.html") print(f" http://localhost:{PORT}/test_parquet.html") -Handler.directory = SERVE_DIR -os.chdir(SERVE_DIR) -socketserver.TCPServer.allow_reuse_address = True -with socketserver.TCPServer(("", PORT), Handler) as httpd: - global server - server = httpd - httpd.allow_reuse_address = True - httpd.serve_forever() +print(f" http://localhost:{PORT}/test_leaks.html") -observer.stop() -observer.join() +port = 8000 +webdir = File(SERVE_DIR) +webdir.contentTypes['.wasm'] = 'application/wasm' +wrapped = EncodingResourceWrapper(webdir, [GzipEncoderFactory()]) +reactor.listenTCP(port, Site(wrapped)) +handler = OnDirectoryChanged() +observer = Observer() +observer.schedule(handler, path=str(CURR_DIR), recursive=True) +observer.start() +reactor.run() diff --git a/libs/filamentjs/tests/test_leaks.html b/libs/filamentjs/tests/test_leaks.html new file mode 100644 index 0000000000..95356d48b1 --- /dev/null +++ b/libs/filamentjs/tests/test_leaks.html @@ -0,0 +1,18 @@ + + + + FilamentJS Test + + + + + + + + + + + \ No newline at end of file diff --git a/libs/filamentjs/tests/test_leaks.js b/libs/filamentjs/tests/test_leaks.js new file mode 100644 index 0000000000..da8db4ac5b --- /dev/null +++ b/libs/filamentjs/tests/test_leaks.js @@ -0,0 +1,135 @@ +Filament.init([ 'bakedColor.filamat' ], () => { + window.VertexAttribute = Filament.VertexAttribute; + window.AttributeType = Filament.VertexBuffer$AttributeType; + window.PrimitiveType = Filament.RenderableManager$PrimitiveType; + window.IndexType = Filament.IndexBuffer$IndexType; + window.Fov = Filament.Camera$Fov; + window.LightType = Filament.LightManager$Type; + const canvas = document.getElementsByTagName('canvas')[0]; + window.app = new App(canvas); +}); + +class App { + constructor(canvas) { + const BAKED_COLOR_PACKAGE = Filament.Buffer(Filament.assets['bakedColor.filamat']); + this.canvas = canvas; + this.engine = Filament.Engine.create(this.canvas); + this.mi = this.engine.createMaterial(BAKED_COLOR_PACKAGE).getDefaultInstance(); + const engine = this.engine; + this.polygon = Filament.EntityManager.get().create(); + this.createPolygon(this.polygon, 3, this.mi); + this.scene = engine.createScene(); + this.scene.addEntity(this.polygon); + this.swapChain = engine.createSwapChain(); + this.renderer = engine.createRenderer(); + this.camera = engine.createCamera(); + this.view = engine.createView(); + this.view.setCamera(this.camera); + this.view.setScene(this.scene); + this.resize(); + this.render = this.render.bind(this); + this.resize = this.resize.bind(this); + window.addEventListener("resize", this.resize); + window.requestAnimationFrame(this.render); + } + + render() { + this.animate(); + this.renderer.render(this.swapChain, this.view); + window.requestAnimationFrame(this.render); + } + + resize() { + const dpr = window.devicePixelRatio; + const width = this.canvas.width = window.innerWidth * dpr; + const height = this.canvas.height = window.innerHeight * dpr; + this.view.setViewport([0, 0, width, height]); + const eye = [0, 0, 3], center = [0, 0, -1], up = [0, 1, 0]; + this.camera.lookAt(eye, center, up); + const aspect = width / height; + this.camera.setProjectionFov(45, aspect, 1.0, 10.0, aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL); + } + + createPolygon(polygon, nsides, minstance) { + const engine = this.engine; + const nverts = nsides + 1; + const ntris = nsides; + const dtheta = Math.PI * 2 / nsides; + // Create typed arrays. + const indices = new Uint16Array(3 * ntris); + const positions = new Float32Array(2 * nverts); + const colors = new Uint32Array(nverts); + const palette = new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff]); + for (let i = 1, j = 0, theta = 0; i < nverts; i++, theta += dtheta) { + positions[i * 2] = Math.cos(theta); + positions[i * 2 + 1] = Math.sin(theta); + colors[i] = palette[i % 3]; + indices[j++] = 0; + indices[j++] = i; + indices[j++] = 1 + (i % (nverts - 1)); + } + // Create vertex buffer. + const vbuilder = Filament.VertexBuffer.Builder() + .vertexCount(nverts) + .bufferCount(2) + .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8) + .attribute(VertexAttribute.COLOR, 1, AttributeType.UBYTE4, 0, 4) + .normalized(VertexAttribute.COLOR); + const vb = vbuilder.build(engine); + vbuilder.delete(); + const pbuf = Filament.Buffer(positions); + const cbuf = Filament.Buffer(colors); + vb.setBufferAt(engine, 0, pbuf); + vb.setBufferAt(engine, 1, cbuf); + pbuf.delete(); + cbuf.delete(); + // Create index buffer. + const ibuilder = Filament.IndexBuffer.Builder() + .indexCount(indices.length) + .bufferType(IndexType.USHORT); + const ib = ibuilder.build(engine); + ibuilder.delete(); + const ibuf = Filament.Buffer(indices); + ib.setBuffer(engine, ibuf); + ibuf.delete(); + // Create renderable component. + const rbuilder = Filament.RenderableManager.Builder(1) + .boundingBox([ [-1, -1, -1], [1, 1, 1] ]) + .material(0, minstance) + .geometry(0, PrimitiveType.TRIANGLES, vb, ib); + rbuilder.build(engine, polygon); + rbuilder.delete(); + vb.delete(); + ib.delete(); + } + + // For now, simply print out malloc'd pointer value and check + // if it increases over time. Is there a better way of doing this? + printMemoryUsage() { + const ptr = Filament._malloc(1); + Filament._free(ptr); + this.maxptr = Math.max(this.maxptr | 0, ptr); + console.log(this.maxptr); + } + + animate() { + // Recreate the polygon every couple frames, changing the number of sides. + this.frame = (this.frame | 0) + 1; + if (this.frame > 2) { + this.frame = 0; + this.nsides = (this.nsides | 0) + 1; + this.nsides = this.nsides % 10; + // Destroy and recreate the renderable component. + this.engine.destroyEntity(this.polygon); + this.createPolygon(this.polygon, 3 + this.nsides, this.mi); + this.printMemoryUsage(); + } + // Rotate the polygon around the Z axis. + const radians = Date.now() / 1000; + const transform = mat4.fromRotation(mat4.create(), radians, [0, 0, 1]); + const tcm = this.engine.getTransformManager(); + const inst = tcm.getInstance(this.polygon); + tcm.setTransform(inst, transform); + inst.delete(); + } +}