-
Novedades del renderizado de Metal para apps inmersivas
Descubre las últimas mejoras en el renderizado de Metal para apps inmersivas con Compositor Services. Aprende a agregar efectos de desplazamiento para resaltar los elementos interactivos de tu app y cómo renderizar con mayor fidelidad y calidad de renderizado dinámico. Descubre el nuevo estilo de inmersión progresiva. Además, descubre cómo puedes llevar experiencias inmersivas a las apps de macOS renderizando directamente el contenido de Metal desde Mac a Vision Pro.
Para aprovechar al máximo esta sesión, primero ve “Discover Metal for immersive apps" del WWDC23.Capítulos
- 0:00 - Introducción
- 1:58 - Nuevas API de bucle de renderizado
- 4:21 - Efectos de desplazamiento
- 10:50 - Calidad de renderización dinámica
- 14:44 - Inmersión progresiva
- 18:32 - Renderizado espacial de macOS
- 23:51 - Próximos pasos
Recursos
- Analyzing the performance of your Metal app
- Rendering hover effects in Metal immersive apps
- Optimizing GPU performance
Videos relacionados
WWDC25
- Descubrir Metal 4
- Explora la entrada de accesorios espaciales en visionOS
- Explorar juegos de Metal 4
- Novedades de SwiftUI
- Novedades de visionOS 26
- Ve más allá con los juegos de Metal 4
WWDC24
WWDC23
-
Buscar este video…
-
-
0:01 - Scene render loop
// Scene render loop extension Renderer { func renderFrame(with scene: MyScene) { guard let frame = layerRenderer.queryNextFrame() else { return } frame.startUpdate() scene.performFrameIndependentUpdates() frame.endUpdate() let drawables = frame.queryDrawables() guard !drawables.isEmpty else { return } guard let timing = frame.predictTiming() else { return } LayerRenderer.Clock().wait(until: timing.optimalInputTime) frame.startSubmission() scene.render(to: drawable) frame.endSubmission() } } -
5:54 - Layer configuration
// Layer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { // Configure other aspects of LayerRenderer let trackingAreasFormat: MTLPixelFormat = .r8Uint if capabilities.supportedTrackingAreasFormats.contains(trackingAreasFormat) { configuration.trackingAreasFormat = trackingAreasFormat } } } -
7:54 - Object render function
// Object render function extension MyObject { func render(drawable: Drawable, renderEncoder: MTLRenderCommandEncoder) { var renderValue: LayerRenderer.Drawable.TrackingArea.RenderValue? = nil if self.isInteractive { let trackingArea = drawable.addTrackingArea(identifier: self.identifier) if self.usesHoverEffect { trackingArea.addHoverEffect(.automatic) } renderValue = trackingArea.renderValue } self.draw(with: commandEncoder, trackingAreaRenderValue: renderValue) } } -
8:26 - Metal fragment shader
// Metal fragment shader struct FragmentOut { float4 color [[color(0)]]; uint16_t trackingAreaRenderValue [[color(1)]]; }; fragment FragmentOut fragmentShader( /* ... */ ) { // ... return FragmentOut { float4(outColor, 1.0), uniforms.trackingAreaRenderValue }; } -
10:09 - Event processing
// Event processing extension Renderer { func processEvent(_ event: SpatialEventCollection.Event) { let object = scene.objects.first { $0.identifier == event.trackingAreaIdentifier } if let object { object.performAction() } } } -
13:08 - Quality constants
// Quality constants extension MyScene { struct Constants { static let menuRenderQuality: LayerRenderer.RenderQuality = .init(0.8) static let worldRenderQuality: LayerRenderer.RenderQuality = .init(0.6) static var maxRenderQuality: LayerRenderer.RenderQuality { menuRenderQuality } } } -
13:32 - Layer configuration
// Layer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { // Configure other aspects of LayerRenderer if configuration.isFoveationEnabled { configuration.maxRenderQuality = MyScene.Constants.maxRenderQuality } } -
13:57 - Set runtime render quality
// Set runtime render quality extension MyScene { var renderQuality: LayerRenderer.RenderQuality { switch type { case .world: Constants.worldRenderQuality case .menu: Constants.menuRenderQuality } } } extension Renderer { func adjustRenderQuality(for scene: MyScene) { guard layerRenderer.configuration.isFoveationEnabled else { return; } layerRenderer.renderQuality = scene.renderQuality } } -
16:58 - SwiftUI immersion style
// SwiftUI immersion style @main struct MyApp: App { @State var immersionStyle: ImmersionStyle var body: some Scene { ImmersiveSpace(id: "MyImmersiveSpace") { CompositorLayer(configuration: MyConfiguration()) { @MainActor layerRenderer in Renderer.startRenderLoop(layerRenderer) } } .immersionStyle(selection: $immersionStyle, in: .progressive, .full) } } -
17:12 - Layer configuration
// Layer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { // Configure other aspects of LayerRenderer if configuration.layout == .layered { let stencilFormat: MTLPixelFormat = .stencil8 if capabilities.drawableRenderContextSupportedStencilFormats.contains( stencilFormat ) { configuration.drawableRenderContextStencilFormat = stencilFormat } configuration.drawableRenderContextRasterSampleCount = 1 } } } -
17:40 - Render loop
// Render loop struct Renderer { let portalStencilValue: UInt8 = 200 // Value not used in other stencil operations func renderFrame(with scene: MyScene, drawable: LayerRenderer.Drawable, commandBuffer: MTLCommandBuffer) { let drawableRenderContext = drawable.addRenderContext(commandBuffer: commandBuffer) let renderEncoder = configureRenderPass(commandBuffer: commandBuffer) drawableRenderContext.drawMaskOnStencilAttachment(commandEncoder: renderEncoder, value: portalStencilValue) renderEncoder.setStencilReferenceValue(UInt32(portalStencilValue)) scene.render(to: drawable, renderEncoder: renderEncoder) drawableRenderContext.endEncoding(commandEncoder: commandEncoder) drawable.encodePresent(commandBuffer: commandBuffer) } } -
20:55 - App structure
// App structure @main struct MyImmersiveMacApp: App { @State var immersionStyle: ImmersionStyle = .full var body: some Scene { WindowGroup { MyAppContent() } RemoteImmersiveSpace(id: "MyRemoteImmersiveSpace") { MyCompositorContent() } .immersionStyle(selection: $immersionStyle, in: .full, .progressive) } } -
21:14 - App UI
// App UI struct MyAppContent: View { @Environment(\.supportsRemoteScenes) private var supportsRemoteScenes @Environment(\.openImmersiveSpace) private var openImmersiveSpace @State private var spaceState: OpenImmersiveSpaceAction.Result? var body: some View { if !supportsRemoteScenes { Text("Remote SwiftUI scenes are not supported on this Mac.") } else if spaceState != nil { MySpaceStateView($spaceState) } else { Button("Open remote immersive space") { Task { spaceState = await openImmersiveSpace(id: "MyRemoteImmersiveSpace") } } } } } -
21:35 - Compositor content and ARKit session
// Compositor content and ARKit session struct MyCompositorContent: CompositorContent { @Environment(\.remoteDeviceIdentifier) private var remoteDeviceIdentifier var body: some CompositorContent { CompositorLayer(configuration: MyConfiguration()) { @MainActor layerRenderer in guard let remoteDeviceIdentifier else { return } let arSession = ARKitSession(device: remoteDeviceIdentifier) Renderer.startRenderLoop(layerRenderer, arSession) } } } -
23:17 - C interoperability
// Swift let remoteDevice: ar_device_t = remoteDeviceIdentifier.cDevice Renderer.start_rendering(layerRenderer, remoteDevice) // C void start_rendering(cp_layer_renderer_t layer_renderer, ar_device_t remoteDevice) { ar_session_t session = ar_session_create_with_device(remoteDevice); // ... }
-
-
- 0:00 - Introducción
El renderizado de Metal en visionOS, junto con Compositor Services, trae nuevas y emocionantes funcionalidades este año, como efectos de desplazamiento sobre objetos con los que se puede interactuar, calidad de renderizado dinámico de tu contenido, un nuevo estilo de inmersión progresiva y la capacidad de renderizar contenido inmersivo en el Vision Pro desde macOS.
- 1:58 - Nuevas API de bucle de renderizado
Se hizo un cambio importante en el bucle de renderizado en visionOS este año. El objeto queryDrawables ahora arroja una matriz de uno o dos elementos dibujables en lugar de solo uno. El segundo elemento dibujable estará presente siempre que grabes un video de alta calidad con Reality Composer Pro. Busca una plantilla que te ayude a iniciar en Xcode. Se admiten tanto Metal como Metal 4.
- 4:21 - Efectos de desplazamiento
Una vez que hayas adoptado la nueva API de bucle de renderizado, podrás empezar a implementar efectos de desplazamiento sobre los objetos con los que se puede interactuar. Con estos efectos, las personas pueden ver qué objetos son interactivos y anticipar los objetivos de sus acciones. El sistema resaltará dinámicamente el objeto que la persona esté viendo. Por ejemplo, en un juego de rompecabezas se enfatizarán las piezas que el jugador puede seleccionar. Puedes hacer esto con la nueva textura de áreas de seguimiento, que define las regiones interactivas en tu escena. Hay algunas consideraciones extra que debes tener en cuenta cuando uses el suavizado multimuestra (MSAA).
- 10:50 - Calidad de renderización dinámica
Ahora puedes dibujar tu contenido con una mayor fidelidad. Al usar la calidad de renderizado dinámico, puedes ajustar la resolución de tu contenido según la complejidad de tus escenas. Se basa en el renderizado foveado, que prioriza la densidad de pixeles donde el usuario está mirando. Puedes establecer una calidad de renderizado máxima y luego ajustar una calidad de ejecución acorde a ese rango. Una mayor calidad mejora la claridad del texto y de la interfaz, pero aumenta el uso de memoria y energía. Equilibrar la calidad y el rendimiento es crucial. Usa herramientas como Instruments y el depurador de Metal para encontrar ese equilibrio.
- 14:44 - Inmersión progresiva
Este año, podrás renderizar contenido dentro de un portal inmersivo progresivo. Con esto, las personas controlan el nivel de inmersión al girar la Digital Crown. Esto las conecta con el entorno real y las ayuda a sentirse más cómodas cuando ven escenas complejas con movimiento. Para implementarlo, solicita al sistema que proporcione un búfer de plantilla con el fin de enmascarar el contenido que se encuentra fuera del límite del portal. El sistema aplica un efecto de difuminado en los bordes, creando una transición perfecta entre entornos reales y renderizados. Los pixeles que se encuentran fuera de la vista no se renderizan, lo que ahorra capacidad de procesamiento. Compartimos los detalles de implementación.
- 18:32 - Renderizado espacial de macOS
El renderizado espacial de macOS permite aprovechar la capacidad de tu Mac para renderizar y transmitir contenido inmersivo al Apple Vision Pro. Esta nueva funcionalidad mejora las apps para Mac con experiencias inmersivas, como vistas previas de modelado 3D en tiempo real. ARKit y worldTrackingProvider ahora están disponibles en macOS. Esto te permite consultar la ubicación del Vision Pro en el espacio. El RemoteImmersiveSpace de macOS aloja CompositorLayer y ARKitSession, como lo haría una app nativa de visionOS. Hay un nuevo remoteDeviceIdentifier que deberás usar para conectar la sesión de ARKit de tu Mac al Vision Pro. Además, todas las API relevantes tienen equivalentes nativos en C.
- 23:51 - Próximos pasos
Estas nuevas capacidades de Metal y Compositor Services en visionOS permiten brindar mejor interactividad, mayor fidelidad y un nuevo estilo de inmersión progresiva a tus apps y juegos. A continuación, consulta “Set the scene with SwiftUI in visionOS” y “What's new in visionOS 26”.