v0.12.6: Binding and renderer fixes
v0.12.6 is a patch release that fixes crashes, memory leaks, and wrong output, and closes parity gaps between the bindings. Every documented example now compiles and runs in Rust, JavaScript, Python, Swift, and Kotlin. The website opens on a playground where you edit a shader and watch it rebuild. Some fixes change behavior that existing code may rely on, and they come last, under Breaking and behavior changes.
Renderer and meshes
Section titled “Renderer and meshes”Pass.set_viewport takes effect. A pass draws only inside its region and crops the part past the target, while the clear color still fills the whole target. Earlier releases ignored the viewport. Passes in one render call also keep their own uniforms instead of all drawing with the last pass’s values.
Three fixes cut memory use and per-frame work. FragmentColor frees passes, shaders, and their storage buffers when the last handle drops, which ends a leak of about 30 MB per rebuild in a simulation of a million particles. Storage buffers upload only when created, resized, or changed by set. At eight million particles, the old upload copied about 256 MB a frame. Shaders that share a source get one render pipeline per vertex layout, and compute passes reuse theirs. In 0.12.5, a mesh with a second vertex layout drew wrong vertices, and every frame leaked memory and compiled new compute pipelines that it never freed.
- After a timeout or failure,
get_imageandread_storagedrop their pooled buffer, and later readbacks no longer hitBufferStillMapped. - A
rendermade only of compute passes acquires no surface frame and keeps running while a window is minimized. - In a shader with two
@vertexor@fragmentfunctions, render pipelines take the first of each. They took the last. - A mesh whose instance list shrinks draws only the instances it still holds.
- Vertices or instances that disagree on an attribute’s format or length make the mesh fail to attach. Such a mismatch once shifted the attributes after it. A value added after the attach fails the render with
MeshError. Scene.loadreads glTF files with application attributes such as_ID.
Shaders and uniforms
Section titled “Shaders and uniforms”Shader.set follows the same rules in every language. Integers must be whole and in range, vectors and matrices take exact counts, and errors read like resolution: expected vec2<f32>, got 3 number(s).
- Integer uniforms work on the web, and
mat2x2in JavaScript, Swift, and Kotlin. Indexed keys get the type check, Python raises on a list of the wrong length, andShader.getreturns current values. On a struct root it returned the defaults. mat3x3<f32>values fill 48 bytes with WGSL’s column padding. Packed into 36, the last two columns shifted.- On the web,
Shader.setbinds only a realTextureand throws for any other object. It used to read that object’s memory as a texture. - Storage buffers hold
atomic<u32>andatomic<i32>, and another shader can read them asarray<u32, N>. Shader.set_registryworks on native builds, with the embedded library as fallback. Full URLs into the default registry use it too, which spares Swift and Kotlin their network errors.- The registry’s
sdf2d/heartreturns Inigo Quilez’s exact signed distance, and outlines and glows trace the shape.pattern/hexgriddraws hexagons instead of tall, overlapping cells.
Textures
Section titled “Textures”Sampler options take effect at creation and after set_sampler_options, which 0.12.5 ignored. A sampler picks up its group’s texture options, PBR’s shared sampler follows the base color map, and sampler_comparison defaults to LessEqual.
Texture.writeandwrite_regiontake tightly packed rows. A 2×2 RGBA write needs 16 bytes, down from 264. Padded rows still upload, andbytes_per_rowonly has to cover the row.create_texturehonorsTextureOptions.usage:TextureUsage::COPY_SRCmakes a texture readable.read_texturefailed on every texturecreate_texturemade.- Python texture methods read NumPy arrays in row-major order. Fortran-ordered arrays uploaded scrambled pixels, and flipped or sliced views raised
ndarray must be contiguous. - Swift
createTexture(pixels, size)builds a full mip chain and throws for a size of other than 2 or 3 numbers. In 0.12.5 it decoded the pixels as an image.
Binding fixes
Section titled “Binding fixes”Android renderer.createTarget(surface) works again. Every call since 0.12.3 ended in Failed to create surface. The target lives on the calling renderer, and render no longer fails with NoContext.
- Swift and Kotlin
pass.setDepthTarget(texture)hand Rust theTextureitself. They passed its handle as aTextureTarget, which is undefined behavior. - iOS
createTargetthrowsInitfor a nullCAMetalLayerpointer instead of reading through it. Model(mesh, material)andmodel.material()share theMaterialin every binding. A rebuilt copy reset its alpha mode and drewMaskandBlendmaterials wrong.- On Swift and Kotlin, and for a list on the web,
Pass.requirelinks each dependency once. The docs example failed on mobile withDuplicateDependency: color. Pythonrenderruns the passes aPassrequires. - In the browser,
scene.setPassescopies its handles, which ends theAttempt to use a moved valueerror on later calls.renderrecords a list as one frame rather than letting its last item replace the others, andloadandWindowTarget.getImage()stop throwing. - Python
Renderer.create_targetkeeps one target per renderer instead of sharing a canvas’s target across devices. - Swift
writeRegiontakes 2, 4, or 6 numbers and throws for other arrays, which it ignored.Vertex.setandInstance.setthrow for a list of other than 1 to 4 numbers, andTextureRegion.fromtraps with a message on an array it can’t read. All three misread such input before.
Parity across languages
Section titled “Parity across languages”Every binding uses Rust’s type names, such as WindowTarget and TextureRegion. CanvasTarget, RenderCanvasTarget, TextureRegionMobile, MobileWindowTarget, and MobileTextureTarget remain aliases.
Pass.addand theScenebuilders return the receiver, andscene.add(model).add(camera)chains.Pass.requiretakes aScene.ScreenRegion(origin, size),[x, y, w, h]viewports, and[r, g, b]clear colors work everywhere.Shader.setaccepts each platform’s number types, matrices flat or as columns, raw bytes for storage and push-constant roots, and bools as 1 or 0. Rust’sset("scale", 2)fills anf32, andShader::getreads matrices and bytes.- Swift and Kotlin
createTexturetakes bytes alone or with a format, a[w, h]size, or options, and every binding has theTextureUsageflags. In Rust, path and URL strings work, the web reads every option, and JavaScript and Python load KTX2. - Python has one readable
Sizeclass, andtexture.size.widthno longer raisesAttributeError. The binding also buildsSamplerOptionsandCompareFunctionand compares value types by value. - A number or list of numbers on a
VertexorInstancetakes the type the shader declares at its location when the mesh attaches, as uniforms do.vertex.set("id", 7)fills anid: u32input in every language, and a value that doesn’t fit fails the attach with an error likeMesh attribute id: expected u32, got -1 at vertex 2. Vertex.UV0and the other constants reach every binding, along with(position, uv, color)vertices. Pythonrendertakes aMesh, andScene.loadworks from.glbbytes and can skip cameras or lights.Materialtexture setters take anycreateTexturesource. Kotlin scene types acceptDouble, and the TypeScript typings allow plain arrays.
Docs and generated examples
Section titled “Docs and generated examples”The iOS and Android healthchecks run each documented example as a test named after its docs/api page, rather than only compiling it. Uncaptured GPU errors go to the file that FC_GPU_ERROR_LOG names and fail the iOS, Android, and Python healthchecks.
Generated examples match their Rust source, and the build removes those of deleted pages. Kotlin’s new createTexture(pixels, listOf(w, h)) fixes its examples, which sent raw pixels to the image decoder and failed.
On this site, method links open their heading instead of a 404, and each method’s anchor is its name, as in /api/scene/scene#remove_pass. Instance and Mipmap.level get pages of their own, and four missing method pages reach the site. The regenerated Python stub gains Scene, Mipmap, and the other types it lacked.
Playground and examples
Section titled “Playground and examples”The playground on the website rebuilds the shader as you edit the WGSL and JavaScript, derives controls from its uniforms, and shows the same program in Rust, Python, Swift, and Kotlin. It also holds the shader artworks and two compute demos with a million particles each, which need WebGPU. /demos redirects to the home page.
In the particles example, particle life grows on a grid: four species follow or flee each other’s density, and the mouse, R, C, and the arrow keys steer it. The three broken particle examples are gone.
Packaging
Section titled “Packaging”Each release tag pins its own xcframework. A dependency declared from: "0.12.5" got 0.12.4’s binary. Move Swift dependencies to 0.12.6. SwiftPM compiles a regenerated wrapper. The committed one predated 0.12.0 and lacked Scene, Model, and the other 0.12 types.
- FragmentColor builds from read-only sources, and docs.rs documents it again, having built no docs for 0.10.7 through 0.12.5.
- Python wheels require
numpy>=1.16. Without it,TextureTarget.get_imagepanicked. - The npm package drops the test harness’s
WasmBindgenTestContextand__wbgtest_*exports.
Breaking and behavior changes
Section titled “Breaking and behavior changes”These changes can break code written against 0.12.5.
Rust
Vertex::setandInstance::setare#[must_use]: use their result. They takeimpl Into<VertexData>, a transport type with no page of its own, in place ofVertexValue. Anf32oru32keeps its type and must match the input, and an unsuffixed literal,f64, ori32takes the shader’s type.PassError::NoCompatibleShadercarries the reason the pass’s last shader gave, and a pass with no shader returnsPassError::NoShader.- Items that were public by accident leave the API: the built-in shader sources, the wgpu device setup helpers,
RenderContext, the buffer and texture pools,TextureData::DynamicImage, and the glam and wgpu conversions ofScreenRegionandUniformData. Passv.to_array()where code passed a glam vector.
JavaScript
- Methods throw
Errorobjects. Most threw bare strings. Reade.messageinstead of comparing the caught value to a string. - Invalid source makes
new Shader(source)throw andShader.fetchreject. Both returned the red fallback, whichShader.default()still gives. Paths and slugs needShader.fetch, and a live editor can keep its last goodShader. - Indices, counts, and
setComputeDispatchsizes take whole numbers, and -1 stops wrapping to 4294967295.Pass.require,createTexture,Vertex.set, andInstance.setthrow for wrong types, which they ignored or read as 0. - Sizes take 2 or 3 numbers, regions 2, 4, or 6, and bytes 0 to 255, each finite and non-negative. Anything else fails. JavaScript clamped or zeroed it. Python follows the same rule.
Python
get_imagereturns auint8array shaped(height, width, ...), layers first for array textures.TextureTargetreturned the transpose: swap[x, y]indices.Texturereturned flat bytes, whichbytes(image)still gives.- Renderer and texture failures raise
FragmentColorErroror a subclass. Handlers that caught the plainExceptionthey raised, or theRuntimeErrorfromread_storage, should catchFragmentColorError.Scene.loadraisesSceneLoadError, also aRuntimeError, and light settersLightError, also aValueError. Shader, texture, mesh, andPass.requirecalls raiseTypeErrorfor an argument of the wrong type, where most raisedFragmentColorError. CatchTypeErrorthere.requirerejects astr, andShader.setrejects theTextureMetathatShader.getreturns.Vertex.setandInstance.setraiseFragmentColorErrorfor a list of other than 1 to 4 numbers. A position passed as an instance raisesTypeErrorinstead of adding an empty instance.- A window target’s
depthchanges from 1 toNone, andcreate_texturereadshttp(s)strings as URLs. usage=replaces the deprecatedusage_bits=. The stub drops plumbing such aspasses, which leaves the bindings in 0.13.
Swift and Kotlin
- Where Rust takes a
u32oru64, such as indices, counts, sizes, and regions, Swift takes onlyUInt32orUInt64and Kotlin onlyUIntorULong. AnIntvariable fails to compile. Check its sign and convert withUInt32(n)orn.toUInt(). - Swift literals compile as written, as in
createTextureTarget([64, 64]), and Kotlin writes64uandlistOf(64u, 64u). The removedIntoverloads trapped on or wrapped negatives. Byte lists keep theirIntforms and throw outside 0 to 255. - A GPU that fails to start reports
FragmentColorError.Initfrom every renderer method, where several saidRender. Kotlin argument errors throwFragmentColorExceptionin place ofIllegalArgumentException. - The generated
set(key:value:)takesUniformValue, replacingUniformData, and documentedshader.setcalls are unchanged. SwiftaddVertexandaddVerticesdon’t throw: drop theirtry.Vertex.setandInstance.setdo throw in Swift: addtrybefore them. - Swift
setViewport([x, y, w, h])reads an origin and size instead of two corners, and the Swift compiler warns on an unused result fromInstance.setand from generated builders such asnoDefaults(). - Kotlin
Shader.setRegistryreturns nothing instead of a throwawayShader. Rebuild libraries compiled against 0.12.5. - Swift deprecates
Shader.new([...])forShader([...]),setRegistry(baseUrl:)forsetRegistry(_:), and the tuple forms ofcreateTextureandcreateStorageTexture. Kotlin deprecatesShaderFetchforShader.fetch.
Every language
Pass.get_inputandPassInputleave the API, and Swift and Kotlin loseMobilePassInput. A pass’s settings are write-only:load_previous,set_clear_color,set_viewport, andset_compute_dispatch.VertexValueleaves every binding: pass numbers. A vertex or instance attribute set from a plain number gets its type from what the shader declares at that location, and JavaScriptInt32ArrayandUint32Arrayvalues count as plain numbers too. The bindings sent every number as anf32.- The attach looks past the first vertex and instance and checks them all. The earliest shader to type an attribute fixes it for the mesh, and a shader that declares another type fails to attach.
- A region built from a size covers exactly that size.
ScreenRegion::from([w, h])added a pixel per side, which failedwrite_region. create_targetreturnsInitializationErrorwhen the adapter can’t present, as on a second WebGL2 canvas, which needs its ownRenderer. It used to succeed and only log.- Samplers with no texture in their group filter linearly instead of picking the nearest texel. glTF textures follow their glTF sampler, and UVs outside 0 to 1 tile unless the file’s sampler clamps.
pattern/hexgridreturns the edge distance and an integer id,xin half cells andythe row. The cell center it returned split hashed cells into stripes along the axes. Its page derives the center.
Install it with:
# Rustcargo add fragmentcolor
# JavaScriptnpm install fragmentcolor
# Pythonpip install fragmentcolor
# Swift (Package.swift).package(url: "https://github.com/vista-art/fragmentcolor", from: "0.12.6")
# Kotlin (download fragmentcolor-0.12.6.aar from the GitHub Release)Release notes: GitHub v0.12.6.
Rafael