Skip to content

Add XMMatrixInverseTranspose for optimal normal vector transformation - #336

Open
RohithPariki wants to merge 2 commits into
microsoft:mainfrom
RohithPariki:feature/matrix-inverse-transpose
Open

Add XMMatrixInverseTranspose for optimal normal vector transformation#336
RohithPariki wants to merge 2 commits into
microsoft:mainfrom
RohithPariki:feature/matrix-inverse-transpose

Conversation

@RohithPariki

Copy link
Copy Markdown

Fixes #9

Description

This PR introduces XMMatrixInverseTranspose, computing (M^-1)^T in a single pass. This is a common operation used for transforming surface normals, where developers previously had to chain XMMatrixTranspose(XMMatrixInverse(nullptr, M)).

Optimization Insight:
The standard XMMatrixInverse internally transposes the matrix first (MT = M^T) and then computes the cofactors of MT to produce adj(M^T) = adj(M)^T. Since the caller ultimately requests the transpose of the inverse, that final transpose cancels out the internal one.

XMMatrixInverseTranspose simply runs the cofactor algorithm directly on M (skipping the initial transpose block entirely) to produce adj(M) / det(M).

Savings:

  • Eliminates ~12 redundant _mm_shuffle_ps instructions on the _XM_SSE_INTRINSICS_ path.
  • Removes the initial matrix transpose on the scalar and NEON paths.

Verification

All paths (Scalar, NEON, and SSE) were successfully implemented and verified against chained calls (Transpose(Inverse(M))) for:

  • Identity Matrices
  • Pure Rotation Matrices (invT(R) == R)
  • Scaling Matrices
  • Arbitrary non-degenerate matrices (ensuring matrix values and determinant outputs match perfectly)
    i can share the test code , if needed.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@walbourn walbourn self-assigned this Aug 4, 2026
@walbourn

walbourn commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Do you have some test code for this? I can integrate it into https://github.com/walbourn/directxmathtest

Comment thread Inc/DirectXMathMatrix.inl

//------------------------------------------------------------------------------

inline XMMATRIX XM_CALLCONV XMMatrixInverseTranspose
@RohithPariki
RohithPariki force-pushed the feature/matrix-inverse-transpose branch from 3f9455d to cff3cc1 Compare August 4, 2026 21:07
@RohithPariki

RohithPariki commented Aug 4, 2026

Copy link
Copy Markdown
Author

Hi @walbourn,

Yes, absolutely! Here is the C++ test code I used to verify the new implementation against the chained XMMatrixTranspose(XMMatrixInverse(...)) method. It covers the identity matrix, pure rotations, scaling, and an arbitrary non-degenerate matrix to ensure numeric consistency across both the resulting matrix and the determinant.

Feel free to integrate this into directxmathtest!

#include <iostream>
#include <iomanip>
#include <cmath>
#include <cstdint>
#include <DirectXMath.h>

using namespace DirectX;

// Helper to compare matrices with epsilon tolerance
bool MatrixApproxEqual(FXMMATRIX A, CXMMATRIX B, float eps = 1e-4f) {
    for (int r = 0; r < 4; r++) {
        XMFLOAT4 a, b;
        XMStoreFloat4(&a, A.r[r]);
        XMStoreFloat4(&b, B.r[r]);
        if (fabsf(a.x - b.x) > eps || fabsf(a.y - b.y) > eps ||
            fabsf(a.z - b.z) > eps || fabsf(a.w - b.w) > eps)
            return false;
    }
    return true;
}

// Helper for beautiful printing
void PrintMatrix(const char* name, FXMMATRIX M) {
    std::cout << "  " << name << ":\n";
    for (int r = 0; r < 4; r++) {
        XMFLOAT4 row;
        XMStoreFloat4(&row, M.r[r]);
        std::cout << "    [ " 
                  << std::setw(8) << std::fixed << std::setprecision(4) << row.x << ", "
                  << std::setw(8) << std::fixed << std::setprecision(4) << row.y << ", "
                  << std::setw(8) << std::fixed << std::setprecision(4) << row.z << ", "
                  << std::setw(8) << std::fixed << std::setprecision(4) << row.w << " ]\n";
    }
}

int main() {
    std::cout << "========================================================\n";
    std::cout << " DirectXMath Unit Test: XMMatrixInverseTranspose\n";
    std::cout << "========================================================\n\n";

    bool allPassed = true;

    // Test 1: Identity Matrix
    {
        std::cout << "[Test 1] Identity Matrix (InvT(I) == I)\n";
        XMMATRIX I = XMMatrixIdentity();
        XMMATRIX result = XMMatrixInverseTranspose(nullptr, I);
        
        bool ok = MatrixApproxEqual(result, I);
        std::cout << "  -> Status: " << (ok ? "PASS" : "FAIL") << "\n\n";
        allPassed &= ok;
    }

    // Test 2: Rotation Matrix
    {
        std::cout << "[Test 2] Rotation Matrix\n";
        std::cout << "  (For pure rotation R, R^-1 = R^T, so (R^-1)^T = R)\n";
        
        // Rotate 45 degrees on Y axis
        XMMATRIX R = XMMatrixRotationY(XM_PIDIV4);
        XMMATRIX result = XMMatrixInverseTranspose(nullptr, R);
        
        bool ok = MatrixApproxEqual(result, R);
        if (!ok) {
            PrintMatrix("Expected (R)", R);
            PrintMatrix("Actual", result);
        }
        std::cout << "  -> Status: " << (ok ? "PASS" : "FAIL") << "\n\n";
        allPassed &= ok;
    }

    // Test 3: Scale Matrix
    {
        std::cout << "[Test 3] Scale Matrix\n";
        std::cout << "  (Scale(2, 3, 4)^-1^T == Scale(0.5, 0.33, 0.25))\n";
        
        XMMATRIX S  = XMMatrixScaling(2.0f, 3.0f, 4.0f);
        XMMATRIX expected = XMMatrixScaling(0.5f, 1.0f/3.0f, 0.25f);
        XMMATRIX result = XMMatrixInverseTranspose(nullptr, S);
        
        bool ok = MatrixApproxEqual(result, expected);
        if (!ok) {
            PrintMatrix("Expected", expected);
            PrintMatrix("Actual", result);
        }
        std::cout << "  -> Status: " << (ok ? "PASS" : "FAIL") << "\n\n";
        allPassed &= ok;
    }

    // Test 4: Arbitrary Matrix Consistency
    {
        std::cout << "[Test 4] Arbitrary Matrix Consistency\n";
        std::cout << "  (Ensures single-pass result exactly matches chained calls)\n";
        
        // Setup an arbitrary non-degenerate matrix
        XMMATRIX M = XMMatrixSet(
            2.0f,  3.0f, 1.0f, 0.0f,
            0.0f,  4.0f, 2.0f, 0.0f,
            1.0f, -1.0f, 3.0f, 0.0f,
            0.0f,  0.0f, 0.0f, 1.0f);

        XMVECTOR detDirect, detChained;
        
        // New optimized method
        XMMATRIX directResult = XMMatrixInverseTranspose(&detDirect, M);
        
        // Old chained method (what users used to do)
        XMMATRIX chainedResult = XMMatrixTranspose(XMMatrixInverse(&detChained, M));

        bool matOk = MatrixApproxEqual(directResult, chainedResult);
        
        XMFLOAT4 d1f, d2f;
        XMStoreFloat4(&d1f, detDirect);
        XMStoreFloat4(&d2f, detChained);
        bool detOk = fabsf(d1f.x - d2f.x) < 1e-4f;

        if (!matOk) {
            PrintMatrix("Direct Result", directResult);
            PrintMatrix("Chained Result", chainedResult);
        }

        std::cout << "  -> Matrix Match Status: " << (matOk ? "PASS" : "FAIL") << "\n";
        std::cout << "  -> Determinant Match Status: " << (detOk ? "PASS" : "FAIL") 
                  << " (Direct=" << d1f.x << ", Chained=" << d2f.x << ")\n\n";
        
        allPassed &= (matOk && detOk);
    }

    std::cout << "========================================================\n";
    if (allPassed) {
        std::cout << " ALL VERIFICATION TESTS PASSED SUCCESSFULLY! \n";
        std::cout << "========================================================\n";
        return 0;
    } else {
        std::cout << " SOME VERIFICATION TESTS FAILED! \n";
        std::cout << "========================================================\n";
        return 1;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add XMMatrixInverseTranspose

3 participants