From ef4741209fb26b854cdb80479fbb03422398591e Mon Sep 17 00:00:00 2001 From: RDW Date: Thu, 10 Mar 2022 08:18:50 +0100 Subject: [PATCH 1/4] API: Add more scene object queries to the Rendering API --- Core/APIs/C_Rendering.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Core/APIs/C_Rendering.js b/Core/APIs/C_Rendering.js index e26233c..04eae0c 100644 --- a/Core/APIs/C_Rendering.js +++ b/Core/APIs/C_Rendering.js @@ -118,3 +118,22 @@ C_Rendering.getNumActiveMeshes = function () { C_Rendering.getNumActiveAnimations = function () { return this.renderer.activeScene.animations.length; }; + +C_Rendering.getNumActiveLights = function () { + return this.renderer.activeScene.lights.length; +}; + +C_Rendering.getNumActiveCameras = function () { + return this.renderer.activeScene.cameras.length; +}; + +C_Rendering.getNumSceneObjects = function () { + return ( + this.getNumActiveMeshes() + + this.getNumActiveAnimations() + + this.getNumActiveMaterials() + + this.getNumActiveTextures() + + this.getNumActiveLights() + + this.getNumActiveCameras() + ); +}; From ade45296a3d10a7d39f1eae52291e81092515bba Mon Sep 17 00:00:00 2001 From: RDW Date: Thu, 10 Mar 2022 11:45:55 +0100 Subject: [PATCH 2/4] API: Add visibility toggles to the PolygonMesh builtin --- Core/API/WebGL/PolygonMesh.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Core/API/WebGL/PolygonMesh.js b/Core/API/WebGL/PolygonMesh.js index d3281ce..0260f08 100644 --- a/Core/API/WebGL/PolygonMesh.js +++ b/Core/API/WebGL/PolygonMesh.js @@ -37,4 +37,19 @@ class PolygonMesh { C_Rendering.removeMesh(this.sceneObject); this.sceneObject = null; } + isShown() { + if (!this.sceneObject) return false; + + return this.sceneObject.isVisible === true; + } + show() { + if (!this.sceneObject) return; + + this.sceneObject.isVisible = true; + } + hide() { + if (!this.sceneObject) return; + + this.sceneObject.isVisible = false; + } } From b72d436c3c3d4de56ea563e822e61cab86d959a7 Mon Sep 17 00:00:00 2001 From: RDW Date: Thu, 10 Mar 2022 11:56:35 +0100 Subject: [PATCH 3/4] API: Add support for hardware instantiation to the WebGL API --- Core/APIs/C_WebGL.js | 55 +++++++++++++++++++++++ Tests/API/WebGL/createInstancedMesh.js | 42 +++++++++++++++++ Tests/API/WebGL/destroyInstancedMeshes.js | 41 +++++++++++++++++ Tests/API/WebGL/getNumInstances.js | 25 +++++++++++ Tests/run-renderer-tests.js | 1 + 5 files changed, 164 insertions(+) create mode 100644 Tests/API/WebGL/createInstancedMesh.js create mode 100644 Tests/API/WebGL/destroyInstancedMeshes.js create mode 100644 Tests/API/WebGL/getNumInstances.js diff --git a/Core/APIs/C_WebGL.js b/Core/APIs/C_WebGL.js index 5b57303..5ca2db4 100644 --- a/Core/APIs/C_WebGL.js +++ b/Core/APIs/C_WebGL.js @@ -6,6 +6,8 @@ const C_WebGL = { [Enum.FOG_MODE_EXPONENTIAL]: BABYLON.Scene.FOGMODE_EXP, [Enum.FOG_MODE_EXPONENTIAL_DENSE]: BABYLON.Scene.FOGMODE_EXP2, }, + meshPrototypes: {}, + meshInstances: {}, }; C_WebGL.createCanvasRenderer = function (canvas, options) { @@ -177,6 +179,59 @@ C_WebGL.createMesh = function (name, geometryBlueprint) { return mesh; }; +C_WebGL.getInstantiationPrototype = function (rootMeshID) { + return this.meshPrototypes[rootMeshID]; +}; + +C_WebGL.createInstancedMesh = function (rootMeshID, geometryBlueprint = null) { + const rootMesh = this.meshPrototypes[rootMeshID]; + const prototypeSceneObject = rootMesh?.sceneObject ?? this.createMesh(rootMeshID + "#Prototype", geometryBlueprint); + const instanceMeshID = rootMeshID + "#Instance" + (this.getNumInstances(rootMeshID) + 1); + const instance = prototypeSceneObject.createInstance(instanceMeshID); + + this.meshInstances[rootMeshID] = this.meshInstances[rootMeshID] ?? []; + this.meshInstances[rootMeshID].push(instance); + + const prototypeMesh = new PolygonMesh(rootMeshID); + prototypeMesh.sceneObject = prototypeSceneObject; + this.meshPrototypes[rootMeshID] = prototypeMesh; + + const mesh = new PolygonMesh(instanceMeshID); + mesh.sceneObject = instance; + + // Prototype meshes shouldn't be rendered since they're only used for instantiation + prototypeMesh.hide(); + + return mesh; +}; + +C_WebGL.getNumInstances = function (rootMeshID) { + if (!this.meshInstances[rootMeshID]) return 0; + + return this.meshInstances[rootMeshID].length; +}; + +C_WebGL.destroyInstancedMeshes = function (rootMeshID) { + const instances = this.meshInstances[rootMeshID]; + + if (!instances) return; // Invalid root mesh ID + + // Destroy instances + instances.forEach((instancedMesh) => { + instancedMesh.dispose(); + }); + delete this.meshInstances[rootMeshID]; + + // Destroy prototype mesh + const rootMesh = this.meshPrototypes[rootMeshID]; + if (!rootMesh) return; // Invalid root mesh ID + + // Have to make sure the mesh is disposed first here; otherwise there will be an error inside BJS ?_? + rootMesh.sceneObject.dispose(); + rootMesh.sceneObject.material.dispose(); + delete this.meshPrototypes[rootMeshID]; +}; + C_WebGL.createDirectionalLight = function (name, properties) { const specularColor = new BABYLON.Color3( properties.specularColor.red, diff --git a/Tests/API/WebGL/createInstancedMesh.js b/Tests/API/WebGL/createInstancedMesh.js new file mode 100644 index 0000000..790a23b --- /dev/null +++ b/Tests/API/WebGL/createInstancedMesh.js @@ -0,0 +1,42 @@ +describe("createInstancedMesh", () => { + const rootMeshID = new UniqueID().toString(); + it("should create a prototype if none was previously created", () => { + const numMeshesBefore = C_Rendering.getNumActiveMeshes(); + const numMaterialsBefore = C_Rendering.getNumActiveMaterials(); + assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype")); + + C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint()); + + const rootMesh = C_WebGL.getInstantiationPrototype(rootMeshID); + assertFalse(rootMesh.isShown()); + + const numMeshesAfter = C_Rendering.getNumActiveMeshes(); + const numMaterialsAfter = C_Rendering.getNumActiveMaterials(); + + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype")); + assertEquals(numMeshesAfter, numMeshesBefore + 2); // One instance, and the prototype itself + assertEquals(numMaterialsAfter, numMaterialsBefore + 1); // One material, shared between all instances + }); + + it("should return a mesh representation of the given geometry", () => { + const blueprint = new GeometryBlueprint(); + const instance = C_WebGL.createInstancedMesh(rootMeshID, blueprint); + assertTrue(instance instanceof PolygonMesh); + }); + + it("should use the existing prototype if one was previously created", () => { + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype")); + const numMeshesBefore = C_Rendering.getNumActiveMeshes(); + const numMaterialsBefore = C_Rendering.getNumActiveMaterials(); + + C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint()); + const numMeshesAfter = C_Rendering.getNumActiveMeshes(); + const numMaterialsAfter = C_Rendering.getNumActiveMaterials(); + + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype")); + assertEquals(numMeshesAfter, numMeshesBefore + 1); // Only one instance, no new prototype + assertEquals(numMaterialsAfter, numMaterialsBefore); // The shared material should be used + }); + + after(() => C_WebGL.destroyInstancedMeshes(rootMeshID)); +}); diff --git a/Tests/API/WebGL/destroyInstancedMeshes.js b/Tests/API/WebGL/destroyInstancedMeshes.js new file mode 100644 index 0000000..6353806 --- /dev/null +++ b/Tests/API/WebGL/destroyInstancedMeshes.js @@ -0,0 +1,41 @@ +describe("destroyInstancedMeshes", () => { + it("should destroy any instanced meshes that were created from the prototype", () => { + const rootMeshID = new UniqueID().toString(); + C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint()); + C_WebGL.createInstancedMesh(rootMeshID); + C_WebGL.createInstancedMesh(rootMeshID); + + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype")); + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance1")); + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance2")); + assertNotUndefined(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance3")); + + C_WebGL.destroyInstancedMeshes(rootMeshID); + + assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Prototype")); + assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance1")); + assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance2")); + assertNull(C_Rendering.getSceneObjectByName(rootMeshID + "#Instance3")); + }); + + it("should remove the prototype mesh itself from the scene", () => { + const numMeshesBefore = C_Rendering.getNumActiveMeshes(); + const numMaterialsBefore = C_Rendering.getNumActiveMaterials(); + + const rootMeshID = new UniqueID().toString(); + C_WebGL.createInstancedMesh(rootMeshID, new GeometryBlueprint()); + assertEquals(C_Rendering.getNumActiveMeshes(), numMeshesBefore + 2); // Prototype + first instance + assertEquals(C_Rendering.getNumActiveMaterials(), numMaterialsBefore + 1); // Shared material + + C_WebGL.destroyInstancedMeshes(rootMeshID); + + assertEquals(C_Rendering.getNumActiveMeshes(), numMeshesBefore); + assertEquals(C_Rendering.getNumActiveMaterials(), numMaterialsBefore); + }); + + it("should do nothing if no prototype exists with the given ID", () => { + const numSceneObjects = C_Rendering.getNumSceneObjects(); + C_WebGL.destroyInstancedMeshes("invalid root mesh ID"); + assertEquals(C_Rendering.getNumSceneObjects(), numSceneObjects); + }); +}); diff --git a/Tests/API/WebGL/getNumInstances.js b/Tests/API/WebGL/getNumInstances.js new file mode 100644 index 0000000..57dad31 --- /dev/null +++ b/Tests/API/WebGL/getNumInstances.js @@ -0,0 +1,25 @@ +describe("getNumInstances", () => { + const rootMeshID = "PrototypeMesh1"; + const blueprint = new GeometryBlueprint(); // Can be empty; we don't need any actual geometry here + + it("should return the number of mesh instances for a given prototype", () => { + // The prototype itself isn't visible and therefore not counted + assertEquals(C_WebGL.getNumInstances(rootMeshID), 0); + C_WebGL.createInstancedMesh(rootMeshID, blueprint); + assertEquals(C_WebGL.getNumInstances(rootMeshID), 1); + C_WebGL.createInstancedMesh(rootMeshID); + assertEquals(C_WebGL.getNumInstances(rootMeshID), 2); + C_WebGL.createInstancedMesh(rootMeshID); + assertEquals(C_WebGL.getNumInstances(rootMeshID), 3); + + // Cleanup + C_WebGL.destroyInstancedMeshes(rootMeshID); + assertEquals(C_WebGL.getNumInstances(rootMeshID), 0); + }); + + it("should return zero if no prototype exists with the given ID", () => { + assertEquals(C_WebGL.getNumInstances("invalidRootMeshID"), 0); + }); + + after(() => C_WebGL.destroyInstancedMeshes(rootMeshID)); +}); diff --git a/Tests/run-renderer-tests.js b/Tests/run-renderer-tests.js index 804f860..6969ded 100644 --- a/Tests/run-renderer-tests.js +++ b/Tests/run-renderer-tests.js @@ -51,6 +51,7 @@ const testSuites = { "API/C_WebAudio/isAudioAvailable.js", "API/C_WebAudio/isAudioContextInitialized.js", ], + C_WebGL: ["API/WebGL/createInstancedMesh.js", "API/WebGL/getNumInstances.js", "API/WebGL/destroyInstancedMeshes.js"], C_Macro: ["API/Macro/restoreMacroCache.js"], C_Resources: ["API/Resources/test-resource-builtin.js"], }; From 71556c98d872aa0157285d9f1f063f5540cec657 Mon Sep 17 00:00:00 2001 From: RDW Date: Thu, 10 Mar 2022 17:04:18 +0100 Subject: [PATCH 4/4] Config: Add some missing builtins to the eslint configuration --- Config/eslint.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Config/eslint.json b/Config/eslint.json index 5c9daca..c94d25f 100644 --- a/Config/eslint.json +++ b/Config/eslint.json @@ -79,6 +79,7 @@ "Frame": "writable", "GLOBAL_MESSAGE_DB": "writable", "GameMenuFrame": "writable", + "GeometryBlueprint": "writable", "HeightMap": "writable", "JpegDecoder": "writable", "Label": "writable", @@ -93,6 +94,7 @@ "OptionsFrame": "writable", "OptionsGroup": "writable", "Paragraph": "writable", + "PolygonMesh": "writable", "PngDecoder": "writable", "Renderer": "writable", "Resource": "writable",