Skip to content

Repository files navigation

libdds

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.

History

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.

Performance against DirectXTex 2.1.1

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.

Warm-cache input

One input per worker is reused after warmup. Large inputs may exceed CPU caches.

Base sizeFormatDirectXTex 2.1.1
1 worker
Optimized
1 worker
Optimized parallel1
8 workers
TimeSpeedupTimeSpeedup
64 × 64BGRA820.000982 ms30.000266 ms3.70×30.000051 ms19.18×3
BGRX80.0107 ms0.0058 ms1.83×0.0014 ms 7.76×
BC10.0321 ms0.0201 ms1.60×0.0033 ms39.68×3
BC30.0344 ms0.0218 ms1.57×0.0040 ms38.63×3
BC40.0187 ms0.0058 ms 3.25×0.0015 ms312.74×3
BC50.0237 ms0.0062 ms 3.81×0.0015 ms316.20×3
1024 × 1024BGRA81.90 ms0.19 ms 10.08×0.11 ms317.51×3
BGRX84.91 ms2.07 ms 2.37×0.56 ms38.81×3
BC15.96 ms2.34 ms 2.55×0.54 ms311.07×3
BC36.95 ms2.69 ms 2.58×0.56 ms312.41×3
BC46.23 ms2.23 ms 2.79×0.52 ms312.01×3
BC57.78 ms2.31 ms 3.37×0.48 ms316.22×3
2048 × 2048BGRA88.95 ms0.79 ms311.32×30.78 ms 11.54×
BGRX820.94 ms9.29 ms 2.25×2.33 ms39.00×3
BC122.54 ms9.32 ms 2.42×2.07 ms310.91×3
BC326.29 ms11.24 ms32.34×32.39 ms311.00×3
BC424.81 ms10.07 ms 2.46×2.05 ms312.10×3
BC530.62 ms10.38 ms 2.95×2.20 ms313.89×3
8192 × 8192BGRA8145.48 ms18.55 ms 7.84×13.15 ms 11.06×
BGRX8353.73 ms147.27 ms 2.40×31.98 ms 11.06×
BC1336.99 ms148.61 ms 2.27×35.82 ms 9.41×
BC3387.57 ms174.53 ms 2.22×42.04 ms39.22×3
BC4391.26 ms170.10 ms 2.30×38.49 ms 10.17×
BC5489.11 ms185.61 ms 2.64×45.92 ms310.65×3

Cold/streaming input4

Base sizeFormatDirectXTex 2.1.1
1 worker
Optimized
1 worker
Optimized parallel1
8 workers
TimeSpeedupTimeSpeedup
64 × 64BGRA80.0020 ms0.0012 ms1.72×0.000453 ms 4.42×
BGRX80.0130 ms30.0063 ms2.06×30.0031 ms34.15×3
BC10.0336 ms0.0214 ms1.57×0.0039 ms38.57×3
BC30.0357 ms0.0222 ms1.61×0.0037 ms39.72×3
BC40.0207 ms30.0060 ms3.46×30.0011 ms319.55×3
BC50.0249 ms0.0063 ms 3.93×0.0011 ms322.80×3
1024 × 1024BGRA82.05 ms0.30 ms 6.81×0.18 ms 11.33×
BGRX85.09 ms2.45 ms 2.08×0.50 ms 10.18×
BC16.04 ms2.32 ms 2.61×0.47 ms 12.88×
BC37.53 ms32.78 ms32.71×30.63 ms311.99×3
BC47.12 ms32.29 ms33.10×30.54 ms313.30×3
BC58.06 ms2.29 ms 3.52×0.43 ms 18.54×
2048 × 2048BGRA89.22 ms1.15 ms 7.99×0.78 ms 11.90×
BGRX821.65 ms9.48 ms 2.28×1.98 ms310.93×3
BC123.01 ms9.53 ms 2.41×1.93 ms311.90×3
BC326.08 ms11.30 ms32.31×32.50 ms310.42×3
BC425.78 ms10.33 ms 2.50×2.02 ms312.78×3
BC531.80 ms10.79 ms 2.95×2.36 ms313.50×3
8192 × 8192BGRA8156.49 ms18.18 ms 8.61×12.98 ms 12.05×
BGRX8357.41 ms149.59 ms 2.39×38.34 ms 9.32×
BC1339.02 ms155.33 ms32.18×341.44 ms38.18×3
BC3387.93 ms179.34 ms 2.16×41.31 ms 9.39×
BC4411.62 ms180.84 ms 2.28×42.65 ms 9.65×
BC5504.46 ms187.46 ms 2.69×44.97 ms 11.22×

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.

Build

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-failure

For 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.

Further reading

Where libdds gains performance

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.

Avoid moving the same pixels twice

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.

Resolve setup once, then process pixels

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.

Reuse calculations within each block

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.

Verify that work disappeared and pixels stayed correct

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.

License

MIT license. Bundled dependency licenses remain in their directories.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages