Need help with betsy?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

186 Stars 9 Forks Other 94 Commits 3 Opened issues


Betsy GPU compressor

Services available


Need anything else?

Contributors list

# 133,932
94 commits

Betsy GPU Compressor

Betsy aims to be a GPU compressor for various modern GPU compression formats such as BC6H, purposedly written in GLSL so that it can be easily incorporated into OpenGL and Vulkan projects.

Compute Shader support is required.

The goal is to achieve both high performance (via efficient GPU usage) and high quality compression.

At the moment it is WIP.

How do I use it?


betsy input.hdr --codec=etc2_rgb --quality=2 output.ktx


betsy --help
for full description

Build Instructions


# Debug
sudo apt install libsdl2-dev ninja-build
mkdir -p build/Debug
cd build/Debug
cmake ../.. -DCMAKE_BUILD_TYPE=Debug -GNinja
cd ../../bin/Debug


sudo apt install libsdl2-dev ninja-build mkdir -p build/Release cd build/Release cmake ../.. -DCMAKE_BUILD_TYPE=Release -GNinja ninja cd ../../bin/Release ./betsy


Double click on buildVisualStudio162019_x64.bat

If you want to use another target edit the beginning of the bat script, or manually:

  1. Point CMake source to %betyrepopath%
  2. Point CMake generation path to %betyrepopath%/build

Build tested on VS2019.

Python scripts

We use to generate strings out of enums like in C#.

We keep the generated cpp files in the repo up to date but if you want to generate them yourself, run:

cd scripts/EnumIterator

Supported formats:

| Format | State |Status| |---------|----------------|------| | ETC1 | Done |
Based on rg-etc1.
AMD Mesa Linux: Requires a very recent Mesa version due to a shader miscompilation issue. See ticket.| | EAC | Done | Used for R11, RG11 and ETC2_RGBA (for encoding the alpha component).
Quality: Maximum, we use brute force to check all possible combinations.| | ETC2 | Done | T, H and P modes implemented. Based on etc2_encoder for T and H modes with a couple minor bugfixes. P was based on the same implementation but uses a much higher quality fitting algorithm; thus neither of the 3 modes will produce the exact same output as the original C version| | BC1 | Done | Based on stb_dxt.| | BC3 | Done | This is just BC1 for RGB + BC4 for alpha | | BC4, BC5| Done | Based on stb_dxt. Supports UNORM & SNORM variants| | BC6H UF | Done | Unsigned variation of B6CH. GLSL port of GPURealTimeBC6H|


When I run betsy, I get a 'Display driver stopped responding and has recovered' error

Some textures are very big and take too long to encode, thus Windows has no way of knowing if the GPU is stuck in an infinite loop. This problem is not unique to betsy and if you've used GPU-based tools before, you may already be familiar with this.

To solve this problem try:

  1. Lowering the --quality setting
  2. Use a smaller resolution texture
  3. Increase the TdrDelay and TdrDdiDelay to a higher value via Registry.

Alternatively if you're on Linux, you may see your X11 session restarts on its own.

Can I encode using the CPU and GPU at the same time?

Betsy does not support CPU encoding. However you can run another 3rd party tool to compress a set of textures while betsy works on a different set.

Note however this could put a lot of stress on your machine (both CPU and GPU to 100%). Make sure your PSU can handle the load and your cooling solution keeps your system from overheating.

If you're overclocking your hardware, your system could become unstable or silently produce incorrect output on a few pixels

Note: You can use Mesa's llvmpipe DLLs to run betsy under the CPU. I did not try this and I don't know what the performance would be like.

Does betsy produce the same quality as the original implementations they were based on? (or bit-exact output)?

In theory yes. In practice there could have been bugs during the port/adaptation, and in some cases there could be precision issues.

For example rg-etc1 used uint64 for storing error comparison, while we use a 32-bit float. While I don't think this should make a difference, I could be wrong.

Another example is in BC1, in the function RefineBlock the variable

can theoretically reach as high as 9437184, which gets dangerously close to 16777216, aka the last integer number that can be accurately represented using 32-bit floating point. It doesn't look like this is an issue, as it is later divided by 65535. But I could be wrong.

There could also be compiler/driver/hardware bugs causing the code to misbehave, as is more common with compute shaders.

Did you write these codecs yourself?

So far I only wrote the EAC codec from scratch, but I used etc2_encoder for reference, particularly figuring out the bit pattern of the bit output and the idea of just using brute force. Unfortunately this version had several bugs which is why I just wrote it from scratch.

The P mode from ETC2 is also based on etc2_encoder but I rewrote the fitting algorithm because the original was way too basic. The new one produces significantly higher quality output.

The rest of the codecs were originally written for different shading languages or architectures. See the supported formats' table for references to the original implementations.

Why does it use OpenGL?

This project has been contracted by Godot to improve its import time process.

One of the main requirements was that the shading language had to be GLSL to ease its integration into Godot.

Vulkan requires a lot of boiler-plate initialization code and dealing with unnecessary complexity that would have burnt too much of the contract's budget which is better spent on improving the conversion algorithms, thus OpenGL was a much more reasonable choice.

OpenGL allowed us to get into writing the application right away while also using GLSL as the main shading language.

Note that betsy has abstracted almost all of the API code to PlatformGL.cpp and EncoderGL.cpp, meaning that it should be possible to port it to Vulkan (in fact it has been made so, so that it is easy to integrate into Godot)

Why does the code have execute01(), execute02(), and so on...?

Many of the compute shaders can be run in parallel via Async Compute and have multiple steps.

Each step must wait for the completion of the previous step in order to process (e.g. barrier)

This means that you can call (assuming a Vulkan solution):

for( int i=0; i < numEncoders; ++i )
for( int i=0; i < numEncoders; ++i )
    encoder[i].barriers01(); // Not implemented in OpenGL

for( int i=0; i < numEncoders; ++i ) encoder[i].execute02(); for( int i=0; i < numEncoders; ++i ) encoder[i].barriers02(); // Not implemented in OpenGL

for( int i=0; i < numEncoders; ++i ) encoder[i].execute03();

to have them all run in parallel



We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.