<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Geolm</title>
    <description>The latest articles on DEV Community by Geolm (@geolm).</description>
    <link>https://dev.to/geolm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3593049%2F4792d773-f70f-47a4-ba97-277383a55dde.png</url>
      <title>DEV Community: Geolm</title>
      <link>https://dev.to/geolm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/geolm"/>
    <language>en</language>
    <item>
      <title>bc_crunch : Compression Algorithm Documentation</title>
      <dc:creator>Geolm</dc:creator>
      <pubDate>Fri, 12 Dec 2025 10:09:13 +0000</pubDate>
      <link>https://dev.to/geolm/bccrunch-compression-algorithm-documentation-4fpo</link>
      <guid>https://dev.to/geolm/bccrunch-compression-algorithm-documentation-4fpo</guid>
      <description>&lt;p&gt;bc_crunch is a tiny, dependency-free C99 library for lossless compression of GPU-compressed texture blocks BC1, BC4, BC3, and BC5.&lt;/p&gt;

&lt;p&gt;You can find it here : &lt;a href="https://github.com/Geolm/bc_crunch" rel="noopener noreferrer"&gt;https://github.com/Geolm/bc_crunch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And contact me on bsky : &lt;a href="https://bsky.app/profile/geolm.bsky.social" rel="noopener noreferrer"&gt;https://bsky.app/profile/geolm.bsky.social&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Core Engine: Adaptive Arithmetic Coding (AAC)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arithmetic Coding&lt;/strong&gt; is a form of entropy encoding that maps an entire sequence of symbols to a single fractional number, often achieving better compression than Huffman coding because it can assign fractional bits per symbol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modeling (range_model)&lt;/strong&gt;: The compressor maintains a probability model for every type of data it encounters (e.g., color red delta, index difference, etc.). This model tracks how often each symbol (byte value or small integer) has occurred so far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adaptive Learning&lt;/strong&gt;: The models are adaptive. After encoding a symbol, the model's counts are immediately updated. This allows the compressor to learn the statistics of the specific texture as it is being compressed, making it highly efficient even for unique data patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renormalization&lt;/strong&gt;: The range_codec manages a base and length interval. As symbols are encoded, this interval shrinks. When it gets too small, a process called renormalization shifts the interval to output compressed bytes, maintaining precision and efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. BC1 Compression (bc1_crunch)
&lt;/h2&gt;

&lt;p&gt;BC1 blocks (8 bytes) consist of two 16-bit color endpoints and one 32-bit block of 16 2-bit indices.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Index Data Compression (The 32-bit Indices)
&lt;/h3&gt;

&lt;p&gt;The 32-bit index pattern, which dictates the color of the 4×4 pixels, is often repeated across a texture. Your algorithm exploits this redundancy with a global dictionary.&lt;/p&gt;

&lt;h4&gt;
  
  
  Global Dictionary (Top Table) Creation:
&lt;/h4&gt;

&lt;p&gt;The compressor first scans the entire texture and builds a histogram of every unique 32-bit index pattern.&lt;/p&gt;

&lt;p&gt;It selects the Top 256 (TABLE_SIZE) most frequently occurring index patterns to form the top_table.&lt;/p&gt;

&lt;h4&gt;
  
  
  Encoding the Top Table:
&lt;/h4&gt;

&lt;p&gt;The list of 256 patterns itself is compressed! Since the table is sorted, the patterns are similar.&lt;/p&gt;

&lt;p&gt;The compressor encodes the difference (delta) between consecutive patterns in the table using Arithmetic Coding, which is very efficient for small, positive differences.&lt;/p&gt;

&lt;h4&gt;
  
  
  Encoding the Block Indices:
&lt;/h4&gt;

&lt;p&gt;For each new block, the algorithm searches the top_table for the index pattern that is closest in binary representation (using Hamming distance, i.e., how many bits are different).&lt;/p&gt;

&lt;p&gt;Reference Index: It encodes the index (table_index) of this nearest match in the top_table.&lt;/p&gt;

&lt;p&gt;Mode Bit: It encodes a single bit (block_mode):&lt;/p&gt;

&lt;p&gt;0 (Match): If the current block indices are exactly the same as the reference.&lt;/p&gt;

&lt;p&gt;1 (Difference): If they are different, it computes the XOR difference and encodes the resulting 32-bit value, one byte at a time, using a dedicated AAC model (table_difference).&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Color Endpoint Compression (The two 16-bit colors)
&lt;/h3&gt;

&lt;p&gt;The color endpoints are compressed using a sophisticated form of Spatial Prediction and De-correlated Delta Encoding.&lt;/p&gt;

&lt;h4&gt;
  
  
  Spatial Prediction (Choosing a Reference):
&lt;/h4&gt;

&lt;p&gt;The blocks are processed in a zig-zag pattern across the texture to maximize spatial locality.&lt;/p&gt;

&lt;p&gt;For a block's color endpoint, the compressor calculates which of two neighbors offers the best prediction: the previous block in the row or the block immediately above it.&lt;/p&gt;

&lt;p&gt;A single bit (color_reference) is encoded to tell the decoder which neighbor (and thus which color) to use as a reference for delta encoding.&lt;/p&gt;

&lt;h4&gt;
  
  
  De-correlated Delta Encoding:
&lt;/h4&gt;

&lt;p&gt;The difference (delta) in Red, Green, and Blue components between the current color and the chosen reference color is calculated.&lt;/p&gt;

&lt;p&gt;Green First: The Green component delta is encoded first.&lt;/p&gt;

&lt;p&gt;R/B Prediction: The algorithm then uses the Green delta to predict the Red and Blue deltas (dred -= dgreen/2, dblue -= dgreen/2). This removes common information (e.g., if Green increases, R and B are likely to increase too), making the residuals smaller and easier to compress.&lt;/p&gt;

&lt;p&gt;The residual Red and Blue deltas are encoded next. This sequence (Green -&amp;gt; Red residual -&amp;gt; Blue residual) significantly increases compression efficiency by de-correlating the color channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. BC4 Compression (bc4_crunch)
&lt;/h2&gt;

&lt;p&gt;BC4 blocks (8 bytes) consist of two 8-bit color endpoints (luminance/alpha) and one 48-bit block of 16 3-bit indices.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Color Endpoint Compression (The two 8-bit colors)
&lt;/h3&gt;

&lt;p&gt;Compression relies on strong spatial prediction for Color 0 and local prediction for Color 1.&lt;/p&gt;

&lt;h4&gt;
  
  
  Color 0 (The First Endpoint):
&lt;/h4&gt;

&lt;p&gt;It uses a Parallelogram Prediction (a common technique in image compression) to predict the current Color 0 value based on surrounding neighbors: Reference = Left + Up - UpLeft.&lt;/p&gt;

&lt;p&gt;The difference (delta) between the actual Color 0 and this spatial reference is calculated (with wrapping modulo 256) and encoded (color_delta[0]).&lt;/p&gt;

&lt;h4&gt;
  
  
  Color 1 (The Second Endpoint):
&lt;/h4&gt;

&lt;p&gt;It's encoded as a delta from the current block's Color 0. This assumes a strong relationship between the two endpoints within the same block.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Index Data Compression (The 48-bit Indices)
&lt;/h3&gt;

&lt;p&gt;This is the most complex part, combining dictionary-based encoding with powerful contextual modeling for dictionary misses.&lt;/p&gt;

&lt;h4&gt;
  
  
  Adaptive Dictionary (Move-to-Front):
&lt;/h4&gt;

&lt;p&gt;A small dictionary (DICTIONARY_SIZE = 256) of 48-bit index patterns is maintained.&lt;/p&gt;

&lt;p&gt;This dictionary is a Move-to-Front (MTF) list: when an entry is used, it is moved to the front (index 0), ensuring the most recently used patterns have the shortest code length.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dictionary Lookup and Mode Bit:
&lt;/h4&gt;

&lt;p&gt;For the current block's 48-bit index pattern (bitfield), the compressor searches for the nearest match in the MTF dictionary based on Hamming distance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mode Bit (use_dict):
&lt;/h4&gt;

&lt;p&gt;1 (Hit/Near Match): If the nearest match is very close (score &amp;lt; 4 bits different), it encodes 1. It then encodes the index of the match and the XOR difference (split into 16 separate 3-bit symbols), similar to BC1. The entry is moved to the front.&lt;/p&gt;

&lt;p&gt;0 (Miss/New Pattern): If the match is poor, it encodes 0. The current pattern is pushed to the front of the MTF dictionary.&lt;/p&gt;

&lt;h4&gt;
  
  
  Contextual Local Prediction (Dictionary Miss):
&lt;/h4&gt;

&lt;p&gt;If a pattern is not found in the dictionary, it's compressed locally using two levels of context:&lt;/p&gt;

&lt;p&gt;Index-Chain XOR: The indices (processed in a zig-zag pattern within the block) are encoded as the XOR difference from the previously encoded index in the chain (block_previous ^ data).&lt;/p&gt;

&lt;p&gt;Contextual Model Selection: The key innovation: the choice of which AAC model to use for this XOR difference is based on two things:&lt;/p&gt;

&lt;p&gt;Endpoint Range: The overall difference between the block's two endpoints (int_abs(color[0] - color[1])). The code uses multiple buckets (e.g., range &amp;lt;8, range &amp;lt;32, or max range) to select a group of models (indices[24]).&lt;/p&gt;

&lt;p&gt;Previous Index Value: Within that group, the specific model is selected by the value of the block_previous index. This means the probability of the next index is modeled based on the current index and the block's general contrast, making the prediction extremely precise.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>dxt</category>
      <category>compression</category>
    </item>
    <item>
      <title>onedraw — a GPU-driven 2D renderer</title>
      <dc:creator>Geolm</dc:creator>
      <pubDate>Sun, 02 Nov 2025 13:24:14 +0000</pubDate>
      <link>https://dev.to/geolm/onedraw-a-gpu-driven-2d-renderer-151c</link>
      <guid>https://dev.to/geolm/onedraw-a-gpu-driven-2d-renderer-151c</guid>
      <description>&lt;p&gt;Hi here's a doc I wrote about my open-source metal renderer, it's the first 2 parts. Next I'll write about the rasterization. &lt;/p&gt;

&lt;p&gt;Don't hesitate to clone the repo, ask questions, report a bug or contact me. Have a nice day, Geolm.&lt;/p&gt;

&lt;p&gt;URL : &lt;a href="https://github.com/Geolm/onedraw" rel="noopener noreferrer"&gt;https://github.com/Geolm/onedraw&lt;/a&gt;&lt;br&gt;
contact : &lt;a href="https://bsky.app/profile/geolm.bsky.social" rel="noopener noreferrer"&gt;Geolm&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals and initial architecture
&lt;/h2&gt;

&lt;p&gt;I started the project with the following objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not triangle-based: shapes are defined using signed distance functions (SDFs).&lt;/li&gt;
&lt;li&gt;High quality: anti-aliased edges by default, perfectly smooth curves (no tessellation required), optimized for high-resolution displays.&lt;/li&gt;
&lt;li&gt;Fast and GPU-driven: offload as much work as possible to the GPU and minimize draw calls.&lt;/li&gt;
&lt;li&gt;Efficient alpha blending: designed to make extensive use of transparency without significant performance cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;To achieve high performance, &lt;code&gt;onedraw&lt;/code&gt; minimizes unnecessary computations during rasterization. Since it primarily uses &lt;a href="https://iquilezles.org/articles/distfunctions2d/" rel="noopener noreferrer"&gt;signed distance functions (SDFs)&lt;/a&gt; to render shapes—and these functions can be relatively expensive—efficient culling is essential.&lt;/p&gt;

&lt;p&gt;The screen is divided into &lt;strong&gt;16×16 pixel tiles&lt;/strong&gt;. A &lt;strong&gt;compute shader&lt;/strong&gt; builds a &lt;strong&gt;linked list of draw commands per tile&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
When a tile is rasterized, the fragment shader has direct access to the exact set of draw commands that affect that tile—ensuring that only relevant shapes are processed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gi9tvltsgwjwf9ifk75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gi9tvltsgwjwf9ifk75.png" alt="Overview" width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  GPU-Driven Pipeline
&lt;/h2&gt;

&lt;p&gt;The entire linked-list generation process happens &lt;strong&gt;on the GPU&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
If a tile contains one or more draw commands, it’s automatically added to a list of tiles to be rendered.&lt;br&gt;&lt;br&gt;
Finally, an &lt;strong&gt;indirect draw call&lt;/strong&gt; is issued to rasterize only those active tiles—eliminating CPU overhead and allowing fully GPU-driven rendering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Binning commands
&lt;/h2&gt;

&lt;p&gt;Binning is the process that generates the &lt;strong&gt;per-tile linked lists&lt;/strong&gt; of draw commands.&lt;br&gt;&lt;br&gt;
It works by performing intersection tests between each draw command and the bounding box of every tile.&lt;br&gt;&lt;br&gt;
Classic intersection methods are used, such as the &lt;strong&gt;Separating Axis Theorem (SAT)&lt;/strong&gt; for oriented shapes and &lt;strong&gt;distance-to-center&lt;/strong&gt; checks for circular ones.&lt;/p&gt;

&lt;p&gt;Some additional factors are also considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anti-aliasing
&lt;/li&gt;
&lt;li&gt;groups of shapes
&lt;/li&gt;
&lt;li&gt;smoothmin
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Anti-aliasing
&lt;/h2&gt;

&lt;p&gt;The width of the anti-aliasing region (defined by the user in &lt;code&gt;onedraw&lt;/code&gt;) must be taken into account during intersection testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovds0d4yoqc3q06d6vu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovds0d4yoqc3q06d6vu3.png" alt="anti-aliasing" width="564" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the example above, even if the disc does not mathematically intersect the tile’s bounding box, the anti-aliasing width extends beyond it.&lt;br&gt;&lt;br&gt;
To avoid visible seams or straight edges along tile borders, the disc is still added to the tile’s linked list so that edge pixels are correctly shaded.&lt;br&gt;
We simply grow the size of the bounding box by the width of the anti-aliasing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Group of shapes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;onedraw&lt;/code&gt; supports combining multiple shapes into a &lt;strong&gt;group&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
A group behaves like a single shape: for example, it can have a global transparency even if it contains multiple layers of shapes, or it can have an outline that applies to the entire group.&lt;/p&gt;

&lt;p&gt;Groups are defined by wrapping shapes between &lt;strong&gt;begin group&lt;/strong&gt; and &lt;strong&gt;end group&lt;/strong&gt; commands.&lt;br&gt;&lt;br&gt;
The end group command triggers the color output (and, if enabled, the outline).&lt;/p&gt;

&lt;p&gt;Each begin/end group command is assigned a &lt;strong&gt;global bounding box&lt;/strong&gt; that encompasses all shapes within the group.&lt;br&gt;&lt;br&gt;
This ensures that both the begin and end commands are included whenever the group affects a tile.&lt;br&gt;&lt;br&gt;
If a group has no shapes intersecting the tile, the linked list is adjusted to skip that unused group, avoiding unnecessary processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Smoothmin
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;smoothmin&lt;/strong&gt; operator allows multiple shapes to blend smoothly, even when they don’t intersect.&lt;br&gt;&lt;br&gt;
The &lt;em&gt;k-factor&lt;/em&gt; (as described on &lt;a href="https://iquilezles.org/articles/smin/" rel="noopener noreferrer"&gt;Inigo Quilez’s website&lt;/a&gt;) controls how far a shape can blend with another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93gukxjjxr5aeh3cpvw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93gukxjjxr5aeh3cpvw2.png" alt="smoothmin" width="689" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During binning, the tile’s bounding box is expanded by this value to ensure that shapes contributing to a smooth blend are not missed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance considerations
&lt;/h2&gt;

&lt;p&gt;Even though binning is performed on the GPU, it’s not free. Let’s look at the numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At 1440p, the screen contains &lt;strong&gt;160 × 90 = 14,400 tiles&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onedraw&lt;/code&gt; supports up to &lt;strong&gt;65,536 draw commands&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In the worst case, that’s around &lt;strong&gt;943 million&lt;/strong&gt; intersection tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With such a naïve approach, the binning step could easily cost more than the actual rasterization. 😬&lt;/p&gt;




&lt;h2&gt;
  
  
  Quantized AABB
&lt;/h2&gt;

&lt;p&gt;The first optimization is to store an &lt;strong&gt;AABB&lt;/strong&gt; (axis-aligned bounding box) for each command and use it as a quick pre-test before running more complex intersection math.&lt;br&gt;&lt;br&gt;
The bounding boxes are &lt;strong&gt;quantized&lt;/strong&gt; to the tile resolution (16 pixels), making the test extremely fast and simple.  &lt;/p&gt;

&lt;p&gt;Additionally, the AABBs are stored in a separate buffer from the draw commands to avoid &lt;strong&gt;cache thrashing&lt;/strong&gt; and improve memory access efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hierarchical binning
&lt;/h2&gt;

&lt;p&gt;Even with AABB pre-tests, having many commands means each tile still performs a significant amount of work.&lt;br&gt;&lt;br&gt;
To reduce this, we introduce &lt;strong&gt;hierarchical binning&lt;/strong&gt; — pre-building a list of commands for larger screen regions.&lt;/p&gt;

&lt;p&gt;A region covers &lt;strong&gt;16 × 16 tiles&lt;/strong&gt;, and each region keeps a list of the commands that affect it.&lt;br&gt;&lt;br&gt;
When binning individual tiles, we only consider the commands from that region’s list instead of all global commands.&lt;/p&gt;

&lt;p&gt;This sounds great, but there’s a catch:&lt;br&gt;
When binning commands for the region in a compute shader, if we assign one thread per region, it becomes very slow — there are relatively few regions but potentially many commands. GPUs perform best when running &lt;strong&gt;a large number of lightweight threads&lt;/strong&gt;, not a few heavy ones.&lt;/p&gt;

&lt;p&gt;To fix this, we &lt;strong&gt;invert the process&lt;/strong&gt;: each thread processes a single command and adds it to the region lists it intersects.&lt;br&gt;&lt;br&gt;
However, since this happens in parallel, we lose the guaranteed &lt;strong&gt;ordering of commands&lt;/strong&gt;, which is critical in 2D rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Predicate + exclusive scan
&lt;/h2&gt;

&lt;p&gt;To keep the order of commands, we use the classic pattern :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkgmhmnrimqgbcyguxbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkgmhmnrimqgbcyguxbe.png" alt="predicate" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;predicate compute shader&lt;/strong&gt; evaluates the visibility of each command (one thread per command) and writes the result — &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; — to a buffer.
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;exclusive scan compute shader&lt;/strong&gt; then processes this buffer to build a compact list of visible command indices.
&lt;/li&gt;
&lt;li&gt;Finally, another compute shader uses this predicate and the indices lists to write the corresponding commands into the output buffer.
This approach allows us to keep a &lt;strong&gt;thread-per-command&lt;/strong&gt; model while efficiently filtering out invisible ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process is applied for all regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intrinsics trick
&lt;/h2&gt;

&lt;p&gt;Typically, the exclusive scan pass consists of multiple cascaded compute shaders to produce the final result. In our case, however, since we have a maximum of 65k commands and know the SIMD group size on Apple Silicon, we can perform the entire operation in a single pass using the &lt;code&gt;simd_prefix_exclusive_sum&lt;/code&gt; function and threadgroup memory.&lt;/p&gt;

&lt;p&gt;You can look at the &lt;a href="https://raw.githubusercontent.com/Geolm/onedraw/refs/heads/main/src/shaders/binning.metal" rel="noopener noreferrer"&gt;binning shader&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>gpudriven</category>
      <category>metal</category>
      <category>graphics</category>
      <category>sdf2d</category>
    </item>
  </channel>
</rss>
