Skip to content

codefiesta/VimKit

Repository files navigation

Build Xcode 16.2+ Swift 6.0+ iOS 18.0+ visionOS 2.0+ macOS 15.0+ License: MIT

VimKit

VimKit is an open-source swift package for reading and rendering VIM files on Apple platforms (iOS, macOS, visionOS) with Metal.

vimkit.mov

Overview

The VimKit package is broken down into 3 seperate modules (VimKit, VimKitCompositor, VimKitShaders).

VIM files are composed of BFAST containers that provide the necessary geometry, assets, entities, and strings buffers used to render and interrogate all of the 3D instances contained in a file.

Although it is possible to render each Instance individually, VimKit leverages instancing to render all Instance's that share the same Mesh in a single draw call.

Geometry.swift and ShaderTypes.h are the best sources to understand the details of how the geometry, positions, indices and data structures used for rendering are organized.

VimKit

Provides the core library for reading and rendering VIM files on macOS and iOS.

References

VimKitCompositor

Provides the core library for rendering VIM files on visionOS (Apple Vision Pro) using CompositorServices.

References

VimKitShaders

C Library that provides types and enums shared between Metal Shaders and Swift.

Direct and Indirect Rendering

VimKit supports both Direct Render Passes (draw commands issued on the CPU) and Indirect Render Passes (draw commands issued on the GPU via Indirect Command Buffers).

Indirect Render Passes (GPU Driven Rendering)

VimKit provides the ability to perform GPU driven rendering by default on all Apple devices with GPU families of .apple4 (Apple A11) or greater. If the device supports indirect command buffers (ICB), the render commands are generated on the GPU to maximize parallelization. The following devices support Indirect Command Buffers:

  • A Mac from mid-2016 and later with macOS 11 and later
  • An iPad with A11 Bionic and later using iPadOS 14.1 and later
  • An iOS device with A11 Bionic and later using iOS 14.1 and later

In order to maximize GPU and CPU parallelization, the Indirect Render Pass will dispatch a thread grid size of width x height where the width == the maximum number of submeshes a mesh can contain and height == the number of instanced meshes the geometry contains. Metal automaticaly calculates the number of threadgroups and provides nonuniform threadgroups if the grid size isn’t a multiple of the threadgroup size.

Indirect.metal and RenderPass+Indirect.swift are the best resources to understand how the kernel code issues rendering instructions on the GPU.

VisionOS Usage

The following is an example of the simplest usage of rendering a VIM file on visionOS:

import CompositorServices
import SwiftUI
import VimKit
import VimKitCompositor

/// The id of our immersive space
fileprivate let immersiveSpaceId = "VimImmersiveSpace"

@main
struct VimViewerApp: App {

    /// Sample Vim File
    let vim = Vim(URL(string: "https://vim02.azureedge.net/samples/residence.v1.2.75.vim")!)

    /// Holds the composite layer configuration
    let configuration = VimCompositorLayerConfiguration()

    /// The ARKit DataProvider context
    let dataProviderContext = DataProviderContext()

    /// Build the scene body
    var body: some Scene {

        // Displays a 2D Window
        WindowGroup {
            Button {
                Task {
                    // Launch the immersive space
                    await openImmersiveSpace(id: immersiveSpaceId)
                }
            } label: {
                Text("Show Immersive Space")
            }
            .task {
                // Start the ARKit HandTracking
                await dataProviderContext.start()
            }
        }

        // Displays the fully immersive 3D scene
        ImmersiveSpace(id: immersiveSpaceId) {
            VimImmersiveSpaceContent(vim: vim,
                                     configuration: configuration,
                                     dataProviderContext: dataProviderContext)
        }
        .immersionStyle(selection: .constant(.full), in: .full)
    }
}