-
Notifications
You must be signed in to change notification settings - Fork 4
Scene Renderer
Scene wallpapers are drawn by wallpaper-scene-renderer — an open-source Vulkan renderer for Wallpaper Engine wallpapers on Linux. It is pulled in as a Git submodule of this plugin (src/) and built alongside the plugin.
Open source Vulkan scene renderer for Wallpaper Engine wallpapers on Linux.
Core capabilities:
- Vulkan 1.1
- Render graph for automatic pass dependencies
- HLSL-to-GLSL shader translation
- Async shader compilation with disk cache
The rest of this page is a feature checklist — useful when you're trying to figure out why a particular Workshop wallpaper doesn't look quite right.
- ✅ Image layers
-
autosize: trueresolves size from the first texture's sprite frame dimensions (ormapWidth/mapHeightfor non-sprite pictures). - Script-referenced layers stay in the main render graph even when initially invisible (so SceneScript visibility toggles actually render — e.g. dino_run's jump sprite).
- Dynamic-asset pool for
thisScene.createLayer(asset)— pre-allocated pool of hidden scene nodes perengine.registerAsset(path)for both image and particle assets. Burst particle FX rearm viaParticleSubSystem::Reseton each pool pop and auto-hide once all particles die.
-
- ✅ Composition / Fullscreen layers
- ✅ Text (static rasterization via FreeType)
- ✅ Text (dynamic via SceneScript — clocks, dates, counters)
- ✅ Sound objects (looping playback via miniaudio)
- ✅ Particle systems — see Particle System
- ✅ 3D models — see 3D Models
- ✅ Light objects (point lights, skylight)
Built-in effects work via the same shader pipeline — any effect that compiles to a valid fragment shader will render.
- ✅ Animation: Scroll, Spin, Shake, Pulse, Water Flow, Water Ripple, Water Waves, Foliage Sway, Swing, Twirl, Cloud Motion, Iris Movement
- ✅ Blur: Blur, Blur Precise, Motion Blur, Radial Blur
- ✅ Interactive: Cursor Ripple, Depth Parallax, X-Ray
- ✅ Color: Blend, Blend Gradient, Chromatic Aberration, Clouds, Color Key, Film Grain, Shimmer, Glitter, Tint, Opacity, VHS, Fire, Light Shafts, Nitro, Reflection, Water Caustics
- ✅ Distortion: Fisheye, Perspective, Refraction, Skew, Transform
- ✅ Enhancement: Edge Detection, God Rays, Local Contrast, Shine
- ❌ Simulation: Advanced Fluid Simulation
- ✅ ColorBlendMode (effectpassthrough for complex blend modes)
- ✅ Mouse position with delay (
g_PointerPosition) - ✅ Parallax (mouse-reactive layer offset)
- ✅ PBR lighting
- ✅ Global bloom
- ✅ Ping-pong FBO pipeline
- ✅ Per-effect visibility control
- ✅ HDR-capable effect rendering
- ✅ Orthographic (2D scenes)
- ✅ Perspective (3D scenes)
- ✅ Zoom
- ✅ Shake (sum-of-sinusoids)
- ✅ Path animation (keyframe interpolation)
- ✅ Parallax (mouse-reactive camera offset)
- ✅ Fade (transitions between camera paths)
- ✅ BGM playback (loop)
- ✅ Spectrum analysis (KissFFT — 16/32/64 bands, L+R channels)
- ✅ Shader uniforms (
g_AudioSpectrum{16,32,64}{Left,Right}, std140 padded) - ✅ System audio capture (PipeWire/PulseAudio monitor source)
- ✅ Playback tap fallback (when system capture unavailable)
- ✅ Runtime toggle between system capture and playback tap
- ✅ Random sound playback (random track selection + inter-track delay)
- ✅ Renderers: Sprite, Rope, Sprite Trail, Rope Trail
- ✅ Geometry shader renderers: GS Sprite, GS Rope, GS Sprite Trail
- ✅ Emitters: Box, Sphere — rate-based, burst, one-per-frame, periodic, duration-limited
- ✅ Initializers:
colorrandom,hsvcolorrandom,colorlist,lifetimerandom,sizerandom,alpharandom,velocityrandom,rotationrandom,angularvelocityrandom,turbulentvelocityrandom,positionoffsetrandom,box,sphere,remapinitialvalue,mapsequencebetweencontrolpoints,mapsequencearoundcontrolpoint,inheritcontrolpointvelocity,inheritinitialvaluefromevent - ✅ Operators:
movement,angularmovement,sizechange,alphafade,alphachange,colorchange,oscillatealpha,oscillatesize,oscillateposition,turbulence,vortex,vortex_v2,controlpointattract,maintaindistancebetweencontrolpoints,reducemovementnearcontrolpoint,remapvalue,controlpointforce,capvelocity,maintaindistancetocontrolpoint,inheritvaluefromevent,boids(Reynolds 1987 flocking) - ✅ Collision:
collisionplane,collisionsphere,collisionbox/collisionbounds,collisionquad,collisionmodel(with restitution) - ✅ Control Points (up to 8) — mouse-follow (
link_mouse), worldspace mode, per-instance offset overrides - ✅ Child particle systems (static, eventfollow, eventspawn, eventdeath)
- ✅ Instance overrides (color, alpha, brightness, count, lifetime, rate, speed, size)
- ✅ Sprite sheet animation (sequential / random frames)
- ✅ Perspective rendering flag
- ❌ Real-time particle lighting (sample SceneLights in particle shader)
- ✅ Audio-responsive emission — bass-band-driven emit-rate scaling (FFT bins 0..3 averaged, exponentially smoothed with fast attack / slow decay, ~0.4× at silence and ~2.0× at full).
- ✅ Multi-bone skeletal animation
- ✅ Play modes: Loop, Mirror, Single
- ✅ Animation blending (blend weights)
- ✅ Bone frame interpolation
- ✅ Animation layer system (visibility, rate)
- ✅ Per-vertex skinning (blend indices + weights)
- ❌ Blend shapes / shape animations
- ❌ Inverse kinematics
- ❌ Texture channel animations
- ❌ Interactive puppet warps (SceneScript-triggered)
- ❌ 3D perspective extrusion
- ✅ MDL format parsing (v13+, multi-submesh)
- ✅ Vertex attributes: position, normal, tangent, texcoord, texcoord1, blend indices/weights
- ✅ Per-submesh materials
- ✅ Camera path animation
- ✅ Depth occlusion & transparency
- ✅ Planar reflection (Y=0 mirror)
- ✅ Normal mapping
- ✅ Lightmap UVs (secondary UV channel)
- ✅ Point lights & skylight
- ✅ Skybox (spherical environment mapping)
- ✅ MSAA x4
- ✅ Anisotropic filtering (16x)
- ✅ HDR rendering (RGBA16F internal RTs)
- ✅ HDR texture formats (BC6H, RGBA16F, RG16F, R16F, RGB565, RGBA1010102)
- ❌ Shadow mapping
- ❌ HDR bloom post-process
- ❌ Spot lights
- ❌ Volumetric lighting
- ✅ Translucent (standard alpha)
- ✅ Translucent_PA (premultiplied alpha)
- ✅ Additive
- ✅ Opaque (screen blend for passthrough compose)
- ✅ Normal
- ✅ Disable (no blending)
- ✅ Complex colorBlendMode via effectpassthrough (BlendColor, etc.)
- ✅ BC1 (DXT1), BC2 (DXT3), BC3 (DXT5), BC7
- ✅ BC6H (HDR compressed)
- ✅ RGBA8, RGB8, RG8, R8
- ✅ RGBA16F, RG16F, R16F
- ✅ RGB565, RGBA1010102
- ✅ TEXB container (v1–v4)
- ✅ FreeImage formats (PNG, JPEG, TGA, BMP, GIF, DDS, PSD, HDR, EXR, etc.)
- ✅ Default values from
project.json - ✅ Runtime visibility bindings (conditional show/hide)
- ✅ Runtime uniform bindings (shader value overrides)
- ✅ Per-wallpaper config persistence (JSON)
- ✅ Combo, slider, color, bool, text property types
- ❌ User-defined custom shaders
- ✅ HLSL-to-GLSL translation (XSC-based)
- ✅ GLSL-to-SPIR-V compilation (glslang)
- ✅ Geometry shader support (VS+GS+FS linked compilation)
- ✅ Async compilation with disk caching
- ✅
#defineinjection (log10, etc.) - ✅ Implicit conversion fixes (
bool()wraps, cross-stage varying upgrades) - ✅ Effect alpha preservation (component-write shaders that omit
.a) - ✅
flatinterpolation qualifier support - ✅ 13 texture slots (
g_Texture0–g_Texture12) with resolution/rotation/translation metadata
The renderer implements most of Wallpaper Engine's authoring-time JavaScript API.
- ✅
engineglobal (IEngine) —registerAudioBuffers,screenResolution,canvasSize,timeOfDay,frametime,runtime,userProperties,setTimeout/setInterval, device detection - ✅
thisSceneglobal (IScene)- Scene property control (bloom, clear color, camera, lighting)
- ❌
bloomEnabledruntime toggle (requires render-graph rebuild)
- ❌
-
getLayer(name),enumerateLayers() -
createLayer(asset)/destroyLayer(layer)— pool-backed for image and particle assets - Camera transforms
- Scene property control (bloom, clear color, camera, lighting)
- ✅
thisLayerglobal (ILayer) — position, angles, scale, visibility, parent/child hierarchy (cycle-checked), animation-layer access - ✅
inputglobal — cursor position,cursorEnter/cursorLeave/cursorClick/cursorDown/cursorUp/cursorMove - ✅
sharedglobal (inter-script data sharing) - ✅ Events:
init,update,destroy,applyUserProperties,resizeScreen - ✅ Media integration (MPRIS D-Bus →
mediaPlaybackChanged,mediaPropertiesChanged,mediaThumbnailChanged,mediaTimelineChanged,mediaStatusChanged) - ✅
ILocalStorage(in-memory key-value store) - ✅ Text-layer dynamic content
- ✅ Color scripts (
colorScriptfield parsed, compiled, evaluated at 30 Hz) - ✅
WEMath,WEColor,WEVectorutility modules - ✅
Vec2,Vec3,Vec4factories (full method sets) - ✅
Mat3,Mat4matrix classes (translation/rotation/scale; view + projection helpers) - ✅
engine.colorScheme—Vec3from theschemecoloruser property - ✅
engine.openUserShortcut(name)— dispatched to MPRIS and to in-scenescene.on('userShortcut', …)listeners - ✅
getEffect(name).visibleruntime per-effect toggle (dirty-tracked) - ✅
scene.on(event, fn)/scene.off(event[, fn])— in-scene event bus covering the lifecycle, media, anduserShortcutevents
- ✅
IVideoTexture—thisLayer.getVideoTexture()returns a proxy withgetCurrentTime,setCurrentTime,duration,rate,play,pause,stop,isPlaying(mpv-backed decoder).
- ✅
IMaterial—thisLayer.getMaterial().setValue(name, value)/getValue(name); uniform writes go through a render-thread queue intocustomShader.constValues, dirty flag re-uploads on next frame. Reads are JS-side cached so they never race the renderer.
- ✅ Sound layer control (
enumerateLayers,play/stop/pause/isPlaying/volume) - ✅
console.logsupport (buffered →LOG_INFOflush)
Partly there. The renderer reads the keyframe tracks Wallpaper Engine stores on a layer's properties and plays them back every frame, so a scene that animates alpha, origin, scale, or angles with no script at all will still move. Loop, Mirror, and Single play modes all work, as do the relative flag (keyframe values are deltas off the layer's base value) and wraploop (the cycle interpolates back to the first keyframe instead of snapping).
Still missing:
- ❌ Bezier / Hermite easing — interpolation between keyframes is linear only, so eased motion looks a little stiff.
- ❌ Color and other non-transform property tracks.
- ❌ Auto-keyframing and script-fired animation events.
- ❌ RGB hardware lighting integration (OpenRGB / liquidctl)
- ❌
IParticleSystem/IParticleSystemInstanceSceneScript control
What it would take to finish each unchecked item above, roughly ordered by scope (smallest → largest).
-
bloomEnabledruntime toggle — bloom passes are baked into the render graph at scene load. Cheapest implementation is a "bypass mode" branch in the combine pass; the cleaner path is a graph rebuild on toggle. -
IParticleSystem/IParticleSystemInstance— expose a stablenodeId → ParticleSubSystemmap through__sceneBridge, then thread emission rate / pause / per-instance CP overrides through the existingParticleModifyqueue. -
User-defined
customshaderuser-properties — accept acustomshaderproperty type and swap the layer's shader pass at runtime through the async compile cache. - RGB hardware lighting — pure plumbing: forward audio-spectrum + dominant-color outputs to OpenRGB / liquidctl over D-Bus.
-
Real-time particle lighting — particles use a separate sprite/rope shader path that ignores
SceneLights. Add per-particle sampling of nearby point lights, broadcast positions via a small uniform array, reuse the PBR shader prelude. -
Spot lights — extend
SceneLight.hppwith cone angle, inner/outer falloff, and direction; add cone culling in shaders. -
HDR bloom post-process — the
combine_hdrvariant exists, but the per-mip bloom convolution chain that feeds it isn't built. - Puppet: blend shapes — linear morph-target blending. Adds vertex streams for shape deltas and per-shape weight uniforms.
- Puppet: 3D perspective extrusion — synthesize Z + normals on a 2D puppet so it picks up lighting and parallax.
- Puppet: texture channel animations — per-bone texcoord offsets, separate from skin matrices.
-
Interactive puppet warps (SceneScript-triggered) — expose Puppet bone manipulation through a
thisLayer.getPuppet()JS proxy. -
Timeline easing & extra tracks — keyframe playback already landed (linear interpolation, loop/mirror/single, parsed in
WPSceneParserand run through the dirty-update queue). What's left is bezier/Hermite easing so motion isn't dead-linear, color and other non-transform tracks, and the editor's auto-keyframing plus script-fired animation events.
- Shadow mapping — depth-only render pass per light, cascade for the skylight, PCF/PCSS sampler, integration into the PBR prelude.
- Volumetric lighting — ray-march from camera through participating media; pairs naturally with shadow mapping.
-
Advanced Fluid Simulation effect — the renderer has no compute pipeline today. Would need
VkComputePipelineplumbing, pressure/velocity field textures, and a Navier-Stokes solver split across passes. - Puppet: inverse kinematics — FABRIK/CCD per chain. Limited value until interactive puppet warps land.
The scene renderer is GPL-2.0, same as the plugin.
Repo · Scene Renderer · GPL-2.0