blog: update GPU-skinned herds post — 4.6.5 (420e74bf8e), mat4x3 palette, far-LOD, in-place bake, Forge-perf-verified

This commit is contained in:
2026-06-16 01:55:51 +01:00
parent 29a13f9ad5
commit c0a5692a3e
2 changed files with 23 additions and 12 deletions
+21 -10
View File
@@ -5,19 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPU-Skinned Herds: One Draw Call for 1,000 Animated Characters in Godot — Tinqs Blog</title>
<meta name="description" content="Godot has no built-in way to render 1,000 skinned characters in one draw call. We built a GPU skinned-instance renderer into Tinqs Engine that does — 25 crocodiles verified, 1,000+ projected. Pre-built binaries for macOS and Windows.">
<meta name="description" content="Godot has no built-in way to render 1,000 skinned characters in one draw call. We built a GPU skinned-instance renderer into Tinqs Engine that does — now with mat4×3 palette, far-LOD dominant-bone, and in-place bake. Pre-built binaries for macOS and Windows.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://www.tinqs.com/blog/gpu-skinned-herds">
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.tinqs.com/blog/gpu-skinned-herds">
<meta property="og:title" content="GPU-Skinned Herds: One Draw Call for 1,000 Animated Characters in Godot">
<meta property="og:description" content="One draw call, 1,000 animated characters. GPU-skinned herd renderer built into the Tinqs Engine fork of Godot.">
<meta property="og:description" content="One draw call, 1,000 animated characters — now with mat4×3 palette, far-LOD dominant-bone, and in-place bake. GPU-skinned herd renderer built into the Tinqs Engine fork of Godot.">
<meta property="og:image" content="https://www.tinqs.com/img/og-cover.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="GPU-Skinned Herds: One Draw Call for 1,000 Animated Characters in Godot">
<meta name="twitter:description" content="One draw call, 1,000 animated characters. GPU-skinned herd renderer built into the Tinqs Engine fork of Godot.">
<meta name="twitter:description" content="One draw call, 1,000 animated characters — now with mat4×3 palette, far-LOD dominant-bone, and in-place bake.">
<meta name="twitter:image" content="https://www.tinqs.com/img/og-cover.jpg">
<script type="application/ld+json">
@@ -25,7 +25,8 @@
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "GPU-Skinned Herds: One Draw Call for 1,000 Animated Characters in Godot",
"datePublished": "2026-06-14",
"datePublished": "2026-06-16",
"dateModified": "2026-06-16",
"author": {
"@type": "Person",
"name": "Ozan Bozkurt"
@@ -275,12 +276,12 @@
<!-- POST -->
<article class="post">
<a href="/blog/" class="post__back">&larr; All Posts</a>
<span class="post__date">14 June 2026</span>
<span class="post__date">16 June 2026 · updated</span>
<h1 class="post__title">GPU-Skinned Herds: One Draw Call for 1,000 Animated Characters in Godot</h1>
<p class="post__lead">Godot gives you one <code>Skeleton3D</code> per character. Want 200 animals in a herd? That's 200 skeleton nodes, 200 draw calls, and 200 <code>AnimationPlayer</code> ticks every frame. Want 1,000? Now you're measuring in seconds per frame, not frames per second.</p>
<div class="post__body">
<p>We built a GPU skinned-instance renderer into Tinqs Engine that packs every pose into a single texture, uploads once, and draws every instance in one call. 25 crocodiles confirmed first. Then we threw 1,000 animals — 12 types mixed, random-walking — at it and the GPU didn't flinch. Same bone count, same animation fidelity, a tiny fraction of the cost.</p>
<p>We built a GPU skinned-instance renderer into Tinqs Engine that packs every pose into a single texture, uploads once, and draws every instance in one call. 25 crocodiles confirmed first. Then we threw 1,000 animals — 12 types mixed, random-walking — at it and the GPU didn't flinch. <strong>Now upgraded:</strong> mat4×3 palette (37% of original VRAM), far-LOD dominant-bone (3 texel fetches at distance), in-place bake (zero foot-slide), and full frustum cull. Same bone count, same animation fidelity, a tiny fraction of the cost.</p>
<h2>Why the engine needs to change</h2>
<p>The standard Godot approach — one <code>Skeleton3D</code> + one <code>MeshInstance3D</code> per character — works for a handful of animated entities. It breaks down hard at crowd scale:</p>
<ul>
@@ -332,12 +333,22 @@ data.update() # upload only dirty instances, not the whole texture</code></pre
<li><strong>No automatic <code>AnimationPlayer</code> integration.</strong> You drive poses. We give you the texture. Freedom to animate however you want.</li>
<li><strong>No GPU occlusion or LOD.</strong> That's the game's job. The engine provides the tool; the game decides what to draw.</li>
</ul>
<h2>What's new in this build (16 June 2026)</h2>
<ul>
<li><strong>mat4x3 palette (B4).</strong> Each bone packs into 3 RGBA16F texels instead of 4 — 37% of the original VRAM and texel fetch cost. Column-major, doctest-guarded.</li>
<li><strong>Far-LOD dominant-bone.</strong> At distance, each instance uses a single nearest-frame bone (~3 texel fetches vs ~24 near). LOD thresholds per-animal, scaled by body size — giraffes stay crisp 3x farther than rats.</li>
<li><strong>In-place bake.</strong> Walk/run clips no longer translate root motion — the bake strips horizontal drift so the sim owns position. Fixed the notorious slide/skate bug across all animal types.</li>
<li><strong>Full frustum cull (C7).</strong> Only on-screen instances hit the GPU. Caught a sign bug where Godot's outward-pointing frustum normals inverted the cull test.</li>
<li><strong>Bulk instance upload (A1).</strong> One <code>MultiMesh.buffer =</code> per herd per frame — zero per-instance native calls.</li>
</ul>
<p>24 doctests green. Visual-verified on Kraken (M1/Metal) and Forge (Windows/RTX).</p>
<h2>Get the build</h2>
<p>Pre-built editor binaries with <code>agent_skinned</code> baked in — no engine compile required. The game's <code>animal_perf_test.tscn</code> lets you toggle 10 / 100 / 1000 animals and read live FPS:</p>
<p>| Platform | Binary | Engine commit |</p>
<p>|&mdash;&mdash;&mdash;-|&mdash;&mdash;&ndash;|&mdash;&mdash;&mdash;&mdash;&mdash;|</p>
<p>| <strong>macOS ARM64</strong> | <a href="https://tinqs.com/tinqs/builds/media/branch/main/engine/macos-arm64/tinqs.macos.editor.arm64.mono" style="color: var(--c-lime);"><code>tinqs.macos.editor.arm64.mono</code></a> | <code>4fe1323</code> (4.6.4, Xcode 26.3) |</p>
<p>| <strong>Windows x64</strong> | <a href="https://tinqs.com/tinqs/builds/media/branch/main/engine/windows-x64/tinqs.windows.editor.x86_64.mono.exe" style="color: var(--c-lime);"><code>tinqs.windows.editor.x86_64.mono.exe</code></a> | <code>64fb5cc</code> (4.6.4, MSVC 2022) |</p>
<table style="border-collapse:collapse;width:100%;margin:12px 0;">
<tr style="border-bottom:1px solid var(--c-border);"><th style="text-align:left;padding:8px;color:var(--c-muted);">Platform</th><th style="text-align:left;padding:8px;color:var(--c-muted);">Binary</th><th style="text-align:left;padding:8px;color:var(--c-muted);">Engine commit</th></tr>
<tr style="border-bottom:1px solid var(--c-border);"><td style="padding:8px;"><strong>macOS ARM64</strong></td><td style="padding:8px;"><a href="https://tinqs.com/tinqs/builds/media/branch/main/engine/macos-arm64/tinqs.macos.editor.arm64.mono" style="color: var(--c-lime);"><code>tinqs.macos.editor.arm64.mono</code></a></td><td style="padding:8px;"><code>4fe1323</code> (4.6.4, Xcode 26.3)</td></tr>
<tr style="border-bottom:1px solid var(--c-border);"><td style="padding:8px;"><strong>Windows x64</strong></td><td style="padding:8px;"><a href="https://tinqs.com/tinqs/builds/media/branch/main/engine/windows-x64/tinqs.windows.editor.x86_64.mono.exe" style="color: var(--c-lime);"><code>tinqs.windows.editor.x86_64.mono.exe</code></a></td><td style="padding:8px;"><code>420e74bf</code> (4.6.5, MSVC 2022) &#x1F195;</td></tr>
</table>
<p>All builds live in the public <a href="https://tinqs.com/tinqs/builds" style="color: var(--c-lime);"><code>tinqs/builds</code></a> repo — engine source is private, but the binaries are yours. See <a href="https://tinqs.com/tinqs/builds/src/branch/main/manifest.json" style="color: var(--c-lime);"><code>manifest.json</code></a> for checksums and build details.</p>
<p>The engine source lives in <a href="https://tinqs.com/tinqs/engine" style="color: var(--c-lime);"><code>tinqs/engine</code></a> (private). Module docs: <code>modules/agent_skinned/README.md</code> and <code>.agents/wiki/agent-skinned-gpu-herd.md</code>.</p>
<hr>
Before
After
+2 -2
View File
@@ -188,9 +188,9 @@
</a>
<a href="gpu-skinned-herds" class="blog-card">
<span class="blog-card__date">14 June 2026</span>
<span class="blog-card__date">16 June 2026 · updated</span>
<h2 class="blog-card__title">GPU-Skinned Herds: One Draw Call for 1,000 Animated Characters in Godot</h2>
<p class="blog-card__excerpt">Godot can't batch-render 1,000 animated characters. We built a GPU skinned-instance herd renderer into the engine itself — already driving crocodile herds in Ariki. Pre-built editor binaries for macOS and Windows.</p>
<p class="blog-card__excerpt">Godot can't batch-render 1,000 animated characters. We built a GPU skinned-instance herd renderer into the engine — now with mat4x3 palette, far-LOD, in-place bake. Pre-built binaries for macOS and Windows.</p>
<span class="blog-card__read">Read &rarr;</span>
</a>
Before
After