<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Igalia on Christian Gmeiner</title>
    <link>https://christian-gmeiner.info/tags/igalia/</link>
    <description>Recent content in Igalia on Christian Gmeiner</description>
    <image>
      <title>Christian Gmeiner</title>
      <url>https://christian-gmeiner.info/papermod-cover.png</url>
      <link>https://christian-gmeiner.info/papermod-cover.png</link>
    </image>
    <generator>Hugo -- 0.152.2</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 20 Apr 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://christian-gmeiner.info/tags/igalia/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>PanVK Extension Sprint: Mesa 26.1</title>
      <link>https://christian-gmeiner.info/2026-04-20-panvk-extensions/</link>
      <pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://christian-gmeiner.info/2026-04-20-panvk-extensions/</guid>
      <description>&lt;p&gt;Last week marks the Mesa 26.1 branch point, and I wanted to take a moment to look back at what happened on the PanVK front.&lt;/p&gt;
&lt;p&gt;Spoiler: it was a busy one.&lt;/p&gt;
&lt;h3 id=&#34;the-landscape&#34;&gt;The landscape&lt;/h3&gt;
&lt;p&gt;PanVK - the Vulkan driver for Arm Mali GPUs (Valhall and newer) - is a collaborative effort. &lt;a href=&#34;https://www.collabora.com/&#34;&gt;Collabora&lt;/a&gt; has been doing incredible work on the compiler backend and the foundational infrastructure. &lt;a href=&#34;https://www.arm.com/&#34;&gt;Arm&lt;/a&gt; themselves are actively contributing to the open source Mali GPU stack as well, reviewing patches and pushing driver quality forward. On the &lt;a href=&#34;https://www.igalia.com/&#34;&gt;Igalia&lt;/a&gt; side, my focus this cycle was &lt;strong&gt;Vulkan extension coverage&lt;/strong&gt;. The kind of work that doesn&amp;rsquo;t make for flashy demos but is absolutely critical for real-world application compatibility - especially for things like &lt;a href=&#34;https://github.com/doitsujin/dxvk&#34;&gt;DXVK&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Last week marks the Mesa 26.1 branch point, and I wanted to take a moment to look back at what happened on the PanVK front.</p>
<p>Spoiler: it was a busy one.</p>
<h3 id="the-landscape">The landscape</h3>
<p>PanVK - the Vulkan driver for Arm Mali GPUs (Valhall and newer) - is a collaborative effort. <a href="https://www.collabora.com/">Collabora</a> has been doing incredible work on the compiler backend and the foundational infrastructure. <a href="https://www.arm.com/">Arm</a> themselves are actively contributing to the open source Mali GPU stack as well, reviewing patches and pushing driver quality forward. On the <a href="https://www.igalia.com/">Igalia</a> side, my focus this cycle was <strong>Vulkan extension coverage</strong>. The kind of work that doesn&rsquo;t make for flashy demos but is absolutely critical for real-world application compatibility - especially for things like <a href="https://github.com/doitsujin/dxvk">DXVK</a>.</p>
<h3 id="why-extensions-matter">Why extensions matter</h3>
<p>A Vulkan driver without extensions is like a car without wheels - technically complete, practically useless. Applications (and translation layers like DXVK, vkd3d-proton, and Zink) probe for specific extensions and adjust their behavior accordingly. Missing even one can mean falling back to a slower path or refusing to run entirely.</p>
<p>Three different things drove the extension work this cycle:</p>
<ul>
<li><strong>The Proton stack</strong> - extensions consumed by DXVK and vkd3d-proton, the translation layers that make D3D9–12 games run on Vulkan.</li>
<li><strong>DDK feature parity</strong> - extensions Arm&rsquo;s binary Mali driver exposes that PanVK didn&rsquo;t yet, tracked in the <a href="https://gitlab.freedesktop.org/panfrost/mesa/-/work_items/125">DDK feature parity ticket</a>.</li>
<li><strong>Catching up on <a href="https://mesamatrix.net/">mesamatrix.net</a></strong> - closing the visible gap with the other Mesa Vulkan drivers (RADV, ANV, Turnip).</li>
</ul>
<p>So I set out to close gaps. Lots of them.</p>
<h3 id="the-proton-stack-essentials">The Proton stack essentials</h3>
<p>These are extensions DXVK and vkd3d-proton actually require - not just nice-to-haves on a recommendation list. Each one unblocks something concrete in the D3D-to-Vulkan translation path.</p>
<p><strong><code>VK_EXT_conditional_rendering</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40452">!40452</a>) was probably the most involved piece of work. D3D12 has predicated rendering (<code>SetPredication</code>), and vkd3d-proton uses this extension to implement it efficiently. It wasn&rsquo;t a simple &ldquo;flip a bit&rdquo; situation - I had to add the core state tracking, wrap all draw and dispatch calls with conditional checks, handle inherited state in secondary command buffers, and make sure meta operations (like internal clears and resolves) properly disable conditional rendering so they don&rsquo;t get accidentally skipped. That ended up being five patches touching draw paths, dispatch, and the secondary command buffer inheritance logic.</p>
<p><strong><code>VK_VALVE_mutable_descriptor_type</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40254">!40254</a>) is one of those extensions that exists purely because Valve needed it. In D3D, descriptor types are more fluid than in Vulkan - a descriptor slot might hold a sampler one frame and a storage buffer the next. vkd3d-proton enables this to avoid expensive descriptor set re-creation when types change. It&rsquo;s a trivial alias of the already-supported <code>VK_EXT_mutable_descriptor_type</code>, so enabling it was a one-liner.</p>
<p><strong><code>VK_EXT_memory_budget</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40246">!40246</a>) lets applications (and both DXVK and vkd3d-proton) query how much GPU memory is actually available versus how much is in use. Without it, apps are flying blind on memory management, which can lead to over-allocation and stuttering. Getting the heap budget reporting right required hooking into the kernel memory accounting. (LF maybe change this to &ldquo;required hooking into the kernel driver&rsquo;s memory accounting function&rdquo;)</p>
<p><strong><code>VK_EXT_attachment_feedback_loop_layout</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40498">!40498</a>) - feedback loops let you read from an attachment that you&rsquo;re simultaneously rendering to (think screen-space effects that sample the current framebuffer). DXVK uses this in its D3D9 hazard layout path to avoid artifacts in certain games.</p>
<p><strong><code>VK_EXT_shader_stencil_export</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39944">!39944</a>) - allows fragment shaders to write stencil values directly, rather than relying on the fixed-function stencil path. DXVK leans on this in its meta-copy and meta-resolve paths, and vkd3d-proton enables it too. The Panfrost stack already supported everything needed; literally a one-line advertisement in <code>physical_device.c</code>.</p>
<p><strong><code>VK_KHR_shader_untyped_pointers</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40457">!40457</a>, v9+) - a newer KHR extension that relaxes pointer type requirements in SPIR-V. DXVK calls this out as a dependency for descriptor heaps. Restricted to v9+ because Bifrost has issues with 8-bit vector loads through untyped pointers combined with 16-bit storage. Also needed to lower memcpy derefs before explicit IO lowering.</p>
<h3 id="catching-up-to-the-ddk">Catching up to the DDK</h3>
<p>The panfrost keeps a <a href="https://gitlab.freedesktop.org/panfrost/mesa/-/work_items/125">DDK feature parity ticket</a> tracking everything Arm&rsquo;s binary Mali driver exposes that PanVK doesn&rsquo;t yet. Four of those got crossed off this cycle:</p>
<ul>
<li><strong><code>VK_ARM_scheduling_controls</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40063">!40063</a>, CSF only) - an ARM-specific extension for controlling shader core scheduling on Command Stream Frontend (CSF) hardware. I also fixed the per-queue shader core count so CSF group creation uses the right values.</li>
<li><strong><code>VK_EXT_legacy_dithering</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39781">!39781</a>) - implements ordered dithering in the blending stage, which some applications expect from legacy APIs. Wired up the existing Panfrost dithering infrastructure (<code>pan_dithered_format_from_pipe_format()</code>) — just plumbing the <code>VK_RENDERING_ENABLE_LEGACY_DITHERING_BIT_EXT</code> flag through the blend descriptor and color attachment internal conversion paths.</li>
<li><strong><code>VK_EXT_rgba10x6_formats</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40653">!40653</a>) - a last-minute addition that just squeezed in before the branch point. This required adding the <code>PIPE_FORMAT_X6R10X6G10X6B10X6A10_UNORM</code> format to Mesa&rsquo;s gallium format table first, then wiring it up in PanVK. Used for 10-bit per channel content in video and HDR scenarios.</li>
<li><strong><code>VK_EXT_astc_decode_mode</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39799">!39799</a>) - controls the format used when decoding ASTC compressed textures, allowing apps to choose lower-precision decoding for performance. The Panfrost hardware already supports controlling ASTC decode precision via the Decode Wide plane descriptor field; just needed to parse <code>VkImageViewASTCDecodeModeEXT</code> from the image view pNext chain and set <code>astc.narrow</code> accordingly. v9+ only because the relevant ASTC plane descriptor fields only exist from Valhall onward.</li>
</ul>
<h3 id="catching-up-on-mesamatrix">Catching up on mesamatrix</h3>
<p><a href="https://mesamatrix.net/">mesamatrix.net</a> tracks Vulkan extension support across the Mesa drivers. The remaining extensions this cycle were about closing the visible gap with RADV, ANV, and Turnip — extensions that don&rsquo;t have a single big consumer driving them, but whose absence shows up as red squares on the matrix and as silent fallbacks in apps that probe for them.</p>
<ul>
<li><strong><code>VK_EXT_color_write_enable</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39913">!39913</a>) - per-attachment control over which color channels actually get written. The common Vulkan runtime already handled all the pipeline state and dynamic command plumbing, and panvk&rsquo;s blend descriptor emission was already consuming <code>color_write_enables</code>, so this was effectively an &ldquo;advertise the feature&rdquo; change.</li>
<li><strong><code>VK_EXT_depth_clamp_control</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39925">!39925</a>) - lets applications specify a custom depth clamp range instead of always clamping to the viewport&rsquo;s minDepth/maxDepth. Mali GPUs have native <code>LOW_DEPTH_CLAMP</code>/<code>HIGH_DEPTH_CLAMP</code> registers, so it was a matter of wiring the existing runtime state through to those.</li>
<li><strong><code>VK_EXT_attachment_feedback_loop_dynamic_state</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40498">!40498</a>) - the dynamic-state companion to <code>VK_EXT_attachment_feedback_loop_layout</code> above; lets you toggle feedback-loop state per draw call without pipeline rebuilds.</li>
<li><strong><code>VK_EXT_map_memory_placed</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40315">!40315</a>) - lets applications control <em>where</em> in their virtual address space GPU memory gets mapped. This simplified <code>pan_kmod_bo_mmap()</code> to always map the whole BO, cleaning up the kernel module interface.</li>
<li><strong><code>VK_EXT_shader_atomic_float</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40506">!40506</a>) - atomic operations on float values in shaders. The existing <code>axchg</code> instruction is type-agnostic, so no compiler changes were needed; image atomics are already lowered to global atomics. Just had to add <code>R32_FLOAT</code> to the storage-image-atomic format flag.</li>
<li><strong><code>VK_EXT_nested_command_buffer</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40120">!40120</a>, v10+) - allows secondary command buffers to call other secondary command buffers. The CSF backend&rsquo;s <code>cs_call()</code> is a hardware call/return instruction that nests naturally, and the existing <code>CmdExecuteCommands</code> already does the caller/callee state merging. The 8-level hardware call stack, minus one for the kernel ringbuffer call and two reserved for future driver use, leaves <code>maxCommandBufferNestingLevel</code> at 5.</li>
<li><strong><code>VK_EXT_image_view_min_lod</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39938">!39938</a>) - allows clamping the minimum LOD at the image view level rather than just the sampler. Mali v6+ has per-texture-descriptor LOD clamp fields independent from the sampler&rsquo;s, so this just plumbs <code>vk_image_view::min_lod</code> through <code>pan_image_view</code> into the texture descriptor — no shader lowering or descriptor merging needed.</li>
<li><strong><code>VK_EXT_zero_initialize_device_memory</code></strong> (<a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39658">!39658</a>) - guarantees that newly allocated device memory is zeroed. The kernel side already does the heavy lifting — <code>panfrost</code>/<code>panthor</code> use <code>drm_gem_shmem</code>, which serves zeroed pages from the shmem subsystem. And since panvk treats layout transitions as no-ops, <code>VK_IMAGE_LAYOUT_ZERO_INITIALIZED_EXT</code> falls out for free. (Did need one format-table fix: dropping <code>STORAGE_IMAGE</code> support from compressed formats to avoid crashes in the new dEQP tests.)</li>
</ul>
<h3 id="by-the-numbers">By the numbers</h3>
<p>That&rsquo;s <strong>18 extensions</strong> across roughly a dozen merge requests - ranging from single-patch additions to multi-patch series like conditional rendering. Collectively they represent a meaningful shift in what PanVK can claim to support: more of the Proton stack working out of the box, four more checkboxes against the DDK, and fewer red squares on the mesamatrix.</p>
<h3 id="whats-next">What&rsquo;s next</h3>
<p>The extension sprint isn&rsquo;t over - there are still gaps to fill, and each one removed makes PanVK more viable for real workloads. But 26.1 was a good milestone. The driver is getting to the point where you can throw a DXVK game at it and have a reasonable expectation that it just works.</p>
<p>Back to it. ⚡</p>
]]></content:encoded>
    </item>
    <item>
      <title>GLES3 on etnaviv: Fixing the Hard Parts</title>
      <link>https://christian-gmeiner.info/2026-02-20-gles3-on-etnaviv-fixing-the-hard-parts/</link>
      <pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://christian-gmeiner.info/2026-02-20-gles3-on-etnaviv-fixing-the-hard-parts/</guid>
      <description>&lt;p&gt;This is the start of a series about getting OpenGL ES 3.0 conformance on Vivante GC7000 hardware using the open-source etnaviv driver in Mesa. Thanks to &lt;a href=&#34;https://www.igalia.com/&#34;&gt;Igalia&lt;/a&gt; for giving me the opportunity to spend some time on these topics.&lt;/p&gt;
&lt;h2 id=&#34;where-we-are&#34;&gt;Where We Are&lt;/h2&gt;
&lt;p&gt;etnaviv has supported GLES2 on Vivante GPUs for a long time. GLES3 support has been progressing steadily, but the remaining dEQP failures are the stubborn ones - the cases where the hardware doesn&amp;rsquo;t quite do what the spec says, and the driver has to get creative.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This is the start of a series about getting OpenGL ES 3.0 conformance on Vivante GC7000 hardware using the open-source etnaviv driver in Mesa. Thanks to <a href="https://www.igalia.com/">Igalia</a> for giving me the opportunity to spend some time on these topics.</p>
<h2 id="where-we-are">Where We Are</h2>
<p>etnaviv has supported GLES2 on Vivante GPUs for a long time. GLES3 support has been progressing steadily, but the remaining dEQP failures are the stubborn ones - the cases where the hardware doesn&rsquo;t quite do what the spec says, and the driver has to get creative.</p>
<p>These aren&rsquo;t missing feature bits or unimplemented extensions. These are the problems where you stare at a command stream trace from the proprietary blob driver and realize they&rsquo;re doing something <em>completely different</em> from what you&rsquo;d expect, because the hardware has a quirk that nobody documented.</p>
<h2 id="the-approach">The Approach</h2>
<p>My workflow for each fix follows a pattern:</p>
<ol>
<li><strong>Run the failing dEQP test</strong>, note the failure mode (wrong pixels, crash, GPU hang)</li>
<li><strong>Capture command stream traces</strong> from both the blob (proprietary driver) and etnaviv for the same test</li>
<li><strong>Compare the traces</strong> - what states differ? What draw calls differ? Is the blob doing extra work?</li>
<li><strong>Understand why</strong> - read the spec, reason about the hardware behavior, figure out what the blob knows that we don&rsquo;t</li>
<li><strong>Implement the fix</strong> in Mesa, test, iterate</li>
</ol>
<p>The blob traces are invaluable. Vivante&rsquo;s proprietary driver has years of hardware workarounds baked in. When something doesn&rsquo;t work, the answer is usually hiding in the trace.</p>
<h2 id="the-hardware">The Hardware</h2>
<p>The primary test target is a GC7000 rev 6214 (HALTI5 generation). This is a capable GPU found in the NXP i.MX8MQ SoC. It has a BLT engine, texture descriptors, and most of the features needed for GLES3 - but also its own set of rasterization quirks and interpolation behaviors that need workarounds.</p>
<p>In the future, I plan to expand the focus to the broader GC7000 GPU family.</p>
<h2 id="up-next">Up Next</h2>
<p>The first post will tackle the R/B swap problem - the PE always writes pixels in BGRA byte order, and we&rsquo;ve been fixing it in the shader. The blob has a simpler answer. Stay tuned.</p>
<h2 id="following-along">Following Along</h2>
<p>All of this work happens upstream in <a href="https://gitlab.freedesktop.org/mesa/mesa">Mesa</a>.</p>
<p>If you&rsquo;re interested in GPU driver development, these posts aim to show what the work actually looks like &ndash; not just the final patch, but the debugging, the trace analysis, and the reasoning that gets you there.</p>
]]></content:encoded>
    </item>
    <item>
      <title>My first Vulkan extension</title>
      <link>https://christian-gmeiner.info/2026-02-13-my-first-vulkan-extension/</link>
      <pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://christian-gmeiner.info/2026-02-13-my-first-vulkan-extension/</guid>
      <description>&lt;p&gt;After years of working on etnaviv - a Gallium/OpenGL driver for Vivante GPUs - I&amp;rsquo;ve been wanting to get into Vulkan. As part of my work at &lt;a href=&#34;https://www.igalia.com/&#34;&gt;Igalia&lt;/a&gt;, the goal was to bring &lt;a href=&#34;https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_blend_operation_advanced.html&#34;&gt;&lt;code&gt;VK_EXT_blend_operation_advanced&lt;/code&gt;&lt;/a&gt; to lavapipe. But rather than going straight there, I started with Honeykrisp - the Vulkan driver for Apple Silicon - as a first target: a real hardware driver to validate the implementation against before wiring it up in a software renderer. My first Vulkan extension, and my first real contribution to Honeykrisp.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After years of working on etnaviv - a Gallium/OpenGL driver for Vivante GPUs - I&rsquo;ve been wanting to get into Vulkan. As part of my work at <a href="https://www.igalia.com/">Igalia</a>, the goal was to bring <a href="https://docs.vulkan.org/refpages/latest/refpages/source/VK_EXT_blend_operation_advanced.html"><code>VK_EXT_blend_operation_advanced</code></a> to lavapipe. But rather than going straight there, I started with Honeykrisp - the Vulkan driver for Apple Silicon - as a first target: a real hardware driver to validate the implementation against before wiring it up in a software renderer. My first Vulkan extension, and my first real contribution to Honeykrisp.</p>
<h1 id="why-this-extension">Why this extension?</h1>
<p>A customer needed advanced blending support in lavapipe, so the extension choice was made for me. But it turned out to be a great fit for a first extension - useful, self-contained, and not a multi-month rabbit hole. Standard Vulkan blending is limited to basic operations like add and subtract with blend factors. That&rsquo;s fine for most rendering, but if you want Photoshop-style effects - multiply, screen, overlay, color dodge, color burn - you&rsquo;re stuck doing it manually in shaders or with extra render passes.</p>
<p>The extension adds 19 blend operations that handle all of this in the fixed-function pipeline. Useful for UI toolkits, image editors, and anywhere you need creative compositing.</p>
<h1 id="the-journey">The journey</h1>
<p>What started as &ldquo;just wire up an extension&rdquo; turned into a proper refactoring adventure. The existing blend infrastructure in Mesa was scattered - OpenGL had its own enum definitions, Vulkan had separate conversions, and the actual NIR blend math lived in glsl-specific code.</p>
<p>So I took a step back and cleaned things up. Moved the blend mode enums into a shared util/blend.h header. Added proper helpers in the Vulkan runtime for converting between API types. Then came the fun part: implementing the actual blend equations in nir/lower_blend.</p>
<p>Each of those 19 blend modes has its own formula from the spec. Some are simple (multiply is just src * dst), others get hairy with conditionals and special cases for luminosity and saturation. About 570 lines of NIR code later, I had a lowering pass that any Mesa driver can use.</p>
<p>For example, here&rsquo;s how the <a href="https://docs.vulkan.org/spec/latest/chapters/framebuffer.html#framebuffer-blend-advanced">spec defines advanced blending</a>. Each mode plugs into a general equation:</p>
<pre tabindex="0"><code>RGB = f(Cs,Cd) * X * p0 + Cs * Y * p1 + Cd * Z * p2
A   =            X * p0 +      Y * p1 +      Z * p2
</code></pre><p>Where <code>p0</code>, <code>p1</code>, <code>p2</code> are weighting factors based on source/destination alpha coverage, and <code>f(Cs,Cd)</code> is the per-mode blend function. For overlay - probably the most recognizable blend mode from Photoshop - the spec defines:</p>
<pre tabindex="0"><code>f(Cs,Cd) = 2*Cs*Cd,              if Cd &lt;= 0.5
           1 - 2*(1-Cs)*(1-Cd),  otherwise
</code></pre><p>And here&rsquo;s that same formula expressed as NIR - Mesa&rsquo;s intermediate representation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kr">inline</span> <span class="n">nir_def</span> <span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="nf">blend_overlay</span><span class="p">(</span><span class="n">nir_builder</span> <span class="o">*</span><span class="n">b</span><span class="p">,</span> <span class="n">nir_def</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="n">nir_def</span> <span class="o">*</span><span class="n">dst</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="cm">/* f(Cs,Cd) = 2*Cs*Cd, if Cd &lt;= 0.5
</span></span></span><span class="line"><span class="cl"><span class="cm">    *            1-2*(1-Cs)*(1-Cd), otherwise
</span></span></span><span class="line"><span class="cl"><span class="cm">    */</span>
</span></span><span class="line"><span class="cl">   <span class="n">nir_def</span> <span class="o">*</span><span class="n">rule_1</span> <span class="o">=</span> <span class="nf">nir_fmul</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">nir_fmul</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">dst</span><span class="p">),</span> <span class="nf">imm3</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">   <span class="n">nir_def</span> <span class="o">*</span><span class="n">rule_2</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="nf">nir_fsub</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">imm3</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">               <span class="nf">nir_fmul</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">nir_fmul</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">nir_fsub</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">imm3</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">src</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                                        <span class="nf">nir_fsub</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">imm3</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">dst</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                         <span class="nf">imm3</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl">   <span class="k">return</span> <span class="nf">nir_bcsel</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">nir_fge</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">imm3</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="mf">0.5f</span><span class="p">),</span> <span class="n">dst</span><span class="p">),</span> <span class="n">rule_1</span><span class="p">,</span> <span class="n">rule_2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>nir_fmul</code>, <code>nir_fsub</code>, <code>nir_bcsel</code> - multiply, subtract, conditional select. Each call builds a node in the shader&rsquo;s IR graph. This is what &ldquo;lowering&rdquo; looks like: translating a high-level blend mode into operations the GPU&rsquo;s shader core can execute. The outer framework - the <code>p0</code>/<code>p1</code>/<code>p2</code> weighting - is handled by the caller; each blend function just implements its <code>f(Cs,Cd)</code>.</p>
<h1 id="the-turnip-surprise">The Turnip surprise</h1>
<p>Everything was working on Honeykrisp, tests were passing, life was good. Then the merge pipeline started failing - on Turnip (the Adreno Vulkan driver). Not my code, not my hardware, but my changes were breaking it.</p>
<p>I reached out for help, and Zan Dobersek stepped up. After some digging, he found the culprit: I was violating a subtle corner of the spec around attachmentCount. Turns out, when certain dynamic states are set and advancedBlendCoherentOperations isn&rsquo;t enabled, attachmentCount gets ignored entirely. My state tracking code wasn&rsquo;t accounting for that.</p>
<p>One fixup commit later, Turnip was happy again. This is the part they don&rsquo;t tell you about Vulkan extensions - you&rsquo;re not just implementing for your driver, you&rsquo;re touching shared infrastructure that every driver depends on. And the Mesa CI will absolutely let you know if you break something.</p>
<h1 id="lavapipe-landed">lavapipe landed</h1>
<p>One week later, lavapipe has it too. This was the original goal, and the shared infrastructure did exactly what it was supposed to - the <a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39612">lavapipe MR</a> is mostly just flipping the extension on. The lowering pass, the enum plumbing, the runtime helpers - all reused as-is. The full <code>dEQP-VK.pipeline.*.blend_operation_advanced.*</code> test suite passes on both drivers.</p>
<p>Two drivers in two weeks. That&rsquo;s what building the right abstractions gets you.</p>
<h1 id="whats-next">What&rsquo;s next</h1>
<p>The shared NIR lowering pass is there for any Mesa Vulkan driver to use. If your hardware doesn&rsquo;t have native advanced blending support, enabling the extension is now mostly plumbing. I&rsquo;m curious to see if other drivers pick it up.</p>
<p>For me, this was a good first step into Vulkan - and into working on Honeykrisp. I&rsquo;m looking forward to what comes next.</p>
<p>The <a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/38929">Honeykrisp/NIR MR</a> and the <a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39612">lavapipe MR</a> are both merged if you want to look at the code. Thanks to Alyssa Rosenzweig for the review and guidance, and to Zan Dobersek for debugging the Turnip regression with me.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
