Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 Spatial ComputingGraphics & Games

WWDC23 · 21 min · Spatial Computing / Graphics & Games

Discover Metal for immersive apps

Find out how you can use Metal to render fully immersive experiences for visionOS. We’ll show you how to set up a rendering session on the platform and create a basic render loop, and share how you can make your experience interactive by incorporating spatial input.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 7 snippets

App architecture swift · at 4:45 ↗
@main
struct MyApp: App {
    var body: some Scene {
        ImmersiveSpace {
            CompositorLayer { layerRenderer in
                let engine = my_engine_create(layerRenderer)
                let renderThread = Thread {
                    my_engine_render_loop(engine)
                }
                renderThread.name = "Render Thread"
                renderThread.start()
            }
        }
    }
}
CompositorLayer Configuration swift · at 10:32 ↗
// CompositorLayer configuration

struct MyConfiguration: CompositorLayerConfiguration {
    func makeConfiguration(capabilities: LayerRenderer.Capabilities,
                           configuration: inout LayerRenderer.Configuration) {

        let supportsFoveation = capabilities.supportsFoveation
        let supportedLayouts = capabilities.supportedLayouts(options: supportsFoveation ?
                                                             [.foveationEnabled] : [])

        configuration.layout = supportedLayouts.contains(.layered) ? .layered : .dedicated

        configuration.isFoveationEnabled = supportsFoveation

        // HDR support
        configuration.colorFormat = .rgba16Float
   }
}
Render loop objectivec · at 12:20 ↗
void my_engine_render_loop(my_engine *engine) {
    my_engine_setup_render_pipeline(engine);

    bool is_rendering = true;
    while (is_rendering) @autoreleasepool {
        switch (cp_layer_renderer_get_state(engine->layer_renderer)) {
            case cp_layer_renderer_state_paused:
                cp_layer_renderer_wait_until_running(engine->layer_renderer);
                break;
            case cp_layer_renderer_state_running:
                my_engine_render_new_frame(engine);
                break;
            case cp_layer_renderer_state_invalidated:
                is_rendering = false;
                break;
        }
    }

    my_engine_invalidate(engine);
}
Render new frame objectivec · at 15:56 ↗
void my_engine_render_new_frame(my_engine *engine) {
    
    cp_frame_t frame = cp_layer_renderer_query_next_frame(engine->layer_renderer);
    if (frame == nullptr) { return; }
    
    cp_frame_timing_t timing = cp_frame_predict_timing(frame);
    if (timing == nullptr) { return; }

    cp_frame_start_update(frame);

    my_input_state input_state = my_engine_gather_inputs(engine, timing);
    my_engine_update_frame(engine, timing, input_state);

    cp_frame_end_update(frame);

    // Wait until the optimal time for querying the input
    cp_time_wait_until(cp_frame_timing_get_optimal_input_time(timing));

    cp_frame_start_submission(frame);

    cp_drawable_t drawable = cp_frame_query_drawable(frame);
    if (drawable == nullptr) { return; }

    cp_frame_timing_t final_timing = cp_drawable_get_frame_timing(drawable);
    ar_pose_t pose = my_engine_get_ar_pose(engine, final_timing);
    cp_drawable_set_ar_pose(drawable, pose);

    my_engine_draw_and_submit_frame(engine, frame, drawable);

    cp_frame_end_submission(frame);
}
App architecture + input support swift · at 18:57 ↗
@main
struct MyApp: App {
    var body: some Scene {
        ImmersiveSpace {
            CompositorLayer(configuration: MyConfiguration()) { layerRenderer in
                let engine = my_engine_create(layerRenderer)
                let renderThread = Thread {
                    my_engine_render_loop(engine)
                }
                renderThread.name = "Render Thread"
                renderThread.start()
                layerRenderer.onSpatialEvent = { eventCollection in
                    var events = eventCollection.map { my_spatial_event($0) }
                    my_engine_push_spatial_events(engine, &events, events.count)
                }
            }
        }
        .upperLimbVisibility(.hidden)
    }
}
Push spatial events objectivec · at 18:57 ↗
void my_engine_push_spatial_events(my_engine *engine,
                                   my_spatial_event *spatial_event_collection,
                                   size_t event_count) {
    os_unfair_lock_lock(&engine->input_event_lock);
    
    // Copy events into an internal queue
    
    os_unfair_lock_unlock(&engine->input_event_lock);
}
Gather inputs objectivec · at 19:57 ↗
my_input_state my_engine_gather_inputs(my_engine *engine,
                                       cp_frame_timing_t timing) {
    my_input_state input_state = my_input_state_create();

    os_unfair_lock_lock(&engine->input_event_lock);
    input_state.current_pinch_collection = my_engine_pop_spatial_events(engine);
    os_unfair_lock_unlock(&engine->input_event_lock);

    ar_hand_tracking_provider_get_latest_anchors(engine->hand_tracking_provider,
                                                 input_state.left_hand,
                                                 input_state.right_hand);

    return input_state;
}

Resources