Delve into the world of graphics and game development. Discuss creating stunning visuals, optimizing game mechanics, and share resources for game developers.

All subtopics
Posts under Graphics & Games topic

Post

Replies

Boosts

Views

Activity

2D Soft Shadows with SpriteKit and Metal
Hi! I'd like to share an implementation of 2D soft shadows using SpriteKit and custom Metal rendering. GitHub Repo SpriteKit-SoftShadows The demo app runs on iOS and Mac Catalyst. The soft shadow implementation is based on Scott Lembcke's algorithm. Pipeline The app uses MTKView to drive the rendering loop at the desired frame rate. Each frame: SpriteKit renders the scene into a Metal texture using SKRenderer. The CPU sends each light's properties and the relevant shape edges to a Metal vertex shader. The vertex shader projects shadow geometry from each edge. A fragment shader calculates the shadow opacity at each pixel, producing a soft shadow mask for each light. A final fragment shader combines the SpriteKit texture, lights, and shadow masks to produce the displayed image. The SwiftUI controls update variables inside the SpriteKit scene through @Observable. The rendering loop consumes the new values on its next cycle. Let me know if you have any feedback!
0
0
214
13h
Are relaxed threadgroup atomics (atomic_fetch_add) officially supported on the Metal 4.0 feature set, or only 4.1?
I'm writing GPU compute kernels (parallel prefix-sum and histogram) that rely on threadgroup-address-space atomics specifically atomic_fetch_add_explicit on a threadgroup atomic_uint, with memory_order_relaxed. Setup: Device: Apple M5, macOS 26.5 Toolchain reports MSL 4.0 / AIR 2.8 (i.e. the "Metal 4.0" feature level) Note: I'm generating AIR (Apple IR) directly rather than emitting MSL source this is through a custom compute backend (Julia's Metal.jl), not the standard MSL front end. What I observe: These threadgroup atomics compile and produce correct results, and give a meaningful speedup over a non-atomic (scan-based) fallback. I've validated correctness across a 256-bin histogram and a full multi-pass radix sort no mismatches. The question: Some capability checks gate threadgroup atomic support behind Metal 4.1, and my device reports 4.0 yet they clearly work. So: Are relaxed integer threadgroup atomics (atomic_fetch_add on threadgroup atomic_uint, relaxed ordering) officially supported on the Metal 4.0 feature set for current Apple Silicon, or is this unsupported behavior that happens to work? Is there a specific MSL version or GPU family that is the true minimum for these operations? Does the answer differ at the AIR / feature-set level (what I'm targeting) vs. the MSL front end, given I'm feeding AIR to the compiler directly? I ask because a downstream library is (reasonably) hesitant to enable this path unless it's officially supported rather than relying on undefined behavior. Any authoritative guidance or a pointer to the relevant feature-set/GPU-family documentation would be hugely appreciated. Thanks!
1
0
394
18h
Game Center matchmaking fails for all non-default users on Apple TV — no IDS registration for secondary users (FB24156316)
Game Center matchmaking fails for all non-default users on Apple TV — no IDS registration for secondary users (FB24156316) On a multi-user Apple TV (tvOS 26.5, Apple TV 4K 3rd gen), Game Center real-time matchmaking fails for every user except the default user, in every app I've tested — including Apple Arcade titles. Filed as FB24156316 with full logs and sysdiagnose; posting here for visibility and in case anyone has shipped multi-user GC multiplayer on tvOS successfully. My game adopts com.apple.developer.user-management (runs-as-current-user-with-user-independent-keychain). The entitlement itself works: on a secondary user's profile the app runs under that user's persona and GKLocalPlayer authenticates as them — the welcome banner shows the right account. But any GKMatchmakerViewController quickmatch hard-fails within ~9 seconds ("Failed to find players"), and accepting an invite fails with GKError 35 ("not signed in to iCloud") even though Settings shows that user's iCloud as signed in. Unified logs show the root cause. When matchmaking starts, gamed can't provision the player's pseudonym because the current user has no identity-services registration: gamed No URI found on any account -- returning nil gamed Failed to fetch pseudonym for local player. Error: GameDaemonCore.PseudonymManagerError.failedToProvision( internalError: Error Domain=com.apple.ids.IDSPseudonymErrorDomain Code=400 "Invalid URI") For the default user, the identical flow succeeds (identityservicesd … resultCode: 0). Across a full day of log capture — profile adds, a remove/re-add, multiple user switches — identityservicesd never once references the secondary users' accounts: registration for them is never attempted, not attempted-and-failed. Meanwhile gamed advertises the nearby-matchmaking Bonjour service with the default user's identity while the foreground app runs as the secondary user. Reproduction matrix: two apps (my shipping game Extreme Violence and Apple Arcade's Crossy Road Castle, which also runs under the correct persona), both sandbox and production Game Center, two unrelated secondary accounts (both healthy elsewhere). Persists across reboot and profile remove/re-add. Default user unaffected. The documentation says the entitlement is all that's needed ("each person who uses your app will have access to… their own Game Center… you don't have to make any code changes" — WWDC20 session 10645). As far as I can tell that promise is currently unfulfillable for online play: there is no API or Settings path that creates the missing IDS registration. Has anyone seen non-default-user matchmaking work on tvOS, on any version? Is there anything an app can do here, or is this purely an OS-side fix? (Related: thread 782163 — a different tvOS matchmaking failure that DTS confirmed as a bug.)
3
0
593
20h
view protocol update ?
Hello ! Thank you guys for all the hard work you are doing on RealityKit For now in my studying Im trying to understand, in general, what will cause my Main app view to redraw everything? Or any view? If i have a struct that use the view protocol, and that struct is being called inside RealityView on the main app, or outside a reality view within a ZStack on the main app, if this view updates, does this will cause everything in my main app view to redraw as well? Or is it only true to @State variables of the main app? or the @State variables of the external view i try to implement? and what about @Observable ? Is there an accurate table that tells the developer what will cause a redraw of things? (not only the things the logic asks for, but all things sitting idle within the view) I try to understand how to separate UI updates, to avoid full redraw of things that havent been changed. to minimize UI compute in my games Before i play around with instruments i need to understand the general architecture.. if i work in a way that is counter designed to the way reality kit should work then the instrument results will not make sense to me. so i feel like i should ask you guys first. Depending on your answer i will know how to arrange my data in my game. And how to design and instantiate all my views Thanks
1
0
268
22h
GCKeyboard reports a phantom “Generic Keyboard” on iOS 27 beta 5
Update: Please disregard this report. The behavior was caused by enabling the hardware keyboard for the device in Xcode 27 beta 5’s Device Hub. This appears to expose a virtual “Generic Keyboard” to the connected physical device, causing GCKeyboardDidConnect to be posted even though no physical keyboard is attached directly to it. Simply disabling the hardware keyboard option was not sufficient. I needed to close Device Hub and restart Xcode for the virtual keyboard to be removed and the expected GCKeyboard behavior to resume. I’m seeing what appears to be a GameController regression on physical iPhone and iPad devices running iOS 27 beta 5. When my app launches with no Bluetooth, USB, or Smart Connector keyboard attached, GCKeyboard.coalesced is initially nil. Shortly afterward, the system posts .GCKeyboardDidConnect for a keyboard named “Generic Keyboard,” and GCKeyboard.coalesced becomes non-nil: === Initial state === GCKeyboard.coalesced: nil cannot add handler to 0 from 0 - dropping === GCKeyboardDidConnect #1 === notification.object: GCKeyboard: 0x10b01cd00 'Generic Keyboard'> GCKeyboard.coalesced: Optional( 0x10b01cd00>) The phantom keyboard then appears to remain connected indefinitely. After this happens: Attaching a real hardware keyboard does not post another .GCKeyboardDidConnect. Detaching the real keyboard does not post .GCKeyboardDidDisconnect. GCKeyboard.coalesced remains non-nil even though no hardware keyboard is attached. This may be interacting with the documented coalescing behavior: the connect notification is posted only for the first keyboard, and the disconnect notification only after the last keyboard disconnects. If the phantom “Generic Keyboard” remains present, a real keyboard is never considered the first or last keyboard. Minimal observer code: print("Initial GCKeyboard.coalesced:", GCKeyboard.coalesced as Any) NotificationCenter.default.addObserver( forName: .GCKeyboardDidConnect, object: nil, queue: .main ) { notification in print("GCKeyboardDidConnect") print("notification.object:", notification.object as Any) print("GCKeyboard.coalesced:", GCKeyboard.coalesced as Any) } NotificationCenter.default.addObserver( forName: .GCKeyboardDidDisconnect, object: nil, queue: .main ) { notification in print("GCKeyboardDidDisconnect") print("notification.object:", notification.object as Any) print("GCKeyboard.coalesced:", GCKeyboard.coalesced as Any) } I can reproduce this on both a physical iPhone and a physical iPad. This is not Simulator keyboard forwarding, and no keyboard is attached when the “Generic Keyboard” appears. Has anyone else observed this behavior on iOS 27 beta 5 or found another API that reliably reports whether a real hardware keyboard is attached? Filed as FB24269002.
0
0
33
23h
[Bug] iPadOS 26: 4-Finger Fast Tap/Swipe Gesture Not Detected (Multitouch Issue)
Hi everyone I'm experiencing an issue with iPadOS 26 regarding multi-touch gesture detection. When performing a quick four-finger gesture (tap and swipe), the system often fails to recognize the input. This especially affects multi-touch gestures, such as rhythm games with difficult levels. Steps to Reproduce: Place four fingers on the screen. Perform a quick tap or a quick horizontal swipe (like the one used to switch apps). Observe whether the gesture is ignored or detected inconsistently. Expected Behavior: 4-finger multitouch gestures should be recognized regardless of gesture speed, just like previous iPadOS versions. Actual Behavior: Gestures fail to be detected when executed quickly—same gestures still work, and miss notes in rhythm games. You can check out my posts on Twitter/x and Facebook: [https://x.com/kokona_fwa/status/1978131164104728949?s=61] Facebook: [https://m.facebook.com/groups/idipad/permalink/24438964899058806/?]
1
1
2.3k
1d
Metal rendering application is not releasing resources
I am developing a metal based ray tracing rendering application (running heavy GPU kernels). I am sometimes "forcefully quitting" my application and I can see the application is not in the activity monitor. But I can see the windowserver is using %97 the GPU. The mac gets hotter and hotter. I kill the windowserver, re-login it is still the case. The only way to fix is to restart the mac. I have checked if there are any zombie processes, there are none. I am 3-4 month into Mac development (I used many rendering APIs e.g. before under Windows and Linux, they release the resources automatically unless the driver is very broken), but I believe when you force quit or exit gracefully, regarding application should release resources. I may be missing some knowledge. Does anybody have an idea? I had added every corner a graceful exit code but once the kernel has some infinite loop the clean up cannot happen. In Windows there are some driver reload mechanisms to recover when GPU is stuck, is there a similar system ?
2
0
1.8k
2d
SpriteKit scene used as SCNView.overlaySKScene crashes due to SKShapeNode
I recently published my first game on the App Store. It uses SceneKit with a SpriteKit overlay. All crashes Xcode downloaded for it so far are related to some SpriteKit/SceneKit internals. The most common crash is caused by SKCShapeNode::_NEW_copyRenderPathData. What could cause such a crash? crash.crash While developing this game (and the BoardGameKit framework that appears in the crash log) over the years I experienced many crashes presumably caused by the SpriteKit overlay (I opened a post SceneKit app randomly crashes with EXC_BAD_ACCESS in jet_context::set_fragment_texture about such a crash in September 2024), and other people on the internet also mention that they experience crashes when using SpriteKit as a SceneKit overlay. Should I use a separate SKView and lay it on top of SCNView rather than setting SCNView.overlaySKScene? That seemed to solve the crashes for a guy on stackoverflow, but is it also encouraged by Apple? I know SceneKit is deprecated, but according to Apple critical bugs would still be fixed. Could this be considered a critical bug?
9
0
2.0k
3d
MPS bf16 softmax produces NaN on M5 Max (regression from M4) — breaks all on-device diffusion inference
Metal Performance Shaders produces from bf16/fp16 softmax for large attention tensors on , forcing the entire local generative-AI ecosystem to fall back to fp32. This is a . Environment Minimal reproduction Decomposed softmax on MPS — diffs = x - maxes produces NaN even though every element is identical (result should be all zeros): Real-world impact Running ComfyUI (the dominant local generative-AI UI) on M5 Max: Root cause (per PyTorch MPS maintainers) PyTorch maintainers (@drisspg, @albanD) have traced this to in the MPS/MPSGraph fused kernels. The NaN originates in the x - maxes subtraction inside softmax, then propagates through the attention block and the entire network. PyTorch cannot fix this — it is in the Metal/MPS kernel layer. Not isolated The M4-era fix ("fixed on macOS 15.1") did not survive onto M5, indicating the MPS fused-kernel precision fix was either reverted or not ported to the M5 GPU architecture. Request
0
0
417
3d
Behaviour of a 0-value `accelerationStructureID`
When constructing a MTLIndirectAccelerationStructureInstanceDescriptor, one specifies an accelerationStructureID. In the equivalents in both Vulkan and DirectX12, one can set it to zero to be an "inactive" instance. However, on Metal, this field does not appear to have any documentation, and thus it is difficult to figure out if there is similar behavior (and no other Metal documentation seems to mention this). Does setting this field to 0 (i.e. null) disable the instance? If not, is there any other way to have an equivalent effect?
0
0
438
4d
Best Way to Use MetalFX in Unreal Engine 5.7 for macOS Port?
Hi everyone, We’re currently porting a high-fidelity AA+ PC title built on Unreal Engine 5.7 to macOS (Apple Silicon), and we’re looking for guidance from anyone with experience in this area. At the moment, the game is already runnable on Mac, but not yet at a playable level — we’re seeing performance around 10–15 FPS on an M4 device. We’re actively analyzing and defining the work needed to reach production-quality performance on macOS. One of the key areas we’re exploring is leveraging MetalFX to improve frame rate. However, it seems there’s no official MetalFX plugin or direct integration available for Unreal Engine. Has anyone here successfully integrated MetalFX into a UE5 rendering pipeline, or found a recommended approach to do so? Any insights on best practices, workflows, or references (docs, samples, etc.) would be greatly appreciated. Thanks in advance!
4
0
1.7k
5d
MTL4FXFrameInterpolator no-op on MTL4CommandBuffer
I'm trying to use the new MTL4FX::FrameInterpolator(the Metal 4 variant that encodes to MTL4::CommandBuffer). It creates fine, accepts all texture bindings, and encodes without any error or assertion. The GPU signals completion via shared event. But the output texture is completely untouched — zero bytes changed from before the encode. It's a silent no-op. I'm trying to call metal-cpp from python by making dynamic library with cpp. Environment: macOS 26.6 (build 25G72) Apple M3, arm64 Xcode 26 SDK MetalFX framework (MTL4FX API, macOS 26+) I'm on M3 Summary: MTL4FX::FrameInterpolator::encodeToCommandBuffer(MTL4::CommandBuffer*) records, commits, and the GPU signals completion, but writes zero bytes to the output texture. The encode is a silent no-op — identical to documented issues 146436460 and 146436741 for MTL4FXTemporalScaler/DenoisedScaler. Diagnostic trace: Output texture zeroed before encode: 0/32768 non-zero bytes Output texture after encode (delta=0.5): 0/32768 non-zero bytes Changed bytes: 0/32768 ← definitive no-op Input textures verified: correct (R=1, G=0 vs R=0, G=1) I dont really know how to explain this, the result its just blank.
1
0
576
6d
What would case a compute kernel to run 100x slower only if it's run after a previous kernel.
I have two compute kernels. The first kernel pre_initization_0 initializes some buffers using a parallel random number generator initialize some buffers. The second kernel pre_sum_weights_0 effectively sums weighted values buffer of a much smaller dimension. #include <metal_stdlib> #include <metal_simdgroup> using namespace metal; struct mt_state { array<uint32_t, 624> array; uint16_t index; }; float random(device mt_state &state) { uint16_t k = state.index; uint16_t j = (k + 1) % 624; uint32_t x = (state.array[k] & 0x80000000U) | (state.array[j] & 0x7fffffffU); uint32_t xA = x >> 1; if (x & 0x00000001U) { xA ^= 0x9908b0dfU; } j = (k + 397) % 624; x = state.array[j]^xA; state.array[k] = x; state.index = (k + 1) % 624; uint32_t y = x^(x >> 11); y = y^((y << 7) & 0x9d2c5680U); y = y^((y << 15) & 0xefc60000U); return static_cast<float> (y^(y >> 18)); } kernel void pre_initization_0( device float *vc30c09b98 [[buffer(0)]], // x used 0 device float *vc30c09c38 [[buffer(1)]], // v_{||} used 0 device float *vc30c09cd8 [[buffer(2)]], // v_{\perp} used 0 device mt_state *sc30c50c18 [[buffer(3)]], constant uint32_t &offset [[buffer(4)]], uint index [[thread_position_in_grid]]) { if (offset + index < 3000000) { device mt_state &rc30c50c18 = sc30c50c18[index]; // used 4 const float rc30c06218 = 2.17689351e-08; // used 1 const float rc30c06318 = -46.7484322; // used 1 const float rc30c0a458 = fma(rc30c06218, random(rc30c50c18), rc30c06318); // used 1 const float rc30c07718 = 5.18059896e-05; // used 1 const float rc30c06798 = -1; // used 1 const float rc30c06618 = 2.32830644e-10; // used 2 const float rc30c06718 = 1.17549435e-38; // used 2 const float rc30c0a4f8 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034f02f8 = log(rc30c0a4f8); // used 1 const float rc30c06818 = rc30c06798*r1034f02f8; // used 1 const float r1034eedf8 = sqrt(rc30c06818); // used 1 const float rc30c06418 = 1.46291812e-09; // used 1 const float rc30c06498 = rc30c06418*random(rc30c50c18); // used 1 const float r1034eee68 = sin(rc30c06498); // used 1 const float rc30c06a18 = r1034eedf8*r1034eee68; // used 1 const float rc30c07698 = rc30c07718*rc30c06a18; // used 1 const float rc30c07598 = 3.3356411e-09; // used 1 const float rc30c07318 = -241213328; // used 1 const float rc30c0a598 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034ef238 = log(rc30c0a598); // used 1 const float rc30c06c98 = rc30c07318*r1034ef238; // used 1 const float rc310b0018 = sqrt(rc30c06c98); // used 1 const float rc30c07618 = rc30c07598*rc310b0018; // used 1 vc30c09b98[offset + index] = rc30c0a458; vc30c09c38[offset + index] = rc30c07698; vc30c09cd8[offset + index] = rc30c07618; } } kernel void pre_sum_weights_0( constant float *vc30c09b98 [[buffer(0)]], // x used 7 device atomic_float *vc30c0a098 [[buffer(1)]], const texture1d<float, access::read> ac310a9000 [[texture(0)]], const texture1d<float, access::read> ac310d8000 [[texture(1)]], const texture1d<float, access::read> ac310d9000 [[texture(2)]], const texture1d<float, access::read> ac310da000 [[texture(3)]], uint index [[thread_position_in_grid]]) { if (index < 3000000) { const float rc30c09b98 = vc30c09b98[index]; // x used 7 const float rc30c07798 = 0.0935904533; // used 2 const float rc30d08318 = rc30c09b98 - rc30c07798; // used 1 const ushort ic30d08398 = (ushort)min(max((rc30d08318 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07c98 = -5.34242535; // used 1 const ushort ic30d08018 = (ushort)min(max((rc30c09b98 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 5 const float rc30c51018 = ac310da000.read(ic30d08018).r; // used 1 const float rc30c0a6d8 = fma(rc30c07c98, rc30c09b98, rc30c51018); // used 1 const float rc30c07a18 = -10.6848507; // used 1 const float rc30c50e98 = ac310d8000.read(ic30d08018).r; // used 1 const float rc30c0a778 = fma(rc30c07a18, rc30c09b98, rc30c50e98); // used 1 const float rc30c07b98 = rc30c0a6d8*rc30c0a778; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08398], rc30c07b98, memory_order_relaxed); // used 1 const float rc30c07c18 = 0.75; // used 1 const float rc30c07e18 = 114.166031; // used 1 const float rc30c50d18 = ac310a9000.read(ic30d08018).r; // used 1 const float rc30d08098 = rc30c50d18 - rc30c09b98; // used 1 const float rc30c07d98 = rc30d08098*rc30d08098; // used 1 const float rc30c07e98 = rc30c07e18*rc30c07d98; // used 1 const float rc30d08218 = rc30c07c18 - rc30c07e98; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08018], rc30d08218, memory_order_relaxed); // used 1 const float rc30d08498 = rc30c07798 + rc30c09b98; // used 1 const ushort ic30d08418 = (ushort)min(max((rc30d08498 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07b18 = 0.5; // used 1 const float rc30c07918 = 1.5; // used 1 const float rc30c07818 = 10.6848507; // used 1 const float rc30c50f98 = ac310d9000.read(ic30d08018).r; // used 1 const float rc30d08298 = rc30c50f98 - rc30c09b98; // used 1 const float rc30c07a98 = rc30c07818*rc30d08298; // used 1 const float rc30d08118 = rc30c07918 - rc30c07a98; // used 1 const float rc30d34018 = rc30d08118*rc30d08118; // used 1 const float rc30d34098 = rc30c07b18*rc30d34018; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08418], rc30d34098, memory_order_relaxed); // used 1 } } Running some timing experiments, If I initialize [[buffer(0)]] of the pre_sum_weights_0 kernel on the CPU, and run the kernel, it will run in 0.0489688 s measured using GPUEndTime - GPUStartTime in a completedHander for the command buffer. If I run the initialization kernel pre_initization_0 before this, the kernel execution time of pre_sum_weights_0 slows to 7.20396 s. Note I am launching these kernels from different command buffers.
1
0
555
1w
GroundingShadowComponent — working on iOS/iPadOS by anyone?
Following up on thread 733918 (visionOS grounding shadows, solved by applying GroundingShadowComponent(castsShadow: true) to every ModelEntity in a hierarchy, not just the root). I've implemented that exact fix in an ARKit + RealityKit app on iOS/iPadOS (LiDAR devices, iOS 18+) — recursively applying the component to every entity with a ModelComponent, confirmed via logging that it's actually set. Real-world grounding shadows still never appear, under every configuration tried: Object anchored via a .plane-type AnchoringComponent.Target ARView.debugOptions.showAnchorGeometry confirms real collision geometry exactly where the object rests Ruled out the debug scene-understanding overlay visually masking the shadow (disabled it, no change) Inter-object shadows (virtual-on-virtual, via DirectionalLightComponent.Shadow) work fine — lighting itself isn't broken, just shadows onto the real-world mesh The pre-iOS 18 path (ARView.environment.sceneUnderstanding.options.insert(.receivesLighting)) also no longer produces a real-floor shadow — tried DirectionalLightComponent(isRealWorldProxy: true) and intensity from 800 up to 4000, no visual change either way. Two questions: Has anyone actually confirmed GroundingShadowComponent rendering a real-world shadow on iOS/iPadOS, or is it visionOS-only in practice despite being available on the iOS 18+ SDK? visionOS's documented fix for the same "lights don't affect passthrough" limitation is the ShadowReceivingOcclusionSurface Shader Graph node — is there an iOS/iPadOS equivalent, or is that visionOS-exclusive? Trying to determine if this is an unreported regression worth filing, or if there's an undocumented step I'm missing.
0
0
334
1w
Pinch gesture not recognized on MTKView when attaching it to a RealityView using a ViewAttachmentComponent in a immersive space
Hello! We are seeing a problem with a SwiftUI view that wraps an MTKView and that MTKView uses gesture recognizers from UIKit. One of those gestures we are using is UIPinchGestureRecognizer. And that gesture isn’t recognized at all when the SwiftUI view is attached to a RealityView using the ViewAttachmentComponent AND the RealityView is being shown in an ImmersiveSpace. If the SwiftUI view is attached to the RealityView using the init that has an attachment closure then pinching works fine there. So this definitely seems like a bug. Here is some code to help you reproduce the problem. Run this on a Vision Pro device. A simple red square will be rendered and if a single tap or pinch gesture is recognized on the red square, it will print to the console. App Code: import SwiftUI @main struct VisionPinchProblemsApp: App { var body: some Scene { WindowGroup { MenuView() } ImmersiveSpace(id: "RedSquare") { RedSquareView() } } } View code: import MetalKit import RealityKit import SwiftUI import UIKit struct MenuView: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var showImmersiveSpace = false @State private var immersiveSpaceIsOpen = false var body: some View { Form { Toggle("Show red square", isOn: $showImmersiveSpace) .task(id: showImmersiveSpace) { if showImmersiveSpace { await openImmersiveSpace(id: "RedSquare") immersiveSpaceIsOpen = true } else { if immersiveSpaceIsOpen { await dismissImmersiveSpace() immersiveSpaceIsOpen = false } } } } .onDisappear { // Attempt to close the immersive space on the way out. Task { if immersiveSpaceIsOpen { await dismissImmersiveSpace() } } } } } struct RedSquareView: View { let metalViewAttachmentID = "metalID" var body: some View { // Adds SwiftUI view using attachments closure. // Pinching and single taps are recognized here! // RealityView { content, attachments in // if let metalViewEntity = attachments.entity(for: metalViewAttachmentID) { // metalViewEntity.position = [0, 1, -1.25] // content.add(metalViewEntity) // } // } placeholder: { // ProgressView() // } attachments: { // Attachment(id: metalViewAttachmentID) { // MetalView() // } // } // Add SwiftUI view using ViewAttachmentComponent. // Pinching is not recognized here! // Single tapping is recognized ! // Why doesn't the red square show up in the Vision Pro simulator? RealityView { content in let metalViewEntity = Entity() let metalView = MetalView() .frame(width: 500, height: 500) let component = ViewAttachmentComponent(rootView: metalView) metalViewEntity.components.set(component) metalViewEntity.position = [0, 1, -1.25] content.add(metalViewEntity) } placeholder: { ProgressView() } } } struct MetalView: UIViewRepresentable { var device: MTLDevice? init() { self.device = MTLCreateSystemDefaultDevice() } func makeUIView(context: Context) -> MTKView { let mtkView = MTKView() mtkView.device = device mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) mtkView.delegate = context.coordinator let pinchGesture = UIPinchGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handlePinch(_:))) mtkView.addGestureRecognizer(pinchGesture) let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) mtkView.addGestureRecognizer(tapGesture) return mtkView } func updateUIView(_ uiView: MTKView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MTKViewDelegate { var parent: MetalView init(_ parent: MetalView) { self.parent = parent } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } guard let descriptor = view.currentRenderPassDescriptor else { return } let commandQueue = parent.device?.makeCommandQueue() let commandBuffer = commandQueue?.makeCommandBuffer() let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) renderEncoder?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } @objc func handlePinch(_ sender: UIPinchGestureRecognizer) { print("Pinch detected") } @objc func handleTap(_ sender: UITapGestureRecognizer) { print("Tap detected") } } }
1
0
434
1w
ManipulationComponent causes makeUIView(context:) to get called twice
Here I have some demo code that is rendering a cylinder "platter" using RealityKit and there is a red circle rendered on top of it which uses Metal and SwiftUI. When the platter appears you will see in the console that makeUIView(context:) is called twice while it is documented that it will only be called once when the view appears for the first time. So this seems like a bug. If you remove ManipulationComponent from the platter's components you will see that this problem goes away so it seems like that is the cause of the problem. Any insight here would be appreciated! Thank you. Here is what is printed in the console: Entity returned from EntityWrapper.makeEntity(context:) was already parented to another entity. This is not supported and may lead to unexpected behavior. SwiftUI adds entities to internally-managed entity hierarchies. Make UI View! This should be called once. Make UI View! This should be called once. Here is the app code: import SwiftUI @main struct SomeApp: App { var body: some Scene { WindowGroup { ContentView() } ImmersiveSpace(id: "TableTop") { TableTopPlatterView() } } } Here is the view code: import MetalKit import RealityKit import SwiftUI struct ContentView: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var showImmersiveSpace = false @State private var immersiveSpaceIsOpen = false var body: some View { Form { Toggle("Show table top", isOn: $showImmersiveSpace) .task(id: showImmersiveSpace) { if showImmersiveSpace { await openImmersiveSpace(id: "TableTop") immersiveSpaceIsOpen = true } else { if immersiveSpaceIsOpen { await dismissImmersiveSpace() immersiveSpaceIsOpen = false } } } } .onDisappear { // Attempt to close the immersive space on the way out. Task { if immersiveSpaceIsOpen { await dismissImmersiveSpace() } } } } } struct TableTopPlatterView: View { private var attachmentID: String { "RedCircle" } var body: some View { RealityView { content, attachments in if let redCircleEntity = attachments.entity(for: attachmentID) { // Lays the red circle in the platter. let rotation = Rotation3D(redCircleEntity.orientation) .rotated(by: .init(angle: .degrees(-90), axis: .x)) redCircleEntity.setOrientation(.init(rotation), relativeTo: nil) redCircleEntity.position.y = 0.026 platterEntity.addChild(redCircleEntity) content.add(platterEntity) } } placeholder: { ProgressView() } attachments: { Attachment(id: attachmentID) { MetalView() .clipShape(.circle) } } } /// The platter entity that the red circle lays on top of. private let platterEntity: ModelEntity = { let anchor = AnchorEntity( .plane( .horizontal, classification: .table, minimumBounds: [0.01, 0.01] ) ) let material = SimpleMaterial( color: .lightGray, roughness: 0.5, isMetallic: false ) let platter = ModelEntity( mesh: .generateCylinder(height: 0.05, radius: 0.475), materials: [material] ) platter.generateCollisionShapes(recursive: false) let components: [any Component] = [ InputTargetComponent(), GroundingShadowComponent(castsShadow: true), ManipulationComponent() // MARK: This is causing makeUIView to get called twice! ] platter.components.set(components) // Placed closer to the user when booted up. platter.position = [0, 1, -1.25] anchor.addChild(platter) return platter }() } // Metal view that renders a red square. struct MetalView: UIViewRepresentable { var device: MTLDevice? init() { self.device = MTLCreateSystemDefaultDevice() } func makeUIView(context: Context) -> MTKView { print("Make UI View! This should be called once.") let mtkView = MTKView() mtkView.device = device mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) mtkView.delegate = context.coordinator return mtkView } func updateUIView(_ uiView: MTKView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MTKViewDelegate { var metalView: MetalView init(_ metalView: MetalView) { self.metalView = metalView } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } guard let descriptor = view.currentRenderPassDescriptor else { return } let commandQueue = metalView.device?.makeCommandQueue() let commandBuffer = commandQueue?.makeCommandBuffer() let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) renderEncoder?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } } }
3
0
1.3k
1w
TextComponent Renders Inverted Text on Mac Catalyst
On Mac Catalyst, RealityKit TextComponent renders vertically inverted text. The issue occurs with both ARView and RealityView. The same text renders correctly on iOS and in a native macOS RealityView application (not Catalyst). Tested on macOS 26 and macOS 27 beta, with Xcode 26 and Xcode 27 Beta. Here is a screenshot of a text entity with a text component rendering the string "Text Component": Workaround On Mac Catalyst: Detect when the model generated by TextComponent becomes available on the text entity. Find the generated UnlitMaterial in the ModelComponent of the text entity. Disable face culling on the material. Invert the Y scale of the text entity. Here is the result: Reproduce Below is the full code that reproduces the problem, with both ARView and RealityView, as well as the workaround implementation. import SwiftUI import RealityKit import Combine // MARK: Text func createTextEntity() -> Entity { let textEntity = Entity() let attributes: [NSAttributedString.Key: Any] = [ .font: MeshResource.Font.systemFont(ofSize: 48, weight: .bold), .foregroundColor: Material.Color.white ] let attributedText = AttributedString( NSAttributedString( string: "Text Component", attributes: attributes ) ) var textComponent = TextComponent() textComponent.text = attributedText textComponent.size = CGSize(width: 400, height: 200) textComponent.backgroundColor = Material.Color.darkGray.cgColor textEntity.components.set(textComponent) return textEntity } // MARK: Camera func createCameraEntity() -> Entity { let cameraEntity = Entity() cameraEntity.components.set(PerspectiveCameraComponent()) cameraEntity.look(at: .zero, from: [0, 0, 0.3], relativeTo: nil) return cameraEntity } // MARK: ARView struct CatalystTextBugARView: UIViewRepresentable { let applyWorkaround: Bool = true func makeUIView(context: Context) -> ARView { let arView = ARView(frame: .zero) arView.cameraMode = .nonAR arView.automaticallyConfigureSession = false arView.environment.background = .color(.black) let rootEntity = AnchorEntity() let textEntity = createTextEntity() #if targetEnvironment(macCatalyst) if applyWorkaround { /// React when the text entity's ModelComponent becomes available or changes context.coordinator.modelDidAddSubscription = arView.scene.subscribe( to: ComponentEvents.DidAdd.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } context.coordinator.modelDidChangeSubscription = arView.scene.subscribe( to: ComponentEvents.DidChange.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } } #endif rootEntity.addChild(textEntity) rootEntity.addChild(createCameraEntity()) arView.scene.addAnchor(rootEntity) return arView } func updateUIView(_ uiView: ARView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator { /// Retain the scene subscriptions for the lifetime of the ARView. var modelDidAddSubscription: (any Cancellable)? var modelDidChangeSubscription: (any Cancellable)? } } // MARK: RealityView struct CatalystTextBugRealityView: View { let applyWorkaround: Bool = true /// Retain the RealityView subscriptions for the lifetime of the view. @State private var modelDidAddSubscription: EventSubscription? @State private var modelDidChangeSubscription: EventSubscription? var body: some View { RealityView { content in let textEntity = createTextEntity() #if targetEnvironment(macCatalyst) if applyWorkaround { /// React when the text entity's ModelComponent becomes available or changes. modelDidAddSubscription = content.subscribe( to: ComponentEvents.DidAdd.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } modelDidChangeSubscription = content.subscribe( to: ComponentEvents.DidChange.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } } #endif content.add(textEntity) content.add(createCameraEntity()) } .background(.black) } } // MARK: Workaround func applyTextBugWorkaround(to textEntity: Entity) { #if targetEnvironment(macCatalyst) guard var modelComponent = textEntity.components[ModelComponent.self] else { return } var foundTextMaterial = false var changedMaterial = false for materialIndex in modelComponent.materials.indices { guard var textMaterial = modelComponent.materials[materialIndex] as? UnlitMaterial else { continue } foundTextMaterial = true /// If face culling is already disabled, skip if case .none = textMaterial.faceCulling { continue } textMaterial.faceCulling = .none modelComponent.materials[materialIndex] = textMaterial changedMaterial = true } guard foundTextMaterial else { return } if changedMaterial { textEntity.components.set(modelComponent) } /// Counteract vertically inverted text. textEntity.scale.y = -abs(textEntity.scale.y) #endif } Usage Run either CatalystTextBugRealityView() or CatalystTextBugARView() on a Mac Catalyst target. Toggle applyWorkaround on either view to try with and without the workaround.
1
0
472
1w
macOS 27 beta: ProMotion refresh cadence is unstable, causing constant scroll judder
FB24091347 On macOS 27.0 beta (26A5388g), MacBook Pro M4 Pro, the built-in ProMotion display never settles on a stable refresh cadence. Scrolling in SwiftUI judders constantly. The same app binary was smooth on macOS 26, and is smooth on a 120 Hz ProMotion iPad. I captured two 60-second Instruments traces — same app, same scene, same scrolling, no external display — changing only the display's refresh-rate setting. On ProMotion the vsync interval standard deviation is 4.093 ms across six different cadences, mostly flip-flopping between 120 Hz and 60 Hz. Forced to a fixed 60 Hz it drops to 0.391 ms with a single cadence. The app presented an identical 59 fps median in both runs — frame production is perfectly steady, the display just holds each frame for an unpredictable length of time. That's what makes this nasty: it's invisible to every frame-rate metric, so it looks like the app got slow when nothing about the app changed. I spent most of a day profiling my own code before realising the app was never the problem. Workaround: force the built-in display to 60 Hz. Worth noting, because it complicates the picture: attaching a 60 Hz Studio Display makes the built-in smooth, but the Studio itself then judders — despite its own vsync cadence measuring perfectly stable. So refresh rate alone isn't the whole story, and there may be a second mechanism. The clean, reproducible, single-variable result is the ProMotion vs forced-60 Hz comparison on the built-in panel. If you can reproduce this on an M-series MacBook Pro on 27 beta, please file a duplicate referencing FB24091347.
0
0
366
1w
Using CARenderer for off-screen rendering of WKWebView results in a blank screen for the web page content on iOS 16 system version.
Using the CRenderer off-screen rendering method for WKWebView results in a blank screen for the web page content on the iOS 16 system version, but it can successfully obtain the web page content screen on the iOS 18 system version. I need a solution to achieve the display of web page content on the 16 system version, with a frame rate of more than 60 frames per second.
1
0
1.1k
1w
2D Soft Shadows with SpriteKit and Metal
Hi! I'd like to share an implementation of 2D soft shadows using SpriteKit and custom Metal rendering. GitHub Repo SpriteKit-SoftShadows The demo app runs on iOS and Mac Catalyst. The soft shadow implementation is based on Scott Lembcke's algorithm. Pipeline The app uses MTKView to drive the rendering loop at the desired frame rate. Each frame: SpriteKit renders the scene into a Metal texture using SKRenderer. The CPU sends each light's properties and the relevant shape edges to a Metal vertex shader. The vertex shader projects shadow geometry from each edge. A fragment shader calculates the shadow opacity at each pixel, producing a soft shadow mask for each light. A final fragment shader combines the SpriteKit texture, lights, and shadow masks to produce the displayed image. The SwiftUI controls update variables inside the SpriteKit scene through @Observable. The rendering loop consumes the new values on its next cycle. Let me know if you have any feedback!
Replies
0
Boosts
0
Views
214
Activity
13h
Are relaxed threadgroup atomics (atomic_fetch_add) officially supported on the Metal 4.0 feature set, or only 4.1?
I'm writing GPU compute kernels (parallel prefix-sum and histogram) that rely on threadgroup-address-space atomics specifically atomic_fetch_add_explicit on a threadgroup atomic_uint, with memory_order_relaxed. Setup: Device: Apple M5, macOS 26.5 Toolchain reports MSL 4.0 / AIR 2.8 (i.e. the "Metal 4.0" feature level) Note: I'm generating AIR (Apple IR) directly rather than emitting MSL source this is through a custom compute backend (Julia's Metal.jl), not the standard MSL front end. What I observe: These threadgroup atomics compile and produce correct results, and give a meaningful speedup over a non-atomic (scan-based) fallback. I've validated correctness across a 256-bin histogram and a full multi-pass radix sort no mismatches. The question: Some capability checks gate threadgroup atomic support behind Metal 4.1, and my device reports 4.0 yet they clearly work. So: Are relaxed integer threadgroup atomics (atomic_fetch_add on threadgroup atomic_uint, relaxed ordering) officially supported on the Metal 4.0 feature set for current Apple Silicon, or is this unsupported behavior that happens to work? Is there a specific MSL version or GPU family that is the true minimum for these operations? Does the answer differ at the AIR / feature-set level (what I'm targeting) vs. the MSL front end, given I'm feeding AIR to the compiler directly? I ask because a downstream library is (reasonably) hesitant to enable this path unless it's officially supported rather than relying on undefined behavior. Any authoritative guidance or a pointer to the relevant feature-set/GPU-family documentation would be hugely appreciated. Thanks!
Replies
1
Boosts
0
Views
394
Activity
18h
Game Center matchmaking fails for all non-default users on Apple TV — no IDS registration for secondary users (FB24156316)
Game Center matchmaking fails for all non-default users on Apple TV — no IDS registration for secondary users (FB24156316) On a multi-user Apple TV (tvOS 26.5, Apple TV 4K 3rd gen), Game Center real-time matchmaking fails for every user except the default user, in every app I've tested — including Apple Arcade titles. Filed as FB24156316 with full logs and sysdiagnose; posting here for visibility and in case anyone has shipped multi-user GC multiplayer on tvOS successfully. My game adopts com.apple.developer.user-management (runs-as-current-user-with-user-independent-keychain). The entitlement itself works: on a secondary user's profile the app runs under that user's persona and GKLocalPlayer authenticates as them — the welcome banner shows the right account. But any GKMatchmakerViewController quickmatch hard-fails within ~9 seconds ("Failed to find players"), and accepting an invite fails with GKError 35 ("not signed in to iCloud") even though Settings shows that user's iCloud as signed in. Unified logs show the root cause. When matchmaking starts, gamed can't provision the player's pseudonym because the current user has no identity-services registration: gamed No URI found on any account -- returning nil gamed Failed to fetch pseudonym for local player. Error: GameDaemonCore.PseudonymManagerError.failedToProvision( internalError: Error Domain=com.apple.ids.IDSPseudonymErrorDomain Code=400 "Invalid URI") For the default user, the identical flow succeeds (identityservicesd … resultCode: 0). Across a full day of log capture — profile adds, a remove/re-add, multiple user switches — identityservicesd never once references the secondary users' accounts: registration for them is never attempted, not attempted-and-failed. Meanwhile gamed advertises the nearby-matchmaking Bonjour service with the default user's identity while the foreground app runs as the secondary user. Reproduction matrix: two apps (my shipping game Extreme Violence and Apple Arcade's Crossy Road Castle, which also runs under the correct persona), both sandbox and production Game Center, two unrelated secondary accounts (both healthy elsewhere). Persists across reboot and profile remove/re-add. Default user unaffected. The documentation says the entitlement is all that's needed ("each person who uses your app will have access to… their own Game Center… you don't have to make any code changes" — WWDC20 session 10645). As far as I can tell that promise is currently unfulfillable for online play: there is no API or Settings path that creates the missing IDS registration. Has anyone seen non-default-user matchmaking work on tvOS, on any version? Is there anything an app can do here, or is this purely an OS-side fix? (Related: thread 782163 — a different tvOS matchmaking failure that DTS confirmed as a bug.)
Replies
3
Boosts
0
Views
593
Activity
20h
view protocol update ?
Hello ! Thank you guys for all the hard work you are doing on RealityKit For now in my studying Im trying to understand, in general, what will cause my Main app view to redraw everything? Or any view? If i have a struct that use the view protocol, and that struct is being called inside RealityView on the main app, or outside a reality view within a ZStack on the main app, if this view updates, does this will cause everything in my main app view to redraw as well? Or is it only true to @State variables of the main app? or the @State variables of the external view i try to implement? and what about @Observable ? Is there an accurate table that tells the developer what will cause a redraw of things? (not only the things the logic asks for, but all things sitting idle within the view) I try to understand how to separate UI updates, to avoid full redraw of things that havent been changed. to minimize UI compute in my games Before i play around with instruments i need to understand the general architecture.. if i work in a way that is counter designed to the way reality kit should work then the instrument results will not make sense to me. so i feel like i should ask you guys first. Depending on your answer i will know how to arrange my data in my game. And how to design and instantiate all my views Thanks
Replies
1
Boosts
0
Views
268
Activity
22h
GCKeyboard reports a phantom “Generic Keyboard” on iOS 27 beta 5
Update: Please disregard this report. The behavior was caused by enabling the hardware keyboard for the device in Xcode 27 beta 5’s Device Hub. This appears to expose a virtual “Generic Keyboard” to the connected physical device, causing GCKeyboardDidConnect to be posted even though no physical keyboard is attached directly to it. Simply disabling the hardware keyboard option was not sufficient. I needed to close Device Hub and restart Xcode for the virtual keyboard to be removed and the expected GCKeyboard behavior to resume. I’m seeing what appears to be a GameController regression on physical iPhone and iPad devices running iOS 27 beta 5. When my app launches with no Bluetooth, USB, or Smart Connector keyboard attached, GCKeyboard.coalesced is initially nil. Shortly afterward, the system posts .GCKeyboardDidConnect for a keyboard named “Generic Keyboard,” and GCKeyboard.coalesced becomes non-nil: === Initial state === GCKeyboard.coalesced: nil cannot add handler to 0 from 0 - dropping === GCKeyboardDidConnect #1 === notification.object: GCKeyboard: 0x10b01cd00 'Generic Keyboard'> GCKeyboard.coalesced: Optional( 0x10b01cd00>) The phantom keyboard then appears to remain connected indefinitely. After this happens: Attaching a real hardware keyboard does not post another .GCKeyboardDidConnect. Detaching the real keyboard does not post .GCKeyboardDidDisconnect. GCKeyboard.coalesced remains non-nil even though no hardware keyboard is attached. This may be interacting with the documented coalescing behavior: the connect notification is posted only for the first keyboard, and the disconnect notification only after the last keyboard disconnects. If the phantom “Generic Keyboard” remains present, a real keyboard is never considered the first or last keyboard. Minimal observer code: print("Initial GCKeyboard.coalesced:", GCKeyboard.coalesced as Any) NotificationCenter.default.addObserver( forName: .GCKeyboardDidConnect, object: nil, queue: .main ) { notification in print("GCKeyboardDidConnect") print("notification.object:", notification.object as Any) print("GCKeyboard.coalesced:", GCKeyboard.coalesced as Any) } NotificationCenter.default.addObserver( forName: .GCKeyboardDidDisconnect, object: nil, queue: .main ) { notification in print("GCKeyboardDidDisconnect") print("notification.object:", notification.object as Any) print("GCKeyboard.coalesced:", GCKeyboard.coalesced as Any) } I can reproduce this on both a physical iPhone and a physical iPad. This is not Simulator keyboard forwarding, and no keyboard is attached when the “Generic Keyboard” appears. Has anyone else observed this behavior on iOS 27 beta 5 or found another API that reliably reports whether a real hardware keyboard is attached? Filed as FB24269002.
Replies
0
Boosts
0
Views
33
Activity
23h
Update license of D3DMetal for redistribution
CrossOvers and Whisky both redistribute D3DMetal in their app. We would like to do so as well but the included license does not make it clear. Please update the license to make the exception clear so we can distribute under the same terms as those apps. FB24262864 is also filed regarding this issue.
Replies
0
Boosts
0
Views
177
Activity
1d
[Bug] iPadOS 26: 4-Finger Fast Tap/Swipe Gesture Not Detected (Multitouch Issue)
Hi everyone I'm experiencing an issue with iPadOS 26 regarding multi-touch gesture detection. When performing a quick four-finger gesture (tap and swipe), the system often fails to recognize the input. This especially affects multi-touch gestures, such as rhythm games with difficult levels. Steps to Reproduce: Place four fingers on the screen. Perform a quick tap or a quick horizontal swipe (like the one used to switch apps). Observe whether the gesture is ignored or detected inconsistently. Expected Behavior: 4-finger multitouch gestures should be recognized regardless of gesture speed, just like previous iPadOS versions. Actual Behavior: Gestures fail to be detected when executed quickly—same gestures still work, and miss notes in rhythm games. You can check out my posts on Twitter/x and Facebook: [https://x.com/kokona_fwa/status/1978131164104728949?s=61] Facebook: [https://m.facebook.com/groups/idipad/permalink/24438964899058806/?]
Replies
1
Boosts
1
Views
2.3k
Activity
1d
Metal rendering application is not releasing resources
I am developing a metal based ray tracing rendering application (running heavy GPU kernels). I am sometimes "forcefully quitting" my application and I can see the application is not in the activity monitor. But I can see the windowserver is using %97 the GPU. The mac gets hotter and hotter. I kill the windowserver, re-login it is still the case. The only way to fix is to restart the mac. I have checked if there are any zombie processes, there are none. I am 3-4 month into Mac development (I used many rendering APIs e.g. before under Windows and Linux, they release the resources automatically unless the driver is very broken), but I believe when you force quit or exit gracefully, regarding application should release resources. I may be missing some knowledge. Does anybody have an idea? I had added every corner a graceful exit code but once the kernel has some infinite loop the clean up cannot happen. In Windows there are some driver reload mechanisms to recover when GPU is stuck, is there a similar system ?
Replies
2
Boosts
0
Views
1.8k
Activity
2d
SpriteKit scene used as SCNView.overlaySKScene crashes due to SKShapeNode
I recently published my first game on the App Store. It uses SceneKit with a SpriteKit overlay. All crashes Xcode downloaded for it so far are related to some SpriteKit/SceneKit internals. The most common crash is caused by SKCShapeNode::_NEW_copyRenderPathData. What could cause such a crash? crash.crash While developing this game (and the BoardGameKit framework that appears in the crash log) over the years I experienced many crashes presumably caused by the SpriteKit overlay (I opened a post SceneKit app randomly crashes with EXC_BAD_ACCESS in jet_context::set_fragment_texture about such a crash in September 2024), and other people on the internet also mention that they experience crashes when using SpriteKit as a SceneKit overlay. Should I use a separate SKView and lay it on top of SCNView rather than setting SCNView.overlaySKScene? That seemed to solve the crashes for a guy on stackoverflow, but is it also encouraged by Apple? I know SceneKit is deprecated, but according to Apple critical bugs would still be fixed. Could this be considered a critical bug?
Replies
9
Boosts
0
Views
2.0k
Activity
3d
MPS bf16 softmax produces NaN on M5 Max (regression from M4) — breaks all on-device diffusion inference
Metal Performance Shaders produces from bf16/fp16 softmax for large attention tensors on , forcing the entire local generative-AI ecosystem to fall back to fp32. This is a . Environment Minimal reproduction Decomposed softmax on MPS — diffs = x - maxes produces NaN even though every element is identical (result should be all zeros): Real-world impact Running ComfyUI (the dominant local generative-AI UI) on M5 Max: Root cause (per PyTorch MPS maintainers) PyTorch maintainers (@drisspg, @albanD) have traced this to in the MPS/MPSGraph fused kernels. The NaN originates in the x - maxes subtraction inside softmax, then propagates through the attention block and the entire network. PyTorch cannot fix this — it is in the Metal/MPS kernel layer. Not isolated The M4-era fix ("fixed on macOS 15.1") did not survive onto M5, indicating the MPS fused-kernel precision fix was either reverted or not ported to the M5 GPU architecture. Request
Replies
0
Boosts
0
Views
417
Activity
3d
Behaviour of a 0-value `accelerationStructureID`
When constructing a MTLIndirectAccelerationStructureInstanceDescriptor, one specifies an accelerationStructureID. In the equivalents in both Vulkan and DirectX12, one can set it to zero to be an "inactive" instance. However, on Metal, this field does not appear to have any documentation, and thus it is difficult to figure out if there is similar behavior (and no other Metal documentation seems to mention this). Does setting this field to 0 (i.e. null) disable the instance? If not, is there any other way to have an equivalent effect?
Replies
0
Boosts
0
Views
438
Activity
4d
Best Way to Use MetalFX in Unreal Engine 5.7 for macOS Port?
Hi everyone, We’re currently porting a high-fidelity AA+ PC title built on Unreal Engine 5.7 to macOS (Apple Silicon), and we’re looking for guidance from anyone with experience in this area. At the moment, the game is already runnable on Mac, but not yet at a playable level — we’re seeing performance around 10–15 FPS on an M4 device. We’re actively analyzing and defining the work needed to reach production-quality performance on macOS. One of the key areas we’re exploring is leveraging MetalFX to improve frame rate. However, it seems there’s no official MetalFX plugin or direct integration available for Unreal Engine. Has anyone here successfully integrated MetalFX into a UE5 rendering pipeline, or found a recommended approach to do so? Any insights on best practices, workflows, or references (docs, samples, etc.) would be greatly appreciated. Thanks in advance!
Replies
4
Boosts
0
Views
1.7k
Activity
5d
MTL4FXFrameInterpolator no-op on MTL4CommandBuffer
I'm trying to use the new MTL4FX::FrameInterpolator(the Metal 4 variant that encodes to MTL4::CommandBuffer). It creates fine, accepts all texture bindings, and encodes without any error or assertion. The GPU signals completion via shared event. But the output texture is completely untouched — zero bytes changed from before the encode. It's a silent no-op. I'm trying to call metal-cpp from python by making dynamic library with cpp. Environment: macOS 26.6 (build 25G72) Apple M3, arm64 Xcode 26 SDK MetalFX framework (MTL4FX API, macOS 26+) I'm on M3 Summary: MTL4FX::FrameInterpolator::encodeToCommandBuffer(MTL4::CommandBuffer*) records, commits, and the GPU signals completion, but writes zero bytes to the output texture. The encode is a silent no-op — identical to documented issues 146436460 and 146436741 for MTL4FXTemporalScaler/DenoisedScaler. Diagnostic trace: Output texture zeroed before encode: 0/32768 non-zero bytes Output texture after encode (delta=0.5): 0/32768 non-zero bytes Changed bytes: 0/32768 ← definitive no-op Input textures verified: correct (R=1, G=0 vs R=0, G=1) I dont really know how to explain this, the result its just blank.
Replies
1
Boosts
0
Views
576
Activity
6d
What would case a compute kernel to run 100x slower only if it's run after a previous kernel.
I have two compute kernels. The first kernel pre_initization_0 initializes some buffers using a parallel random number generator initialize some buffers. The second kernel pre_sum_weights_0 effectively sums weighted values buffer of a much smaller dimension. #include <metal_stdlib> #include <metal_simdgroup> using namespace metal; struct mt_state { array<uint32_t, 624> array; uint16_t index; }; float random(device mt_state &state) { uint16_t k = state.index; uint16_t j = (k + 1) % 624; uint32_t x = (state.array[k] & 0x80000000U) | (state.array[j] & 0x7fffffffU); uint32_t xA = x >> 1; if (x & 0x00000001U) { xA ^= 0x9908b0dfU; } j = (k + 397) % 624; x = state.array[j]^xA; state.array[k] = x; state.index = (k + 1) % 624; uint32_t y = x^(x >> 11); y = y^((y << 7) & 0x9d2c5680U); y = y^((y << 15) & 0xefc60000U); return static_cast<float> (y^(y >> 18)); } kernel void pre_initization_0( device float *vc30c09b98 [[buffer(0)]], // x used 0 device float *vc30c09c38 [[buffer(1)]], // v_{||} used 0 device float *vc30c09cd8 [[buffer(2)]], // v_{\perp} used 0 device mt_state *sc30c50c18 [[buffer(3)]], constant uint32_t &offset [[buffer(4)]], uint index [[thread_position_in_grid]]) { if (offset + index < 3000000) { device mt_state &rc30c50c18 = sc30c50c18[index]; // used 4 const float rc30c06218 = 2.17689351e-08; // used 1 const float rc30c06318 = -46.7484322; // used 1 const float rc30c0a458 = fma(rc30c06218, random(rc30c50c18), rc30c06318); // used 1 const float rc30c07718 = 5.18059896e-05; // used 1 const float rc30c06798 = -1; // used 1 const float rc30c06618 = 2.32830644e-10; // used 2 const float rc30c06718 = 1.17549435e-38; // used 2 const float rc30c0a4f8 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034f02f8 = log(rc30c0a4f8); // used 1 const float rc30c06818 = rc30c06798*r1034f02f8; // used 1 const float r1034eedf8 = sqrt(rc30c06818); // used 1 const float rc30c06418 = 1.46291812e-09; // used 1 const float rc30c06498 = rc30c06418*random(rc30c50c18); // used 1 const float r1034eee68 = sin(rc30c06498); // used 1 const float rc30c06a18 = r1034eedf8*r1034eee68; // used 1 const float rc30c07698 = rc30c07718*rc30c06a18; // used 1 const float rc30c07598 = 3.3356411e-09; // used 1 const float rc30c07318 = -241213328; // used 1 const float rc30c0a598 = fma(rc30c06618, random(rc30c50c18), rc30c06718); // used 1 const float r1034ef238 = log(rc30c0a598); // used 1 const float rc30c06c98 = rc30c07318*r1034ef238; // used 1 const float rc310b0018 = sqrt(rc30c06c98); // used 1 const float rc30c07618 = rc30c07598*rc310b0018; // used 1 vc30c09b98[offset + index] = rc30c0a458; vc30c09c38[offset + index] = rc30c07698; vc30c09cd8[offset + index] = rc30c07618; } } kernel void pre_sum_weights_0( constant float *vc30c09b98 [[buffer(0)]], // x used 7 device atomic_float *vc30c0a098 [[buffer(1)]], const texture1d<float, access::read> ac310a9000 [[texture(0)]], const texture1d<float, access::read> ac310d8000 [[texture(1)]], const texture1d<float, access::read> ac310d9000 [[texture(2)]], const texture1d<float, access::read> ac310da000 [[texture(3)]], uint index [[thread_position_in_grid]]) { if (index < 3000000) { const float rc30c09b98 = vc30c09b98[index]; // x used 7 const float rc30c07798 = 0.0935904533; // used 2 const float rc30d08318 = rc30c09b98 - rc30c07798; // used 1 const ushort ic30d08398 = (ushort)min(max((rc30d08318 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07c98 = -5.34242535; // used 1 const ushort ic30d08018 = (ushort)min(max((rc30c09b98 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 5 const float rc30c51018 = ac310da000.read(ic30d08018).r; // used 1 const float rc30c0a6d8 = fma(rc30c07c98, rc30c09b98, rc30c51018); // used 1 const float rc30c07a18 = -10.6848507; // used 1 const float rc30c50e98 = ac310d8000.read(ic30d08018).r; // used 1 const float rc30c0a778 = fma(rc30c07a18, rc30c09b98, rc30c50e98); // used 1 const float rc30c07b98 = rc30c0a6d8*rc30c0a778; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08398], rc30c07b98, memory_order_relaxed); // used 1 const float rc30c07c18 = 0.75; // used 1 const float rc30c07e18 = 114.166031; // used 1 const float rc30c50d18 = ac310a9000.read(ic30d08018).r; // used 1 const float rc30d08098 = rc30c50d18 - rc30c09b98; // used 1 const float rc30c07d98 = rc30d08098*rc30d08098; // used 1 const float rc30c07e98 = rc30c07e18*rc30c07d98; // used 1 const float rc30d08218 = rc30c07c18 - rc30c07e98; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08018], rc30d08218, memory_order_relaxed); // used 1 const float rc30d08498 = rc30c07798 + rc30c09b98; // used 1 const ushort ic30d08418 = (ushort)min(max((rc30d08498 - -46.7484322)/0.0935904533,(float)0),(float)999); // used 1 const float rc30c07b18 = 0.5; // used 1 const float rc30c07918 = 1.5; // used 1 const float rc30c07818 = 10.6848507; // used 1 const float rc30c50f98 = ac310d9000.read(ic30d08018).r; // used 1 const float rc30d08298 = rc30c50f98 - rc30c09b98; // used 1 const float rc30c07a98 = rc30c07818*rc30d08298; // used 1 const float rc30d08118 = rc30c07918 - rc30c07a98; // used 1 const float rc30d34018 = rc30d08118*rc30d08118; // used 1 const float rc30d34098 = rc30c07b18*rc30d34018; // used 1 atomic_fetch_add_explicit(&vc30c0a098[ic30d08418], rc30d34098, memory_order_relaxed); // used 1 } } Running some timing experiments, If I initialize [[buffer(0)]] of the pre_sum_weights_0 kernel on the CPU, and run the kernel, it will run in 0.0489688 s measured using GPUEndTime - GPUStartTime in a completedHander for the command buffer. If I run the initialization kernel pre_initization_0 before this, the kernel execution time of pre_sum_weights_0 slows to 7.20396 s. Note I am launching these kernels from different command buffers.
Replies
1
Boosts
0
Views
555
Activity
1w
GroundingShadowComponent — working on iOS/iPadOS by anyone?
Following up on thread 733918 (visionOS grounding shadows, solved by applying GroundingShadowComponent(castsShadow: true) to every ModelEntity in a hierarchy, not just the root). I've implemented that exact fix in an ARKit + RealityKit app on iOS/iPadOS (LiDAR devices, iOS 18+) — recursively applying the component to every entity with a ModelComponent, confirmed via logging that it's actually set. Real-world grounding shadows still never appear, under every configuration tried: Object anchored via a .plane-type AnchoringComponent.Target ARView.debugOptions.showAnchorGeometry confirms real collision geometry exactly where the object rests Ruled out the debug scene-understanding overlay visually masking the shadow (disabled it, no change) Inter-object shadows (virtual-on-virtual, via DirectionalLightComponent.Shadow) work fine — lighting itself isn't broken, just shadows onto the real-world mesh The pre-iOS 18 path (ARView.environment.sceneUnderstanding.options.insert(.receivesLighting)) also no longer produces a real-floor shadow — tried DirectionalLightComponent(isRealWorldProxy: true) and intensity from 800 up to 4000, no visual change either way. Two questions: Has anyone actually confirmed GroundingShadowComponent rendering a real-world shadow on iOS/iPadOS, or is it visionOS-only in practice despite being available on the iOS 18+ SDK? visionOS's documented fix for the same "lights don't affect passthrough" limitation is the ShadowReceivingOcclusionSurface Shader Graph node — is there an iOS/iPadOS equivalent, or is that visionOS-exclusive? Trying to determine if this is an unreported regression worth filing, or if there's an undocumented step I'm missing.
Replies
0
Boosts
0
Views
334
Activity
1w
Pinch gesture not recognized on MTKView when attaching it to a RealityView using a ViewAttachmentComponent in a immersive space
Hello! We are seeing a problem with a SwiftUI view that wraps an MTKView and that MTKView uses gesture recognizers from UIKit. One of those gestures we are using is UIPinchGestureRecognizer. And that gesture isn’t recognized at all when the SwiftUI view is attached to a RealityView using the ViewAttachmentComponent AND the RealityView is being shown in an ImmersiveSpace. If the SwiftUI view is attached to the RealityView using the init that has an attachment closure then pinching works fine there. So this definitely seems like a bug. Here is some code to help you reproduce the problem. Run this on a Vision Pro device. A simple red square will be rendered and if a single tap or pinch gesture is recognized on the red square, it will print to the console. App Code: import SwiftUI @main struct VisionPinchProblemsApp: App { var body: some Scene { WindowGroup { MenuView() } ImmersiveSpace(id: "RedSquare") { RedSquareView() } } } View code: import MetalKit import RealityKit import SwiftUI import UIKit struct MenuView: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var showImmersiveSpace = false @State private var immersiveSpaceIsOpen = false var body: some View { Form { Toggle("Show red square", isOn: $showImmersiveSpace) .task(id: showImmersiveSpace) { if showImmersiveSpace { await openImmersiveSpace(id: "RedSquare") immersiveSpaceIsOpen = true } else { if immersiveSpaceIsOpen { await dismissImmersiveSpace() immersiveSpaceIsOpen = false } } } } .onDisappear { // Attempt to close the immersive space on the way out. Task { if immersiveSpaceIsOpen { await dismissImmersiveSpace() } } } } } struct RedSquareView: View { let metalViewAttachmentID = "metalID" var body: some View { // Adds SwiftUI view using attachments closure. // Pinching and single taps are recognized here! // RealityView { content, attachments in // if let metalViewEntity = attachments.entity(for: metalViewAttachmentID) { // metalViewEntity.position = [0, 1, -1.25] // content.add(metalViewEntity) // } // } placeholder: { // ProgressView() // } attachments: { // Attachment(id: metalViewAttachmentID) { // MetalView() // } // } // Add SwiftUI view using ViewAttachmentComponent. // Pinching is not recognized here! // Single tapping is recognized ! // Why doesn't the red square show up in the Vision Pro simulator? RealityView { content in let metalViewEntity = Entity() let metalView = MetalView() .frame(width: 500, height: 500) let component = ViewAttachmentComponent(rootView: metalView) metalViewEntity.components.set(component) metalViewEntity.position = [0, 1, -1.25] content.add(metalViewEntity) } placeholder: { ProgressView() } } } struct MetalView: UIViewRepresentable { var device: MTLDevice? init() { self.device = MTLCreateSystemDefaultDevice() } func makeUIView(context: Context) -> MTKView { let mtkView = MTKView() mtkView.device = device mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) mtkView.delegate = context.coordinator let pinchGesture = UIPinchGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handlePinch(_:))) mtkView.addGestureRecognizer(pinchGesture) let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) mtkView.addGestureRecognizer(tapGesture) return mtkView } func updateUIView(_ uiView: MTKView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MTKViewDelegate { var parent: MetalView init(_ parent: MetalView) { self.parent = parent } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } guard let descriptor = view.currentRenderPassDescriptor else { return } let commandQueue = parent.device?.makeCommandQueue() let commandBuffer = commandQueue?.makeCommandBuffer() let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) renderEncoder?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } @objc func handlePinch(_ sender: UIPinchGestureRecognizer) { print("Pinch detected") } @objc func handleTap(_ sender: UITapGestureRecognizer) { print("Tap detected") } } }
Replies
1
Boosts
0
Views
434
Activity
1w
ManipulationComponent causes makeUIView(context:) to get called twice
Here I have some demo code that is rendering a cylinder "platter" using RealityKit and there is a red circle rendered on top of it which uses Metal and SwiftUI. When the platter appears you will see in the console that makeUIView(context:) is called twice while it is documented that it will only be called once when the view appears for the first time. So this seems like a bug. If you remove ManipulationComponent from the platter's components you will see that this problem goes away so it seems like that is the cause of the problem. Any insight here would be appreciated! Thank you. Here is what is printed in the console: Entity returned from EntityWrapper.makeEntity(context:) was already parented to another entity. This is not supported and may lead to unexpected behavior. SwiftUI adds entities to internally-managed entity hierarchies. Make UI View! This should be called once. Make UI View! This should be called once. Here is the app code: import SwiftUI @main struct SomeApp: App { var body: some Scene { WindowGroup { ContentView() } ImmersiveSpace(id: "TableTop") { TableTopPlatterView() } } } Here is the view code: import MetalKit import RealityKit import SwiftUI struct ContentView: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var showImmersiveSpace = false @State private var immersiveSpaceIsOpen = false var body: some View { Form { Toggle("Show table top", isOn: $showImmersiveSpace) .task(id: showImmersiveSpace) { if showImmersiveSpace { await openImmersiveSpace(id: "TableTop") immersiveSpaceIsOpen = true } else { if immersiveSpaceIsOpen { await dismissImmersiveSpace() immersiveSpaceIsOpen = false } } } } .onDisappear { // Attempt to close the immersive space on the way out. Task { if immersiveSpaceIsOpen { await dismissImmersiveSpace() } } } } } struct TableTopPlatterView: View { private var attachmentID: String { "RedCircle" } var body: some View { RealityView { content, attachments in if let redCircleEntity = attachments.entity(for: attachmentID) { // Lays the red circle in the platter. let rotation = Rotation3D(redCircleEntity.orientation) .rotated(by: .init(angle: .degrees(-90), axis: .x)) redCircleEntity.setOrientation(.init(rotation), relativeTo: nil) redCircleEntity.position.y = 0.026 platterEntity.addChild(redCircleEntity) content.add(platterEntity) } } placeholder: { ProgressView() } attachments: { Attachment(id: attachmentID) { MetalView() .clipShape(.circle) } } } /// The platter entity that the red circle lays on top of. private let platterEntity: ModelEntity = { let anchor = AnchorEntity( .plane( .horizontal, classification: .table, minimumBounds: [0.01, 0.01] ) ) let material = SimpleMaterial( color: .lightGray, roughness: 0.5, isMetallic: false ) let platter = ModelEntity( mesh: .generateCylinder(height: 0.05, radius: 0.475), materials: [material] ) platter.generateCollisionShapes(recursive: false) let components: [any Component] = [ InputTargetComponent(), GroundingShadowComponent(castsShadow: true), ManipulationComponent() // MARK: This is causing makeUIView to get called twice! ] platter.components.set(components) // Placed closer to the user when booted up. platter.position = [0, 1, -1.25] anchor.addChild(platter) return platter }() } // Metal view that renders a red square. struct MetalView: UIViewRepresentable { var device: MTLDevice? init() { self.device = MTLCreateSystemDefaultDevice() } func makeUIView(context: Context) -> MTKView { print("Make UI View! This should be called once.") let mtkView = MTKView() mtkView.device = device mtkView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) mtkView.delegate = context.coordinator return mtkView } func updateUIView(_ uiView: MTKView, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MTKViewDelegate { var metalView: MetalView init(_ metalView: MetalView) { self.metalView = metalView } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } guard let descriptor = view.currentRenderPassDescriptor else { return } let commandQueue = metalView.device?.makeCommandQueue() let commandBuffer = commandQueue?.makeCommandBuffer() let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) renderEncoder?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } } }
Replies
3
Boosts
0
Views
1.3k
Activity
1w
TextComponent Renders Inverted Text on Mac Catalyst
On Mac Catalyst, RealityKit TextComponent renders vertically inverted text. The issue occurs with both ARView and RealityView. The same text renders correctly on iOS and in a native macOS RealityView application (not Catalyst). Tested on macOS 26 and macOS 27 beta, with Xcode 26 and Xcode 27 Beta. Here is a screenshot of a text entity with a text component rendering the string "Text Component": Workaround On Mac Catalyst: Detect when the model generated by TextComponent becomes available on the text entity. Find the generated UnlitMaterial in the ModelComponent of the text entity. Disable face culling on the material. Invert the Y scale of the text entity. Here is the result: Reproduce Below is the full code that reproduces the problem, with both ARView and RealityView, as well as the workaround implementation. import SwiftUI import RealityKit import Combine // MARK: Text func createTextEntity() -> Entity { let textEntity = Entity() let attributes: [NSAttributedString.Key: Any] = [ .font: MeshResource.Font.systemFont(ofSize: 48, weight: .bold), .foregroundColor: Material.Color.white ] let attributedText = AttributedString( NSAttributedString( string: "Text Component", attributes: attributes ) ) var textComponent = TextComponent() textComponent.text = attributedText textComponent.size = CGSize(width: 400, height: 200) textComponent.backgroundColor = Material.Color.darkGray.cgColor textEntity.components.set(textComponent) return textEntity } // MARK: Camera func createCameraEntity() -> Entity { let cameraEntity = Entity() cameraEntity.components.set(PerspectiveCameraComponent()) cameraEntity.look(at: .zero, from: [0, 0, 0.3], relativeTo: nil) return cameraEntity } // MARK: ARView struct CatalystTextBugARView: UIViewRepresentable { let applyWorkaround: Bool = true func makeUIView(context: Context) -> ARView { let arView = ARView(frame: .zero) arView.cameraMode = .nonAR arView.automaticallyConfigureSession = false arView.environment.background = .color(.black) let rootEntity = AnchorEntity() let textEntity = createTextEntity() #if targetEnvironment(macCatalyst) if applyWorkaround { /// React when the text entity's ModelComponent becomes available or changes context.coordinator.modelDidAddSubscription = arView.scene.subscribe( to: ComponentEvents.DidAdd.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } context.coordinator.modelDidChangeSubscription = arView.scene.subscribe( to: ComponentEvents.DidChange.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } } #endif rootEntity.addChild(textEntity) rootEntity.addChild(createCameraEntity()) arView.scene.addAnchor(rootEntity) return arView } func updateUIView(_ uiView: ARView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator { /// Retain the scene subscriptions for the lifetime of the ARView. var modelDidAddSubscription: (any Cancellable)? var modelDidChangeSubscription: (any Cancellable)? } } // MARK: RealityView struct CatalystTextBugRealityView: View { let applyWorkaround: Bool = true /// Retain the RealityView subscriptions for the lifetime of the view. @State private var modelDidAddSubscription: EventSubscription? @State private var modelDidChangeSubscription: EventSubscription? var body: some View { RealityView { content in let textEntity = createTextEntity() #if targetEnvironment(macCatalyst) if applyWorkaround { /// React when the text entity's ModelComponent becomes available or changes. modelDidAddSubscription = content.subscribe( to: ComponentEvents.DidAdd.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } modelDidChangeSubscription = content.subscribe( to: ComponentEvents.DidChange.self, on: textEntity, componentType: ModelComponent.self ) { _ in applyTextBugWorkaround(to: textEntity) } } #endif content.add(textEntity) content.add(createCameraEntity()) } .background(.black) } } // MARK: Workaround func applyTextBugWorkaround(to textEntity: Entity) { #if targetEnvironment(macCatalyst) guard var modelComponent = textEntity.components[ModelComponent.self] else { return } var foundTextMaterial = false var changedMaterial = false for materialIndex in modelComponent.materials.indices { guard var textMaterial = modelComponent.materials[materialIndex] as? UnlitMaterial else { continue } foundTextMaterial = true /// If face culling is already disabled, skip if case .none = textMaterial.faceCulling { continue } textMaterial.faceCulling = .none modelComponent.materials[materialIndex] = textMaterial changedMaterial = true } guard foundTextMaterial else { return } if changedMaterial { textEntity.components.set(modelComponent) } /// Counteract vertically inverted text. textEntity.scale.y = -abs(textEntity.scale.y) #endif } Usage Run either CatalystTextBugRealityView() or CatalystTextBugARView() on a Mac Catalyst target. Toggle applyWorkaround on either view to try with and without the workaround.
Replies
1
Boosts
0
Views
472
Activity
1w
macOS 27 beta: ProMotion refresh cadence is unstable, causing constant scroll judder
FB24091347 On macOS 27.0 beta (26A5388g), MacBook Pro M4 Pro, the built-in ProMotion display never settles on a stable refresh cadence. Scrolling in SwiftUI judders constantly. The same app binary was smooth on macOS 26, and is smooth on a 120 Hz ProMotion iPad. I captured two 60-second Instruments traces — same app, same scene, same scrolling, no external display — changing only the display's refresh-rate setting. On ProMotion the vsync interval standard deviation is 4.093 ms across six different cadences, mostly flip-flopping between 120 Hz and 60 Hz. Forced to a fixed 60 Hz it drops to 0.391 ms with a single cadence. The app presented an identical 59 fps median in both runs — frame production is perfectly steady, the display just holds each frame for an unpredictable length of time. That's what makes this nasty: it's invisible to every frame-rate metric, so it looks like the app got slow when nothing about the app changed. I spent most of a day profiling my own code before realising the app was never the problem. Workaround: force the built-in display to 60 Hz. Worth noting, because it complicates the picture: attaching a 60 Hz Studio Display makes the built-in smooth, but the Studio itself then judders — despite its own vsync cadence measuring perfectly stable. So refresh rate alone isn't the whole story, and there may be a second mechanism. The clean, reproducible, single-variable result is the ProMotion vs forced-60 Hz comparison on the built-in panel. If you can reproduce this on an M-series MacBook Pro on 27 beta, please file a duplicate referencing FB24091347.
Replies
0
Boosts
0
Views
366
Activity
1w
Using CARenderer for off-screen rendering of WKWebView results in a blank screen for the web page content on iOS 16 system version.
Using the CRenderer off-screen rendering method for WKWebView results in a blank screen for the web page content on the iOS 16 system version, but it can successfully obtain the web page content screen on the iOS 18 system version. I need a solution to achieve the display of web page content on the 16 system version, with a frame rate of more than 60 frames per second.
Replies
1
Boosts
0
Views
1.1k
Activity
1w