From ce7e9cbde7a96f7db354a0990a2bd8c8bbdb1361 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 23 Jul 2026 23:08:34 +0200 Subject: [PATCH] Fix crash in CAOpenGLLayer shadow-copy init on macOS 27 beta macOS 27 beta routes contentsScale changes (window moving between screens) through Core Animation's implicit-animation path, which builds a presentation copy of the layer via init(layer:). Our override re-ran visual setters on that copy, and -[CAOpenGLLayer setColorspace:] dereferences render state the shadow copy doesn't own, crashing with EXC_BAD_ACCESS. Drop the redundant setter block (super.init(layer:) already copies presentation values) and disable implicit actions around the contentsScale writes in MPVOGLView so the presentation-copy path is never entered for scale updates. --- Yattee/Services/Player/MPV/MPVOGLView.swift | 15 +++++++++++++-- Yattee/Services/Player/MPV/MPVOpenGLLayer.swift | 11 ++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Yattee/Services/Player/MPV/MPVOGLView.swift b/Yattee/Services/Player/MPV/MPVOGLView.swift index 8bb32914..fb146f2a 100644 --- a/Yattee/Services/Player/MPV/MPVOGLView.swift +++ b/Yattee/Services/Player/MPV/MPVOGLView.swift @@ -12,6 +12,7 @@ import AppKit import CoreMedia import CoreVideo import Libmpv +import QuartzCore // MARK: - MPVOGLView @@ -168,8 +169,13 @@ final class MPVOGLView: NSView { stopDisplayLink() startDisplayLink() - // Update contents scale for new window + // Update contents scale for new window. Disable implicit actions so + // Core Animation never builds a presentation copy of the GL layer + // for this change (crashes on macOS 27 beta). + CATransaction.begin() + CATransaction.setDisableActions(true) videoLayer?.contentsScale = window.backingScaleFactor + CATransaction.commit() // Reattaching (e.g. macOS player sheet dismissed via ESC then re-expanded) // detaches this shared view without stopping playback. macOS drawing is @@ -182,9 +188,14 @@ final class MPVOGLView: NSView { override func viewDidChangeBackingProperties() { super.viewDidChangeBackingProperties() - // Update contents scale when backing properties change + // Update contents scale when backing properties change. Disable implicit + // actions so Core Animation never builds a presentation copy of the GL + // layer for this change (crashes on macOS 27 beta). if let scale = window?.backingScaleFactor { + CATransaction.begin() + CATransaction.setDisableActions(true) videoLayer?.contentsScale = scale + CATransaction.commit() } // Update display refresh rate diff --git a/Yattee/Services/Player/MPV/MPVOpenGLLayer.swift b/Yattee/Services/Player/MPV/MPVOpenGLLayer.swift index d11da7a3..54928e9a 100644 --- a/Yattee/Services/Player/MPV/MPVOpenGLLayer.swift +++ b/Yattee/Services/Player/MPV/MPVOpenGLLayer.swift @@ -236,15 +236,12 @@ final class MPVOpenGLLayer: CAOpenGLLayer { self.bufferDepth = previousLayer.bufferDepth self.isSetup = previousLayer.isSetup + // super.init(layer:) already copies presentation values; re-invoking + // setters like `colorspace` on the shadow copy dereferences render + // state the copy doesn't own and crashes (macOS 27 beta routes + // contentsScale changes through this path on screen changes). super.init(layer: layer) - autoresizingMask = previousLayer.autoresizingMask - backgroundColor = previousLayer.backgroundColor - isOpaque = previousLayer.isOpaque - colorspace = previousLayer.colorspace - contentsFormat = previousLayer.contentsFormat - isAsynchronous = previousLayer.isAsynchronous - Task { @MainActor in LoggingService.shared.debug("MPVOpenGLLayer: created shadow copy", category: .mpv) }