There is a flickering and slight dimming occurring specifically on skysphere, at initial load of the scene, when using Attachment. This is observed in the simulator and on the real device.
Since we cannot upload a video illustrating the undesirable behaviour, I have to describe how to setup the project for you to observe it.
To replicate the issue, follow these steps:
- Create a new visionOS app using Xcode template, see image.
- Configure the project to launch directly into an immersive space (set
Preferred Default Scene Session Role
toImmersive Space Application Session Role
in Info.plist), see image. - Replace all swift files with those you will find in the attached texts.
- Add the skysphere image asset
Skydome_8k
found at this Apple Sample App Presenting an artist’s scene. - Launch the app in debug mode via Xcode and onto the AVP device or simulator
- Continuously open and dismiss the skysphere by pressing on buttons
Open Skysphere
andClose
. - Observe the skysphere flicker and dim upon display of the skysphere.
The current workaround is commented in file ThreeSixtySkysphereRealityView
at lines 65, 70, 71, and 72. Uncomment these lines, and the flickering and dimming do not occur.
- Are we using attachments wrongly?
- Is this behavior known and documented?
- Or, is there really a bug in visionOS?
import SwiftUI enum RealityViewScene { case intro case threeSixtySkyShpere } /// Maintains app-wide state @MainActor @Observable class AppModel { var showingScene: RealityViewScene = .intro }
import AVKit import OSLog import SwiftUI import RealityKit import RealityKitContent struct InitialImmersiveView: View { let logger = Logger(subsystem: SUBSYSTEM, category: "InitialImmersiveView") @Environment(\.scenePhase) var scenePhase @Environment(AppModel.self) var appModel private let rootEntity = Entity() var body: some View { RealityView { content, attachments in logger.info("\(#function) \(#line) `content, attachments in`") updateScene(content: content, attachments: attachments) } update: { content, attachments in logger.info("\(#function) \(#line) `update: { content, attachments in`") updateScene(content: content, attachments: attachments) } placeholder: { let _ = logger.info("\(#function) \(#line) `placeholder`") ProgressView() } attachments: { let _ = logger.info("\(#function) \(#line) `attachments`") Attachment(id: "OpenSkysphereView") { Button { appModel.showingScene = .threeSixtySkyShpere } label: { Text("Open Skysphere") } } } .onChange(of: scenePhase, { oldValue, newValue in logger.info("\(#function) \(#line) `onChange(of: scenePhase` scenePhase oldValue \(String(describing: oldValue)) scenePhase newValue \(String(describing: newValue))") switch newValue { case .active: logger.info("\(#function) \(#line) scenePhase newValue \(String(describing: newValue))") case .background: logger.info("\(#function) \(#line) scenePhase newValue \(String(describing: newValue))") unloadScenes() case .inactive: logger.info("\(#function) \(#line) scenePhase newValue \(String(describing: newValue))") @unknown default: logger.warning("\(#function) \(#line) scenePhase newValue \(String(describing: newValue))") } }) } func updateScene(content: RealityViewContent, attachments: RealityViewAttachments) { logger.info("\(#function) \(#line)") guard scenePhase == .active else { logger.info("\(#function) \(#line) escaping since app backgrounded") return } if rootEntity.children.isEmpty { Task{ await loadScenes(content: content, attachments: attachments) } } } func loadScenes(content: RealityViewContent, attachments: RealityViewAttachments) async { logger.info("\(#function) \(#line)") guard let openSkysphereViewButton = attachments.entity(for: "OpenSkysphereView") else { logger.error("\(#function) \(#line) missing attachment") assertionFailure("missing attachment") return } openSkysphereViewButton.position = [0.0, 0.1, -2.5] openSkysphereViewButton.scale = scaleForAttachements rootEntity.addChild(openSkysphereViewButton) content.add(rootEntity) } func unloadScenes() { logger.info("\(#function) \(#line)") rootEntity.children.removeAll() } }
import SwiftUI import RealityKit import RealityKitContent struct MainImmersiveView: View { @Environment(AppModel.self) private var appModel var body: some View { switch appModel.showingScene { case .intro: InitialImmersiveView() case .threeSixtySkyShpere: ThreeSixtySkysphereRealityView() } } }
import OSLog import SwiftUI let SUBSYSTEM = Bundle.main.bundleIdentifier! let scaleForAttachements = SIMD3(2.5, 2.5, 2.5) @main struct TestSkysphereAttachmentFlickerApp: App { let logger = Logger(subsystem: SUBSYSTEM, category: "TestVideoLeakOneImmersiveViewApp") @State private var appModel = AppModel() @Environment(\.openImmersiveSpace) var openImmersiveSpace @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace var body: some Scene { Group { ImmersiveSpace(id: "MainImmersiveView") { MainImmersiveView() .environment(appModel) } .immersionStyle(selection: .constant(.mixed), in: .mixed) } } }
import OSLog import RealityKit import RealityKitContent import SwiftUI struct ThreeSixtySkysphereRealityView: View { let logger = Logger(subsystem: SUBSYSTEM, category: "ThreeSixtySkysphereRealityView") @Environment(\.scenePhase) var scenePhase @Environment(AppModel.self) var appModel let rootEntity = Entity() let threeSixtySkysphereEntity = Entity() var body: some View { RealityView { content, attachments in logger.info("\(#function) \(#line) `RealityView { content, attachments in`") await loadScenes(content: content, attachments: attachments) } update: { content, attachments in logger.info("\(#function) \(#line) `update: { content, attachments in`") } placeholder: { let _ = logger.info("\(#function) \(#line) `placeholder`") ProgressView() } attachments: { let _ = logger.info("\(#function) \(#line) `attachments`") Attachment(id: "AttachmentID.exitThreeSixty") { Button(action: { logger.info("\(#function) \(#line) button pressed") appModel.showingScene = .intro }) { Text("Close") } .glassBackgroundEffect() .cornerRadius(25) } } } func loadScenes(content: RealityViewContent, attachments: RealityViewAttachments) async { logger.info("\(#function) \(#line)") addSkyboxImage(entity: threeSixtySkysphereEntity) rootEntity.addChild(threeSixtySkysphereEntity) guard let exitThreeSixtyAttachement = attachments.entity(for: "AttachmentID.exitThreeSixty") else { logger.error("\(#function) \(#line) missing attachment") assertionFailure("missing attachment") return } exitThreeSixtyAttachement.scale = scaleForAttachements let anchorEntity = AnchorEntity(world: [0, 0.6, -1.8]) anchorEntity.addChild(exitThreeSixtyAttachement) rootEntity.addChild(anchorEntity) // exitThreeSixtyAttachement.isEnabled = false content.add(rootEntity) // This delay in displaying the attachment resolves an issue whereby there would // otherwise be a slight flicker and dimming upon initial load of the scene. // DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) { // exitThreeSixtyAttachement.isEnabled = true // } } func addSkyboxImage(entity: Entity) { do { let uiImage = UIImage(named: "Skydome_8k")! // There is a hard limit of 8,192px width images in this `TextureResource.generate` method // If we try to generate a texture from an image of 8,193px width, texture generation will fail. let texture = try TextureResource(image: uiImage.cgImage!, options: TextureResource.CreateOptions.init(semantic: nil)) var material = UnlitMaterial() material.color = .init(texture: .init(texture)) entity.components.set(ModelComponent( mesh: .generateSphere(radius: 1000), materials: [material]) ) entity.scale *= .init(x: -1, y: 1, z: 1) entity.transform.translation += SIMD3(0.0, 1.0, 0.0) entity.transform.rotation = simd_quatf(angle: 89.5, axis: [0, 1, 0]) // rotate so that user faces the sea } catch (let error) { logger.error("\(#function) \(#line) texture not loaded with error \(error.localizedDescription)") assertionFailure("texture not loaded with error \(error.localizedDescription)") } } } #Preview { ThreeSixtySkysphereRealityView() }
Replies
1
Boosts
0
Views
358
Participants
1