Repository navigation
Round each component in SSE and ARM-NEON XMStoreXDecN4 - #365
0z5a (0z5a) wants to merge 1 commit into
Conversation
The SIMD paths scale the y, z, and w components by 511 * 2^10, 511 * 2^20, and 3 * 2^29 before the float to int conversion and then mask off the low bits, so only the x component gets rounded; y, z, and w are floored. ARM-NEON also truncates x. The same value can therefore be stored differently depending on its lane, and differently from the scalar path, which rounds every component: XMStoreXDecN4 of (0.7, 0.7, 0.7, 0) gives x=358, y=357, z=357 on SSE. Scale by (511, 511, 511, 3), round with XMVectorRound, then shift each lane into place with an exact power-of-two multiply.
|
DirectXTex uses this to implement the |
|
WSL issues fixed in #366 |
|
Tested:
|
|
With these changes I get the following DirectXTex changes. I'll need to look at each one in detail, but they look quite different. |
|
Thanks for running DirectXTex. Every mismatch in that log is one LSB in Y and/or Z; X and W are the same. For each pair, one value is the old SSE result and the other is exactly what
In the old code X is rounded by the float-to-int conversion, but Y and Z are scaled by 511·2^10 and 511·2^20 first, so their fractional bits land below the mask and are dropped. That truncates positive values and floors negative ones, e.g. -0.75 gives -384 instead of -383. So the DirectXTex baselines for (Built with clang for x86_64 and arm64 from |
Fixes #364
The SSE and ARM-NEON paths of
XMStoreXDecN4multiply by{ 511, 511 * 1024, 511 * 1048576, 3 * 536870912 }, do one float to int conversion, and mask each field. The fraction bits of y, z and w fall below the masks, so those lanes are floored; only x is rounded (SSE,_mm_cvtps_epi32) or truncated (ARM-NEON,vcvtq_s32_f32). The no-intrinsics path and the documented pseudocode round every component, so the same value can pack differently by lane and by platform:(0.7, 0.7, 0.7, 0.5)gives x=358 y=357 z=357 w=1 on SSE, x=357 y=357 z=357 w=1 on ARM-NEON, and x=358 y=358 z=358 w=2 in no-intrinsics.This change scales by
{ 511, 511, 511, 3 }, rounds withXMVectorRound, and then multiplies by{ 1, 1024, 1048576, 536870912 }. The rounded values are integers that fit in 10 bits, so the power-of-two multiply and the conversion are exact, and the existing masking and packing stay as they are. The largest value is still 3 * 2^29.Same class of problem as #357 (byte stores), which does not cover
XMStoreXDecN4. This branch does not changeDIRECTX_MATH_VERSION; #357 bumps it to 322, which would also cover this change if that is preferred.In a differential run over about 1,700 four-lane inputs, the ARM-NEON result now equals the no-intrinsics result on every input, and SSE2/SSE4.1/AVX2 on every input without a NaN (NaN inputs keep their current SSE result). No other function's output changed.
Test for
walbourn/directxmathtest, added at the end of Test190 (XMStoreXDecN4) inmath3/math3tests.cpp:Tested (math3, whole suite, Debug):
rounding 0: 56559566 ... 96659966, 1, 2) and passes after; no other failures.BUILD_NO_INTRINSICS: passes before and after.XMQuaternionLn) fails on this toolchain with or without the change..github/linters/.clang-format: no changes.Not tested: MSVC, Windows ARM64/ARM64EC, ARM32, and the DirectXTex/DirectXMesh suites.
AI disclosure: the fix, the test and this description were drafted with an AI coding assistant (Claude Code, claude-opus-5-5), which also found the bug with a cross-path differential harness and ran the builds above.