Library / SDK
redorav/hlslpp avatar
redorav/hlslpp

HLSL++: HLSL syntax and portable SIMD math in C++

Math library using HLSL syntax with multiplatform SIMD support

1,162 stars76 forksC++MIT

At a glance

What is it?
HLSL++ is a header only C++ math library that speaks HLSL, so the same vector, matrix and swizzling expressions compile on the CPU and read like the shader code next to them. It carries SSE, AVX, NEON, WebAssembly and scalar backends, adds things HLSL does not have such as quaternions, float8 vectors and constant buffer aware interop types, and is aimed squarely at game developers who keep both sides of the bridge in their heads.
Who is it for?
Adopt HLSL++ if you maintain a codebase where CPU side math and shader code have to agree, and if the compile time cost of the single header include is acceptable for your iteration loop. Do not adopt it for a project that has no shaders, and do not adopt it if you need arbitrary precision maths or a full linear algebra library, because the scope is deliberately narrow.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One syntax for the CPU side and the shader side

HLSL++ is described in the README as a header-only math library for C++ with the same syntax as the HLSL shading language. It features swizzling and all the operators and functions from the HLSL documentation.

The stated purpose is to ease the C++ to shader bridge by providing common syntax, and the audience is mainly game developers, although the README notes it can be used for any application requiring fast, portable math.

That bridge is a real daily problem in a game engine. The same concept, a direction, a colour, a transform, appears in a shader and in the code that feeds it, and in two languages where the types do not match. Writing float4 in the shader and a custom struct plus accessor functions on the CPU side means the two representations drift, and the drift shows up as a rendering bug that is hard to read. HLSL++ makes the CPU side use the same vocabulary.

The example makes the point concretely:

hlsl
// Native types
float4 foo4 = float4(1, 2, 3, 4);

// Swizzling
float3 bar3 = foo4.xzy;

// HLSL functions
float2 logFoo2 = log(bar3.xz);

// Swizzle of swizzle
foo4.wx = logFoo2.yx;

Swizzling is the part that has no C++ equivalent, and the assignment through a swizzle of a swizzle is the kind of expression that would otherwise need a temporary and a copy. The library also adds a visualisation aid for the debugger, with natvis files provided for Visual Studio debugging allowing you to see both vectors and the result of the swizzling in the debugging window in a programmer-friendly way.

The licence is MIT, the last push was on 2026-10-03, and the recent releases are 3.7 from 2025-07-04, 3.8 from 2025-12-06 and 3.9 from 2026-05-16.

What it adds on top of HLSL itself

The README is explicit that the library adds functionality HLSL does not natively provide, and the feature list backs that up.

The additions are convenient matrix functions, quaternions, data packing functions and extended vectors such as float8, that is an 8-component float, that take advantage of wide SIMD registers. The matrix helpers are the most immediately useful in a game project, because the example shows the construction style:

hlsl
frustum cameraFrustum = frustum::field_of_view_y(fovYRadians, aspectRatio, nearPlane, farPlane);
float4x4 projectionMatrix = float4x4::perspective(projection(cameraFrustum, zclip::zero, zdirection::reverse, zplane::finite));

float4x4 identityMatrix = float4x4::identity();

float4x4 rotationAxisMatrix = float4x4::rotation_axis(axis, angleRadians);

A frustum is built from named parameters, converted into a projection matrix, and a rotation is expressed as an axis and an angle. None of that is in HLSL, and all of it is the kind of thing a game engine writes once and every project rewrites badly.

The data packing functions are the other addition worth calling out, because they are the bridge to graphics APIs:

hlsl
uint rgba8Packed     = pack_float4_rgba8_unorm(foo4);
float4 rgba8Unpacked = unpack_rgba8_unorm_float4(rgba8Packed);

The type list is broad. There are float1 through float4 and float8, int1 through int4, uint1 through uint4, double1 through double4, floatNxM matrices and quaternion. The operators cover addition, multiplication, subtraction and division for all vector and matrix types, with per-component comparison operators, and the README notes the absence of a ternary operator because overloading is disallowed in C++.

The function list is long and is essentially the HLSL function set, from abs, acos and all through saturate, select, smoothstep and trunc, with the bit functions countbits, firstbithigh, firstbitlow and reversebits for integers, plus copysign as an addition, and determinant, transpose, inverse and adjoint for matrices, and matrix multiplication for all NxM matrices.

The scalar fallback, and the switch that forces it

The requirements section is short and has one important nuance.

The only required features are a C++ compiler supporting anonymous unions, and SIMD extensions depending on your target platform, that is SSE, NEON or WASM. If your target platform does not have SIMD support, it can also fall back to a scalar implementation. As a curiosity the README notes, it also includes an Xbox 360 implementation.

The fallback is the part that makes the library portable, and the switch that controls it is worth knowing. To force the scalar version of the library, define HLSLPP_SCALAR globally.

The README explains the difference in one sentence. The scalar library is only different from the SIMD version in its use of regular floats to represent vectors, and it should only be used if your platform, for example an embedded one, does not have native SIMD support. It can also be used to compare performance.

So the library is not locked to a SIMD target, and an embedded platform without NEON or SSE can still use the same syntax. The cost is that the scalar path is a different implementation underneath, and the two are not interchangeable in performance terms, which is exactly why the flag exists as a global define rather than as a runtime option.

The platform list in the README gives the deployment targets in a way that is worth reading literally, because it names devices rather than instruction sets alone. SSE, AVX, AVX2 and AVX512 cover x86 and x64 devices such as a PC, an Intel Mac, a PS4 or 5, and an Xbox One or Series. NEON covers ARM devices such as Android, Mac M1, iOS and the Switch. And WASM is listed on its own.

Three ways to include it, with a compile time trade off

The how to use section is about the include strategy, and it gives three options that map onto three different situations.

cpp
// The quickest way, expensive in compile times but good for fast iteration
#include "hlsl++.h"

// If you care about compile times in your cpp files
#include "hlsl++/vector_float.h"
#include "hlsl++/matrix_float.h"

// If you only need type information (e.g. in header files) and don't use any functions
#include "hlsl++/vector_float_type.h"
#include "hlsl++/quaternion_type.h"

The single header pulls in everything, which is convenient and slow to compile. The per-type headers compile faster. The type-only headers give you the declarations without the function bodies, which is what you want in a header file that many translation units include, so that the definitions live in one place.

There are four notes attached to these instructions, and every one of them is a trap somebody has already hit.

Remember to add an include path to include. The README also notes that the include structure changed in 3.9 to remove prefixes and move towards a sensible folder hierarchy, and that this note will be removed in 4.0, which means the current arrangement is transitional.

On Windows, the library defines min and max, so if you are using this library and the windows.h header, remember to define NOMINMAX before including it.

The [] operators make use of the union directly, so the generated code is up to the compiler, and the README says to use with care.

And the individual swizzle members, such as .x and .y, have the & operator overridden to take the address of the individual component, which the README calls very useful to pass to libraries that expect data pointers like imgui.

The repository layout supports the multi platform story: there are Linux.sh and Xcode.sh scripts, batch files for Visual Studio 2010, 2015, 2017, 2019 and 2022, a premake/ directory and a separate premake-xbox360/ one, a hlslpp-config.cmake for CMake based projects, unit_tests/ and a hlsl++.lua file, which is a Lua binding for the library.

The interop namespace exists because SIMD vectors do not pack like a struct

The interoperability section is short but it contains the most technical point in the README, and it is about layout rather than arithmetic.

Since HLSL++'s internal types are SIMD vectors, care has to be taken when uploading or copying the memory around for systems that expect different data layouts. Shader metadata can be used to create C++ structs to make it convenient to upload to the GPU, so a subset of HLSL++ can be declared in a way that is compatible with constant buffers. The interop namespace provides alternate classes that can copy from the SIMD vectors, and they do not assume anything about alignment.

cpp
struct alignas(16) MyData
{
  hlslpp::interop::float4 data0;
  hlslpp::interop::float3 data1;
  float data2;
  hlslpp::interop::float3x4 data3;
};

A struct declared this way will work correctly between C++ and shaders, as the packing rules are compatible, whereas with the non-interop versions of these classes this would have been an error. The README adds the standard advice, to be careful to add the necessary padding between types and align as necessary, and notes that a reflection system should be able to align structs appropriately, for example for constant buffers, and ensure sizes are respected. It links a visualizer for HLSL constant buffer layout rules for the details.

The practical takeaway is that the ordinary types are optimised for computation and the interop types are optimised for transfer, and picking the wrong one produces a struct that is silently misaligned. The error would be a rendering artefact, not a compile error, which is why the interop path is worth the extra include.

Editorial conclusion

Adopt HLSL++ if you maintain a codebase where CPU side math and shader code have to agree, and if the compile time cost of the single header include is acceptable for your iteration loop. Do not adopt it for a project that has no shaders, and do not adopt it if you need arbitrary precision maths or a full linear algebra library, because the scope is deliberately narrow. Verify first that the SIMD backend you need is the one your target actually has, that the include path change in 3.9 has not broken your build, and that the interop types match the layout your shader compiler expects for constant buffers.

Frequently asked questions

What is HLSL++ and who is it for?

It is a header-only C++ math library with the same syntax as the HLSL shading language, featuring swizzling and the operators and functions from the HLSL documentation. The README says it is aimed mainly at game developers, to ease the C++ to shader bridge, but can be used for any application requiring fast, portable math.

Which platforms does HLSL++ support?

SSE, AVX, AVX2 and AVX512 on x86 and x64 devices such as PCs, Intel Macs, PS4 and 5 and Xbox One and Series, NEON on ARM devices such as Android, Mac M1, iOS and the Switch, and WASM. There is also a scalar fallback for platforms without SIMD and an Xbox 360 implementation.

How do I force the scalar version of HLSL++?

Define HLSLPP_SCALAR globally. The scalar library differs from the SIMD version only in using regular floats to represent vectors, and the README says it should only be used where the platform has no native SIMD support, for example an embedded one, though it can also be used to compare performance.

What does the interop namespace in HLSL++ do?

Because the internal types are SIMD vectors, the interop namespace provides alternate classes that can copy from them without assuming anything about alignment, so a struct declared with them is compatible with constant buffer layouts. The README shows an alignas(16) struct built from hlslpp::interop::float4 and similar types.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. redorav/hlslpp on GitHub
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/redorav-hlslpp.svg)](https://hysenlabs.com/projects/redorav-hlslpp)

Was this useful?