// 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);
}
}
}