<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Fragment Color | Blog</title><description/><link>https://fragmentcolor.org/</link><language>en</language><item><title>v0.11.0: all planned languages release</title><link>https://fragmentcolor.org/blog/announcing-v0-11-0/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-11-0/</guid><description>Swift and Kotlin via uniffi complete the planned set of languages. The release also unifies the texture API, adds shader composition + KTX2 loading, fixes a class of correctness bugs, and upgrades to wgpu 29.</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.11.0 completes the set of languages FragmentColor was designed for: &lt;strong&gt;Rust, JavaScript, Python, Swift, and Kotlin&lt;/strong&gt;. The same &lt;code dir=&quot;auto&quot;&gt;Renderer&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Pass&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Texture&lt;/code&gt; types now run natively on iOS and Android, at parity with the desktop and web bindings.&lt;/p&gt;
&lt;p&gt;The other half of the release is a substantial pass over the API itself. The texture API now has a single, unified shape; &lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt; gained composition primitives that set up the upcoming registry; KTX2 and wider source-format coverage land for production asset pipelines; texture decoding moves off the renderer thread; method naming got a top-to-bottom audit; and the underlying wgpu/naga stack moves from 27 to 29.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-mobile-looks-like&quot;&gt;What mobile looks like&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;On iOS (Swift):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; FragmentColor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; renderer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Renderer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; target &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderer.&lt;/span&gt;&lt;span&gt;createTarget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;layer&lt;/span&gt;&lt;span&gt;: metalLayer&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;// your CAMetalLayer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;: wgslSource&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; renderer.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shader, target&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On Android (Kotlin):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; org.fragmentcolor.&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; renderer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Renderer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; target &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderer.&lt;/span&gt;&lt;span&gt;createTarget&lt;/span&gt;&lt;span&gt;(surface)             &lt;/span&gt;&lt;span&gt;// android.view.Surface&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shader.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(wgslSource)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;renderer.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(shader, target)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Bindings are generated with &lt;a href=&quot;https://github.com/mozilla/uniffi-rs&quot;&gt;uniffi&lt;/a&gt;, so the type names match the Rust core and the concepts carry over verbatim as you move between platforms.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;distribution&quot;&gt;Distribution&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Swift&lt;/strong&gt; ships as a Swift Package. Add &lt;code dir=&quot;auto&quot;&gt;https://github.com/vista-art/fragmentcolor&lt;/code&gt; as a package dependency (&lt;code dir=&quot;auto&quot;&gt;from: &quot;0.11.0&quot;&lt;/code&gt;); SPM pulls the matching &lt;code dir=&quot;auto&quot;&gt;fragmentcolor.xcframework.zip&lt;/code&gt; from the GitHub release and verifies it against a pinned SHA-256.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kotlin&lt;/strong&gt; ships as an &lt;code dir=&quot;auto&quot;&gt;.aar&lt;/code&gt; attached to the GitHub release for 0.11.x. Maven Central publishing is in progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every PR that touches mobile code rebuilds the xcframework and runs a headless smoke test on an iPhone simulator. The Android equivalent boots a KVM-accelerated emulator and runs &lt;code dir=&quot;auto&quot;&gt;connectedAndroidTest&lt;/code&gt; against the Kotlin bindings.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;five-language-healthchecks&quot;&gt;Five-language healthchecks&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every public method has a Rust example in &lt;code dir=&quot;auto&quot;&gt;docs/api/&lt;/code&gt;. The build script transpiles each example into JavaScript, Python, Swift, and Kotlin, then aggregates the per-language outputs into compile-only test files that ride along with each platform’s existing healthcheck.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;./healthcheck&lt;/code&gt; now exercises ~88 examples on every binding. JavaScript and Python execute them through Playwright and a headless wheel; Swift and Kotlin compile them as part of &lt;code dir=&quot;auto&quot;&gt;xcodebuild&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;connectedAndroidTest&lt;/code&gt;. The Rust files in &lt;code dir=&quot;auto&quot;&gt;docs/api/&lt;/code&gt; are the source of truth, so a doctest regression on Rust is the only thing that can let an inconsistency through.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;a-unified-texture-api&quot;&gt;A unified texture API&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Texture creation in v0.10.x had three transports (&lt;code dir=&quot;auto&quot;&gt;TextureSpec&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;StorageTextureInput&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;PrepareSpec&lt;/code&gt;) and around nine entry points (&lt;code dir=&quot;auto&quot;&gt;create_texture_with_size&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;create_texture_with_format&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;create_texture_prepared&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;create_storage_texture_with_data&lt;/code&gt;, …). v0.11.0 collapses the surface to three entry points that all accept the same input shapes:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tex&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_texture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;image.png&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;              &lt;/span&gt;&lt;span&gt;// file path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tex&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_texture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;png_bytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;                &lt;/span&gt;&lt;span&gt;// encoded bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tex&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_texture&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;rgba&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;h&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;           &lt;/span&gt;&lt;span&gt;// raw pixels&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_storage_texture&lt;/span&gt;&lt;span&gt;(([&lt;/span&gt;&lt;span&gt;256&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;256&lt;/span&gt;&lt;span&gt;], TextureFormat&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Rgba16Float))&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chain&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; TextureMipChain&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;prepare&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;png_bytes&lt;/span&gt;&lt;span&gt;, TextureFormat&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Rgba8UnormSrgb))&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tex&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_texture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;chain&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;                    &lt;/span&gt;&lt;span&gt;// pre-baked&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The same call shapes work in JavaScript, Python, Swift, and Kotlin, with each binding using its native syntax for the tuple/options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;TextureMipChain&lt;/code&gt; is a new public type on every binding.&lt;/strong&gt; It builds the CPU mipmap chain off-thread from encoded image bytes or raw pixels, so a tile-cache loader on its own worker thread can fold the resize pass into the same hop and hand the renderer a finished chain. In one consumer app, tile uploads dropped from 30-50 ms of GPU-thread work to a single &lt;code dir=&quot;auto&quot;&gt;queue.write_texture&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;Renderer::create_texture&lt;/code&gt; no longer blocks the renderer thread on native targets.&lt;/strong&gt; Decoding, the Triangle-filter mipmap chain, and the per-level texture writes run on a named background worker; the public API stays &lt;code dir=&quot;auto&quot;&gt;await&lt;/code&gt;-shaped, but the caller’s GPU and event-loop threads are no longer pinned for the duration. On the web, the work runs inline because the underlying GPU types can’t be moved across threads. Drop heavy decode into a Web Worker yourself if you need parallelism there.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;shader-composition&quot;&gt;Shader composition&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Shader::new&lt;/code&gt; now classifies its input by shape: a single string is raw WGSL/GLSL &lt;strong&gt;source&lt;/strong&gt;, a registry &lt;strong&gt;slug&lt;/strong&gt; like &lt;code dir=&quot;auto&quot;&gt;&quot;sdf2d/circle&quot;&lt;/code&gt;, an &lt;code dir=&quot;auto&quot;&gt;https://&lt;/code&gt; &lt;strong&gt;URL&lt;/strong&gt;, or a local &lt;strong&gt;path&lt;/strong&gt;. An array mixing any of those is resolved (fetched, read, looked up), deduped by source hash, and concatenated in order before validation.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shader&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sdf2d/circle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;noise/simplex2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;main_src&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shader&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://fragmentcolor.org/shaders/sdf2d/circle.wgsl&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shader&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sdf2d/circle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;// resolves through the registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Shader::fetch&lt;/code&gt; is the async path; it classifies input the same way as &lt;code dir=&quot;auto&quot;&gt;new&lt;/code&gt; and is available on every binding (&lt;code dir=&quot;auto&quot;&gt;await Shader.fetch(...)&lt;/code&gt; in JS and Python, &lt;code dir=&quot;auto&quot;&gt;try await Shader.fetch(...)&lt;/code&gt; in Swift, &lt;code dir=&quot;auto&quot;&gt;Shader.fetch(...)&lt;/code&gt; in Kotlin). Override the slug base URL with &lt;code dir=&quot;auto&quot;&gt;Shader::set_registry(base_url)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the on-ramp to the curated registry that lands in v0.14.x. The composition machinery (parts, dedup, slug-to-URL resolution) is in place today; the shader collection at &lt;code dir=&quot;auto&quot;&gt;shaders.fragmentcolor.org&lt;/code&gt; fills in over the 0.11.x to 0.14.x window. GLSL is supported as a single part; mixing GLSL with WGSL or with other parts is rejected.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;production-texture-loading-ktx2-and-wider-formats&quot;&gt;Production texture loading: KTX2 and wider formats&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;KTX2 container support.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;TextureInput&lt;/code&gt; gained three KTX2 variants (&lt;code dir=&quot;auto&quot;&gt;Ktx2Bytes&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Ktx2Path&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Ktx2Url&lt;/code&gt;) that go through the regular &lt;code dir=&quot;auto&quot;&gt;Renderer::create_texture&lt;/code&gt; entry point alongside JPEG and PNG. KTX2 inputs trust the file’s declared format and pre-baked mip chain; &lt;code dir=&quot;auto&quot;&gt;TextureOptions.format&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;TextureOptions.mipmaps&lt;/code&gt; are deliberately ignored for them, because re-running either step would round-trip through a worse approximation.&lt;/p&gt;
&lt;p&gt;Format coverage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uncompressed RGBA8, RGBA16F, and the R / G / RG / RGBA 8- and 16-bit families.&lt;/li&gt;
&lt;li&gt;BC1-BC7.&lt;/li&gt;
&lt;li&gt;ETC2 RGB, RGBA, and RGB-A1.&lt;/li&gt;
&lt;li&gt;ASTC 4×4 and 8×8.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mapping from Vulkan &lt;code dir=&quot;auto&quot;&gt;VkFormat&lt;/code&gt; values follows the KTX2 spec. Compression features are requested opportunistically at device creation, so adapters without a given feature still get a working device. A KTX2 load that targets a format the GPU can’t sample fails at upload with a clear error rather than at sample time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wider source-image coverage.&lt;/strong&gt; Source-image decoding now picks the right pixel buffer for the target format: &lt;code dir=&quot;auto&quot;&gt;R8Unorm&lt;/code&gt; via &lt;code dir=&quot;auto&quot;&gt;to_luma8&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;R16Unorm&lt;/code&gt; via &lt;code dir=&quot;auto&quot;&gt;to_luma16&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Rg8Unorm&lt;/code&gt; via &lt;code dir=&quot;auto&quot;&gt;to_luma_alpha8&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Rg16Unorm&lt;/code&gt; via &lt;code dir=&quot;auto&quot;&gt;to_luma_alpha16&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Rgba16Unorm&lt;/code&gt; via &lt;code dir=&quot;auto&quot;&gt;to_rgba16&lt;/code&gt;. The previous code path went through &lt;code dir=&quot;auto&quot;&gt;to_rgba8&lt;/code&gt; for everything, silently truncating 16-bit-per-channel input. Height maps and mask buffers loaded through &lt;code dir=&quot;auto&quot;&gt;Renderer::create_texture(path)&lt;/code&gt; were producing 8-bit output without a warning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mipmaps and trilinear filtering by default.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;Renderer::create_texture&lt;/code&gt; now generates a full mipmap chain at upload (Triangle filter via &lt;code dir=&quot;auto&quot;&gt;image::imageops::resize&lt;/code&gt;), and the default linear sampler picks &lt;code dir=&quot;auto&quot;&gt;mipmap_filter: Linear&lt;/code&gt; when &lt;code dir=&quot;auto&quot;&gt;smooth: true&lt;/code&gt;. The moving moiré you’d see when zooming out on a textured quad with high-frequency detail (canvas weave in painted JPEGs is the canonical case) is gone. Opt out with &lt;code dir=&quot;auto&quot;&gt;TextureOptions { mipmaps: false, .. }&lt;/code&gt; for textures you’ll only ever sample at 1:1.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;compute-api-improvements&quot;&gt;Compute API improvements&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Two direct additions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Seed a storage texture from a CPU blob in one call.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// (size, format) allocates empty; (size, format, bytes) seeds from a buffer.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_storage_texture&lt;/span&gt;&lt;span&gt;(([&lt;/span&gt;&lt;span&gt;256&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;256&lt;/span&gt;&lt;span&gt;], TextureFormat&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Rgba16Float, &lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Read back the contents of any registered texture in its native format.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pixels&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_texture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;tex&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Readbacks (&lt;code dir=&quot;auto&quot;&gt;Renderer::read_texture&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Texture::get_image&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;TextureTarget::get_image&lt;/code&gt;) poll the device internally before mapping the staging buffer, so a &lt;code dir=&quot;auto&quot;&gt;render → readback&lt;/code&gt; sequence is deterministic without any explicit synchronization on the caller’s side.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;TextureFormat::Rgba16Float&lt;/code&gt; joins the supported set: 16-bit float per channel, filterable, and storage-writable without a feature opt-in. The right pick for iterative simulations where 8-bit precision isn’t enough and 32-bit is overkill.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;compute-correctness-fixes&quot;&gt;Compute correctness fixes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Three correctness fixes also land:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sampled textures and samplers now include &lt;code dir=&quot;auto&quot;&gt;ShaderStages::COMPUTE&lt;/code&gt; in their bind-group layouts, matching uniforms and read-only storage buffers. &lt;code dir=&quot;auto&quot;&gt;textureSample&lt;/code&gt; from a compute shader works directly, without the workaround of declaring sources as &lt;code dir=&quot;auto&quot;&gt;texture_storage_2d&amp;#x3C;..., read&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;texture_storage_2d&amp;#x3C;..., read_write&gt;&lt;/code&gt; maps to combined &lt;code dir=&quot;auto&quot;&gt;LOAD | STORE&lt;/code&gt; access. &lt;code dir=&quot;auto&quot;&gt;textureStore&lt;/code&gt; against a &lt;code dir=&quot;auto&quot;&gt;read_write&lt;/code&gt; texture passes validation, and ping-pong pairs that can collapse to a single texture do so.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Texture::get_image&lt;/code&gt; advances the wgpu event loop on native (&lt;code dir=&quot;auto&quot;&gt;device.poll(Wait)&lt;/code&gt;), so the readback callback always completes.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;an-apple-silicon-compute-barrier&quot;&gt;An Apple Silicon compute barrier&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Apple Silicon needs a submission boundary to flush tile-based storage writes before a later compute pass can sample them through &lt;code dir=&quot;auto&quot;&gt;texture_2d&amp;#x3C;f32&gt;&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;textureLoad&lt;/code&gt;. Without the boundary, the sample silently returns zeros.&lt;/p&gt;
&lt;p&gt;v0.11.0 detects the platform and inserts the boundary automatically between sequential compute passes on macOS and iOS. There’s nothing to opt into; non-Apple targets are unaffected.&lt;/p&gt;
&lt;p&gt;The same class of TBDR bug also affects &lt;code dir=&quot;auto&quot;&gt;compute → render&lt;/code&gt; sequences inside one command buffer; the auto-split for that case is queued for v0.11.x. Until it lands, the workaround is to issue two separate &lt;code dir=&quot;auto&quot;&gt;Renderer::render&lt;/code&gt; calls. Each one ends with its own submission, which is enough of a boundary in practice.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;r16unorm-and-the-16-bit-norm-family-now-works-on-every-adapter-that-advertises-it&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;R16Unorm&lt;/code&gt; (and the 16-bit norm family) now works on every adapter that advertises it&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A bug surfaced through a consumer’s shader path: an &lt;code dir=&quot;auto&quot;&gt;R16Unorm&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;TextureMipChain&lt;/code&gt; that round-tripped cleanly through &lt;code dir=&quot;auto&quot;&gt;prepare → device.create_texture&lt;/code&gt; produced a silently-invalid texture on Apple Silicon, then exploded on the first &lt;code dir=&quot;auto&quot;&gt;create_view()&lt;/code&gt; with an &lt;code dir=&quot;auto&quot;&gt;InvalidResource&lt;/code&gt; cascade that drowned consumer logs 60 times per second. Same for &lt;code dir=&quot;auto&quot;&gt;Rg16Unorm&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Rgba16Unorm&lt;/code&gt;, and the three &lt;code dir=&quot;auto&quot;&gt;*Snorm&lt;/code&gt; variants.&lt;/p&gt;
&lt;p&gt;Three layered fixes, so the failure mode no longer reaches the user:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The adapter feature probe opportunistically requests &lt;code dir=&quot;auto&quot;&gt;TEXTURE_FORMAT_16BIT_NORM&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;FLOAT32_FILTERABLE&lt;/code&gt; on every adapter that advertises them.&lt;/li&gt;
&lt;li&gt;A &lt;code dir=&quot;auto&quot;&gt;TextureError::UnsupportedFormatForUsage&lt;/code&gt; guard fails fast at the API boundary on adapters that don’t.&lt;/li&gt;
&lt;li&gt;A new &lt;code dir=&quot;auto&quot;&gt;RenderContext::validate(label, op)&lt;/code&gt; helper folds bind-group and view creation through wgpu’s validation scope, so any remaining validation failures come back as one programmatic error instead of a four-tier cascade.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;api-naming-pass&quot;&gt;API naming pass&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A cross-cutting audit. The rule: one verb, or at most three words. Suffixes only when they disambiguate genuinely distinct inputs (&lt;code dir=&quot;auto&quot;&gt;from_file&lt;/code&gt; vs &lt;code dir=&quot;auto&quot;&gt;from_bytes&lt;/code&gt;). No &lt;code dir=&quot;auto&quot;&gt;_async&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;_kind&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;_object&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;_with_X&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;_checked&lt;/code&gt; variants. Internal helpers paid the same tax.&lt;/p&gt;
&lt;p&gt;Consumer-visible changes (Rust):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;TextureTarget::get_image_async&lt;/code&gt; → removed. &lt;code dir=&quot;auto&quot;&gt;Target::get_image&lt;/code&gt; is now &lt;code dir=&quot;auto&quot;&gt;async fn&lt;/code&gt; on the trait, mirroring &lt;code dir=&quot;auto&quot;&gt;Texture::get_image()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Pass::add_mesh_to_shader(mesh, shader)&lt;/code&gt; → removed. The body was &lt;code dir=&quot;auto&quot;&gt;shader.add_mesh(mesh)?&lt;/code&gt;; use &lt;code dir=&quot;auto&quot;&gt;shader.add_mesh(mesh)&lt;/code&gt; directly.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;App::on_event_kind&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;on_window_event_kind&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;on_device_event_kind&lt;/code&gt; → drop &lt;code dir=&quot;auto&quot;&gt;_kind&lt;/code&gt;. The catch-all variants &lt;code dir=&quot;auto&quot;&gt;on_event(f)&lt;/code&gt; (no &lt;code dir=&quot;auto&quot;&gt;kind&lt;/code&gt; arg) were removed entirely; kind-filtered registration is the only way.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_color_target_id(id)&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;set_color_target(id)&lt;/code&gt;. The arg name carries the type.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;create_external_texture_from_native&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;create_external_texture&lt;/code&gt; (free functions) → &lt;code dir=&quot;auto&quot;&gt;ExternalTextureHandle::from_native&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;from_video&lt;/code&gt; (associated functions).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;create_texture_with_size&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;_with_format&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;_with&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;_prepared&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;create_texture(input)&lt;/code&gt;. Same shape for &lt;code dir=&quot;auto&quot;&gt;create_storage_texture&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;TextureMipChain::prepare&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sync/async pair unification borrows the &lt;code dir=&quot;auto&quot;&gt;blocking&lt;/code&gt; submodule convention from &lt;code dir=&quot;auto&quot;&gt;reqwest::blocking&lt;/code&gt;: &lt;code dir=&quot;auto&quot;&gt;shader::input::resolve_async&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;resolve&lt;/code&gt; (top-level, async); the previous sync &lt;code dir=&quot;auto&quot;&gt;resolve&lt;/code&gt; moves to &lt;code dir=&quot;auto&quot;&gt;blocking::resolve&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;frame-is-gone&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;Frame&lt;/code&gt; is gone&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Frame&lt;/code&gt; was a thin collector over &lt;code dir=&quot;auto&quot;&gt;Pass&lt;/code&gt; objects. After the dependency-graph refactor a few releases ago, it held no capability that &lt;code dir=&quot;auto&quot;&gt;Pass::require()&lt;/code&gt; (DAG) or an iterable of &lt;code dir=&quot;auto&quot;&gt;Pass&lt;/code&gt; (sequential rendering) didn’t already cover. &lt;code dir=&quot;auto&quot;&gt;Renderer::render&lt;/code&gt; already accepts a single pass, a &lt;code dir=&quot;auto&quot;&gt;Vec&amp;#x3C;Pass&gt;&lt;/code&gt;, or a slice of either, so every &lt;code dir=&quot;auto&quot;&gt;Frame&lt;/code&gt; use case transliterates directly. Each public symbol multiplies across five language bindings, and shrinking that surface is worth the transliteration cost.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mut&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Frame&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_pass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_pass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;vec!&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The same migration applies in JS, Python, Swift, and Kotlin: pass the array directly to &lt;code dir=&quot;auto&quot;&gt;renderer.render&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;upgrading-wgpu-27--29&quot;&gt;Upgrading wgpu 27 → 29&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The internal wgpu/naga stack moves from 27.0.1 to 29.0.1; the public FragmentColor API is unchanged. The upgrade involved a sizeable adapter pass internally: &lt;code dir=&quot;auto&quot;&gt;SurfaceError&lt;/code&gt; was removed, push constants were renamed to “immediates”, &lt;code dir=&quot;auto&quot;&gt;depth_write_enabled&lt;/code&gt; became &lt;code dir=&quot;auto&quot;&gt;Option&lt;/code&gt;-typed, bind-group-layout slots became gap-tolerant, and the instance descriptor changed shape.&lt;/p&gt;
&lt;p&gt;The one user-visible WGSL change comes from that rename: wgpu 29 accepts &lt;code dir=&quot;auto&quot;&gt;var&amp;#x3C;immediate&gt;&lt;/code&gt; where 27 accepted &lt;code dir=&quot;auto&quot;&gt;var&amp;#x3C;push_constant&gt;&lt;/code&gt;. Update any shader that still uses the old spelling; naga’s WGSL front end no longer recognizes it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;roadmap&quot;&gt;Roadmap&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Next up beyond 0.11.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Example iOS and Android apps under &lt;code dir=&quot;auto&quot;&gt;platforms/swift/examples/&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;platforms/kotlin/examples/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Maven Central publishing for the Kotlin AAR.&lt;/li&gt;
&lt;li&gt;Swift Package Index registration.&lt;/li&gt;
&lt;li&gt;Expanded mobile healthchecks (textures, immediates, full render loops).&lt;/li&gt;
&lt;li&gt;A revamped &lt;code dir=&quot;auto&quot;&gt;RenderPass&lt;/code&gt; API that gives you more of &lt;code dir=&quot;auto&quot;&gt;wgpu::RenderPass&lt;/code&gt; with sensible defaults.&lt;/li&gt;
&lt;li&gt;A &lt;code dir=&quot;auto&quot;&gt;compute → render&lt;/code&gt; auto-split, covering the second half of the Apple TBDR hazard.&lt;/li&gt;
&lt;li&gt;Custom blending.&lt;/li&gt;
&lt;li&gt;The first end-to-end &lt;code dir=&quot;auto&quot;&gt;external_texture&lt;/code&gt; implementation. The API surface is in place on every binding; the per-platform plumbing is the gap.&lt;/li&gt;
&lt;li&gt;Snapshot testing and hot reload.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;v0.12.0 is the asset-pipeline cut: KTX2 streaming, glTF, Basis Universal, post-FX templates. v0.14.0 is the live-coding REPL plus WGSL composition with a hosted registry. v1.0 is API stability.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Install it with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Rust&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# JavaScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Python&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rendercanvas&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;glfw&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Swift (Package.swift)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.package(url:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://github.com/vista-art/fragmentcolor&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;0.11.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Kotlin (download fragmentcolor-0.11.0.aar from the GitHub Release)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Release notes: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/v0.11.0&quot;&gt;GitHub v0.11.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>rust</category><category>swift</category><category>kotlin</category><category>ios</category><category>android</category><category>webgpu</category><category>milestone</category></item><item><title>v0.10.11: direct texture writes and external video on the web</title><link>https://fragmentcolor.org/blog/announcing-v0-10-11/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-10-11/</guid><description>Writing pixel data directly into textures, zero-copy HTML video textures on the web, and updated Python integration with RenderCanvas 2.0.</description><pubDate>Mon, 30 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.10.11 introduces direct writes to textures from CPU memory, zero-copy HTML video textures on the web, and updated Python integration with RenderCanvas 2.0. It is the final desktop and web release before mobile.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;writing-to-textures&quot;&gt;Writing to textures&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Bitmap and pixel data can now be written directly into a texture, without a shader pass:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;texture&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create_texture_with_size&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;texture&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;pixel_bytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The API also includes &lt;code dir=&quot;auto&quot;&gt;Texture::write_with&lt;/code&gt; for partial updates with explicit origin, size, and bytes-per-row, and &lt;code dir=&quot;auto&quot;&gt;Renderer::update_texture&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;update_texture_with&lt;/code&gt; on the renderer side. &lt;code dir=&quot;auto&quot;&gt;Renderer::unregister_texture&lt;/code&gt; removes a texture from the registry on demand.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;sampling-html-video-on-the-web&quot;&gt;Sampling HTML &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;video&gt;&lt;/code&gt; on the web&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The web binding gains a zero-copy path from the browser’s video decoder via WebGPU’s &lt;code dir=&quot;auto&quot;&gt;texture_external&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;video&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;video&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;texture&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createExternalTextureFromHtmlVideo&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;video&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;texture&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This avoids the extra &lt;code dir=&quot;auto&quot;&gt;drawImage(video, …)&lt;/code&gt; step. Chromium has full support; Safari’s WebGPU has the binding too, though its API changes from time to time. Please open an issue if something behaves unexpectedly.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;rendercanvas-20-compatibility&quot;&gt;RenderCanvas 2.0 compatibility&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Renderer::create_target&lt;/code&gt; reads screen-present info directly from RenderCanvas and builds the surface from that, replacing the previous custom context name. Pinning an older RenderCanvas is no longer necessary.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;per-language-doc-overrides&quot;&gt;Per-language doc overrides&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The system that generates JS and Python examples from Rust doc-strings supports per-language override snippets for cases where a direct translation reads awkwardly. The texture-write pages are the immediate use case: &lt;code dir=&quot;auto&quot;&gt;&amp;#x26;[u8]&lt;/code&gt; in Rust, &lt;code dir=&quot;auto&quot;&gt;Uint8Array&lt;/code&gt; in JS, and &lt;code dir=&quot;auto&quot;&gt;bytes&lt;/code&gt; in Python all want slightly different framing.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Mobile is next. &lt;a href=&quot;https://fragmentcolor.org/blog/announcing-v0-11-0/&quot;&gt;v0.11.0&lt;/a&gt; is in development.&lt;/p&gt;
&lt;p&gt;Release notes: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/v0.10.11&quot;&gt;GitHub v0.10.11&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>rust</category><category>webgpu</category><category>python</category><category>javascript</category></item><item><title>v0.10.10: long-lived WASM sessions</title><link>https://fragmentcolor.org/blog/announcing-v0-10-10/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-10-10/</guid><description>Stability improvements for long-running browser sessions where the WebAssembly memory may detach during the session.</description><pubDate>Tue, 21 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.10.10 improves the stability of the WASM bindings during long-running browser sessions. When the &lt;code dir=&quot;auto&quot;&gt;ArrayBuffer&lt;/code&gt; detaches mid-session, the binding switches to a live view of &lt;code dir=&quot;auto&quot;&gt;wasm.memory.buffer&lt;/code&gt; and continues operating.&lt;/p&gt;
&lt;p&gt;This release matters most for long-lived browser surfaces such as kiosks, data-viz dashboards, and generative-art screensavers, where a tab can stay open for hours or days.&lt;/p&gt;
&lt;p&gt;A few smaller items also land:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;TextureInput&lt;/code&gt; on the JS bridge treats a zero-byte-length &lt;code dir=&quot;auto&quot;&gt;ArrayBuffer&lt;/code&gt; as detached or empty and returns an empty byte vector. This covers the case where a texture was loaded, the page sat idle, and memory then moved.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ImageData&lt;/code&gt; extraction uses the native &lt;code dir=&quot;auto&quot;&gt;Clamped&amp;#x3C;Vec&amp;#x3C;u8&gt;&gt;&lt;/code&gt; path and removes an extra &lt;code dir=&quot;auto&quot;&gt;copy_to&lt;/code&gt; step.&lt;/li&gt;
&lt;li&gt;The &lt;code dir=&quot;auto&quot;&gt;ShaderHero&lt;/code&gt; component drops its per-frame debug instrumentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The public API is unchanged.&lt;/p&gt;
&lt;p&gt;Release notes: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/v0.10.10&quot;&gt;GitHub v0.10.10&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>webgpu</category><category>javascript</category><category>bugfix</category></item><item><title>v0.10.9: stable type identity through minified builds</title><link>https://fragmentcolor.org/blog/announcing-v0-10-9/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-10-9/</guid><description>Public types now carry stable string brands so identity checks survive Terser and similar bundler passes.</description><pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.10.9 ships a same-day follow-up to &lt;a href=&quot;https://fragmentcolor.org/blog/announcing-v0-10-8/&quot;&gt;v0.10.8&lt;/a&gt; for production builds that go through a minifier. It applies to anyone shipping FragmentColor through Terser or a similar bundler that rewrites class names.&lt;/p&gt;
&lt;p&gt;The JavaScript bindings previously used class names as identity tokens (for example, to tell a &lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt; apart from a &lt;code dir=&quot;auto&quot;&gt;Texture&lt;/code&gt;). After minification, those names collapsed to single letters, and the identity check appeared as “Invalid target type” from &lt;code dir=&quot;auto&quot;&gt;Renderer.render()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;v0.10.9 introduces stable string brands via &lt;code dir=&quot;auto&quot;&gt;impl_fc_kind!(TypeName, &quot;TypeName&quot;)&lt;/code&gt; for every public type. The brand is written into the type at compile time and survives any minification pass.&lt;/p&gt;
&lt;p&gt;Release notes: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/v0.10.9&quot;&gt;GitHub v0.10.9&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>javascript</category><category>bugfix</category></item><item><title>v0.10.8: non-blocking uniforms, structured errors, and a new gallery</title><link>https://fragmentcolor.org/blog/announcing-v0-10-8/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-10-8/</guid><description>Per-frame uniform updates from worker threads now run lock-free. Error variants get explicit shapes. The website ships a live shader gallery.</description><pubDate>Thu, 16 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.10.8 lands three focused improvements informed by real-world use of the library, plus a refreshed visual front for the website.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;non-blocking-shaderset&quot;&gt;Non-blocking &lt;code dir=&quot;auto&quot;&gt;shader.set()&lt;/code&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Uniform updates queue a last-wins write that the renderer flushes right before binding, without taking a lock on the render thread. Per-frame updates from worker threads are now safe at the API level. Read methods still return &lt;code dir=&quot;auto&quot;&gt;ShaderError::Busy&lt;/code&gt; when another consumer holds the lock; writes always succeed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;structured-error-variants&quot;&gt;Structured error variants&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;RendererError&lt;/code&gt; now includes &lt;code dir=&quot;auto&quot;&gt;MsaaViewMissing&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;DepthSampleCountMismatch&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;InitializationError&lt;/code&gt; includes &lt;code dir=&quot;auto&quot;&gt;AdapterNotSet&lt;/code&gt;. Callers can match on these as enum variants and design recovery paths around them.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;a-live-shader-demo&quot;&gt;A live shader demo&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The homepage gets a &lt;code dir=&quot;auto&quot;&gt;ShaderHero&lt;/code&gt; component that runs an interactive shader at the top of the page. The component is the basis for a future gallery of works and serves as a continuous visual check of support across browsers.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;smaller-things&quot;&gt;Smaller things&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;WASM memory is pre-grown by 64 MiB at initialization, eliminating a class of mid-frame &lt;code dir=&quot;auto&quot;&gt;memory.grow&lt;/code&gt; stalls during the first few frames.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Pass&lt;/code&gt; infers its kind (render vs compute) from the first attached shader; the kind no longer has to be set explicitly.&lt;/li&gt;
&lt;li&gt;The npm package ships branded JS prototypes, so &lt;code dir=&quot;auto&quot;&gt;instanceof&lt;/code&gt; checks survive minification. (Details in v0.10.9 below.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full changelog: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/v0.10.8&quot;&gt;GitHub v0.10.8&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>rust</category><category>webgpu</category><category>javascript</category></item><item><title>v0.10.7: feature parity across Rust, JavaScript, and Python</title><link>https://fragmentcolor.org/blog/announcing-v0-10-7/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-10-7/</guid><description>Textures, meshes, multi-pass, MSAA, and a build that fails locally if any binding is missing a method.</description><pubDate>Sun, 05 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.10.7 covers desktop and web, and makes feature parity across Rust, JavaScript, and Python an enforced build requirement. v0.11 is reserved for the upcoming iOS and Android releases.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;enforced-feature-parity&quot;&gt;Enforced feature parity&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every public object is documented in &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/tree/main/docs/api&quot;&gt;docs/api&lt;/a&gt;, every doc has an &lt;code dir=&quot;auto&quot;&gt;## Example&lt;/code&gt; section, and the build refuses to compile if a method is missing in any binding. The JavaScript and Python examples on this site are extracted from the same healthcheck scripts that run in CI on every push.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;textures-and-samplers-are-part-of-the-core-api&quot;&gt;Textures and samplers are part of the core API&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Textures load from bytes, paths, or URLs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; fragmentcolor &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Renderer, Shader&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;renderer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Renderer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;texture &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderer.&lt;/span&gt;&lt;span&gt;create_texture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://fragmentcolor.org/images/logo.png&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shader.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;...your shader...&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shader.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; texture&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shader.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sampler&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;linear&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Sample counts give you MSAA with automatic resolve. Storage textures support shader-side writes. Depth textures end up in the right layout because the renderer reads your shader’s binding metadata directly.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;meshes-and-multi-pass-without-the-extra-setup&quot;&gt;Meshes and multi-pass without the extra setup&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Shader.add_mesh()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Pass.add_mesh_to_shader()&lt;/code&gt; cover vertex-layout configuration. The library inspects your shader’s &lt;code dir=&quot;auto&quot;&gt;@location&lt;/code&gt; inputs and refuses to attach a mesh that doesn’t fit. Multiple meshes per pass work, instancing works, and pipelines are cached by &lt;code dir=&quot;auto&quot;&gt;(shader, format, sample_count)&lt;/code&gt;, so reusing the same shader across passes is cheap.&lt;/p&gt;
&lt;p&gt;A new &lt;code dir=&quot;auto&quot;&gt;Frame&lt;/code&gt; object combines passes into a render graph (for example, “blur-x after scene, blur-y after blur-x”), with explicit &lt;code dir=&quot;auto&quot;&gt;require()&lt;/code&gt; edges that set the order.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;one-interface-for-everything-you-upload-to-a-gpu&quot;&gt;One interface for everything you upload to a GPU&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Uniforms, uniform arrays, storage buffers, storage buffer arrays, vertex buffers, index buffers, textures, storage textures, samplers, push constants. All of them go through &lt;code dir=&quot;auto&quot;&gt;shader.set(&quot;path.to.field&quot;, value)&lt;/code&gt;, and the layout is inferred from your WGSL. Push constants fall back to a uniform buffer on targets that don’t support them.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;offscreen-headless-and-ci-friendly&quot;&gt;Offscreen, headless, and CI-friendly&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Rendering to a &lt;code dir=&quot;auto&quot;&gt;TextureTarget&lt;/code&gt; on any platform without a window is the documented path for headless tests. Useful if you want to snapshot-test a shader, or run generative art from a cron job.&lt;/p&gt;
&lt;p&gt;If you want to try this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Rust&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# JavaScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Python&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rendercanvas&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;glfw&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Full changelog: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/v0.10.7&quot;&gt;GitHub v0.10.7&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Start here: &lt;a href=&quot;https://fragmentcolor.org/welcome&quot;&gt;/welcome&lt;/a&gt; · &lt;a href=&quot;https://fragmentcolor.org/api&quot;&gt;/api&lt;/a&gt; · &lt;a href=&quot;https://github.com/vista-art/fragmentcolor&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>rust</category><category>webgpu</category><category>python</category><category>javascript</category><category>milestone</category></item><item><title>v0.10.5: First Python Release</title><link>https://fragmentcolor.org/blog/announcing-v0-10-5/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/announcing-v0-10-5/</guid><description>FragmentColor now installs from PyPI on Windows, Linux, and macOS in a single command, with a fully automated wheel pipeline behind it.</description><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;v0.10.5 is the first &lt;a href=&quot;https://fragmentcolor.org/blog/first/&quot;&gt;FragmentColor&lt;/a&gt; release published to PyPI:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rendercanvas&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;glfw&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It is also the first release with an automated publishing process. Each of the patches in-between focused on a specific area:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;v0.10.0-2: Library full rewrite, Shader object, first public release with the FragmentColor name.&lt;/li&gt;
&lt;li&gt;v0.10.3: Moved the crate to Rust’s 2024 edition.&lt;/li&gt;
&lt;li&gt;v0.10.4: Corrected a wheel import and completed the &lt;code dir=&quot;auto&quot;&gt;Pass&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;Frame&lt;/code&gt; API.&lt;/li&gt;
&lt;li&gt;v0.10.5: Added the automated system that builds and publishes wheels for every desktop OS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Release notes: &lt;a href=&quot;https://github.com/vista-art/fragmentcolor/releases/tag/0.10.5&quot;&gt;GitHub v0.10.5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I plan to publish it on npm on v0.10.6.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>python</category></item><item><title>v0.10.0: The Shader Object</title><link>https://fragmentcolor.org/blog/first/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/first/</guid><description>The v0.10.0 release introduces the Shader-centered design, completes the rename from PLRender, and sets the shape of the API for what comes next.</description><pubDate>Thu, 15 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the first release of FragmentColor.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fragmentcolor.org&quot;&gt;FragmentColor&lt;/a&gt; is a universal GPU abstraction layer where you can simply write some WGSL or GLSL code, forget about pipeline setup, set uniforms by key-value, and it will render on every device in existence. That’s it.&lt;/p&gt;
&lt;p&gt;This version is a full rewrite of the original PLRender engine from Pupil Labs, removing every module and keeping only three simple objects: the &lt;strong&gt;Shader&lt;/strong&gt;, the &lt;strong&gt;Renderer&lt;/strong&gt;, and the &lt;strong&gt;Target&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The central idea behind FragmentColor is &lt;strong&gt;runtime shader reflection&lt;/strong&gt;. The shader code dictates the structures around it, and there is no need for you to think about buffer layouts and manual pipeline setup; you can just set your uniforms with the dot notation using the same name you declared in the shader itself:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shader&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;shader source defining my_uniform&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;my_uniform&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;renderer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shader&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Feel free to &lt;a href=&quot;https://fragmentcolor.com/welcome&quot;&gt;read the docs&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Install it on your favorite platform with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fragmentcolor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;design-goals&quot;&gt;Design Goals&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://fragmentcolor.org&quot;&gt;FragmentColor&lt;/a&gt; aims to be the simplest GPU abstraction API in existence: being both simpler &lt;strong&gt;and&lt;/strong&gt; more powerful than OpenGL. It builds on top of &lt;code dir=&quot;auto&quot;&gt;wgpu&lt;/code&gt; and abstracts away all the low-level boilerplate.&lt;/p&gt;
&lt;p&gt;The core is minimalist on purpose. Only a few basic types (&lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Pass&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Texture&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Renderer&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Target&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Vertex&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Mesh&lt;/code&gt;) can combine into anything you want to build or quickly prototype.&lt;/p&gt;
&lt;p&gt;We’re also keeping up with &lt;a href=&quot;https://wgpu.rs&quot;&gt;wgpu&lt;/a&gt; and naga as they evolve, the WebGPU standards, and the WASM ecosystem. Every time there is a new API change or new upstream capability, we aim to implement it as quickly as possible and make the abstraction around it automated and minimal.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;credits--acknowledgments&quot;&gt;Credits / Acknowledgments&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Renderer API draws heavily from &lt;a href=&quot;https://pygfx.org&quot;&gt;pygfx&lt;/a&gt;, a beautiful Python rendering engine that aims at a higher level than us. It is closer to Three.js for Python, with its own scene graph and renderable objects. FragmentColor uses pygfx’s &lt;a href=&quot;https://github.com/pygfx/rendercanvas&quot;&gt;RenderCanvas&lt;/a&gt; abstraction for the Python render surface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Shader-centric design came from Pupil Labs, as stated in our &lt;a href=&quot;&quot;&gt;origin story&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some of the GPU optimization techniques were inspired by &lt;a href=&quot;https://ruffle.rs&quot;&gt;Ruffle&lt;/a&gt; Flash Player engine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Although most of the old code did not survive the full refactor, I owe most of my learning to kwark’s (the wgpu contributor) and his minimalistic prototype engine &lt;a href=&quot;https://github.com/kvark/baryon&quot;&gt;Baryon&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And, of course, nobody starts their wgpu journey without passing through the fantastic &lt;a href=&quot;https://sotrh.github.io/learn-wgpu/&quot;&gt;Learn Wgpu&lt;/a&gt; series by Ben Hansen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>release</category><category>milestone</category><category>history</category></item><item><title>Where it all started</title><link>https://fragmentcolor.org/blog/thank-you-pupil-labs/</link><guid isPermaLink="true">https://fragmentcolor.org/blog/thank-you-pupil-labs/</guid><description>The codebase that became FragmentColor was first written inside Pupil Labs and released to the public under the MIT license. A short note on the seed of the design idea that came out of it.</description><pubDate>Fri, 15 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://fragmentcolor.org&quot;&gt;FragmentColor&lt;/a&gt; was first written inside &lt;a href=&quot;https://pupil-labs.com&quot;&gt;Pupil Labs&lt;/a&gt;, an eye-tracking company from Berlin.&lt;/p&gt;
&lt;p&gt;It was originally called &lt;strong&gt;PLRender&lt;/strong&gt;, an attempt at an in-house engine for bridging the gap between JavaScript and Python, and my early attempts at building with &lt;a href=&quot;https://wgpu.rs&quot;&gt;wgpu&lt;/a&gt;,  which was at version &lt;code dir=&quot;auto&quot;&gt;0.17&lt;/code&gt; at the time.&lt;/p&gt;
&lt;p&gt;PLRender’s initial exploratory design was driven by a “we’ll need everything eventually” motivation: it had a scene graph, an ECS, reflection-based components, a few rendering primitives, collision detection, and (added later as an afterthought) a generic &lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt; object that could take arbitrary GLSL input.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt; object turned out to be the only piece we actually needed. That observation is what eventually became the &lt;a href=&quot;http://localhost:4321/blog/first/&quot;&gt;v0.10.0&lt;/a&gt; version: a full hard-reset rewrite that removed EVERYTHING from the library but the &lt;code dir=&quot;auto&quot;&gt;Shader&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;I’m grateful to Pupil Labs for generously releasing the code under the MIT license, and, in particular, Moritz, who asked the right question at the right time.&lt;/p&gt;
&lt;p&gt;Rafael&lt;/p&gt;</content:encoded><category>history</category></item></channel></rss>