arkavo-org / arkavo-org/VRMMetalKit

Optimize outline rendering with Indirect Command Buffers

Open
#88 1 comment 0 reactions 0 assignees View on GitHub
enhancement performance
Dominant language
Swift
Stars
6
Forks
2
Avg merge
18h 51m
Merged PRs (30d)
26

Description

## Summary

MToon outlines currently use a two-pass approach (render geometry, then render inverted/extruded geometry for outlines). This can be optimized using Metal's Indirect Command Buffers (ICB) to reduce CPU overhead and draw call latency.

## Current Implementation

In `VRMRenderer`, outlines are rendered as a separate pass:
1. First pass: Render front faces with MToon shading
2. Second pass: Render back faces with vertex extrusion for outlines

Each mesh with outlines requires separate draw call encoding.

## Optimization Opportunity

### Indirect Command Buffers

ICBs allow the GPU to encode draw commands, eliminating per-draw CPU overhead:

```swift
// Create ICB once
let icbDescriptor = MTLIndirectCommandBufferDescriptor()
icbDescriptor.commandTypes = [.draw, .drawIndexed]
icbDescriptor.inheritBuffers = false
icbDescriptor.maxVertexBufferBindCount = 8
icbDescriptor.maxFragmentBufferBindCount = 4

let indirectBuffer = device.makeIndirectCommandBuffer(
descriptor: icbDescriptor,
maxCommandCount: maxMeshCount * 2, // base + outline per mesh
options: []
)
```

### GPU-Driven Encoding

A compute shader can populate the ICB based on visibility and material properties:

```metal
kernel void encodeOutlineCommands(
device MTLIndirectRenderCommand* commands [[buffer(0)]],
constant MeshInfo* meshes [[buffer(1)]],
constant uint& meshCount [[buffer(2)]],
uint id [[thread_position_in_grid]]
) {
if (id >= meshCount) return;

MeshInfo mesh = meshes[id];
if (mesh.outlineWidth <= 0) return; // Skip meshes without outlines

// Encode outline draw command
commands[id].setVertexBuffer(mesh.vertexBuffer, 0);
commands[id].setFragmentBuffer(mesh.materialBuffer, 0);
commands[id].drawIndexedPrimitives(
primitiveType: triangle,
indexCount: mesh.indexCount,
indexType: uint16,
indexBuffer: mesh.indexBuffer,
indexBufferOffset: 0,
instanceCount: 1,
baseVertex: 0,
baseInstance: id
);
}
```

### Execute with Single Call

```swift
renderEncoder.executeCommandsInBuffer(
indirectBuffer,
range: 0..20)
- Complex VRM models
- Lower-end devices where CPU is the bottleneck

Contributor guide

Open the contributing guide

Research direction

Start by locating VRMRenderer and tracing the existing two-pass MToon outline rendering. Review the ICB and compute-kernel requirements against the repository's current Metal targets, then benchmark the existing and proposed paths. Done means the outline pass uses ICBs where supported, has the stated older-device fallback, and includes before-and-after CPU/GPU measurements.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
computer-graphics, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.