How to create ktx2 correctly

Hi guys,

first of all I would like to say big thank you to @donmccurdy for many usefull advices on this forum. I would like to ask, if you can tell me what am I doing wrong regards to KTX2 files generation. What I do is following:

  1. Download the following application Release v4.0.0 · KhronosGroup/KTX-Software · GitHub
  2. Based on the documentation Khronos Texture Tools: toktx I take my JPG file and generate KTX2 file: toktx --t2 Atlas1.ktx2 Atlas1.jpg
  3. Upload the file to CDN
  4. Prepare Three.js project with KTX2Loader (three.js docs) and Basis Universal GPU Texture Compression (https://appspowerplaymanager.vshcdn.net/images/winter-sports/minigame/basis) downloaded from three.js/examples/js/libs/basis at dev · mrdoob/three.js · GitHub
  5. When I tried to load the texture created by the described tool set I received:
    Error: THREE.KTX2Loader: .transcodeImage failed.
    at transcode (7ca89d84-a942-4a9a-bbc6-3923efc8d57e:107:23)
    at 7ca89d84-a942-4a9a-bbc6-3923efc8d57e:46:103

I assumed that the problem is on my script but it was not true. I tested my script with textures found on Three.js Github and all 3 of them are working correctly:

  • sample_etc1s.ktx2
  • sample_uastc.ktx2
  • sample_uastc_zstd.ktx2

All 3 of them are correctly loaded in my project. So I assume that the problem is not in JS/TS but in the KTX2 file that I generated. I also tried to compress the KTX2 file via ktxsc tool: ktxsc --encode etc1s --clevel 5 -qlevel 255 -o Atlas1.ktx2 Atlas1.ktx2

But without any possitive result. So I would like to ask if you can give me some advice what am I doing wrong. This is the list of all files:

THANK YOU.

2 Likes

Hi @Dominik_Halvonik! As far as I can tell your file is OK, and it passes the KTX2 validation tool. I’m not sure why there would be a transcoding error. Sometimes errors happen if the input dimensions are invalid — dimensions must be multiples of 4, and sometimes must be powers of two — but this file is fine on both counts. Testing the KTX2 file in https://sandbox.babylonjs.com/ it looks OK to me, and testing it with the code from the webgl / loader / ktx2 example also works. If this isn’t working on your device with the same texture and the same code, I think you may need to share the code and details about your device, browser, and GPU.

Aside — you might want to include the --genmipmap if this texture will be shown on a 3D mesh. It adds some to the file size but eliminates flickering when the mesh or camera move. But that shouldn’t be causing the error here.

Hi @donmccurdy . Thank you very much for your response. There are 2 versions of this story, long and short.

Short one
The only missing step was to add --genmipmap to the toktx --t2 Atlas1.ktx2 Atlas1.jpg command. That was it. Easy when you know what to do :smiley: .

Long one
I found GUI tool which basically did what I wanted. The tools is called PVRTextTool and you can find it on this URL - PVRTexTool - Imagination Developers . To be honest it saved me because it does exacatly what I needed. I opened the JPEG file then clicked on the compression tab on the left side of the application and choose BASISU ETC1S (because I am on web) sRGB option. Also I selected Encoding Quality to Best and checked Generate MIPMaps (which is the option that I did not specified via the command line tool toktx). The file was “compressed” but it was still in JPEG so I clicked on Saved As option and selected KTX2 file formant. When I uploaded this file to my CDN everything was working as it should. There was one exception that some logos and faces on specific textures were a bit problematic regards the quality point of view. So for thouse few exceptions I done the same process but with UASTC option (output file was 3x bigger that with ETC1S but still it was ok). I was still a bit frustrated that I did not understand why I could not create the KTX2 files with Khronos Texture Tools. So I tried to create the file via PVRTextTool - open JPEG / Save As KTX2 and used the ktxsc command line tool for compression like this:

ktxsc --encode etc1s --clevel 5 -qlevel 255 -o Atlas1.ktx2 Atlas1.ktx2

When I used this file, all was woring as it should, again. I noticed one thing, that the KTX2 file generated from PVRTextTool has 712 KB and the file generated from ktxsc command line tool was smaller 592 KB. Which is quite nice. So what I did I converted all my textures from JPEG to KTX2 via combining these 2 tools and all of them are working as expected. So in the end there are 2 possible ways how to create KTX2 file which is usable in Three.js:

1. Via GUI SW called PVRTextTool

  • Donwload/Install SW from PVRTexTool - Imagination Developers
  • Open PNG/JPEG texture file in this SW
  • Click on compression, select BASESU ETC1S - Best quality, Generate MIPMaps
  • Save file as KTX2
  • DONE
    This solution is for less experienced developers because it is really easy, but basically you have no control overy anything. You can choose only the quality and that is all. Also the size of the file is bigger with the “same” settings compare to Khronos Texture Tools.

2. Via Command Line SW from Khronos Texture Tools

  • Download/Install SW from Releases · KhronosGroup/KTX-Software · GitHub
  • Take your time and study the documentation for toktx and ktxsc - Khronos Texture Software: KTX Tools
  • Use the command for creating the KTX2 file from JPEG/PNG like this:
    toktx --t2 --genmipmap Atlas1.ktx2 Atlas1.jpg this will create the KTX2 file in uncompressed format
  • Compress the file via ktxsc like this:
    ktxsc --encode etc1s --clevel 5 -qlevel 255 -o Atlas1C2.ktx2 Atlas1.ktx2
    you can also use better compression with UASTC (higher quality / bigger file):
    ktxsc --encode uastc --uastc_quality 4 --uastc_rdo_l [.25,10] --uastc_rdo_d 4096 --uastc_rdo_b 10.0 --uastc_rdo_s 18.0 --zcmp 20 -o Atlas1C2.ktx2 Atlas1.ktx2
  • DONE

In both scenarios you need to use KTX2Loader (three.js docs) with Basis Universal GPU Texture Compression tool (three.js/examples/js/libs/basis at dev · mrdoob/three.js · GitHub) but that is a different topic :smiley: . One importat thing, if you are like me, you will also try to use ASTC compression because you will read a lot of good about that but keep in mind that it will NOT WORK!!! The reason is that the Basis Universal GPU Texture Compression tool which is responsible for interpreting KTX2 file to GPU supports, for now, ETC1S and UASTC. So the KTX2 file is valid, but the Three.js components responsbile for handeling this process do not support it for now. So do not lose time to find a workaround like me :smiley: .

Ok in the end, the journy to KTX2 land was really iluminating for me in regards of 3D graphics and GPU world. The problem is, that even when I migrated most of the textures in my game from WebP to KTX2 I have the same original problem. On some iOS devices (not only on the old ones) the games just crashed, something like reloading the web page or the web browser crash. The same game is working fine almost on every Android device. These are some videos where you can see the behavior:

  • Three.js iOS issue - YouTube
  • Three.js iOS issue - YouTube
    What you can see is Vue.js app with UI, after clicking on Start button iframe is displayed with Three.js app that represents my game. After few seconds the iframe crashed and its closed. This is happening only on iOS mobile devices. Can you please guide me to the right direction, why this is happening? I have 2 iPhones and on iPhone 13 Pro Max it is working as it should, also on my iPhone 12 it is working correctly. But on some devices like iPhone X etc, this is what is happening all the time.

Thank you

2 Likes

Ah sorry for that. As you’ve found, KTX2Loader supports only the “Basis Universal” compressed formats, ETC1S and UASTC. I’m not aware that ASTC has any advantage over UASTC (they are very similar), with the big drawback that ASTC is only supported on certain devices and GPUs. But it would not be a big effort to add ASTC support if there’s a reason to do so.

After few seconds the iframe crashed and its closed. This is happening only on iOS mobile devices. Can you please guide me to the right direction, why this is happening?

Is it possible to share a demo others can test? I’m not sure why this would happen, if the memory usage is within reasonable limits. Do you still get crashes if you limit the memory allocation to <500 MB or so?

Hi @donmccurdy , thank you very much for your response. Based on your response I prepared demo of my game which can be found over here:

https://github.com/dominikhalvonik/three-js-demo

The only thing you need to do is clone the repo, run:

npm install

and then you will just run:

npm run dev

after that you can access the game via following URL (you have different IP):

http://172.24.18.175:8080/?token=XXX&mode=Y&discipline_id=Z

It will be great if you can have a look on it because to be honest I ran out off options :smiley:

Hi guys, specially @donmccurdy :smiley: , after lots of debugging I found a solution of my problem. The problem was with Apples WebKit component - WKWebView and it`s implementation of WebGL 2.0. The full story is very good documentated in this Unity forum post - https://forum.unity.com/threads/ios-15-webgl-2-issue.1176116/ . So that is the problem, but how to solve it? In the end it was quite easy (pseudo JS code):

/Mac OS|iPhone|iPad/i.test(navigator.userAgent) ? WebGL1Renderer : WebGLRenderer

And now it works without any problem. The crashes are random on WebGL 2.0 and I could not define, when or why they are happening (connected to Safari DevTools via XCode debug project etc.). But when I switched to WebGL1Renderer for Apple devices all works fine. To be honest, this is happening only on version 15+. So it has nothing to do with GPU VRAM usage, as I assumed.

So the last problem that I have is that I have no idea, if it is possible to merge 2 KTX2 texture together. I am doing that with JPEG/PNG via this package: GitHub - kissmybackend/merge-images: Easily compose images together without messing around with canvas
Based on my understanding the way I am doing it now - via Base64 hashing, will not be possible, but I would like to know if it is even possible. Because in the game, it is much easier to take 3 different images and combine them than creates multiple monolitic textures (change of skier’s jersey).

Thank you

Oof that’s quite a bug. It sounds like they’re saying this was fixed in Safari 15.5, are you on an older version? Or does it seem like it might not be fully fixed? There was a lot of change under the hood in Safari over the past year to add WebGL 2 support, I suppose that’s related.

To merge/overlay KTX2 images would require one of two solutions I think:

  • (A) decompress the images on the CPU and overlay them using Canvas (or similar)
  • (B) upload the images to the GPU and overlay them using WebGL, output to Canvas

In both cases the resulting image will not be compressed, re-compressing these images on the client is unusual and a bit slow, but you could.

Hi @donmccurdy , we tried this on 15.6 and we have the same problem. So they fixed something, but there is definetly a problem that is still remaining. So the current “fix” with WebGL 1 is working. Thank you for your response regards the KTX2 merging possibilities. To be honest, we will stick to WebP format because in the end even when we will merge them, they will be send to GPU in uncompressed format, as you said. Today I noticed one interesting behavior, that when the skier is moving, the textures of logos next to the springboard has “grain” on it. But when camera is not moving the textures are correctly displayed. I will try to add printscreen:

Logos with KTX2:

Logos with WebP:

The code is the same. The textures are “the same” because we created both textures from the same source. If you want to test it on the provided demo, you can go to src/app/config/texturesConfig.ts and change the:

[TexturesNames.ads]: {
        ext: 'ktx2',
        dir: `${texturesDir}/hill/`
    },

to

[TexturesNames.ads]: {
        ext: 'webp',
        dir: `${texturesDir}/hill/`
    },

And you will see the difference. My question is if this is something that is normal for KTX2 + Three.js or we are doing something incorrectly. Thank you

My guess would be that grain is related to mipmaps, does this KTX2 texture include the --genmipmap flag? And do the two textures have the same .minFilter and .magFilter settings?

Hi @donmccurdy , you were right again!!! So the problem was, that I created the KTX2 files via PVRTextTool. But that tool has the option for generating MipMaps not when the file is created, but when the file is compressed (BASISU UASTC sRGB or Linear / BASISU ETC1S sRGB or Linear). And because I was unable to create the KTX2 file with toktx command line app I created the file without MipMaps and just compressed them via ktxsc. So what I did in the end was this simple command:

Create KTX2 and compress it with ETC1S

toktx --2d --genmipmap --target_type RGBA --t2 --encode etc1s --clevel 5 --qlevel 255 Atlas.ktx2 Atlas2.jpg

Create KTX2 and compress it with UASTC

toktx --2d --genmipmap --target_type RGBA --t2 --encode uastc --uastc_quality 0 --uastc_rdo_l [.25,10] --uastc_rdo_d 4096 --uastc_rdo_b 10.0 --uastc_rdo_s 18.0 --zcmp 22 logo.ktx2 logo.png

Now all is working exactly as it should, no graphical glitches, better VRAM usage. Everything is great. But I have 1 last question that doesn’t let me sleep. When I start this post, I used WebP format for all textures. Now almost all my textures are in KTX2 and they are compressed with ETC1S option. Each texture file compressed with ETC1S is definetly smaller than original WebP files. There is only one exception and that is the logo.ktx2 file. When I compress this file with ETC1S it was in bad quality, which is fine, I just used UASTC instead and the quality is perfect even on the highest compression options. The thing is that now, the KTX2 file is bigger than the WebP file (WebP - 761 KB / KTX2+UASTC - 983 KB) . So my question is:

When I have uncompressed texture(WebP/JPEG/PNG) with smaller file size and Super Compressed texture(Basis/KTX2) with bigger size, which one should I use? Which one will take less GPU VRAM?

Once again, thank you for your response. I promise this is my last question regards this topic :smiley: .

1 Like

WebP/JPEG/PNG textures will always use more GPU VRAM than Basis/KTX2, assuming the same resolution, regardless of the size of the files on disk. You can compare estimated GPU VRAM cost by loading a model in https://gltf.report/. Typically the difference is 4-8x, but this depends a bit on which format the KTX2 file is transcoded to for the GPU.

Sometimes it’s also possible to increase the resolution of the ETC1S file to get higher quality, while still having lower memory usage and reasonable file size.

With all that being said – I wouldn’t lose sleep over a few uncompressed WebP/JPEG/PNG images if you need them. It really depends on your performance budget and what you’re trying to build. For example a WebXR application rendering at 90 FPS will need to be much more careful about dropped frames caused by uploading an uncompressed texture, vs. a 30 FPS application pre-loading textures before the experience begins.

Hi @donmccurdy , once again, thank you for providing valuable guindence, the https://gltf.report/ really helped me debug my current app. So just to sum up the knowlage that was described in this topic.

There are 2 easy ways of creating KTX2 files from your existing JPEG/PNG files:

  1. Via PVRTexTool - GUI app that is less performant but easier to use
  2. Via Khronos Texture Tools specificly toktx - command line app which is much better when you know what you are doing

PVRTexTool

  • Download/install the app from the link found in this topic
  • Open the PNG/JPEG file in the PVRTexTool
  • On the left menu click on - Encode the current texture to a new format
  • Select the following combination: Group/API: Super Compression, Name / Type / Colour Space: BASISU ETC1S / Unsigned Normalised Byte / sRGB. You can also use UASTC if you want (bigger size on disk, better quality)
  • Select checkbox Generate MIPMaps
  • Select BASISU Encoding Quality - Best
  • Click Encode
  • After encoding is finished clicked on File - Save As and select .ktx2 format

Now you have your KTX2 texture ready for your KTX2Loader in Three.js.

Pros : Easy to use
Cons : Not getting the best results - the textures are bigger (in terms of size on the disk) and the quality is not so great as you can get.

toktx.

  • Download/install the app from the link found in this topic
  • Open CMD and naviage where your PNG/JPEG texture can be found (example: cd Downloads/mytextures/)
  • Add the following command:
toktx --2d --genmipmap --target_type RGBA --t2 --encode etc1s --clevel 5 --qlevel 255 Atlas.ktx2 Atlas2.jpg

Which can be translated like this: Create KTX2 texture named Atlas.ktx2 from original file Atlas2.jpg. The KTX2 texture will be 2D (–2d) with generated MipMaps (–genmipmap) and the target color type is RGBA (–target_type RGBA). Because it is KTX file I am specifing that I want it in version 2 (–t2). Also compress it with ETC1S (–encode etc1s) and I want MAX quality (–encode etc1s --qlevel 255) together with MAX compression (–encode etc1s --clevel 5).
Now you have your KTX2 texture ready for your KTX2Loader in Three.js.

Pros : Better end results, smaller textures, better quality, multiple other functionalities like resizing etc.
Cons : Much harder to use when you are not familiar with terms and the texture problematics.

But after this 1 month I definetly recomend usage of toktx command line tool. Because when you know what flags you need, it is easy to write some Go or PHP or C++ script which will convert your textures without any problem. So I concider this issue as resolved. @donmccurdy do you want to add anything else? If no, I will mark this last response as answer to my original question (of course it is just a summary of all your advices, so I am not the one who solved my issue, I just a person who sum it down :smiley: )

4 Likes

That’s a good summary! I’d just add that you may need slightly different flags to optimally compress non-color textures (normal, occlusion/roughness/metallic). There are also some tools that can make this easier if your textures are embedded in glTF/GLB files. The Khronos KTX2 artist guide goes into a bit more detail on both of those things: 3D-Formats-Guidelines/KTXArtistGuide.md at 71a48276a2c424fb7aa553c44878caeafb0d077a · KhronosGroup/3D-Formats-Guidelines · GitHub

1 Like

if someone is still looking for a converter, you can use this https://www.png2ktx.com/
feedbacks welcome, planning to add more options in future.

the storage limit was exceeded, should work now.

Hi @dk46 , I tried the output of your project and I have to say, it is not working. I took JPG file which has 929 KB, I converted it via Khronos Tools and the output with ETC1 compression was file 684 KB. I also uploaded the file to your project and the output file has 27 KB. I was very excited because that is seriouse reduce, but when I uploaded the file to Three.js the texture was loaded (no error when loading the KTX2 file) but the texture was not loaded correctlz - only black space.

Hi Dominik, this happens when the image has ICC colour profile chunk, and “toktx” doesn’t supports it also the web-tool removes the ICC profile before converting it to ktx2. this could be the reason for the black image.

I will check is there a way to covert or remove from ICC profile without affecting the image quality.
btw can you give me the image to test.

Sorry for reopen this topic but I have an issue with my models using KTX2.
The GLB source is 3Mb and using the memory viewer (on the gltf-viewer from @donmccurdy ) it is using 25Mb.
But the GLB using KTX is 7Mb and 53Mb in memory!
Not sure why.
The command I’m using to create the optimized GLB is:

gltf-transform uastc input.glb output.glb --level 3 --rdo 1 --zstd 18

is it something wrong? or something I should adjust to get better results? What is the most recommended way to optimize it?

Hm. Is it possible to share the file? I don’t think it’s possible for KTX compression to increase the memory cost of a PNG or JPEG texture…