A high-performance, standalone CPU-only port of DirectXTex 2.1.1, designed to preserve as much of the original upstream code as possible. The commit history clearly separates unmodified extraction → correctness fixes → optimizations.
It retains the DirectX namespace and CPU image-processing APIs without WIC, COM, WRL or Direct3D runtime dependencies.
| Layer | Contents |
|---|---|
| DirectXTex 2.1.1 extraction | 22 unmodified upstream CPU files and license, followed by bundled dependencies, with only line endings normalized. |
| Build and correctness | Portable build, alignment/rounding fixes, overflow checks, DDS validation, tests and CI. |
| Optimizations | Caller-owned output, borrowed DDS views, prepared conversion, SIMD packing, writer dispatch, BC4/BC5 palette caching and unity grouping. |
Upstream files are imported unmodified before portability changes, correctness fixes and optimizations. Upstream updates follow the same order.
AMD Ryzen 9 9950X · Windows x64 · Clang-CL 22.1.8 Release. Each image includes its complete mip chain. Inputs are in memory and use the same repeated random-colour tile in both builds.
The optimized build uses borrowed DDS input and writes decoded/converted pixels into freshly allocated caller buffers, without pooling. All times are ms per image, lower is better. Both speedups use DirectXTex 2.1.1, single-thread within each table as their baseline and are calculated from unrounded measurements.
2–<3×
3–<5×
≥5×. Green marks stable gains. Uncertain results remain plain with a note.
One input per worker is reused after warmup. Large inputs may exceed CPU caches.
| Base size | Format | DirectXTex 2.1.1 1 worker | Optimized 1 worker | Optimized parallel1 8 workers | ||
|---|---|---|---|---|---|---|
| Time | Speedup | Time | Speedup | |||
| 64 × 64 | BGRA82 | 0.000982 ms3 | 0.000266 ms | 3.70×3 | 0.000051 ms | 19.18×3 |
| BGRX8 | 0.0107 ms | 0.0058 ms | 1.83× | 0.0014 ms | ||
| BC1 | 0.0321 ms | 0.0201 ms | 1.60× | 0.0033 ms3 | 9.68×3 | |
| BC3 | 0.0344 ms | 0.0218 ms | 1.57× | 0.0040 ms3 | 8.63×3 | |
| BC4 | 0.0187 ms | 0.0058 ms | 0.0015 ms3 | 12.74×3 | ||
| BC5 | 0.0237 ms | 0.0062 ms | 0.0015 ms3 | 16.20×3 | ||
| 1024 × 1024 | BGRA8 | 1.90 ms | 0.19 ms | 0.11 ms3 | 17.51×3 | |
| BGRX8 | 4.91 ms | 2.07 ms | 0.56 ms3 | 8.81×3 | ||
| BC1 | 5.96 ms | 2.34 ms | 0.54 ms3 | 11.07×3 | ||
| BC3 | 6.95 ms | 2.69 ms | 0.56 ms3 | 12.41×3 | ||
| BC4 | 6.23 ms | 2.23 ms | 0.52 ms3 | 12.01×3 | ||
| BC5 | 7.78 ms | 2.31 ms | 0.48 ms3 | 16.22×3 | ||
| 2048 × 2048 | BGRA8 | 8.95 ms | 0.79 ms3 | 11.32×3 | 0.78 ms | |
| BGRX8 | 20.94 ms | 9.29 ms | 2.33 ms3 | 9.00×3 | ||
| BC1 | 22.54 ms | 9.32 ms | 2.07 ms3 | 10.91×3 | ||
| BC3 | 26.29 ms | 11.24 ms3 | 2.34×3 | 2.39 ms3 | 11.00×3 | |
| BC4 | 24.81 ms | 10.07 ms | 2.05 ms3 | 12.10×3 | ||
| BC5 | 30.62 ms | 10.38 ms | 2.20 ms3 | 13.89×3 | ||
| 8192 × 8192 | BGRA8 | 145.48 ms | 18.55 ms | 13.15 ms | ||
| BGRX8 | 353.73 ms | 147.27 ms | 31.98 ms | |||
| BC1 | 336.99 ms | 148.61 ms | 35.82 ms | |||
| BC3 | 387.57 ms | 174.53 ms | 42.04 ms3 | 9.22×3 | ||
| BC4 | 391.26 ms | 170.10 ms | 38.49 ms | |||
| BC5 | 489.11 ms | 185.61 ms | 45.92 ms3 | 10.65×3 | ||
Cold/streaming input4
| Base size | Format | DirectXTex 2.1.1 1 worker | Optimized 1 worker | Optimized parallel1 8 workers | ||
|---|---|---|---|---|---|---|
| Time | Speedup | Time | Speedup | |||
| 64 × 64 | BGRA8 | 0.0020 ms | 0.0012 ms | 1.72× | 0.000453 ms | |
| BGRX8 | 0.0130 ms3 | 0.0063 ms | 2.06×3 | 0.0031 ms3 | 4.15×3 | |
| BC1 | 0.0336 ms | 0.0214 ms | 1.57× | 0.0039 ms3 | 8.57×3 | |
| BC3 | 0.0357 ms | 0.0222 ms | 1.61× | 0.0037 ms3 | 9.72×3 | |
| BC4 | 0.0207 ms3 | 0.0060 ms | 3.46×3 | 0.0011 ms3 | 19.55×3 | |
| BC5 | 0.0249 ms | 0.0063 ms | 0.0011 ms3 | 22.80×3 | ||
| 1024 × 1024 | BGRA8 | 2.05 ms | 0.30 ms | 0.18 ms | ||
| BGRX8 | 5.09 ms | 2.45 ms | 0.50 ms | |||
| BC1 | 6.04 ms | 2.32 ms | 0.47 ms | |||
| BC3 | 7.53 ms3 | 2.78 ms3 | 2.71×3 | 0.63 ms3 | 11.99×3 | |
| BC4 | 7.12 ms3 | 2.29 ms3 | 3.10×3 | 0.54 ms3 | 13.30×3 | |
| BC5 | 8.06 ms | 2.29 ms | 0.43 ms | |||
| 2048 × 2048 | BGRA8 | 9.22 ms | 1.15 ms | 0.78 ms | ||
| BGRX8 | 21.65 ms | 9.48 ms | 1.98 ms3 | 10.93×3 | ||
| BC1 | 23.01 ms | 9.53 ms | 1.93 ms3 | 11.90×3 | ||
| BC3 | 26.08 ms | 11.30 ms3 | 2.31×3 | 2.50 ms3 | 10.42×3 | |
| BC4 | 25.78 ms | 10.33 ms | 2.02 ms3 | 12.78×3 | ||
| BC5 | 31.80 ms | 10.79 ms | 2.36 ms3 | 13.50×3 | ||
| 8192 × 8192 | BGRA8 | 156.49 ms | 18.18 ms | 12.98 ms | ||
| BGRX8 | 357.41 ms | 149.59 ms | 38.34 ms | |||
| BC1 | 339.02 ms | 155.33 ms3 | 2.18×3 | 41.44 ms3 | 8.18×3 | |
| BC3 | 387.93 ms | 179.34 ms | 41.31 ms | |||
| BC4 | 411.62 ms | 180.84 ms | 42.65 ms | |||
| BC5 | 504.46 ms | 187.46 ms | 44.97 ms | |||
For native BGRA8, the optimized path borrows input pixels. Every mode copies all output pixels to a consumer buffer. Output hashes match across both implementations and all workers. Results include correctness fixes and API/allocation changes, and depend on the input pattern and machine.
1 Batch time divided by all images completed across eight workers on eight performance cores. Each worker processes whole images serially. This measures throughput. The speedup includes optimization and concurrency against the DirectXTex 2.1.1 single-thread baseline.
2 This uncompressed mip chain is only 21.3 KiB and is repeatedly processed after warmup, favoring CPU caches. The tiny parallel value represents combined throughput, not individual-image latency.
3 Timing variation exceeds 5%, or the speedup uses such a measurement. Treat the result as indicative.
4 Each worker cycles through independent input buffers totaling at least 128 MiB and two images, exceeding the shared CPU cache. Inputs stay in RAM. Code is warmed up and disk I/O is excluded. Hardware prefetching and cache hits remain possible.
Requires CMake 3.20 and C++17. Dependencies are bundled.
cmake -S . -B build/libdds -DCMAKE_BUILD_TYPE=Release
cmake --build build/libdds --config Release
ctest --test-dir build/libdds -C Release --output-on-failureFor embedding, use add_subdirectory and link libdds::libdds. Include
DirectXTex.h. LIBDDS_BUILD_TESTS defaults on for standalone builds and off
when embedded. Do not link libdds and full DirectXTex into the same executable.
The performance work targets two costs around DirectXTex's CPU algorithms: moving pixels between allocations and repeating decisions inside pixel loops. The changes remove that work while reusing existing decoding and conversion arithmetic. The performance section above contains the measured gains. This section explains how the commits produce them.
An application may already hold both the DDS bytes and its final output buffer. Loading through an owning image and decoding into another temporary image adds storage work before those pixels reach their destination.
The validated DDS views commit removes the input copy for eligible layouts. Its view loader validates the DDS and creates surface descriptors pointing into the caller's bytes. Native pixels can be consumed there. Compressed pixels still need decoding, but their encoded data need not be copied first. Layouts requiring repair or stronger alignment retain the owning fallback, and borrowed input must stay alive and unchanged.
The caller-owned decompression and caller-owned conversion commits remove the other intermediate: they write directly into the application's destination. This avoids allocating and initializing a whole-image result only to copy it again. Conversion can still allocate temporary scanlines. These storage savings require callers to adopt the new APIs. Existing allocating overloads remain available.
After removing avoidable storage work, repeated format handling becomes another source of cost. The prepared scanline conversion commit resolves format metadata and transfer flags once per image in CPU conversion and BC encoding/decoding. Scanline processing reuses that state and skips conversion arithmetic when numeric and channel semantics already match and no transfer-function change remains.
The decompression writer selection commit similarly chooses the RGBA8 or generic output path before the block loop. The batched RGBA8 packing commit gives conversion and decompression a shared writer that packs four pixels at a time using SSE2 or NEON. It preserves the existing rounding order and stores the remaining pixels individually. The unity grouping commit makes these shared stores available for compiler inlining by building compression and conversion together.
BC4 and BC5 have another repeated calculation: pixels select from eight interpolated values per channel. The interpolation palette caching commit computes those values once per block, then indexes them for each pixel. It retains the original interpolation formulas and signed-endpoint rules. The earlier unaligned BC4/BC5 payload safety fix supplies safe local word loads independently of this optimization.
These changes affect different workloads differently. Native images benefit mainly from avoided allocation and copying. Compressed images also benefit from reduced setup, packing and interpolation work, but still pay for decoding and writing every output pixel. Their combined speedups cannot be attributed to any one commit.
The development profiles record the disappearance of per-block format-table searches. Later borrowed-view profiles show the owning input allocation/copy path disappearing for the measured array workload. These observations support the implementation's intended savings. The end-to-end benchmarks measure the combined effect.
The tests compare borrowed and owning paths, check padded outputs and guards, and exercise rounding, transfer functions and partial blocks. Exhaustive BC4/BC5 endpoint/index cases feed upstream comparisons. The commit notes report SIMD/scalar CPU and upstream-parity validation.
MIT license. Bundled dependency licenses remain in their directories.