// SPDX-FileCopyrightText: 2023 Unity Technologies and the KTX for Unity authors // SPDX-License-Identifier: Apache-2.0 #if !(UNITY_ANDROID || UNITY_WEBGL) || UNITY_EDITOR #define LOCAL_LOADING #endif using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Networking; using Unity.Collections; namespace KtxUnity { /// /// Loads a KTX or Basis Universal texture from the StreamingAssets folder, a URL, or a buffer. /// public abstract class TextureBase { /// /// Loads a KTX or Basis Universal texture from the StreamingAssets folder /// see https://docs.unity3d.com/Manual/StreamingAssets.html /// /// Path to the file, relative to StreamingAssets /// Depicts if texture is sampled in linear or /// sRGB gamma color space. /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public async Task LoadFromStreamingAssets( string filePath, bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { var url = GetStreamingAssetsUrl(filePath); return await LoadFile(url, linear, layer, faceSlice, mipLevel, mipChain); } /// /// Loads a KTX or Basis Universal texture from the StreamingAssets folder /// see https://docs.unity3d.com/Manual/StreamingAssets.html /// /// Path to the file, relative to StreamingAssets /// Desired texture format /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public async Task LoadFromStreamingAssets( string filePath, GraphicsFormat targetFormat, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { var url = GetStreamingAssetsUrl(filePath); return await LoadFile(url, targetFormat, layer, faceSlice, mipLevel, mipChain); } /// /// Loads a KTX or Basis Universal texture from a URL /// /// URL to the ktx/basis file to load /// Depicts if texture is sampled in linear or /// sRGB gamma color space. /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public async Task LoadFromUrl( string url, bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { return await LoadFile(url, linear, layer, faceSlice, mipLevel, mipChain); } /// /// Loads a KTX or Basis Universal texture from a URL /// /// URL to the ktx/basis file to load /// Desired texture format /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public async Task LoadFromUrl( string url, GraphicsFormat targetFormat, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { return await LoadFile(url, targetFormat, layer, faceSlice, mipLevel, mipChain); } /// /// Loads a KTX or Basis Universal texture from a buffer /// /// Native buffer that holds the ktx/basis file /// Depicts if texture is sampled in linear or /// sRGB gamma color space. /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public async Task LoadFromBytes( NativeSlice data, bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { KtxNativeInstance.CertifySupportedPlatform(); var result = new TextureResult { errorCode = Open(data) }; if (result.errorCode != ErrorCode.Success) return result; result = await LoadTexture2D(linear, layer, faceSlice, mipLevel, mipChain); Dispose(); return result; } /// /// Loads a KTX or Basis Universal texture from a buffer /// /// Native buffer that holds the ktx/basis file /// Desired texture format /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// // ReSharper disable once MemberCanBePrivate.Global public async Task LoadFromBytes( NativeSlice data, GraphicsFormat targetFormat, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { KtxNativeInstance.CertifySupportedPlatform(); var result = new TextureResult { errorCode = Open(data) }; if (result.errorCode != ErrorCode.Success) return result; result = await LoadTexture2D(targetFormat, layer, faceSlice, mipLevel, mipChain); Dispose(); return result; } /// /// Converts a relative sub path within StreamingAssets /// and creates an absolute URI from it. Useful for loading /// via UnityWebRequests. /// /// Path, relative to StreamingAssets. Example: path/to/file.ktx /// Platform independent URI that can be loaded via UnityWebRequest public static string GetStreamingAssetsUrl(string subPath) { var path = Path.Combine(Application.streamingAssetsPath, subPath); #if LOCAL_LOADING path = $"file://{path}"; #endif return path; } #region LowLevelAPI /// /// Loads a texture from memory. /// Part of the low-level API that provides finer control over the /// loading process. /// /// /// /// /// Input texture data /// if loading was successful /// or an error specific code otherwise. public abstract ErrorCode Open(NativeSlice data); /// /// Creates a from the previously opened /// texture. /// Transcodes or decodes the texture into a GPU compatible format /// (if required) and uploads it to GPU memory. /// Part of the low-level API that provides finer control over the /// loading process. /// /// /// /// Depicts if texture is sampled in linear or /// sRGB gamma color space. /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public abstract Task LoadTexture2D( bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ); /// /// Creates a from the previously opened /// texture. /// Transcodes or decodes the texture into a desired GPU compatible format /// (if required) and uploads it to GPU memory. /// Part of the low-level API that provides finer control over the /// loading process. /// /// /// /// Desired texture format /// Texture array layer to import /// Cubemap face or 3D/volume texture slice to import. /// Lowest mipmap level to import (where 0 is /// the highest resolution). Lower mipmap levels (of higher resolution) /// are being discarded. Useful to limit texture resolution. /// If true, a mipmap chain (if present) is imported. /// A that contains an /// , the resulting texture and its orientation. /// public abstract Task LoadTexture2D( GraphicsFormat targetFormat, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ); /// /// Releases all resources. /// Part of the low-level API that provides finer control over the /// loading process. /// /// /// /// /// public abstract void Dispose(); #endregion async Task LoadFile( string url, bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { var webRequest = UnityWebRequest.Get(url); var asyncOp = webRequest.SendWebRequest(); while (!asyncOp.isDone) { await Task.Yield(); } if (!string.IsNullOrEmpty(webRequest.error)) { #if DEBUG Debug.LogErrorFormat("Error loading {0}: {1}",url,webRequest.error); #endif return new TextureResult(ErrorCode.OpenUriFailed); } var buffer = webRequest.downloadHandler.data; using (var bufferWrapped = new ManagedNativeArray(buffer)) { return await LoadFromBytes( bufferWrapped.nativeArray, linear, layer, faceSlice, mipLevel, mipChain ); } } async Task LoadFile( string url, GraphicsFormat targetFormat, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { var webRequest = UnityWebRequest.Get(url); var asyncOp = webRequest.SendWebRequest(); while (!asyncOp.isDone) { await Task.Yield(); } if (!string.IsNullOrEmpty(webRequest.error)) { #if DEBUG Debug.LogErrorFormat("Error loading {0}: {1}",url,webRequest.error); #endif return new TextureResult(ErrorCode.OpenUriFailed); } var buffer = webRequest.downloadHandler.data; using (var bufferWrapped = new ManagedNativeArray(buffer)) { return await LoadFromBytes( bufferWrapped.nativeArray, targetFormat, layer, faceSlice, mipLevel, mipChain ); } } internal static TranscodeFormatTuple? GetFormat(IMetaData meta, ILevelInfo li, bool linear = false) { return TranscodeFormatHelper.GetFormatsForImage(meta, li, linear); } } }