v0.19.0
EditLÖVR v0.19.0, codename Forbidden Jorts, was released on June 7th, 2026. This version has been 478 days in the making, with 684 commits from 11 authors.
The main highlights of this release are:
- Raytracing
- New Audio Spatializer
- Animated Controller Models
- HDR Color Output
- New Error Screen
- Async Task System
- Luau Support
- New Vector API
Contents
Raytracing
LÖVR supports GPU ray tracing now! Shaders can shoot rays into the world and get back hit info, using it to implement lighting effects like shadows or ambient occlusion. Here's a ray traced ambient occlusion effect:
A new Raytracer object holds a collection of Mesh and Model objects that the rays can hit.
Dynamic geometry and animated models are supported. Most Vulkan GPUs support the necessary ray
tracing features, even some mobile GPUs like the one in the Quest 3!
Spatial Audio
The audio module was almost completely rewritten for v0.19.0! SteamAudio is included by default now, along with a bunch of new features, better performance, and API improvements across the board.
SteamAudio
SteamAudio was previously available as an audio spatializer, but wasn't included by default because it was closed source and its file size was huge. However, SteamAudio was open sourced recently, so it can be shipped with LÖVR by default now! LÖVR also upgraded to the latest version of SteamAudio and exposed more of its features.
AudioMesh
There's a new AudioMesh object that defines the geometry that audio bounces off of in the scene,
influencing reverb and occlusion effects. Audio meshes replace the old lovr.audio.setGeometry
function. Compared to the old method, audio meshes can have multiple materials, and they can also
be added, removed, and repositioned dynamically.
Ambisonic Sounds
It's now possible to play ambisonic sounds from WAV files. These are 3D soundfields that support rotation, binaural spatialization, and reverb. They're a great way to add immersive 360 audio all around the listener using a single audio source, instead of creating, positioning, and mixing dozens of individual ones.
Improved Reverb
There are lots of new conf.lua options to configure the type of reverb, the max number of rays and
bounces used during ray tracing, and the max duration of the reverb tail. LÖVR also computes reverb
on a background thread, allows choosing between per-source and listener-centric reverb, and supports
changing the reverb volume for each Source independently.
Binaural Spatialization
lovr.audio.setHRTF let you specify a custom SOFA file to use for binaural audio spatialization.
Binaural audio spatialization is now a continuous value between 0 and 1 instead of a boolean. This makes it possible to smoothly transition between stereo audio and binaurally spatialized audio, useful for objects close to the listener.
Miscellaneous
- There's a new
AudioStreamobject, replacing the awkward "stream" mode for Sounds. - The Oculus Spatializer and the simple spatializer were removed, so projects don't need to worry about which spatializer is active anymore -- it's just always SteamAudio.
- It is no longer necessary to create an audio stream to get microphone input. LÖVR automatically
creates an internal stream for capture devices and exposes it via
lovr.audio.getStream, making it easier to get microphone input. - LÖVR double buffers changes to sources and commits them once per frame by default, so it is possible to atomically play several sources at once or modify their parameters without the audio thread running in the middle of the updates.
Controller Models
lovr.headset.newModel can load animated controller models now using the new XR_EXT_render_model
OpenXR extension. Calling lovr.headset.animate is all that's needed to animate the controller to
match the button state:
There's also a lovr.modelschanged event, called when new models are available or old ones are no
longer needed.
Microgestures
Quest added support for "microgestures" which are performed by swiping the thumb along the index finger. This is exposed as a virtual dpad on each hand.
HDR Displays
LÖVR has experimental support for HDR displays on Windows and macOS. Set t.graphics.hdr = true in
conf.lua to request an HDR10 window, and use lovr.graphics.isHDR to check if it was supported.
t.graphics.hdr = true. The one on
the right will only look correct on an HDR display.
Currently, LÖVR doesn't do any automatic color space conversion for you -- you are expected to write
PQ-encoded Rec2020 colors to the window texture. There are some new shader helpers like
linearToPQ and sRGBToRec2020 to help out with converting sRGB colors to HDR.
Error Screen
The menacing error screen has been upgraded into a fancy new stack trace explorer. It supports viewing source code and inspecting variables for entries in the stack trace. It works in VR too!
Multithreading
This version adds a new coroutine-based task system in the lovr.task module. Tasks make it easy
to run expensive work on background threads without blocking the main thread. This is useful for
things like asset loading, GPU readbacks, physics updates, map generation, or any other
CPU-intensive logic.
Tasks use the same Lua scope as the main thread, and their code looks synchronous despite running
work on background threads. They're more "ergonomic" than managing Thread and Channel objects.
Here's a simple example that loads a Model in the background and assigns it to the model
variable once it's ready (possibly several frames later):
lovr.task.start(function()
model = lovr.graphics.newModel('teapot.glb')
end)
See the Tasks guide for a full explanation of how it works.
Rendering
The graphics module is thread-safe now! Pass objects can be record draws on any thread, and
multiple Pass objects can be recorded in parallel. Also, graphics objects like Texture,
Model, and Shader can be created on any thread now.
Internals
LÖVR internally multithreads a few things too now, including:
- Shader compilation (sometimes also called "pipeline creation")
- MSDF glyph rasterization
- Reverb simulation
- Texture loading for models
Model Improvements
There were some important improvements to the Model API:
- Models use multiple threads to load their textures.
- LÖVR supports importing and rendering two sets of UVs, commonly used for lightmapping.
- Models free their CPU memory after loading, and no longer hold onto their
ModelData. - A new concept called a "part" has been added, which represents parts of a model's mesh that use
different materials.
Pass:drawPartis a nifty new function that draws a single mesh or a part of aModel. Since each part can be drawn individually, render states and shader variables can change in between each draw. - It's much easier to get the vertices in a ModelData, as they are converted to a consistent vertex format now.
ModelDataexposes the data in its blend shapes.
Luau
LÖVR supports Luau as an additional option for the Lua implementation to use, alongside PUC Lua and LuaJIT. Luau is very close to Lua 5.1, but comes with some nice benefits like:
- Syntax extensions, like compound assignment operators (
+=) - Type checking
- Builtin vector type
- Native code generation
Crucially, Luau has a mascot:

See the Luau guide for the full details about Luau support in LÖVR.
Vectors
The vector API has been reworked significantly, addressing several long-standing pain points:
- Vectors are immutable now. Functions that return vectors always return a new vector instead of modifying their input.
- Temporary vectors have been removed. Vectors are always "permanent" now, and it is no longer necessary to worry about temporary vector lifetimes.
- Vectors are tables instead of userdata now. This allows JIT compilation of code that uses vectors, and reduces C method call overhead. As a bonus, LÖVR functions that take vectors also accept any vector-shaped table.
- The new vector API is compatible with Luau's
vectortype.
There were some unavoidable breaking changes here. Briefly,
vec2andvec4were removed.- The
:set,:add,:sub,:mul, and:divmethods were removed (can use regular operators now, like =, +, -, *, /). - Operations like
:cross,:normalize, and:lerpreturn new vectors instead of mutating. - Swizzles were removed.
To ease porting, this gist implements the old vector API using tables.
See vector and quaternion for more details about the new API.
WebGPU
WebGPU is NOT supported yet, but there's been a lot of work on the WebGPU renderer recently and it's getting very close! Here, have a screenshot of LÖVR running in a browser, as a treat:

Other Stuff
Pass:setProjectionhas a more friendly API.Pass:getViewRayis a new function that returns a world-space ray for a given pixel. It's really useful for doing mouse picking withWorld:raycast.- The headset simulator logic was moved to Lua, customizable using
lovr.simulate. lovr.headset.getBatteryreturns controller battery level.Layer:setOriginlets you attach aLayerto a device, keeping it smoothly tracked independent of framerate.- LÖVR has Steam Frame controller bindings, including new dpad/bumper buttons.
- The GPU allocator is much smarter now, eliminating some situations where LÖVR would run out of VRAM due to fragmentation. Small LÖVR projects also use less VRAM now.
- The desktop window is centered by default.
- Fullscreen can be toggled at runtime with
lovr.system.setWindowFullscreen. lovr.system.setMouseModelets you enable/disable relative mouse mode.Curve:stepandCurve:getLengthare some very useful newCurvemethods.
Community
- dovuro released lovr-base-shading, an easy way to add lighting to projects
- immortalx released 3damp, lovr-sprite, and rubiks_cube!
- snowkittykira added TypeScript type definitions
- xiejiangzhi released line_mesh, which builds fancy line meshes with varying thickness, colors, and UVs
- bjornbytes added Luau type definitions
- LÖVR is featured on OpenXR Inventory, showing the OpenXR features LÖVR uses
- +KZ released a demo for Brain Evil: Dark World
- NVIDIA released a LÖVR plugin for CloudXR
- dovuro released iui, an immediate mode UI library that works with LÖVR and LÖVE
- frityet added Teal type definitions
- COSMORAMA Space Flight Simulator was announced
- dovuro released lovr-tga, a TGA image loader
Changelog
Add
General
- Add
lovr.taskmodule andlovr.taskreadycallback. - Add support for compiling with Luau as the Lua implementation, instead of Lua/LuaJIT.
- Add support for passing Luau
vectorandquaterniontypes to functions. - Add
vectorandquaternionmodules.
Audio
- Add
AudioMeshandlovr.audio.newAudioMesh. - Add support for ambisonic source playback.
- Add
lovr.audio.setHRTFto set a custom HRTF. - Add
Source:get/setAbsorption. - Add
Source:get/setCone. - Add
Source:get/setFalloff. - Add
Source:get/setOcclusion. - Add
Source:get/setReverb. - Add
Source:get/setSpatialization. - Add
lovr.audio.updateto atomically commit changes to audio sources. - Add
lovr.audio.getStream. - Add
t.audio.debugandt.audio.reverbsettings tolovr.conf.
Data
- Add
AudioStreamandlovr.data.newAudioStream. - Add
Blob:setI8/setU8/setI16/setU16/setI32/setU32/setF32/setF64. - Add
lovr.data.newBlobView. - Add
Model(Data):getNodeChild/getNodeSibling. - Add
Model(Data):getNodeMesh(replaces:getNodeMeshes). - Add
ModelData:getMeshBlendShapeCount/getMeshBlendShapeName/getBlendVertex. - Add
ModelData:getMeshPartCount. - Add
ModelData:getMeshDrawRange. - Add support for importing two sets of UVs in
ModelData. - Add
Sound:get/setFrame.
Graphics
- Add
Raytracer,lovr.graphics.newRaytracer, andraytracingGraphicsFeature. - Add
raytraceroption tolovr.graphics.newMeshandlovr.graphics.newModel. - Add
Mesh:buildRaytracerandModel:buildRaytracer. - Add
VertexUV2builtin vertex attribute andUV2shader variable, for lightmapping. - Add
t.graphics.hdrandlovr.graphics.isHDR. - Add
pqToLinear,linearToPQ,sRGBToRec2020, andrec2020ToSRGBshader helpers. - Add
Pass:getViewRay. - Add
Pass:setBlendState. - Add
Pass:drawPartto draw a single part of aModel. - Add
Model:meshesto iterate over model nodes with meshes. - Add
Model:is/setNodeVisible. - Add
lovr.graphics.getStatsfor GPU memory allocation statistics. - Add support for creating 3D texture views.
- Add
Mesh:get/setBaseVertex. - Add support for custom array strides in buffer formats.
- Add
bgra8TextureFormat. - Add
Buffer:newBlob.
Headset
- Add
lovr.modelschangedevent. - Add
lovr.headset.getModelKeys. - Add support for "foveated quad" and "mono" display configurations.
- Add
Layer:get/setOriginto lock Layer poses to a Device. - Add
dpup,dpdown,dpleft,dprightDeviceButton. - Add
bumperDeviceButton. - Add
bodyDevicefor full-body tracking inlovr.headset.getSkeleton. - Add
lovr.simulatecallback to customize the headset simulator. - Add support for Touch Pro controllers.
- Add
thumbrestHeadsetAxis, for pressure-sensitive thumbrests. - Add
t.headset.extensionsto enable extra OpenXR extensions. - Add
lovr.headset.connectandt.headset.connect. - Add
lovr.headset.setPosition/Orientation/Poseandlovr.headset.setButton.
Math
- Add
Curve:getLengthandCurve:step. - Add
Mat4:setPosition/Orientation/Scale/Pose.
Physics
- Add support for non-uniform scale in
ConvexShapeandMeshShape.
System
- Add
t.window.centered. - Add
lovr.system.is/setWindowFullscreen. - Add
lovr.system.get/setMouseMode.
Thread
- Add
lovr.thread.call. - Add
lovr.thread.getWorkerCount.
Change
- Change model loading to use multiple threads to load images.
- Change
Pass:setProjectionto accept aProjectionType. - Change
lovr.graphics.newModelto no longer retain itsModelData. - Change functions to accept tables for vector/quaternion arguments instead of userdata.
- Change
requireto have better errors when files/plugins aren't found. - Change default require path to also search for
.luaufiles, when using Luau. - Change
lovr.headset.newModelto also take a lightuserdata. - Change
lovr.headset.newModelto be async. - Change
lovr.headset.isTrackedto also take aModel. - Change
lovr.headset.getPosition/Orientation/Direction/Poseto also take aModel. - Change
TerrainShapeto supportnilheights (treated as holes). - Change
Curve:renderto no longer always return 2 points for curves with 2 control points. - Change
ModelData:getMeshVertexto return data in a consistent vertex format. - Change
ModelData:getMeshDrawMode/getMeshMaterialto take an optional part index. - Change
ModelData:getWidth/Height/Depth/Dimensions/BoundingBoxto take an optional mesh/part index. - Change
lovr.graphics.newBufferto also accept a Luauvectorfor the initial buffer data. - Change
Pass:transformto also take a pose (7 numbers for the position and orientation). - Change
Mat4to be a regular object instead of a vector type. - Change
lovr.graphics.submitto have better performance when called multiple times per frame. - Change
Pass:textand Font methods to also take nested tables of multicolor strings. - Change
lovr.audio.setDevicesink parameter to be abooleanor anAudioStream. - Change
lovr.audio.start/stopto return error messages on failure. - Change
lovr.audio.newSourceto create non-spatial sources by default. - Change
lovr.audio.newSourceto also accept anAudioStream. - Change audio device to start when playing a Source for the first time, instead of immediately.
- Change
lovr.data.newSoundto take a channel count instead of a channel layout. - Change
lovr.data.newSoundandlovr.audio.newSourceto be async. - Change
lovr.data.newImageandlovr.graphics.newTextureto be async. - Change
lovr.data.newModelDataandlovr.graphics.newModelto be async. - Change
lovr.graphics.compileShaderandlovr.graphics.newShaderto be async. - Change
Buffer:getDataandTexture:getPixelsto be async. - Change
lovr.timer.sleepto be async. - Change
Mesh:getVerticesandMesh:getIndicesto returnnilfor meshes withgpustorage. - Change
lovr.system.pollEventsto take an optional timeout.
Fix
- Fix
ConvexShapescale not working when created from a table of points. - Fix
ConvexShape:getPointto apply the shape's center of mass and scale. - Fix memory leak with
ConvexShapeandMeshShape. - Fix crash when creating invalid
ConvexShape/MeshShape/TerrainShape. - Fix issue where OBJ models loaded without materials would have inverted UVs.
- Fix issue where cube/array textures would only regenerate 1 mipmap.
- Fix crash in
Pass:sendwhen using tables to write nested structs to a uniform buffer. - Fix crash when drawing text on a pass without a canvas.
- Fix crash when using a 3D texture as a canvas texture.
- Fix validation message when clearing a 3D texture.
- Fix issue where normalized buffer fields were rounded incorrectly.
- Fix possible crash related to
Thread:wait. - Fix possible crash in Model animation.
- Fix
Collider:setDegreesOfFreedomto preserve mass/inertia. - Fix missing error in
CapsuleShape:setRadius/LengthandCylinderShape:setRadius/Length. - Fix issue where buffer memory wasn't recycled effectively.
- Fix thread stack traces to use the correct filename.
- Fix
t.headset.seated. - Fix multiple issues with GPU memory allocation.
- Fix
dtto use delta time fromlovr.timerwhen headset is idle. - Fix
lovr.filesystem.newFileto search all mounted archives properly. - Fix
Texture:setPixelswhen copying from anImagewith an offset. - Fix issue with
Shape:containsPoint. - Fix
Collider:moveKinematicwhen called on a kinematic collider. - Fix fixed foveated rendering on Quest 3.
- Fix
lovr.math.randomNormalreturning incorrect results after reseeding.
Deprecate
- Deprecate variant of
lovr.headset.newModelthat takes aDevice(use a model key). - Deprecate
Model(Data):getNodeChildren(use:getNodeChildand:getNodeSibling). - Deprecate base vertex argument in
Mesh:get/setDrawRange(useMesh:get/setBaseVertex). - Deprecate
Model:getVertexBuffer/getIndexBuffer/getMesh(create your own fromModelData). - Deprecate
lovr.math.drain(it does nothing now, since temporary vectors were removed). - Deprecate
lovr.math.vec2/vec3/vec4/quat/newVec2/newVec3/newVec4/newQuat(usevectorandquaternion). - Deprecate
lovr.math.mat4(uselovr.math.newMat4). - Deprecate
t.math.globals(usevectorandquaternion). - Deprecate
Layer:getPass(use your ownPass). - Deprecate ability to use zero for window dimensions in
lovr.conf(uset.window.fullscreen). - Deprecate variant of
Pass:setProjectionthat takes 4 numeric FOV angles (useProjectionType).
Remove
- Remove
animatedflag inlovr.headset.newModel(all models are animated now). - Remove support for creating
ConvexShapeandMeshShapefrom aModel(useModelData). - Remove support for passing a
Modeltolovr.audio.setGeometry(useModelData). - Remove
ModelData:getBlobCountandModelData:getBlob. - Remove
ModelData:getNodeMeshes(useModelData:getNodeMesh). - Remove
ModelData:getMeshVertexFormat/getMeshIndexFormat. - Remove
ModelData:getTriangleCount(useModelData:getMeshVertexCount/getMeshIndexCount). - Remove
ModelData:getTriangles(useModelData:getMeshVertex/getMeshIndex). - Remove
Model(Data):getBoundingSphere. - Remove
Model:getVertexCount(useModelData:getMeshVertexCount). - Remove
Model:getData(create and keep aModelDataaround as needed). - Remove userdata vector types (use tables, and the
vectorandquaternionmodules). - Remove
lovr.headset.getHandles(use FFI). - Remove
lovr.audio.getSpatializer(SteamAudio is always active). - Remove
lovr.audio.setGeometry(useAudioMesh). - Remove
lovr.audio.get/setAbsorption(useSource:get/setAbsorption). - Remove
Source:get/setDirectivity(useSource:get/setCone). - Remove
Source:is/setEffectEnabled(use new effect getters/setters). - Remove
Sound:getCapacityandSound:isStream(useAudioStream). - Remove
lovr.headset.getBoundsGeometry. - Remove
Buffer:mapData.







