Status as of 2026-03-15: Phases 1-7, extension mod port, multi-version support, MC depth comparison, cloud rendering, and DH 3.0 compatibility complete. LODs render with correct colors, depth, lightmap, transparency, compositing, SSAO, fog, noise, earth curvature, overdraw prevention, cloud depth, weather ordering, and MC depth occlusion via an extension mod (mixin-based, no DH source modifications). Supports MC 1.20.6 (VulkanMod 0.4.2) and MC 1.21.11 (VulkanMod 0.6.1) from a single codebase. Compatible with both DH 2.4.x and 3.0.x.
The Vulkan backend is implemented as a Fabric extension mod (dh-vulkanmod) in the vulkan/ subproject. It requires Distant Horizons to be installed and uses mixins to intercept DH's rendering pipeline without modifying DH's source code. All version-specific API differences are centralized in Compat.java using Manifold preprocessor directives.
Core files under vulkan/src/main/java/com/braffolk/dhvulkan/:
VulkanRenderContext.java— Pipeline creation, UBO management, shader conversion, draw callsVulkanRenderEngine.java—VulkanBackendimpl, per-frame uniform fill, VBO cache, draw dispatchDhVulkanFramebuffer.java— DH-owned Vulkan framebuffer with color+depth, auto-resizeDhCompositePipeline.java— Fullscreen triangle composite with depth biasDhSsaoPipeline.java— 2-pass SSAO post-processDhFogPipeline.java— 2-pass fog post-process (distance + height)DhDepthReaderPipeline.java— Copies MC depth to sampleable R32F texture (required on NVIDIA Windows)IVulkanRenderDelegate.java— Interface for the Vulkan delegateDhVulkanMixinPlugin.java— IMixinConfigPlugin for resolving mixin conflicts between DH and VulkanModcompat/Compat.java— Single source of truth for all version-specific API differences
Mixins under vulkan/src/main/java/com/braffolk/dhvulkan/mixin/:
MixinLodBufferContainer— InterceptsuploadBuffersDirect()to store raw vertex data directlyMixinLodRenderer— RedirectsLodRenderer.renderLodPass()to the Vulkan delegateMixinLevelRenderer— Triggers composite after MC terrain rendersMixinGLProxy— Creates dummy GLProxy, bypasses GL thread assertionsMixinGLBuffer— Cancels GL buffer operations (bind, create, upload, destroy)MixinGLVertexBuffer— Duck interface for storing Vulkan buffer handles on VBOsMixinGLState— Cancels GL state machine operationsMixinLightMapWrapper— Cancels raw GL calls in DH's LightMapWrapper (prevents crashes in Vulkan context)
Vulkan shaders under vulkan/src/main/resources/shaders/vulkan/:
dh_terrain.vert/dh_terrain.frag— Terrain rendering shaders (withgl_ClipDistanceoverdraw)dh_apply.vert/dh_apply.frag— Composite shadersdh_ssao.vert/dh_ssao.frag/dh_ssao_apply.frag— SSAO shadersdh_fog.frag/dh_fog_apply.frag— Fog shadersdh_depth_read.frag— MC depth reader (copies depth to R32F)
Root causes found and fixed:
- White LODs + 180° culling: Vertex and fragment shaders had different UBO layouts at
binding=0. Fragment shader read matrix data asuClipDistance→ garbage values → wrong discard boolcompile error: Changedbool→intin UBO but forgot!var→var == 0conversion- Dark colors:
VTextureSelector.getImage(0)returns block atlas, not lightmap. Sampling atlas with lightmap UVs → garbage. Bypassed with full brightness constant - Transparency: MC's blend state inherited by DH pipeline. Disabled
PipelineState.blendInfo.enabled - No depth writes: MC had
depthMask=false. Overridden totrue - Lightmap always daytime: Three bugs — (a) texelFetch coordinates swapped (blockLight,skyLight) vs original (skyLight,blockLight), (b) unnecessary
.bgrswizzle, (c) replaced CPULightmapManagerwith MC's framebuffer lightmap viaGlTexture.glId()→VkGlTexture→VulkanImage
- LODs render behind MC terrain via depth bias
- Uses MC's projection matrix (not DH's) for compatible depth values
- Lightmap: uses MC's framebuffer-rendered lightmap via VulkanMod's GL emulation layer
- Added
setBlendState(boolean)toIVulkanRenderDelegate— togglesPipelineState.blendInfo - Sets blend functions:
SRC_ALPHA/ONE_MINUS_SRC_ALPHA(RGB),ONE/ONE_MINUS_SRC_ALPHA(alpha) - Re-binds pipeline after blend state change (VulkanMod bakes blend into pipeline objects)
- VBO cache refactored with
CachedBuffer(tracks Vulkan buffer + ByteBuffer identity) - Invalidation: detects when
vulkanBufferHandlechanges and frees old GPU buffer - Proper cleanup in
cleanup()frees all cached Vulkan buffers - Vertex buffers use
GPU_MEM(device-local VRAM) with automatic staging via VulkanMod
- Full save/restore in
beginFrame()/endFrame():cull,depthMask,depthFun,topology,polygonMode,blendInfo(all 6 fields) - Explicit
topology = TRIANGLE_LIST,polygonMode = FILLset per frame
- DH-owned Vulkan framebuffer (
DhVulkanFramebuffer.java) — color (RGBA8) + depth attachments withSAMPLED_BIT - Render pass with
LOAD_OP_CLEAR/STORE_OP_STOREon both attachments,finalLayout = SHADER_READ_ONLY_OPTIMAL - Render pass switching — flow: End MC pass → Begin DH pass → Render LODs → End DH pass → Rebind MC pass → Composite
- Composite pipeline (
DhCompositePipeline.java) — fullscreen triangle (viagl_VertexIndex) with depth writes viagl_FragDepth - Composite shaders (
dh_apply.vert,dh_apply.frag) — Vulkan GLSL 450, Y-flip for framebuffer coords - Depth bias (+0.0001 in composite shader) — LODs always slightly behind MC terrain, no z-fighting
-
uClipDistance— overdraw prevention viagl_ClipDistance[0](hardware vertex-level clipping) + fragment shader dithered fade - Window resize — auto-recreate framebuffer via
Renderer.addOnResizeCallback() - Back-face culling enabled — ~50% fragment reduction, +50fps on M1 Max (170fps vs 120fps)
- Explicit depth test —
depthTest=true,depthFun=GL_LEQUALensures Early-Z hardware is active - Removed
polygonOffset— no longer needed with own framebuffer + depth bias
- Transparent block overlap: Per-pixel MC depth comparison is now implemented (see MC Depth Comparison below). The
deferredCompositepath copies MC's depth to an R32F texture for sampling. - Vulkan constants inlined:
DhVulkanFramebuffer.javaandDhCompositePipeline.javainline Vulkan VK10 constants asstatic final intbecauseorg.lwjgl.vulkanis not on the fabric module's compile classpath. - VRenderSystem uses GL constants: All
depthFunand similar values must use GL constants (e.g.,GL_LEQUAL=515,GL_ALWAYS=519), not raw Vulkan values. VulkanMod converts them internally viaPipelineState.DepthState.glToVulkan(). - Dummy UBO at binding 0: VulkanMod's pipeline system requires at least one UBO. The composite pipeline uses a 4-byte dummy.
- SSAO — Vulkan-native 2-pass post-process, see details below
- Noise texture — integrated into terrain fragment shader (
flat_shaded.frag): procedural per-block dithering viauNoiseEnabled,uNoiseSteps,uNoiseIntensity,uNoiseDropoffuniforms populated from config - Fog — Vulkan-native 2-pass post-process via
DhFogPipeline.java, see details below - Earth curvature — vertex shader curves terrain based on
uEarthRadius - Wireframe debug — needs
VK_POLYGON_MODE_LINEpipeline variant - Cloud rendering — custom Vulkan cloud renderer with VBO mesh geometry, correct depth compositing against LOD terrain. Supports fast/fancy modes. VM 0.6+ only.
Files:
DhSsaoPipeline.java— orchestrates both passes, manages resourcesdh_ssao.vert— shared fullscreen triangle vertex shader (gl_VertexIndex, Y-flip)dh_ssao.frag— pass 1: spiral depth-sampling occlusion computationdh_ssao_apply.frag— pass 2: optional bilateral blur + multiplicative apply
Architecture:
- Pass 1 renders into an intermediate R16F
Framebuffer(raw occlusion values 0.0–1.0) - Pass 2 reads the R16F texture + DH depth, applies blur (currently disabled for perf), and multiplicatively blends onto DH's color buffer using
LOAD_OP_LOAD - Inserted in
VulkanRenderDelegate.endFrame()between ending DH's render pass and the composite pass - Gated on
Config.Client.Advanced.Graphics.Ssao.enableSsao
Current Parameters (tuned for Vulkan path):
uSampleCount=4(GL uses 6, reduced for perf)uRadius=4.0,uStrength=0.18,uMinLight=0.30,uBias=0.025gBlurRadius=0(blur disabled for performance, bilinear filtering on R16F suffices)- Pass 2 render pass is cached (created once, reused every frame)
Important
Critical: Do NOT apply Y-flip to depth reconstruction in dh_ssao.frag.
The Vulkan vertex shader flips TexCoord.y for framebuffer sampling, and the natural instinct is to negate Y when converting TexCoord→NDC for the inverse projection matrix (since MC's projection uses GL convention with Y=+1 at top). However, testing showed this produces overly aggressive, unnatural occlusion with harsh dark halos. The non-flipped version produces visually correct, MC-like ambient occlusion. This is likely because VulkanMod's viewport flip already handles the Y convention, making the depth values in DH's framebuffer consistent with the TexCoord mapping.
Warning
Performance: SSAO at retina resolution is expensive. On M1 Max at retina fullscreen (~5M pixels), SSAO costs ~35% FPS (155→99). Half-resolution rendering was attempted but caused horizontal banding artifacts — likely needs better bilinear upscale handling or a dedicated upscale pass. Current mitigations: sample count reduced to 4, blur disabled. Future options: compute shader single-pass, temporal accumulation, or quarter-res with smart upscale.
Note
Projection matrix: The Vulkan SSAO uses mcProjectionMatrix (not dhProjectionMatrix) because DH's LODs are rendered with MC's projection in the Vulkan path (for depth compatibility with MC terrain compositing). This differs from the GL path which uses dhProjectionMatrix. The parameters were tuned accordingly.
Files:
DhFogPipeline.java— orchestrates both passes, manages resources (~25 config-driven uniforms)dh_fog.frag— pass 1: depth → world pos reconstruction, far fog + height fog (3 falloff types, 10 mixing modes)dh_fog_apply.frag— pass 2: depth-gated fog texture passthrough with SRC_ALPHA blend- Reuses
dh_ssao.vertfor fullscreen triangle vertex shader
Architecture:
- Pass 1 renders into intermediate RGBA16F
Framebuffer(fog color + alpha 0.0–1.0) - Pass 2 reads fog texture + DH depth, applies depth-gated fog with
SRC_ALPHA / ONE_MINUS_SRC_ALPHAblend onto DH's color buffer - Inserted in
VulkanRenderDelegate.endFrame()after SSAO, before composite - Gated on
Config.Client.Advanced.Graphics.Fog.enableDhFog - Pass 2 render pass is cached (same pattern as SSAO)
Important
Projection matrix for fog: Like SSAO, the fog pipeline uses mcProjectionMatrix * dhModelViewMatrix for world position reconstruction. Initially dhProjectionMatrix was used (matching the GL path), but this caused all LODs to render as 100% fog because the depth values were produced with mcProjectionMatrix. The inverse matrix must always match the matrix that produced the depth buffer.
Goal: Per-pixel depth comparison between MC terrain and DH LODs, so LODs are hidden wherever MC has rendered.
DhDepthReaderPipeline.java— Renders MC's swapchain depth into a sampleable R32F color texture via a separate render pass. Required because Vulkan can't sample a depth attachment while it's bound to the active render pass.dh_depth_read.frag— Simple passthrough shader that samples the depth texture and writes togl_FragDepth/ color output.deferredComposite()inVulkanRenderDelegate— Called fromMixinLevelRendererafter MC terrain renders. Reads MC depth via the depth reader, then composites DH's framebuffer with per-pixel depth comparison.- Works on all platforms: macOS (MoltenVK), Windows (NVIDIA, AMD), Linux.
- Respects DH's
vanillaFadeModesetting:- NONE: Composites in
endFrame()immediately (no deferred pass). MC depth available for debug mode 6 only. - SINGLE/DOUBLE:
endFrame()composites without MC depth, thendeferredComposite()re-composites with MC depth from the depth reader.
- NONE: Composites in
- Unified rendering path — no version-specific branching.
- Vertex shader:
gl_ClipDistance[0] = length(vertexWorldPos.xz) - uClipDistance— hardware clips LOD geometry within MC's render distance. Zero fragment shader cost for clipped geometry. - Fragment shader: Dithered fade via
smoothstep(uClipDistance, uClipDistance * 1.5, viewDist)+ Bayer matrix noise — smooth visual transition when dithering is enabled. uClipDistancecomputed fromrenderDistChunks * 16 * overdraw, matching original DH's distribution.
Important
Do NOT modify the projection matrix for overdraw clipping. An earlier attempt used Mat4f.setClipPlanes() to push the near clip plane to the overdraw distance. This broke depth ordering — LODs rendered in front of MC terrain because the different near/far clip planes produced incompatible depth buffer values. The gl_ClipDistance approach clips without affecting depth values.
Warning
VM 0.4.2 descriptor caching issue. On VulkanMod 0.4.2, texture descriptors bound to VTextureSelector slots persist across pipeline binds. If endFrame() binds a white fallback texture to slot 4 (for null MC depth), that fallback descriptor persists into deferredComposite()'s composite call, overriding the real MC depth texture. Fix: endFrame() skips compositing entirely for debug mode 6 when deferredComposite will run.
Note
Depth reader timing is critical. readDepth() must be called AFTER Renderer.endRenderPass() (MC depth is no longer an active attachment) and BEFORE Compat.rebindMainTarget() (MC's render pass restarts). The prepareMcDepthForSampling() / restoreMcDepthView() split ensures depth-only image views persist through the descriptor write.
Caution
NVIDIA: VulkanMod's GPU_MEM vertex buffers produce incorrect data in fullscreen quad geometry.
When using a 4-vertex quad with index buffer (drawIndexed(vbo, ibo, 6)), NVIDIA only rasterized one of the two triangles. The vertex buffer data was read incorrectly, likely caused by VulkanMod's VMA sub-allocation offsets or async staging buffer transfers.
Fix: All fullscreen passes (composite, SSAO, fog) generate positions from gl_VertexIndex in the vertex shader, bypassing vertex buffer data entirely. A single oversized triangle at NDC (-1,-1), (3,-1), (-1,3) covers the entire viewport — the GPU clips the excess. This is also a Vulkan best practice (fewer vertices, no shared diagonal edge, no helper lane overhead).
The vPosition input is declared but unused (required by VulkanMod's pipeline vertex format binding). A dummy 24-byte vertex buffer is still bound.
Warning
Composite depth test must use GL_ALWAYS (519).
The composite runs before MC terrain, so MC's depth buffer contains DONT_CARE garbage. GL_LEQUAL fails against random values. MoltenVK implicitly clears to 1.0, hiding the bug on macOS.
- VulkanMod does not support shader packs — this phase is blocked until it does
- DH's Iris integration (
IRIS_ACCESSOR) is skipped for Vulkan path - Custom shader overrides via
IDhApiShaderProgramremain GL-only
- Handle renderer reset —
cleanup()now resetsinitFailedflag, allowing re-initialization after settings changes. Previously, a transient init failure permanently disabled rendering with no errors. - Config hot-reload —
DhVulkanConfig.reload()throttled to once per second (was reading JSON from disk every frame) - Per-frame allocation optimization — pre-allocated
Mat4f,float[16],Vec3ffields avoid heap allocations on the hot path - Handle VulkanMod not being loaded (graceful fallback to GL)
- Thread safety: DH runs LOD generation on worker threads — Vulkan buffer uploads must happen on the render thread
Goal: Support MC 1.20.6 (VulkanMod 0.4.2) alongside MC 1.21.11 (VulkanMod 0.6.1) from one codebase.
| Area | VM 0.6.1 (MC 1.21.11) | VM 0.4.2 (MC 1.20.6) |
|---|---|---|
| Buffer imports | net.vulkanmod.vulkan.memory.buffer.* |
net.vulkanmod.vulkan.memory.* |
| IndexBuffer | Constructor takes IndexType.UINT32 |
No IndexType param |
| Drawer.drawIndexed | Supports UINT32 | Hardcodes UINT16 |
| Buffer.scheduleFree | scheduleFree() |
freeBuffer() |
| Uniform suppliers | Info.setBufferSupplier() at construction |
Only resolves built-in MC names; custom uniforms need manual wiring |
| VertexFormatElement | new VFE(id, index, Type, Usage, count) |
new VFE(index, Type, Usage, count) |
| VertexFormat | VertexFormat.builder().add().build() |
new VertexFormat(ImmutableMap) |
| SwapChain access | Renderer.getInstance().getSwapChain() |
Vulkan.getSwapChain() |
| beginRenderPass | Renderer.getInstance().beginRenderPass() |
Requires reflection to access currentCmdBuffer |
| GlTexture lightmap | GlTexture → VkGlTexture bridge exists |
No GL texture bridge (lightmap unavailable) |
| Noise intensity config | Pre-scaled 0–1 float | Unscaled integer (needs * 0.01) |
| Mixin conflicts | None | DH's MixinTextureUtil conflicts with VM's MTextureUtil |
| GL context | Partial GL emulation | No GL context — raw GL calls crash |
- All version differences handled in
Compat.javavia#if MC_VER >= MC_1_21_1/#elseManifold directives DhVulkanMixinPlugin(IMixinConfigPlugin) strips conflicting DH mixins on MC 1.20.6MixinLightMapWrappercancels raw GL calls that would crash in VM 0.4.2's Vulkan-only contextCompat.drawIndexed()bypasses VM 0.4.2's Drawer to issue raw Vulkan commands withVK_INDEX_TYPE_UINT32Compat.setUniformSuppliers()manually wires DH'sMappedBuffersuppliers into VM 0.4.2'sUniformsubclasses
Goal: Port the Vulkan backend from a DH source fork to a standalone Fabric extension mod that works alongside unmodified DH releases.
The fork modified LodBufferContainer.uploadBuffersDirect() directly to add a Vulkan code path that bypasses vbo.uploadBuffer() and stores raw ByteBuffer data on the VBO. The initial extension mod approach used a low-level mixin on GLBuffer.uploadBuffer() to intercept data — this caused missing east/west faces because the interception was too deep in the call stack and version-dependent.
The fix: MixinLodBufferContainer intercepts uploadBuffersDirect() at HEAD, replicating the fork's approach. This grabs raw ByteBuffers from the quad builder before any GL operations, stores them via a duck interface, and cancels the entire GL upload path.
- [NEW]
MixinLodBufferContainer.java— Clean buffer upload interception - [NEW]
MixinLodRenderer.java— Redirects rendering to VulkanRenderDelegate - [NEW]
MixinLevelRenderer.java— Composite trigger after MC terrain - [NEW]
MixinGLProxy.java— Dummy GLProxy, thread assertion bypass - [NEW]
MixinGLBuffer.java— GL call cancellation - [NEW]
MixinGLVertexBuffer.java— Duck interface (IVulkanVertexBuffer) - [NEW]
DhVulkanModEntrypoint.java— Fabric entrypoint - [NEW]
DhVulkanConfig.java— Mod config
- Intercept data at the highest stable level (LodBufferContainer), not deep in the GL stack
- Duck interfaces (
IVulkanVertexBuffer) are the clean way to add fields to DH classes via mixin - DH's
GLProxy.queueRunningOnRenderThread()works correctly even with a dummy GLProxy - Terrain shaders (
standard.vert,flat_shaded.frag) load from DH's jar at runtime — no need to bundle
Phase 8a (core refactor) complete. VulkanBackend interface extracted, pipelines in core/pipeline/, RenderUniforms/VkVertexData created, DH 2.4 mixins organized under mixin/shared/. Both DH 2.4.x and 3.0.x supported from a single codebase via DhConfigHelper abstraction.
DH 3.0 introduces AbstractDhRenderApiDefinition, a pluggable rendering backend with injectable singletons for all render passes (IDhMetaRenderer, IDhTerrainRenderer, IDhSsaoRenderer, IDhFogRenderer, IDhFarFadeRenderer) and factory methods (IVertexBufferWrapper, ILodContainerUniformBufferWrapper). This eliminates 6 of our 8 current mixins.
Instead of intercepting DH's GL pipeline via mixins, we register a VkDhRenderApiDefinition that provides Vulkan implementations of each renderer interface. DH calls our code directly.
Still needed: MixinLightMapWrapper (DH's MixinLightTexture calls GL-dependent uploadLightmap() every frame from outside the rendering API), MixinLevelRenderer (post-MC-terrain composite timing), and a small MixinDependencySetup to register our API definition.
dhvulkan/
├── core/ # Vulkan engine, DH-agnostic, zero DH imports
│ ├── VulkanBackend -- central rendering interface
│ ├── VulkanRenderEngine -- refactored from VulkanRenderDelegate
│ ├── pipeline/ -- SSAO, fog, composite, depth reader
│ └── data/ -- RenderUniforms, VkVertexData
├── dh24/ # DH 2.4.x integration, all 8 current mixins + adapter
│ ├── mixin/ -- MixinLodRenderer, MixinGLProxy, etc.
│ ├── duck/ -- IVulkanVertexBuffer, etc.
│ └── Dh24RenderDelegate -- translates DH 2.4 types to core types
├── api/ # DH 3.0 integration, API impls + 2-3 mixins
│ ├── VkDhRenderApiDefinition
│ ├── renderer/ -- VkMetaRenderer, VkTerrainRenderer, etc.
│ ├── VkVertexBufferWrapper
│ └── mixin/ -- MixinDependencySetup, MixinLightMapWrapper, MixinLevelRenderer
├── bridge/ # Version detection + integration interface
│ ├── DhIntegration -- interface both paths implement
│ └── DhVersionDetector -- runtime class presence check
└── compat/ # MC version compat (existing)
Nothing in core/, api/, bridge/, or compat/ imports from dh24/. Dropping DH 2.4 support = delete dh24/ + edit 2 files (dh-vulkanmod.mixins.json, DhVulkanModEntrypoint.java).
DhVersionDetector checks for AbstractDhRenderApiDefinition class presence at runtime. DhVulkanMixinPlugin.shouldApplyMixin() conditionally loads only the appropriate mixin set.
- Phase 8a: Refactor core. Extract
VulkanBackendinterface fromVulkanRenderDelegate, move pipelines tocore/pipeline/, createRenderUniforms/VkVertexData. No behavior change. Move current mixins todh24/. Verify existing DH 2.4 functionality. - Phase 8b: Add DH 3.0 path. Implement
VkDhRenderApiDefinition+ renderer impls +VkVertexBufferWrapper. AddMixinDependencySetup. Wire conditional loading. Test with DH 3.0.
| File | Purpose |
|---|---|
compat/Compat.java |
Single source of truth for all version-specific API differences |
VulkanRenderContext.java |
Pipeline, UBOs, shader conversion, draw API |
VulkanRenderEngine.java |
Per-frame uniforms, VBO cache, draw dispatch, render pass switching |
VulkanCloudRenderer.java |
Custom cloud rendering with VBO mesh and depth compositing |
DhFrameProfiler.java |
Lightweight per-frame timing for all rendering phases |
DhVulkanFramebuffer.java |
DH-owned Vulkan framebuffer (color + depth) |
DhCompositePipeline.java |
Fullscreen triangle composite pipeline |
DhSsaoPipeline.java |
2-pass SSAO post-process |
DhFogPipeline.java |
2-pass fog post-process |
DhDepthReaderPipeline.java |
Copies MC depth to sampleable R32F texture |
DhVulkanMixinPlugin.java |
IMixinConfigPlugin for mixin conflict resolution |
DhVulkanConfig.java |
JSON config with throttled hot-reload |
MixinLodBufferContainer.java |
Intercepts buffer upload, stores raw vertex data |
MixinLodRenderer.java |
Redirects LodRenderer to Vulkan delegate |
MixinLevelRenderer.java |
Triggers deferredComposite after MC terrain |
MixinLightMapWrapper.java |
Cancels raw GL calls in DH's lightmap code |
dh_terrain.vert / dh_terrain.frag |
Terrain shaders (gl_ClipDistance overdraw) |
dh_apply.vert / dh_apply.frag |
Vulkan composite shaders |
dh_depth_read.frag |
MC depth reader shader |
VertexFormats.java (DH) |
DH's custom vertex format definition (16 bytes) |
PipelineState.java (VulkanMod) |
Render state (cull, blend, depth) |
Offset Size GL Attr Vulkan Format Shader Input
0 8B attr 0 R16G16B16A16_SINT ivec4 vPosition (x,y,z + meta)
8 4B attr 1 R8G8B8A8_UNORM vec4 color (RGBA)
12 4B attr 2 R32_SINT (material/normal/padding, unused by shader currently)
DH packs position (3 unsigned shorts) + light/micro-offset metadata into vPosition.a.
The shader casts ivec4 → uvec4 for bitwise operations on the metadata.