Repository: playcanvas/engine (JavaScript)

Issue:
Splats multi app - Failed to compile vertex shader
Currently, it's not possible to render splats when multiple `Application` instances exist within the page. When separate splat assets are loaded into separate Applications, it fails to render either splat and throws a `Failed to compile vertex shader` error.

See [this repro](https://codesandbox.io/p/sandbox/3np3rf?file=%2Fsrc%2Fmain.js%3A17%2C14).  Both splats should load and render, but neither do. Removing one viewer causes the other to work correctly

Related to - https://github.com/playcanvas/engine/issues/7471
For context, a user is building a gallery of splats in a single page [following issue](https://github.com/playcanvas/react/issues/80).




Fix (patch):
diff --git a/src/framework/handlers/gsplat.js b/src/framework/handlers/gsplat.js
index dd34a15768f..74bd60fef43 100644
--- a/src/framework/handlers/gsplat.js
+++ b/src/framework/handlers/gsplat.js
@@ -14,7 +14,7 @@ class GSplatHandler extends ResourceHandler {
      */
     constructor(app) {
         super(app, 'gsplat');
-        this.parser = new PlyParser(app.graphicsDevice, app.assets, 3);
+        this.parser = new PlyParser(app, 3);
     }
 
     load(url, callback, asset) {
diff --git a/src/framework/parsers/gsplat-resource.js b/src/framework/parsers/gsplat-resource.js
index 4a6cf86154d..a92109e0f5b 100644
--- a/src/framework/parsers/gsplat-resource.js
+++ b/src/framework/parsers/gsplat-resource.js
@@ -4,6 +4,7 @@ import { GSplat } from '../../scene/gsplat/gsplat.js';
 import { GSplatCompressed } from '../../scene/gsplat/gsplat-compressed.js';
 
 /**
+ * @import { AppBase } from '../app-base.js'
  * @import { GSplatData } from '../../scene/gsplat/gsplat-data.js'
  * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
  * @import { SplatMaterialOptions } from '../../scene/gsplat/gsplat-material.js'
@@ -16,10 +17,10 @@ import { GSplatCompressed } from '../../scene/gsplat/gsplat-compressed.js';
  */
 class GSplatResource {
     /**
-     * @type {GraphicsDevice}
+     * @type {AppBase}
      * @ignore
      */
-    device;
+    app;
 
     /**
      * @type {GSplatData}
@@ -40,19 +41,19 @@ class GSplatResource {
     comments = null;
 
     /**
-     * @param {GraphicsDevice} device - The graphics device.
+     * @param {AppBase} app - The app.
      * @param {GSplatData} splatData - The splat data.
      * @param {string[]} comments - The PLY file header comments
      * @ignore
      */
-    constructor(device, splatData, comments) {
-        this.device = device;
+    constructor(app, splatData, comments) {
+        this.app = app;
         this.splatData = splatData;
         this.comments = comments;
     }
 
     destroy() {
-        this.device = null;
+        this.app = null;
         this.splatData = null;
         this.splat?.destroy();
         this.splat = null;
@@ -60,7 +61,7 @@ class GSplatResource {
 
     createSplat() {
         if (!this.splat) {
-            this.splat = this.splatData.isCompressed ? new GSplatCompressed(this.device, this.splatData) : new GSplat(this.device, this.splatData);
+            this.splat = this.splatData.isCompressed ? new GSplatCompressed(this.app.graphicsDevice, this.splatData) : new GSplat(this.app.graphicsDevice, this.splatData);
         }
         return this.splat;
     }
@@ -75,7 +76,7 @@ class GSplatResource {
 
         const splatInstance = this.createInstance(options);
 
-        const entity = new Entity();
+        const entity = new Entity(undefined, this.app);
         const component = entity.addComponent('gsplat', {
             instance: splatInstance
         });
diff --git a/src/framework/parsers/ply.js b/src/framework/parsers/ply.js
index 4f98fdc3aec..b0597c3570c 100644
--- a/src/framework/parsers/ply.js
+++ b/src/framework/parsers/ply.js
@@ -5,7 +5,7 @@ import { GSplatResource } from './gsplat-resource.js';
 /**
  * @import { AssetRegistry } from '../asset/asset-registry.js'
  * @import { Asset } from '../asset/asset.js'
- * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
+ * @import { AppBase } from '../app-base.js'
  * @import { ResourceHandlerCallback } from '../handlers/handler.js'
  */
 
@@ -566,23 +566,18 @@ const readPly = async (reader, propertyFilter = null, progressFunc = null) => {
 const defaultElementFilter = val => true;
 
 class PlyParser {
-    /** @type {GraphicsDevice} */
-    device;
-
-    /** @type {AssetRegistry} */
-    assets;
+    /** @type {AppBase} */
+    app;
 
     /** @type {number} */
     maxRetries;
 
     /**
-     * @param {GraphicsDevice} device - The graphics device.
-     * @param {AssetRegistry} assets - The asset registry.
+     * @param {AppBase} app - The app instance.
      * @param {number} maxRetries - Maximum amount of retries.
      */
-    constructor(device, assets, maxRetries) {
-        this.device = device;
-        this.assets = assets;
+    constructor(app, maxRetries) {
+        this.app = app;
         this.maxRetries = maxRetries;
     }
 
@@ -623,7 +618,7 @@ class PlyParser {
 
                 // construct the resource
                 const resource = new GSplatResource(
-                    this.device,
+                    this.app,
                     data.isCompressed && asset.data.decompress ? data.decompress() : data,
                     comments
                 );