v0.17.0 === LÖVR v0.17.0, codename **Tritium Gourmet**, was released on October 14th, 2023. This version has been 364 days in the making, with 555 commits from 9 authors. This release includes tons of bugfixes and usability improvements for the new graphics module, along with the following new features: - Passthrough, enabling mixed reality experiences - `TerrainShape`, for 3D heightfields in physics simulations - Blend shapes, for facial animation and making meshes squishy - HTTP, with support for HTTPS - Frustum culling, a quick way to improve rendering performance - Rounded rectangles with thickness, great for VR UI - Mouse input This is a massive release with lots to chew on, so let's break things down into atomic, bite-sized pieces! Passthrough --- With passthrough, you can now layer a view of the real world underneath whatever your project renders. This is great for mixed reality experiences, or just as a way to avoid tripping over furniture and punching walls. To enable it, just call `lovr.headset.setPassthrough(true)`. LÖVR also sets OpenXR blend modes now, which means projects will render properly on AR devices like the Magic Leap and HoloLens. Blend Shapes --- `Model` now supports blend shapes! These are often used for facial animation or other types of mesh squishing, which is difficult to implement with skeletal animation. `Model:setBlendShapeWeight` sets the weight of a blend shape, and weights can be animated with keyframe animations as well. They also use compute shaders instead of vertex shaders. This means: - There's no limit on the number of active blend shapes - They won't be evaluated multiple times when there are multiple cameras, or when a model is drawn multiple times in the same pose - Extra style points Roundrect --- Thick rounded rectangles are a very common shape to use for UI in VR. Previously we all had to generate meshes, import them as models, use SDF shaders, or piece them together with cylinders and boxes. Now, `Pass:roundrect` is built in! A virtual keyboard made of rounded rectangles Pictured above is [chui](https://github.com/jmiskovic/chui), a UI library made entirely of these rounded rectangles. TerrainShape --- `TerrainShape` is a new physics shape that lets you add heightfields to physics simulations. Terrain can be provided as an `Image`, or as a Lua function for procedural terrain. Also, if you need a simple ground plane, you can just pass a size to get flat terrain. It's much more convenient than messing around with a box collider. Frustum Culling --- Frustum culling is an optimization that skips rendering objects that are out of view. For 3D scenes with content surrounding the player, this is a quick way to reduce GPU overhead, especially when objects have lots of vertices. Frustum culling can be enabled using `Pass:setViewCull`. Any object with a bounding box will be culled against the cameras, including `Model` objects and most shape primitives. `Mesh` objects can compute their bounding boxes with `Mesh:computeBoundingBox`. Plugins --- There are 2 new builtin plugins and a new module: - The `http` plugin does HTTP requests, with support for HTTPS. - The `enet` plugin is a UDP networking library (back as a plugin). - The `utf8` module backports the `utf8` library from Lua 5.3. The cool part about having these as plugins is that they are 100% optional -- you can delete the library files if you don't need them and LÖVR will still work fine. Universal APK --- Previously, APK downloads were only compatible with the Oculus Quest. In v0.17.0 LÖVR switched to a "universal" APK system where multiple OpenXR loaders are bundled in a single APK. As a result, APKs will now work on most if not all Android-based headsets. This does increase file size a bit, but it should be alleviated as more vendors converge on the standard OpenXR loader. Buffer Format Improvements --- There's a handy new `Shader:getBufferFormat` method which will parse the format of a buffer from shader code. This means you don't need to type out the buffer's format again in Lua and keep it in sync with the shader code: format = shader:getBufferFormat('mybuffer') buffer = lovr.graphics.newBuffer(format, data) Buffer formats and shader constants now support nested structure and array types. Buffer fields can also have names, and buffer data can be given as key-value pairs instead of only lists of numbers. Finally, you can also send a table directly to a uniform buffer variable instead of needing to create a buffer first. pass:send('lightData', { position = vec3(x, y, z), color = 0xffffee }) Graphics Improvements --- The graphics module has been streamlined a bit as we shake out the new Vulkan renderer. ### Temporary Objects Temporary Buffer/Pass objects were really tricky due to the way they got invalidated whenever `lovr.graphics.submit` was called: pass = lovr.graphics.getPass('render', canvas) -- do stuff with pass lovr.graphics.submit(pass) pass:cube() --> Error! Can't use the pass after it's submitted! This version, `lovr.graphics.getBuffer` and `lovr.graphics.getPass` have been deprecated and replaced by `lovr.graphics.newBuffer` and `lovr.graphics.newPass`. These "permanent" types behave like all other objects, and you can call `lovr.graphics.submit` without messing them up. For passes, instead of getting a new one every frame, you can create it once and call `Pass:reset` at the beginning of a frame to reset it to a fresh state. There's also the option of recording its draws once and submitting it over and over again, to reduce the Lua overhead of recording draws. ### Pass Types Passes no longer have a "type" that defines what commands can be recorded on them. Instead, all `Pass` objects can receive both graphics and compute work, with computes running before the draws whenever the pass is submitted. For transfers, these methods have been moved onto `Buffer` and `Texture` objects themselves. So if you want to change the data in a Buffer, call `Buffer:setData`. Uploading to a Texture is `Texture:setPixels`. There's no need for a dedicated transfer pass. The transfers happen in order and finish before subsequent graphics submissions. -- Old local pass = lovr.graphics.getPass('transfer') pass:copy(image, texture) lovr.graphics.submit(pass) -- New texture:setPixels(image) ### Mesh The `Mesh` object has returned! This is mostly a convenience object that holds a vertex and index buffer, a `Material`, draw range, and bounding box. `Pass:mesh` still exists as a lower-level way to draw meshes with `Buffer` objects. Compute Barriers --- This is a small change, but there's a new `Pass:barrier` function that lets you sequence multiple compute shader dispatches within a pass. Since computes within a pass all ran at the same time, you previously had to use multiple Pass objects to get computes to wait for each other, which is costly. With `Pass:barrier`, all computes before the barrier will finish before further compute work starts. pass:setShader(computer) pass:compute() pass:compute() pass:barrier() pass:compute() --> this compute will wait for the first two Headless VR --- The headset module can now be used in headless mode (spooky!). This means it will still work even when the graphics module is disabled. The intended use case is for console applications that don't need to render anything but still want to use pose data. Note that this only works on certain XR runtimes -- currently monado and SteamVR are known to work. New Headset Simulator --- The headset simulator incorporated changes from the [`lovr-mousearm`](https://github.com/jmiskovic/lovr-mousearm) library. The virtual hand is now placed at the projected mouse position, and the scroll wheel can be used to control the hand distance. The shift key can also be used to "sprint", which is great for moving through large worlds. Mouse Input --- Mouse input has been added to `lovr.system`. You'll find the following new methods and callbacks: - `lovr.system.getMouseX` - `lovr.system.getMouseY` - `lovr.system.getMousePosition` - `lovr.system.isMouseDown` - `lovr.mousepressed` - `lovr.mousereleased` - `lovr.mousemoved` - `lovr.wheelmoved` You might not need the `lovr-mouse` library anymore! Tally Changes --- Recording GPU timing info is now as simple as calling `lovr.graphics.setTimingEnabled`. Stats will be made available via `Pass:getStats`, with a frame or two of delay. Timing stats are also active by default when `t.graphics.debug` is set. Pixel tallies (occlusion queries) also have a new revamped API. Instead of using a `Tally` object and a transfer pass, `Pass:beginTally` and `Pass:finishTally` will start and stop an occlusion query. The results for tallies can be copied to a Buffer after the Pass with `Pass:setTallyBuffer`. Quality of Life --- There are also a bunch of small improvements worth mentioning. Drawing a texture on a plane no longer requires a call to `Pass:setMaterial`. Instead, `Pass:draw` can take a `Texture`: -- Old pass:setMaterial(texture) pass:plane(x, y, z, w, h) pass:setMaterial() -- New pass:draw(texture, x, y, z, w) All objects now have the `Object:type` method to return their type name as a string. There's a new `Image:mapPixel` function which is an easier way to set all the pixels of an Image. Shaders now support `#include` to load shader code from LÖVR's filesystem. When `t.graphics.debug` is set, shaders include debug info now. This allows graphics debugging tools like RenderDoc to inspect variables in a shader and step through each line interactively. Vectors have capitalized constructors to create permanent vectors, e.g. `Vec3` can be used in addition to `vec3`. Vectors also have named constants: `vec3.up`, `vec4.one`, etc. Physics has `World:queryBox` and `World:querySphere` to query all the Shapes that intersect a volume. Community --- LÖVR's Slack is now deprecated because Slack held our messages hostage! We migrated all the chat history over to a [Matrix](https://matrix.org) homeserver hosted on `#community:lovr.org` and bridged everything to a new Discord server. Keeping the source of truth for chat on a self-hosted, open source platform ensures the community is hopefully a bit more resilient to future corporate monkey business. Speaking of corporate monkey business, LÖVR has entrenched itself further into the GitHub ecosystem by adding continuous builds via GitHub Actions! This means us mere mortals can have up-to-date builds for all platforms without touching CMake or the Android SDK. For the remaining masochists among us who choose to build LÖVR from source, there has been a change to the branching system. The `dev` branch is now the default branch, and `master` has been renamed to `stable`. The development workflow otherwise remains the same, where new features go into `dev` and bugfixes are made on `stable`. The following commands will update a local clone: $ git branch -m master stable $ git fetch origin $ git branch -u origin/stable stable $ git remote set-head origin -a Changelog --- ### Add #### General - Add `enet` plugin. - Add `http` plugin. - Add `utf8` module. - Add `:type` method to all objects and vectors. - Add `--graphics-debug` command line option, which sets `t.graphics.debug` to true. #### Audio - Add `lovr.audio.getDevice`. #### Data - Add `Image:mapPixel`. - Add a variant of `Blob:getString` that takes a byte range. - Add `Blob:getI8/getU8/getI16/getU16/getI32/getU32/getF32/getF64`. #### Filesystem - Add argument to `lovr.filesystem.load` to restrict chunks to text/binary. #### Graphics - Add `Pass:roundrect`. - Add variant of `Pass:cone` that takes two `vec3` endpoints. - Add variant of `Pass:draw` that takes a `Texture`. - Add `Pass:setViewCull` to enable frustum culling. - Add `Model:getBlendShapeWeight`, and `Model:setBlendShapeWeight`. - Add `Model(Data):getBlendShapeCount`, `Model(Data):getBlendShapeName`. - Add support for animated blend shape weights in Model animations. - Add support for arrays, nested structs, and field names to Buffer formats. - Add `Shader:getBufferFormat`. - Add back `Mesh` object (sorry!). - Add `Model:clone`. - Add `materials` flag to `lovr.graphics.newModel`. - Add `lovr.graphics.isInitialized` (mostly internal). - Add support for using depth textures in multisampled render passes. - Add `depthResolve` feature to `lovr.graphics.getFeatures`. - Add a variant of `Texture:newView` that creates a 2D slice of a layer/mipmap of a texture. - Add a variant of `Pass:send` that takes tables for uniform buffers. - Add `Buffer:getData` and `Buffer:newReadback`. - Add `Texture:getPixels/setPixels/clear/newReadback/generateMipmaps`. - Add support for stepping through shaders in e.g. RenderDoc when `t.graphics.debug` is set. - Add `lovr.graphics.is/setTimingEnabled` to record GPU durations for each Pass object. - Add `lovr.graphics.newPass`. - Add `Pass:setCanvas` and `Pass:setClear`. - Add `Pass:getStats`. - Add `Pass:reset`. - Add `Pass:getScissor` and `Pass:getViewport`. - Add `Pass:barrier`. - Add `Pass:beginTally/finishTally` and `Pass:get/setTallyBuffer`. - Add support for `#include` in shader code. #### Headset - Add `lovr.headset.getPassthrough/setPassthrough/getPassthroughModes`. - Add support for more headsets in Android APKs (Quest, Pico, Vive Focus all work). - Add support for controller input on Pico Neo 3 and Pico Neo 4 devices. - Add support for controller input on Magic Leap 2. - Add `hand/*/pinch`, `hand/*/poke`, and `hand/*/grip` Devices. - Add support for using `lovr.headset` when `lovr.graphics` is disabled, on supported runtimes. - Add support for `elbow/left` and `elbow/right` poses on Ultraleap hand tracking. - Add `lovr.headset.getDirection`. - Add `lovr.headset.isVisible` and `lovr.visible` callback. - Add `lovr.recenter` callback. - Add `floor` Device. - Add `t.headset.seated` and `lovr.headset.isSeated`. - Add `lovr.headset.stopVibration`. - Add `radius` fields to joint tables returned by `lovr.headset.getSkeleton`. - Add support for "sprinting" in the headset simulator using the shift key. #### Math - Add capitalized globals for creating permanent vectors (`Vec2`, `Vec3`, `Vec4`, `Quat`, `Mat4`). - Add `Vec3:transform`, `Vec4:transform`, and `Vec3:rotate`. - Add vector constants (`vec3.up`, `vec2.one`, `quat.identity`, etc.). - Add `Mat4:getTranslation/getRotation/getScale/getPose`. - Add variant of `Vec3:set` that takes a `Quat`. - Add `Mat4:reflect`. #### Physics - Add `TerrainShape`. - Add `World:queryBox` and `World:querySphere`. - Add `World:getTags`. - Add `Shape:get/setPose`. - Add missing `lovr.physics.newMeshShape` function. - Add `Collider:isDestroyed`. #### System - Add `lovr.system.wasKeyPressed/wasKeyReleased`. - Add `lovr.system.getMouseX`, `lovr.system.getMouseY`, and `lovr.system.getMousePosition`. - Add `lovr.system.isMouseDown`. - Add `lovr.mousepressed`, `lovr.mousereleased`, `lovr.mousemoved`, and `lovr.wheelmoved` callbacks. - Add `lovr.system.has/setKeyRepeat`. #### Thread - Add support for vectors and lightuserdata with `Channel:push` and `Channel:pop`. ### Change - Change `lovr.graphics.submit` to no longer invalidate Pass objects. - Change vector constructors to be callable metatables (allows adding custom methods). - Change max size of vector pool to hold 16 million numbers instead of 64 thousand. - Change `Pass:setBlendMode`/`Pass:setColorWrite` to take an optional attachment index. - Change OpenXR driver to throttle update loop when headset session is idle. - Change `lovr.graphics.newModel` to work when the asset references paths starting with `./`. - Change `lovr.graphics.newModel` to error when the asset references paths starting with `/`. - Change `lovr.graphics.newBuffer` to take format first instead of length/data. - Change `Pass:setStencilWrite` to only set the "stencil pass" action when given a single action, instead of all 3. - Change `lovr.graphics` to show a message box on Windows when Vulkan isn't supported. - Change plugin loader to call `JNI_OnLoad` on Android so plugins can use JNI. - Change `t.headset.overlay` to also support numeric values to control overlay sort order. - Change `World:isCollisionEnabledBetween` to take `nil`s, which act as wildcard tags. - Change `Mat4:__tostring` to print matrix components. - Change `World:raycast` to prevent subsequent checks when the callback returns `false`. - Change `lovr.system.isKeyDown` to take multiple keys. - Change `lovr.headset.isDown/isTouched` to return nil instead of nothing. - Change `lovr.headset.getTime` to always start at 0 to avoid precision issues. - Change Pass viewport and scissor to apply to all draws in the Pass, instead of per-draw. - Change nogame/error screen to use a transparent background on AR/passthrough headsets. - Change VR simulator to use projected mouse position for hand pose (scroll controls distance). - Change `lovr.headset.getDriver` to also return the OpenXR runtime name. - Change `pitchable` flag in `lovr.audio.newSource` to default to true. - Change `Buffer:setData`, `Buffer:clear`, and `Buffer:getPointer` to work on permanent Buffers. - Change `lovr.timer.sleep` to have higher precision on Windows. - Change `lovr.headset.animate` to no longer require a `Device` argument (current variant is deprecated). ### Fix - Fix `lovr.physics.newBoxShape` always creating a cube. - Fix several issues related to VRAM leaks when creating Textures. - Fix issue on Linux where the Vulkan library wouldn't get loaded properly. - Fix macOS to not require the Vulkan SDK to be installed. - Fix recentering on Quest. - Fix headset mirror window to properly render in mono when a VR headset is connected. - Fix window size not updating on resize or on highdpi displays. - Fix `MeshShape` not working properly with some OBJ models. - Fix `Model:getNodeScale` to properly return the scale instead of the rotation. - Fix `Mat4:set` and `Mat4` constructors to properly use TRS order when scale is given. - Fix `lovr.graphics.newShader` to work with `nil` shader code (uses `unlit` shader). - Fix crash when `t.graphics.debug` is set but the validation layers aren't installed. - Fix a few memory leaks with Readbacks, Fonts, and compute Shaders. - Fix `Pass:setProjection` to use an infinite far plane by default. - Fix `Texture:hasUsage`. - Fix `Pass:points` / `Pass:line` to work with temporary vectors. - Fix `lovr.event.quit` to properly exit on Android/Quest. - Fix mounted zip paths sometimes not working with `lovr.filesystem.getDirectoryItems`. - Fix issue where temporary vectors didn't work in functions that accept colors. - Fix crash when rendering multiple identical torus shapes in the same Pass. - Fix `lovr.graphics.newShader` to error properly if the push constants block is too big. - Fix default `lovr.log` to not print second `string.gsub` result. - Fix issue where `lovr.data.newImage` wouldn't initialize empty `Image` object pixels to zero. - Fix `Model:getMaterial` to work when given a string. - Fix issue where `Vec3:angle` would sometimes return NaNs. - Fix OpenXR driver when used with AR headsets. - Fix vertex tangents (previously they only worked if the `normalMap` shader flag was set). - Fix error when minimizing the desktop window on Windows. - Fix `DistanceJoint:getAnchors`. - Fix issue where `Material:getProperties` returned an incorrect uvScale. - Fix crash when uvShift/uvScale were given as tables in `lovr.graphics.newMaterial`. - Fix retina macOS windows. - Fix issue where `Pass:capsule` didn't render anything when its length was zero. - Fix confusing console message when OpenXR is not installed. - Fix issue where OBJ UVs were upside down. - Fix issue where equirectangular skyboxes used an incorrect z direction. - Fix seam when rendering equirectangular skyboxes with mipmaps. - Fix font wrap when camera uses a projection matrix with a flipped up direction. - Fix Model import when vertex colors are stored as vec3. - Fix 24-bit WAV import. - Fix issue with 3-letter vec3 swizzles. - Fix error when subtracting a vector from a number. - Fix error when adding a number and a temporary vector (in that order). - Fix issue where `t.window = nil` wasn't working as intended. - Fix bug where some fonts would render glyphs inside out. ### Deprecate - Deprecate `lovr.graphics.getPass` (`lovr.graphics.newPass` should be used instead). - Deprecate `Pass:getType`. All Pass objects support both compute and rendering (computes run before draws). - Deprecate `Pass:getTarget` (renamed to `Pass:getCanvas`). - Deprecate `Pass:getSampleCount` (`Pass:getCanvas` returns a `samples` key). - Deprecate `lovr.graphics.getBuffer` (Use `lovr.graphics.newBuffer` or tables). - Deprecate variant of `lovr.graphics.newBuffer` that takes length/data as the first argument (put format first). - Deprecate `lovr.headset.get/setDisplayFrequency` (it's named `lovr.headset.get/setRefreshRate` now). - Deprecate `lovr.headset.getDisplayFrequencies` (it's named `lovr.headset.getRefreshRates` now). - Deprecate `lovr.headset.getOriginType` (use `lovr.headset.isSeated`). - Deprecate variant of `lovr.headset.animate` that takes a `Device` argument (just pass the `Model` now). - Deprecate `Buffer:getPointer` (renamed to `Buffer:mapData`). ### Remove - Remove `transfer` passes (methods on `Buffer` and `Texture` objects can be used instead). - Remove `Pass:copy` (use `Buffer:setData` and `Texture:setPixels`). - Remove `Pass:read` (use `Buffer:newReadback` and `Texture:newReadback`). - Remove `Pass:clear` (use `Buffer:clear` and `Texture:clear`). - Remove `Pass:blit` (use `Texture:setPixels`). - Remove `Pass:mipmap` (use `Texture:generateMipmaps`). - Remove `Tally` (use `lovr.graphics.setTimingEnabled` or `Pass:beginTally/finishTally`). - Remove `lovr.event.pump` (it's named `lovr.system.pollEvents` now). - Remove `t.headset.offset` (use `t.headset.seated`). - Remove `mipmaps` flag from render passes (they always regenerate mipmaps now).