@@ -44,7 +44,7 @@ use gltf::{
4444 accessor:: Iter ,
4545 image:: Source ,
4646 mesh:: { util:: ReadIndices , Mode } ,
47- Material , Node , Semantic ,
47+ Document , Material , Node , Semantic ,
4848} ;
4949use serde:: { Deserialize , Serialize } ;
5050#[ cfg( feature = "bevy_animation" ) ]
@@ -72,7 +72,7 @@ use self::{
7272 } ,
7373 mesh:: { primitive_name, primitive_topology} ,
7474 scene:: { node_name, node_transform} ,
75- texture:: { texture_sampler, texture_transform_to_affine2} ,
75+ texture:: { texture_sampler, texture_source , texture_transform_to_affine2} ,
7676 } ,
7777} ;
7878use crate :: convert_coordinates:: GltfConvertCoordinates ;
@@ -92,6 +92,9 @@ pub enum GltfError {
9292 /// Invalid glTF file.
9393 #[ error( "invalid glTF file: {0}" ) ]
9494 Gltf ( #[ from] gltf:: Error ) ,
95+ /// Unsupported required glTF extension.
96+ #[ error( "unsupported required glTF extension: {0}" ) ]
97+ UnsupportedRequiredExtension ( String ) ,
9598 /// Binary blob is missing.
9699 #[ error( "binary blob is missing" ) ]
97100 MissingBlob ,
@@ -114,6 +117,17 @@ pub enum GltfError {
114117 /// The image URI was unable to be resolved with respect to the asset path.
115118 #[ error( "invalid image uri: {0}. asset path error={1}" ) ]
116119 InvalidImageUri ( String , ParseAssetPathError ) ,
120+ /// A texture did not have an image source.
121+ #[ error( "texture {0} is missing an image source" ) ]
122+ MissingImageSource ( usize ) ,
123+ /// A texture extension contained an invalid image source.
124+ #[ error( "texture {texture} contains an invalid KHR_texture_basisu source: {value}" ) ]
125+ InvalidTextureBasisuSource {
126+ /// The texture index.
127+ texture : usize ,
128+ /// The invalid source value.
129+ value : String ,
130+ } ,
117131 /// Failed to read bytes from an asset path.
118132 #[ error( "failed to read bytes from an asset path: {0}" ) ]
119133 ReadAssetBytesError ( #[ from] ReadAssetBytesError ) ,
@@ -248,6 +262,7 @@ impl GltfLoader {
248262 } else {
249263 gltf:: Gltf :: from_slice_without_validation ( bytes) ?
250264 } ;
265+ reject_unsupported_empty_texture_extensions ( & gltf. document ) ?;
251266
252267 // clone extensions to start with a fresh processing state
253268 let mut extensions = loader. extensions . read ( ) . await . clone ( ) ;
@@ -619,6 +634,7 @@ impl GltfLoader {
619634 for texture in gltf. textures ( ) {
620635 let image = load_image (
621636 texture. clone ( ) ,
637+ & gltf. document ,
622638 & buffer_data,
623639 & linear_textures,
624640 load_context. path ( ) ,
@@ -641,11 +657,13 @@ impl GltfLoader {
641657 let textures = IoTaskPool :: get ( ) . scope ( |scope| {
642658 gltf. textures ( ) . for_each ( |gltf_texture| {
643659 let asset_path = load_context. path ( ) . clone ( ) ;
660+ let gltf_document = & gltf. document ;
644661 let linear_textures = & linear_textures;
645662 let buffer_data = & buffer_data;
646663 scope. spawn ( async move {
647664 load_image (
648665 gltf_texture,
666+ gltf_document,
649667 buffer_data,
650668 linear_textures,
651669 & asset_path,
@@ -1180,9 +1198,22 @@ impl AssetLoader for GltfLoader {
11801198 }
11811199}
11821200
1201+ fn reject_unsupported_empty_texture_extensions ( document : & Document ) -> Result < ( ) , GltfError > {
1202+ for extension in document. extensions_required ( ) {
1203+ if matches ! ( extension, "EXT_texture_webp" | "MSFT_texture_dds" ) {
1204+ return Err ( GltfError :: UnsupportedRequiredExtension (
1205+ extension. to_string ( ) ,
1206+ ) ) ;
1207+ }
1208+ }
1209+
1210+ Ok ( ( ) )
1211+ }
1212+
11831213/// Loads a glTF texture as a bevy [`Image`] and returns it together with its label.
11841214async fn load_image < ' a , ' b > (
11851215 gltf_texture : gltf:: Texture < ' a > ,
1216+ gltf_document : & ' a Document ,
11861217 buffer_data : & [ Vec < u8 > ] ,
11871218 linear_textures : & HashSet < usize > ,
11881219 gltf_path : & ' b AssetPath < ' b > ,
@@ -1197,7 +1228,16 @@ async fn load_image<'a, 'b>(
11971228 texture_sampler ( & gltf_texture, default_sampler)
11981229 } ;
11991230
1200- match gltf_texture. source ( ) . source ( ) {
1231+ let gltf_image = texture_source ( & gltf_texture, gltf_document) . map_err ( |source| {
1232+ GltfError :: InvalidTextureBasisuSource {
1233+ texture : gltf_texture. index ( ) ,
1234+ value : source,
1235+ }
1236+ } ) ?;
1237+ let gltf_image =
1238+ gltf_image. ok_or_else ( || GltfError :: MissingImageSource ( gltf_texture. index ( ) ) ) ?;
1239+
1240+ match gltf_image. source ( ) {
12011241 Source :: View { view, mime_type } => {
12021242 let start = view. offset ( ) ;
12031243 let end = view. offset ( ) + view. length ( ) ;
@@ -2121,6 +2161,28 @@ mod test {
21212161 use bevy_reflect:: TypePath ;
21222162 use bevy_world_serialization:: WorldSerializationPlugin ;
21232163
2164+ #[ derive( TypePath ) ]
2165+ struct FakeKtx2Loader ;
2166+
2167+ impl AssetLoader for FakeKtx2Loader {
2168+ type Asset = Image ;
2169+ type Error = std:: io:: Error ;
2170+ type Settings = ImageLoaderSettings ;
2171+
2172+ async fn load (
2173+ & self ,
2174+ _reader : & mut dyn bevy_asset:: io:: Reader ,
2175+ _settings : & Self :: Settings ,
2176+ _load_context : & mut LoadContext < ' _ > ,
2177+ ) -> Result < Self :: Asset , Self :: Error > {
2178+ Ok ( Image :: default ( ) )
2179+ }
2180+
2181+ fn extensions ( & self ) -> & [ & str ] {
2182+ & [ "ktx2" ]
2183+ }
2184+ }
2185+
21242186 fn test_app ( dir : Dir ) -> App {
21252187 let mut app = App :: new ( ) ;
21262188 let reader = MemoryAssetReader { root : dir } ;
@@ -2691,6 +2753,173 @@ mod test {
26912753 } ) ;
26922754 }
26932755
2756+ #[ test]
2757+ fn reads_khr_texture_basisu_source ( ) {
2758+ let ( mut app, dir) = test_app_custom_asset_source ( ) ;
2759+
2760+ app. init_asset :: < GltfMaterial > ( ) ;
2761+
2762+ dir. insert_asset_text (
2763+ Path :: new ( "abc.gltf" ) ,
2764+ r#"
2765+ {
2766+ "asset": {
2767+ "version": "2.0"
2768+ },
2769+ "extensionsUsed": [
2770+ "KHR_texture_basisu"
2771+ ],
2772+ "textures": [
2773+ {
2774+ "source": 0,
2775+ "extensions": {
2776+ "KHR_texture_basisu": {
2777+ "source": 1
2778+ }
2779+ }
2780+ }
2781+ ],
2782+ "images": [
2783+ {
2784+ "uri": "abc.png"
2785+ },
2786+ {
2787+ "uri": "abc.ktx2"
2788+ }
2789+ ],
2790+ "materials": [
2791+ {
2792+ "pbrMetallicRoughness": {
2793+ "baseColorTexture": {
2794+ "index": 0,
2795+ "texCoord": 0
2796+ }
2797+ }
2798+ }
2799+ ]
2800+ }
2801+ "# ,
2802+ ) ;
2803+ dir. insert_asset_text ( Path :: new ( "abc.png" ) , "png" ) ;
2804+ dir. insert_asset_text ( Path :: new ( "abc.ktx2" ) , "ktx2" ) ;
2805+
2806+ app. init_asset :: < Image > ( )
2807+ . register_asset_loader ( FakeKtx2Loader ) ;
2808+
2809+ let asset_server = app. world ( ) . resource :: < AssetServer > ( ) . clone ( ) ;
2810+ let handle: Handle < Gltf > = asset_server. load ( "custom://abc.gltf" ) ;
2811+ run_app_until ( & mut app, |_world| {
2812+ asset_server
2813+ . is_loaded_with_dependencies ( & handle)
2814+ . then_some ( ( ) )
2815+ } ) ;
2816+ }
2817+
2818+ #[ test]
2819+ fn reads_required_khr_texture_basisu_source ( ) {
2820+ let ( mut app, dir) = test_app_custom_asset_source ( ) ;
2821+
2822+ app. init_asset :: < GltfMaterial > ( ) ;
2823+
2824+ dir. insert_asset_text (
2825+ Path :: new ( "abc.gltf" ) ,
2826+ r#"
2827+ {
2828+ "asset": {
2829+ "version": "2.0"
2830+ },
2831+ "extensionsUsed": [
2832+ "KHR_texture_basisu"
2833+ ],
2834+ "extensionsRequired": [
2835+ "KHR_texture_basisu"
2836+ ],
2837+ "textures": [
2838+ {
2839+ "extensions": {
2840+ "KHR_texture_basisu": {
2841+ "source": 0
2842+ }
2843+ }
2844+ }
2845+ ],
2846+ "images": [
2847+ {
2848+ "uri": "abc.ktx2"
2849+ }
2850+ ],
2851+ "materials": [
2852+ {
2853+ "pbrMetallicRoughness": {
2854+ "baseColorTexture": {
2855+ "index": 0,
2856+ "texCoord": 0
2857+ }
2858+ }
2859+ }
2860+ ]
2861+ }
2862+ "# ,
2863+ ) ;
2864+ dir. insert_asset_text ( Path :: new ( "abc.ktx2" ) , "ktx2" ) ;
2865+
2866+ app. init_asset :: < Image > ( )
2867+ . register_asset_loader ( FakeKtx2Loader ) ;
2868+
2869+ let asset_server = app. world ( ) . resource :: < AssetServer > ( ) . clone ( ) ;
2870+ let handle: Handle < Gltf > = asset_server. load ( "custom://abc.gltf" ) ;
2871+ run_app_until ( & mut app, |_world| {
2872+ asset_server
2873+ . is_loaded_with_dependencies ( & handle)
2874+ . then_some ( ( ) )
2875+ } ) ;
2876+ }
2877+
2878+ #[ test]
2879+ fn invalid_khr_texture_basisu_source_is_an_error ( ) {
2880+ let ( mut app, dir) = test_app_custom_asset_source ( ) ;
2881+
2882+ dir. insert_asset_text (
2883+ Path :: new ( "abc.gltf" ) ,
2884+ r#"
2885+ {
2886+ "asset": {
2887+ "version": "2.0"
2888+ },
2889+ "extensionsUsed": [
2890+ "KHR_texture_basisu"
2891+ ],
2892+ "textures": [
2893+ {
2894+ "extensions": {
2895+ "KHR_texture_basisu": {
2896+ "source": 0
2897+ }
2898+ }
2899+ }
2900+ ]
2901+ }
2902+ "# ,
2903+ ) ;
2904+
2905+ app. init_asset :: < Image > ( ) ;
2906+
2907+ let asset_server = app. world ( ) . resource :: < AssetServer > ( ) . clone ( ) ;
2908+ let handle: Handle < Gltf > = asset_server. load ( "custom://abc.gltf" ) ;
2909+ run_app_until ( & mut app, |_| match asset_server. load_state ( & handle) {
2910+ LoadState :: Failed ( err) => {
2911+ let err = err. to_string ( ) ;
2912+ assert ! (
2913+ err. contains( "invalid KHR_texture_basisu source: 0" ) ,
2914+ "incorrect error message: {err}"
2915+ ) ;
2916+ Some ( ( ) )
2917+ }
2918+ LoadState :: Loading => None ,
2919+ state => panic ! ( "Unexpected load state: {state:?}" ) ,
2920+ } ) ;
2921+ }
2922+
26942923 #[ test]
26952924 fn image_error_is_an_error ( ) {
26962925 let ( mut app, dir) = test_app_custom_asset_source ( ) ;
0 commit comments