// SPDX-FileCopyrightText: 2023 Unity Technologies and the KTX for Unity authors // SPDX-License-Identifier: Apache-2.0 using System.Threading.Tasks; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Profiling; using Unity.Collections; namespace KtxUnity { /// /// Loads a Basis Universal texture from the StreamingAssets folder, a URL, or a buffer. /// public class BasisUniversalTexture : TextureBase { NativeSlice m_InputData; NativeArray m_TextureData; MetaData m_MetaData; TextureOrientation m_Orientation; /// public override ErrorCode Open(NativeSlice data) { KtxNativeInstance.CertifySupportedPlatform(); m_InputData = data; return ErrorCode.Success; } /// public override async Task LoadTexture2D( bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { KtxNativeInstance.CertifySupportedPlatform(); return await LoadTexture2DInternal( linear, layer, 0, mipLevel, mipChain); } /// public override async Task LoadTexture2D( GraphicsFormat targetFormat, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { KtxNativeInstance.CertifySupportedPlatform(); return await LoadTexture2DInternal( true, layer, 0, mipLevel, mipChain, targetFormat); } /// public override void Dispose() { } internal async Task LoadFromBytesInternal( NativeSlice data, bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true ) { m_InputData = data; var result = await LoadTexture2DInternal(linear, layer, faceSlice, mipLevel, mipChain); Dispose(); return result; } async Task LoadTexture2DInternal( bool linear = false, uint layer = 0, uint faceSlice = 0, uint mipLevel = 0, bool mipChain = true, GraphicsFormat? targetFormat = null ) { var transcoder = BasisUniversal.GetTranscoderInstance(); while (transcoder == null) { await Task.Yield(); transcoder = BasisUniversal.GetTranscoderInstance(); } var result = new TextureResult(); GraphicsFormat format; if (transcoder.Open(m_InputData)) { m_MetaData = transcoder.LoadMetaData(); var formatTuple = targetFormat.HasValue ? TranscodeFormatHelper.GetTranscodeFormats(targetFormat.Value) : GetFormat(m_MetaData, m_MetaData.images[layer].levels[0], linear); if (formatTuple.HasValue) { var formats = formatTuple.Value; #if KTX_VERBOSE Debug.LogFormat("LoadTexture2D to GraphicsFormat {0} ({1})",formats.format,formats.transcodeFormat); #endif format = formats.format; result.errorCode = await Transcode( transcoder, formats.transcodeFormat, layer, mipLevel, mipChain ); } else { BasisUniversal.ReturnTranscoderInstance(transcoder); result.errorCode = ErrorCode.UnsupportedFormat; return result; } m_Orientation = TextureOrientation.KtxDefault; if (!transcoder.GetYFlip()) { // Regular basis files (no y_flip) seem to be m_Orientation |= TextureOrientation.YUp; } BasisUniversal.ReturnTranscoderInstance(transcoder); } else { BasisUniversal.ReturnTranscoderInstance(transcoder); result.errorCode = ErrorCode.LoadingFailed; return result; } Profiler.BeginSample("LoadBytesRoutineGpuUpload"); m_MetaData.GetSize(out var width, out var height, layer, mipLevel); var flags = TextureCreationFlags.None; if (mipChain && m_MetaData.images[layer].levels.Length - mipLevel > 1) { flags |= TextureCreationFlags.MipChain; } result.texture = new Texture2D((int)width, (int)height, format, flags); result.orientation = m_Orientation; #if KTX_UNITY_GPU_UPLOAD // TODO: native GPU upload #else #endif result.texture.LoadRawTextureData(m_TextureData); result.texture.Apply(false, true); m_TextureData.Dispose(); Profiler.EndSample(); return result; } async Task Transcode( BasisUniversalTranscoderInstance transcoder, TranscodeFormat transcodeFormat, uint layer, uint mipLevel, bool mipChain ) { var result = ErrorCode.Success; Profiler.BeginSample("BasisUniversalJob"); var job = new BasisUniversalJob { layer = layer, mipLevel = mipLevel, result = new NativeArray(1, KtxNativeInstance.defaultAllocator) }; var jobHandle = BasisUniversal.LoadBytesJob( ref job, transcoder, transcodeFormat, mipChain ); m_TextureData = job.textureData; Profiler.EndSample(); while (!jobHandle.IsCompleted) { await Task.Yield(); } jobHandle.Complete(); if (!job.result[0]) { m_TextureData.Dispose(); result = ErrorCode.TranscodeFailed; } job.sizes.Dispose(); job.offsets.Dispose(); job.result.Dispose(); return result; } } }