Wallpapers for Windows
PrimoEngine also runs on Windows 11 (23H2 or newer, x64 and ARM64 PCs). The format is the same
.livewallpaper v1: a ZIP with manifest.json + content/. The Windows version is out in
beta: download it at https://engine.oprimo.dev.
In short: web and video wallpapers work on both systems with the same file. A
shader is one package per system: metal on the Mac, hlsl on Windows.
| Type | Mac | Windows |
|---|---|---|
web | ✅ | ✅ same file, same window.LiveWallpaper API |
video | ✅ | ✅ same file (Premium on both) |
metal | ✅ | ❌ refused: This wallpaper was made for Mac (Metal shader). Look for its Windows version. |
hlsl | ❌ (the Mac doesn’t know the type and rejects the manifest) | ✅ |
minMacOS is ignored on Windows.
Shaders on Windows: the hlsl type
An hlsl package is the twin of a metal package: same id, same config, its own entry.
With the same id, the app treats both as the same wallpaper and the settings keep their names. To
share, send two files, for example aurora-mac.livewallpaper and aurora-windows.livewallpaper.
manifest.json:
{
"schemaVersion": 1,
"id": "com.example.hello-shader",
"version": "1.0.0",
"title": "Hello Shader",
"author": { "handle": "@you" },
"type": "hlsl",
"entry": "content/shader.hlsl",
"config": [
{ "key": "speed", "type": "float", "min": 0.1, "max": 3.0, "default": 1.0, "label": "Speed" },
{ "key": "tint", "type": "color", "default": "#FFFFFF", "label": "Tint" }
],
"capabilities": { "network": [] }
}
content/shader.hlsl (it is hello-shader in HLSL; download it ready
as hello-shader-windows):
// Contract with PrimoEngine: keep this struct as-is (32 bytes, register b0).
struct Uniforms {
float2 resolution; // drawable size in pixels
float time; // seconds since the wallpaper started
float speed; // config key "speed" (default 1.0)
float4 tint; // config key "tint" as RGB 0..1 (a = 1)
};
cbuffer PrimoUniforms : register(b0) { Uniforms u; };
// Required: the pixel function, named exactly f_main.
float4 f_main(float4 fragCoord : SV_Position) : SV_Target {
float2 uv = fragCoord.xy / u.resolution; // 0..1, origin top-left
float t = u.time * u.speed;
float v = sin(uv.x * 8.0 + t) + sin(uv.y * 6.0 - t * 0.7)
+ sin(length(uv - 0.5) * 14.0 - t * 1.3);
float3 col = 0.5 + 0.5 * cos(6.28318 * (v * 0.2 + float3(0.0, 0.33, 0.67)));
return float4(col * u.tint.rgb, 1.0);
}
The contract
- The app compiles the shader with FXC as
ps_5_0(Direct3D 11) on import. - Pixel stage only. There is no
v_main: the app draws the full-screen triangle. - Entry
f_main, takingSV_Position(pixels, origin top-left, like Metal’s[[position]]) and returning onefloat4toSV_Target. Return the same colors the Metal twin returns. - Uniforms at
register(b0), at most 240 bytes. Declaring just the first 32 bytes (the struct above) is the norm. Settings reach the shader the same way they do on the Mac. - Optional: a
SamplerStateatregister(s0)(linear, clamp). - No
#include: there is no way to pull in another file. - A shader that loops forever stalls the GPU for ~2 s; Windows resets the driver and PrimoEngine quarantines the wallpaper. Keep loops short (~64 iterations per pixel).
Shaders that react to music
With "capabilities": { "audioReactive": true }, the shader gets a summary of the sound the PC is
playing, never the audio itself. The values are the Mac’s (see
Audio-reactive shaders); only the layout changes, because HLSL
pads arrays to 16 bytes:
struct AudioUniforms {
float4 bands4[8]; // band i = bands4[i >> 2][i & 3], 32 bands from 30 Hz to 16 kHz, 0..1
float4 levels; // x bass, y mid, z treble, w overall level
float beat, bassTime, levelTime, midTime;
float4 beatAges; // seconds since the last 4 beats, newest first; 9 = none
float active; // eases to 0 after ~2 s of silence
};
cbuffer PrimoAudio : register(b1) { AudioUniforms audio; };
Texture2D<float4> history : register(t0); // optional: 32 × 64, r channel, row 0 = now
float band(int i) { return audio.bands4[i >> 2][i & 3]; }
Read the history with history.Load(int3(band, row, 0)).r. Without music (and in thumbnails)
everything is 0 and beatAges is 9: design a good still look.
From Metal to HLSL
| Metal | HLSL |
|---|---|
#include <metal_stdlib>, using namespace metal;, v_main | delete |
fragment float4 f_main(float4 p [[position]], constant Uniforms& u [[buffer(0)]]) | cbuffer PrimoUniforms : register(b0) { Uniforms u; }; and float4 f_main(float4 p : SV_Position) : SV_Target |
constant AudioUniforms& audio [[buffer(1)]], audio.bands[i] | cbuffer PrimoAudio : register(b1), audio.bands4[i >> 2][i & 3] |
texture2d<float> history [[texture(0)]], history.read(uint2(x, y)).r | Texture2D<float4> history : register(t0), history.Load(int3(x, y, 0)).r |
fract, mix, rsqrt, atan2(y, x) | frac, lerp, rsqrt, atan2(y, x) |
fmod(x, y) | fmod(x, y) (same; GLSL’s mod is x - y * floor(x / y)) |
float2x2 m; m * v | mul(v, m) with the same constructor: the product order flips |
float3(1.0) | float3(1.0, 1.0, 1.0) or (float3)1.0 |
Check the result side by side with the Mac: colors and motion should match.
What Windows refuses on import
The app compiles and inspects every hlsl shader on import, wherever it came from:
| Message | What to do |
|---|---|
Shader is missing the required 'f_main' fragment function. | Name the pixel function f_main. |
Shader didn't compile: … | Fix the error shown. #include always fails. |
Shader rejected: the source is larger than 512 KB. | Make the file smaller. |
Shader rejected: … writable … | Remove writable resources (RWTexture2D, RWBuffer, structured buffers, atomics). |
Shader reads audio (register(b1)) but the manifest doesn't declare … | Add "capabilities": { "audioReactive": true }, or drop b1. |
Shader rejected: '…' is bound outside … | Use only b0, b1, t0 and s0. |
Shader rejected: the uniforms … are larger than … | b0 holds up to 240 bytes and b1 up to 192. |
File names
Windows refuses names the Mac accepts, with Corrupt archive: unsafe path ’…’. Avoid them in any package meant for both:
\and:anywhere, and< > " | ? *;- empty folders in a path (
a//b) and names ending in.or a space; CON,PRN,AUX,NUL,COM1toCOM9,LPT1toLPT9as a name, with or without an extension (for examplecontent/nul.txt).
Pack and test
python3 pack.py <folder> accepts type: "hlsl": it checks the manifest, f_main, the registers
and the forbidden resources, and writes the .livewallpaper. It does not compile HLSL; the app does
that on import. In PrimoEngine for Windows, import with the Import button or by double-clicking
the file.
Windows doesn’t ship Python: install Python 3 from the Microsoft Store (or python.org) and run
py pack.py <folder>.
To have an AI write one, use the HLSL shader prompt.