diff --git a/Assets/Scripts/SparkGltfastAddon.cs b/Assets/Scripts/SparkGltfastAddon.cs index 09942ab..6c1ab29 100644 --- a/Assets/Scripts/SparkGltfastAddon.cs +++ b/Assets/Scripts/SparkGltfastAddon.cs @@ -6,22 +6,15 @@ using UnityEngine; // glTFast import add-on that overrides the default JPEG/PNG image loading, so models loaded at -// runtime (ObjectMode's useRuntimeLoad path) get their textures created through our pipeline — -// later, Spark-encoded on device. +// runtime get their textures created through our pipeline // // We implement IDefaultImageFormatLoader (not plain ITextureImageLoader): it's glTFast's hook for // *overriding the core PNG/JPEG decode*, selected per image via IsAbleToLoad(ImageFormat). Plain -// ITextureImageLoader is for extension formats (WebP/KTX) that redirect the image source — not us. +// ITextureImageLoader is for extension formats (WebP/KTX) that redirect the image source. // // Registration is global (RuntimeInitializeOnLoadMethod), so it applies to every runtime // GltfImport.Load. The editor ScriptedImporter doesn't run RuntimeInitializeOnLoad callbacks, so // edit-time glTF imports are unaffected. -// -// STEP 4 (this version): decode the JPEG/PNG, then Spark-encode it on device. Colorspace comes -// from glTFast's per-slot `linear` flag (srgb = !linear). The channel format is fixed RGB for now; -// once gltFast is extended to thread per-texture channel usage (step 3) the addon will pick -// RG/R/RGBA per slot. Fixed RGB is safe for the demo's models: their textures are opaque, and -// normal maps still render correctly because the shader reconstructs Z from X,Y regardless of B. static class SparkGltfastAddon { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] @@ -76,22 +69,35 @@ static bool IsJpeg(System.ReadOnlySpan d) => static bool IsPng(System.ReadOnlySpan d) => d.Length >= 8 && d[0] == 0x89 && d[1] == 0x50 && d[2] == 0x4E && d[3] == 0x47; + // Required channel-unaware overload — forward to the channel-aware one with all channels. public Task LoadImage( NativeArray.ReadOnly data, bool linear, bool readable, bool generateMipMaps, CancellationToken cancellationToken) + => LoadImage(data, linear, readable, generateMipMaps, cancellationToken, 0xF); + + // Channel-aware override — picks the Spark format from glTFast's per-image channel mask. + public Task LoadImage( + NativeArray.ReadOnly data, + bool linear, + bool readable, + bool generateMipMaps, + CancellationToken cancellationToken, + int channelMask) { // Decode the compressed bytes into a temporary GPU texture for Spark to sample. `linear` // (from the texture's slot usage) sets the colorspace; LoadImage uploads + mips it. var decoded = new Texture2D(2, 2, TextureFormat.RGBA32, generateMipMaps, linear); decoded.LoadImage(data.ToArray(), markNonReadable: true); + + // Channel-minimal Spark format from glTFast's per-image channel mask. + var format = FormatForChannels(channelMask); Debug.Log($"[SparkAddon] LoadImage: decoded {data.Length}B → {decoded.width}x{decoded.height} " + - $"{decoded.graphicsFormat}; Spark-encoding (linear={linear}, srgb={!linear}, mips={generateMipMaps})…"); + $"{decoded.graphicsFormat}; Spark-encoding {format} (linear={linear}, mask=0x{channelMask:X}, mips={generateMipMaps})…"); - // Spark-encode on device. Fixed RGB until gltFast threads per-texture channel usage. - var compressed = Spark.EncodeTexture(decoded, SparkFormat.RGB, srgb: !linear, mips: generateMipMaps); + var compressed = Spark.EncodeTexture(decoded, format, srgb: !linear, mips: generateMipMaps); if (compressed != null) { @@ -104,4 +110,17 @@ public Task LoadImage( Debug.LogWarning($"[SparkAddon] ✗ Spark.EncodeTexture returned null; using uncompressed {decoded.graphicsFormat}"); return Task.FromResult(new ImageResult(decoded)); } + + // Smallest Spark format whose contiguous channel set covers the mask's highest set channel. + // e.g. normal R|G → RG, occlusion R → R, metallic-roughness G|B → RGB. A mask of 0 (image + // not reached by glTFast's slot pass, e.g. an extension-only texture) falls back to RGBA + // so we never under-allocate channels. + static SparkFormat FormatForChannels(int mask) + { + if ((mask & 0x8) != 0) return SparkFormat.RGBA; // A + if ((mask & 0x4) != 0) return SparkFormat.RGB; // B + if ((mask & 0x2) != 0) return SparkFormat.RG; // G + if ((mask & 0x1) != 0) return SparkFormat.R; // R + return SparkFormat.RGBA; // unknown → safe superset + } } diff --git a/Packages/manifest.json b/Packages/manifest.json index d82da88..e1a9302 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,6 +1,6 @@ { "dependencies": { - "com.unity.cloud.gltfast": "6.19.0", + "com.atteneder.gltfast": "file:../../gltFast", "com.unity.collab-proxy": "2.11.4", "com.unity.feature.development": "1.0.2", "com.unity.memoryprofiler": "1.1.12", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 3cc182e..d58458f 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,5 +1,17 @@ { "dependencies": { + "com.atteneder.gltfast": { + "version": "file:../../gltFast", + "depth": 0, + "source": "local", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.mathematics": "1.3.3", + "com.unity.burst": "1.8.29", + "com.unity.collections": "2.6.6" + } + }, "com.ludicon.spark": { "version": "file:com.ludicon.spark", "depth": 0, @@ -16,19 +28,6 @@ }, "url": "https://packages.unity.com" }, - "com.unity.cloud.gltfast": { - "version": "6.19.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.29", - "com.unity.collections": "2.6.6", - "com.unity.mathematics": "1.3.3", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, "com.unity.collab-proxy": { "version": "2.11.4", "depth": 0,