From 5da454f6521e2ca660ccf2f3e2d951e330de3c5a Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Fri, 26 Oct 2018 12:27:56 -0700 Subject: [PATCH] JavaScript: concise texture creation functions This makes our createTexture* helpers more consistent with the createMaterial helper, and extends the wrapped Engine class with new methods. Before, clients did something like this: const ao = Filament.createTextureFromPng(Filament.assets['foo.png'], engine); ... engine.destroyTexture(ao); Now, they can do: const ao = engine.createTextureFromPng('foo.png'); ... engine.destroyTexture(ao); This is more symmetric and concise. In general the JS API now supports a class of "buffer-like" objects which exploit dynamic typing in order to accept any of the following: BufferDescriptor, asset string, or Uint8Array. --- libs/filamentjs/CMakeLists.txt | 1 + libs/filamentjs/docs/build.py | 10 +++ libs/filamentjs/docs/tutorial_redball.md | 10 +-- libs/filamentjs/docs/tutorial_triangle.md | 4 +- libs/filamentjs/extensions.js | 100 ++++++++++++++++++++++ libs/filamentjs/jsbindings.cpp | 9 +- libs/filamentjs/tests/test_parquet.js | 23 ++--- libs/filamentjs/utilities.js | 43 +++------- libs/filamentjs/wasmloader.js | 13 +-- 9 files changed, 138 insertions(+), 75 deletions(-) create mode 100644 libs/filamentjs/extensions.js diff --git a/libs/filamentjs/CMakeLists.txt b/libs/filamentjs/CMakeLists.txt index e6c5fbf897..ffccb1a81f 100644 --- a/libs/filamentjs/CMakeLists.txt +++ b/libs/filamentjs/CMakeLists.txt @@ -4,6 +4,7 @@ project(filamentjs) set(JAVASCRIPT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/wasmloader.js + ${CMAKE_CURRENT_SOURCE_DIR}/extensions.js ${CMAKE_CURRENT_SOURCE_DIR}/utilities.js) set(CPP_SRC diff --git a/libs/filamentjs/docs/build.py b/libs/filamentjs/docs/build.py index 2f874e6b39..a1d1d10ea2 100755 --- a/libs/filamentjs/docs/build.py +++ b/libs/filamentjs/docs/build.py @@ -278,6 +278,15 @@ def gather_docstrings(paths): "detail": None, "children": [] } + + # Check if this is continuation of a previous type. + if brief== '': + for existing_type in result: + if existing_type['name'] == name: + entity = existing_type + result.remove(existing_type) + break + top = stack[-1]["tags"] if 'root' in top: result.append(entity) @@ -436,6 +445,7 @@ def build_reference(): ROOT_DIR + 'libs/filamentjs/jsenums.cpp', ROOT_DIR + 'libs/filamentjs/utilities.js', ROOT_DIR + 'libs/filamentjs/wasmloader.js', + ROOT_DIR + 'libs/filamentjs/extensions.js', ]) markdown = build_reference_markdown(doctree) rendered = mistletoe.markdown(markdown, PygmentsRenderer) diff --git a/libs/filamentjs/docs/tutorial_redball.md b/libs/filamentjs/docs/tutorial_redball.md index a245cf103e..a0ef7116c3 100644 --- a/libs/filamentjs/docs/tutorial_redball.md +++ b/libs/filamentjs/docs/tutorial_redball.md @@ -139,8 +139,7 @@ Next let's create a material instance from the package that we built at the begi Replace the **create material** comment with the following snippet. ```js {fragment="create material"} -const material_package = Filament.Buffer(Filament.assets['plastic.filamat']); -const material = engine.createMaterial(material_package); +const material = engine.createMaterial('plastic.filamat'); const matinstance = material.createInstance(); const red = [0.8, 0.0, 0.0]; @@ -269,8 +268,7 @@ Filament provides a JavaScript utility to make this simpler, simply replace the **create IBL** comment with the following snippet. ```js {fragment="create IBL"} -const ibldata = Filament.assets['pillars_2k_ibl.ktx']; -const indirectLight = Filament.createIblFromKtx(ibldata, engine, {'rgbm': true}); +const indirectLight = engine.createIblFromKtx('pillars_2k_ibl.ktx'); indirectLight.setIntensity(50000); scene.setIndirectLight(indirectLight); ``` @@ -302,9 +300,7 @@ Filament provides a Javascript utility to make this easier. Replace **create skybox** with the following. ```js {fragment="create skybox"} -const skydata = Filament.assets['pillars_2k_skybox.ktx']; -const skytex = Filament.createTextureFromKtx(skydata, engine, {'rgbm': true}); -const skybox = Filament.Skybox.Builder().environment(skytex).build(engine); +const skybox = engine.createSkyFromKtx('pillars_2k_skybox.ktx'); scene.setSkybox(skybox); ``` diff --git a/libs/filamentjs/docs/tutorial_triangle.md b/libs/filamentjs/docs/tutorial_triangle.md index 8d35e41827..d0c68f6fc2 100644 --- a/libs/filamentjs/docs/tutorial_triangle.md +++ b/libs/filamentjs/docs/tutorial_triangle.md @@ -135,8 +135,6 @@ const TRIANGLE_COLORS = Filament.Buffer(new Uint32Array([ 0xff00ff00, 0xff0000ff, ])); - -const MATERIAL_PACKAGE = Filament.Buffer(Filament.assets['triangle.filamat']); ``` Next we'll use the positions and colors buffers to create a single `VertexBuffer` object. @@ -195,7 +193,7 @@ After extracting the material instance, we can finally create a renderable compo triangle by setting up a bounding box and passing in the vertex and index buffers. ```js {fragment="create entities"} -const mat = engine.createMaterial(MATERIAL_PACKAGE); +const mat = engine.createMaterial('triangle.filamat'); const matinst = mat.getDefaultInstance(); Filament.RenderableManager.Builder(1) .boundingBox([[ -1, -1, -1 ], [ 1, 1, 1 ]]) diff --git a/libs/filamentjs/extensions.js b/libs/filamentjs/extensions.js new file mode 100644 index 0000000000..745535d864 --- /dev/null +++ b/libs/filamentjs/extensions.js @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Private utility that converts an asset string or Uint8Array into a low-level buffer descriptor. +function getBufferDescriptor(buffer) { + if ('string' == typeof buffer || buffer instanceof String) { + buffer = Filament.assets[buffer]; + } + if (buffer instanceof Uint8Array) { + buffer = Filament.Buffer(buffer); + } + return buffer; +} + + Filament.loadClassExtensions = function() { + + /// Engine ::core class:: + + /// create ::static method:: + /// canvas ::argument:: + /// options ::argument:: + /// ::retval:: an instance of [Engine] + Filament.Engine.create = function(canvas, options) { + const defaults = { + majorVersion: 2, + minorVersion: 0, + antialias: false, + depth: false, + alpha: false + }; + options = Object.assign(defaults, options); + Filament.createContext(canvas, true, true, options); + return Filament.Engine._create(); + }; + + /// createMaterial ::method:: + /// package ::argument:: asset string, or Uint8Array, or [Buffer] with filamat contents + /// ::retval:: an instance of [createMaterial] + Filament.Engine.prototype.createMaterial = function(buffer) { + return this._createMaterial(getBufferDescriptor(buffer)); + }; + + /// createTextureFromKtx ::method:: Utility function that creates a [Texture] from a KTX file. + /// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with KTX file contents + /// options ::argument:: Options dictionary. For now, the `rgbm` boolean is the only option. + /// ::retval:: [Texture] + Filament.Engine.prototype.createTextureFromKtx = function(buffer, options) { + return Filament._createTextureFromKtx(getBufferDescriptor(buffer), this, options); + }; + + /// createIblFromKtx ::method:: Utility function that creates an [IndirectLight] from a KTX file. + /// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with KTX file contents + /// options ::argument:: Options dictionary. For now, the `rgbm` boolean is the only option. + /// ::retval:: [IndirectLight] + Filament.Engine.prototype.createIblFromKtx = function(buffer, options) { + return Filament._createIblFromKtx(getBufferDescriptor(buffer), this, options); + } + + /// createSkyFromKtx ::method:: Utility function that creates a [Skybox] from a KTX file. + /// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with KTX file contents + /// options ::argument:: Options dictionary. For now, the `rgbm` boolean is the only option. + /// ::retval:: [Skybox] + Filament.Engine.prototype.createSkyFromKtx = function(buffer, options) { + options = options || {'rgbm': true}; + const skytex = this.createTextureFromKtx(buffer, options); + return Filament.Skybox.Builder().environment(skytex).build(this); + }; + + /// createTextureFromPng ::method:: Creates a 2D [Texture] from the raw contents of a PNG file. + /// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with PNG file contents + /// options ::argument:: JavaScript object with optional `rgbm`, `noalpha`, and `nomips` keys. + /// ::retval:: [Texture] + Filament.Engine.prototype.createTextureFromPng = function(buffer, options) { + return Filament._createTextureFromPng(getBufferDescriptor(buffer), this, options); + } + + /// loadFilamesh ::method:: Consumes the contents of a filamesh file and creates a renderable. + /// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with filamesh contents + /// definstance ::argument:: Optional default [MaterialInstance] + /// matinstances ::argument:: Optional object that gets populated with name => [MaterialInstance] + /// ::retval:: JavaScript object with keys `renderable`, `vertexBuffer`, and `indexBuffer`. \ + /// These are of type [Entity], [VertexBuffer], and [IndexBuffer]. + Filament.Engine.prototype.loadFilamesh = function(buffer, definstance, matinstances) { + return Filament._loadFilamesh(this, getBufferDescriptor(buffer), definstance, matinstances); + } + +}; diff --git a/libs/filamentjs/jsbindings.cpp b/libs/filamentjs/jsbindings.cpp index 14f8aa53bf..d2b0428b84 100644 --- a/libs/filamentjs/jsbindings.cpp +++ b/libs/filamentjs/jsbindings.cpp @@ -260,7 +260,7 @@ value_array("mat3") // CORE FILAMENT CLASSES // --------------------- -/// Engine ::core class:: Central manager and resource owner. Typically this is a singleton. +/// Engine ::core class:: Central manager and resource owner. class_("Engine") .class_function("_create", (Engine* (*)()) [] { return Engine::create(); }, allow_raw_pointers()) @@ -317,12 +317,7 @@ class_("Engine") .function("destroyCamera", (void (*)(Engine*, Camera*)) [] (Engine* engine, Camera* camera) { engine->destroy(camera); }, allow_raw_pointers()) - /// createMaterial ::method:: - /// package ::argument:: buffer descriptor created with [Buffer] with filamat contents - /// ::retval:: an instance of [createMaterial] - .function("createMaterial", EMBIND_LAMBDA(Material*, (Engine* engine, BufferDescriptor mbd), { - // TODO: consider adding a BufferDescriptor API to Material::Builder, then possibly removing - // this convenient helper method. + .function("_createMaterial", EMBIND_LAMBDA(Material*, (Engine* engine, BufferDescriptor mbd), { return Material::Builder().package(mbd.bd->buffer, mbd.bd->size).build(*engine); }), allow_raw_pointers()) /// destroyMaterial ::method:: diff --git a/libs/filamentjs/tests/test_parquet.js b/libs/filamentjs/tests/test_parquet.js index 0fbacf9400..24833de643 100644 --- a/libs/filamentjs/tests/test_parquet.js +++ b/libs/filamentjs/tests/test_parquet.js @@ -37,21 +37,19 @@ class App { .build(engine, sunlight); this.scene.addEntity(sunlight); - const ibldata = Filament.assets['venetian_crossroads_2k_ibl.ktx']; - const indirectLight = this.ibl = Filament.createIblFromKtx(ibldata, engine, {'rgbm': true}); + const ibldata = 'venetian_crossroads_2k_ibl.ktx'; + const indirectLight = this.ibl = engine.createIblFromKtx(ibldata); this.scene.setIndirectLight(indirectLight); const radians = 1.0; indirectLight.setRotation(mat3.fromRotation(mat3.create(), radians, [0, 1, 0])) indirectLight.setIntensity(10000); - const skydata = Filament.assets['venetian_crossroads_2k_skybox.ktx']; - const skytex = Filament.createTextureFromKtx(skydata, engine, {'rgbm': true}); - const skybox = Filament.Skybox.Builder().environment(skytex).build(engine); + const skydata = 'venetian_crossroads_2k_skybox.ktx'; + const skybox = engine.createSkyFromKtx(skydata); this.scene.setSkybox(skybox); - const MATERIAL_PACKAGE = Filament.Buffer(Filament.assets['parquet.filamat']); - const material = engine.createMaterial(MATERIAL_PACKAGE); + const material = engine.createMaterial('parquet.filamat'); const matinstance = material.createInstance(); const sampler = new Filament.TextureSampler( @@ -59,17 +57,14 @@ class App { Filament.MagFilter.LINEAR, Filament.WrapMode.CLAMP_TO_EDGE); - const ao = Filament.createTextureFromPng( - Filament.assets['floor_ao_roughness_metallic.png'], engine); - const basecolor = Filament.createTextureFromPng( - Filament.assets['floor_basecolor.png'], engine, {'srgb': true}); - const normal = Filament.createTextureFromPng(Filament.assets['floor_normal.png'], engine); + const ao = engine.createTextureFromPng('floor_ao_roughness_metallic.png'); + const basecolor = engine.createTextureFromPng('floor_basecolor.png', {'srgb': true}); + const normal = engine.createTextureFromPng('floor_normal.png'); matinstance.setTextureParameter('aoRoughnessMetallic', ao, sampler) matinstance.setTextureParameter('baseColor', basecolor, sampler) matinstance.setTextureParameter('normal', normal, sampler) - const meshdata = Filament.assets['shader_ball.filamesh']; - const mesh = Filament.loadFilamesh(engine, meshdata, matinstance); + const mesh = engine.loadFilamesh('shader_ball.filamesh', matinstance); this.shaderball = mesh.renderable; this.scene.addEntity(mesh.renderable); diff --git a/libs/filamentjs/utilities.js b/libs/filamentjs/utilities.js index 697c3b156d..8be55c52f1 100644 --- a/libs/filamentjs/utilities.js +++ b/libs/filamentjs/utilities.js @@ -59,14 +59,7 @@ Filament.PixelBuffer = function(typedarray, format, datatype) { return bd; }; -/// loadFilamesh ::function:: Consumes the contents of a filamesh file and creates a renderable. -/// engine ::argument:: [Engine] -/// typedarray ::argument:: Uint8Array with contents of filamesh file -/// definstance ::argument:: Optional default [MaterialInstance] -/// matinstances ::argument:: Optional object that gets populated with name => [MaterialInstance] -/// ::retval:: JavaScript object with keys `renderable`, `vertexBuffer`, and `indexBuffer`. \ -/// These are of type [Entity], [VertexBuffer], and [IndexBuffer]. -Filament.loadFilamesh = function(engine, typedarray, definstance, matinstances) { +Filament._loadFilamesh = function(engine, buffer, definstance, matinstances) { matinstances = matinstances || {}; const registry = new Filament.MeshIO$MaterialRegistry(); for (var key in matinstances) { @@ -75,7 +68,7 @@ Filament.loadFilamesh = function(engine, typedarray, definstance, matinstances) if (definstance) { registry.set("DefaultMaterial", definstance); } - const mesh = Filament.MeshIO.loadMeshFromBuffer(engine, Filament.Buffer(typedarray), registry); + const mesh = Filament.MeshIO.loadMeshFromBuffer(engine, buffer, registry); const keys = registry.keys(); for (var i = 0; i < keys.size(); i++) { const key = keys.get(i); @@ -223,12 +216,7 @@ Filament.loadMathExtensions = function() { // Texture helpers // --------------- -/// createTextureFromKtx ::function:: Utility function that creates a [Texture] from a KTX file. -/// ktxdata ::argument:: Uint8Array with the contents of a KTX file. -/// engine ::argument:: [Engine] -/// options ::argument:: Options dictionary. For now, the `rgbm` boolean is the only option. -/// ::retval:: [Texture] -Filament.createTextureFromKtx = function(ktxdata, engine, options) { +Filament._createTextureFromKtx = function(ktxdata, engine, options) { options = options || {}; const Sampler = Filament.Texture$Sampler; @@ -236,7 +224,7 @@ Filament.createTextureFromKtx = function(ktxdata, engine, options) { const PixelDataFormat = Filament.PixelDataFormat; const gl = Filament.ctx; - const ktx = options['ktx'] || new Filament.KtxBundle(Filament.Buffer(ktxdata)); + const ktx = options['ktx'] || new Filament.KtxBundle(ktxdata); const nlevels = ktx.getNumMipLevels(); const ktxformat = ktx.info().glInternalFormat; const rgbm = !!options['rgbm']; @@ -285,15 +273,10 @@ Filament.createTextureFromKtx = function(ktxdata, engine, options) { return tex; }; -/// createIblFromKtx ::function:: Utility function that creates an [IndirectLight] from a KTX file. -/// ktxdata ::argument:: Uint8Array with the contents of a KTX file. -/// engine ::argument:: [Engine] -/// options ::argument:: Options dictionary. For now, the `rgbm` boolean is the only option. -/// ::retval:: [IndirectLight] -Filament.createIblFromKtx = function(ktxdata, engine, options) { - options = options || {}; - const iblktx = options['ktx'] = new Filament.KtxBundle(Filament.Buffer(ktxdata)); - const ibltex = Filament.createTextureFromKtx(ktxdata, engine, options); +Filament._createIblFromKtx = function(ktxdata, engine, options) { + options = options || {'rgbm': true}; + const iblktx = options['ktx'] = new Filament.KtxBundle(ktxdata); + const ibltex = Filament._createTextureFromKtx(ktxdata, engine, options); const shstring = iblktx.getMetadata("sh"); const shfloats = shstring.split(/\s/, 9 * 3).map(parseFloat); return Filament.IndirectLight.Builder() @@ -302,12 +285,7 @@ Filament.createIblFromKtx = function(ktxdata, engine, options) { .build(engine); }; -/// createTextureFromPng ::function:: Creates a 2D [Texture] from the raw contents of a PNG file. -/// pngdata ::argument:: Uint8Array with the contents of a PNG file. -/// engine ::argument:: [Engine] -/// options ::argument:: JS object with optional `srgb`, `rgbm`, `noalpha`, and `nomips` keys. -/// ::retval:: [Texture] -Filament.createTextureFromPng = function(pngdata, engine, options) { +Filament._createTextureFromPng = function(pngdata, engine, options) { const Sampler = Filament.Texture$Sampler; const TextureFormat = Filament.Texture$InternalFormat; const PixelDataFormat = Filament.PixelDataFormat; @@ -317,7 +295,8 @@ Filament.createTextureFromPng = function(pngdata, engine, options) { const rgbm = !!options['rgbm']; const noalpha = !!options['noalpha']; const nomips = !!options['nomips']; - const decodedpng = Filament.decodePng(Filament.Buffer(pngdata), noalpha ? 3 : 4); + + const decodedpng = Filament.decodePng(pngdata, noalpha ? 3 : 4); var texformat, pbformat, pbtype; if (noalpha) { diff --git a/libs/filamentjs/wasmloader.js b/libs/filamentjs/wasmloader.js index bda2f8d318..ffe562b4e9 100644 --- a/libs/filamentjs/wasmloader.js +++ b/libs/filamentjs/wasmloader.js @@ -77,18 +77,7 @@ Filament.init = function(assets, onready) { // WebAssembly module. The JS classes that correspond to core Filament classes (e.g., Engine) // are not guaranteed to exist until this function is called. Filament.postRun = function() { - Filament.Engine.create = function(canvas, options) { - const defaults = { - majorVersion: 2, - minorVersion: 0, - antialias: false, - depth: false, - alpha: false - }; - options = Object.assign(defaults, options); - Filament.createContext(canvas, true, true, options); - return Filament.Engine._create(); - }; + Filament.loadClassExtensions(); if (--Filament.remainingInitializationTasks == 0 && Filament.onReady) { Filament.onReady(); }