Skip to content

Add XMMatrixInverseTranspose for optimal normal vector transformation - #336

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

Add XMMatrixInverseTranspose for optimal normal vector transformation#336
RohithPariki wants to merge 3 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

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 Fixed
@RohithPariki
RohithPariki force-pushed the feature/matrix-inverse-transpose branch from 3f9455d to cff3cc1 比较 August 4, 2026 21:07
@RohithPariki

RohithPariki commented Aug 4, 2026

Copy link
Copy Markdown
Author

Hi Chuck Walbourn (@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;
        
        // 新建 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;
    }
}

注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

项目

None yet

Development

Successfully merging this pull request may close these issues.

Add XMMatrixInverseTranspose

3 participants