1c04e77293
apple / screenshots (push) Has been cancelled
apple / swift (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / web (push) Has been cancelled
ci / rust (push) Has been cancelled
android-screenshots / screenshots (push) Successful in 2m16s
deb / build-publish (push) Successful in 3m26s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
windows-host / package (push) Successful in 6m48s
release / apple (push) Successful in 7m45s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m22s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m37s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
android / android (push) Successful in 9m35s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m32s
linux-client-screenshots / screenshots (push) Successful in 2m31s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m53s
web-screenshots / screenshots (push) Successful in 2m32s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m37s
flatpak / build-publish (push) Failing after 3m47s
docker / deploy-docs (push) Failing after 1m9s
feat(apple): add cursor capture on iPad
70 lines
3.5 KiB
Swift
70 lines
3.5 KiB
Swift
import XCTest
|
|
|
|
#if canImport(Metal)
|
|
import CoreVideo
|
|
import Metal
|
|
import QuartzCore
|
|
@testable import PunktfunkKit
|
|
|
|
final class MetalPresenterTests: XCTestCase {
|
|
/// `MetalVideoPresenter.make()` compiles the runtime Metal shaders (the BT.709/BT.2020 YUV→RGB
|
|
/// fragment shaders plus the Catmull-Rom luma sampler). A `nil` result on a GPU-equipped host
|
|
/// means a shader failed to compile — this catches a malformed shader before it silently
|
|
/// degrades stage-2 to a stage-1 fallback on device.
|
|
func testPresenterInitCompilesShaders() throws {
|
|
guard MTLCreateSystemDefaultDevice() != nil else {
|
|
throw XCTSkip("no Metal device available in this environment")
|
|
}
|
|
XCTAssertNotNil(
|
|
MetalVideoPresenter.make(),
|
|
"stage-2 Metal shaders failed to compile (presenter init returned nil)")
|
|
}
|
|
|
|
/// The HDR fix: `configure(hdr:)` must put the layer into the BT.2020-PQ EDR configuration with a
|
|
/// reference-white anchor (`edrMetadata`) — the missing anchor was what made HDR render "too
|
|
/// bright". SDR must use the plain 8-bit path with EDR off and no metadata. A mid-session flip is a
|
|
/// per-mode reconfigure, so the round trip back to SDR must fully restore the SDR config.
|
|
func testConfigureHDRSetsEDRAnchor() throws {
|
|
guard let presenter = MetalVideoPresenter.make() else {
|
|
throw XCTSkip("no Metal device available in this environment")
|
|
}
|
|
presenter.configure(hdr: true)
|
|
XCTAssertEqual(presenter.layer.pixelFormat, .rgba16Float, "HDR uses an EDR-capable drawable")
|
|
XCTAssertNotNil(presenter.layer.colorspace, "HDR layer must be tagged (itur_2100_PQ)")
|
|
XCTAssertTrue(
|
|
presenter.layer.wantsExtendedDynamicRangeContent, "EDR must be requested on all platforms")
|
|
XCTAssertNotNil(
|
|
presenter.layer.edrMetadata,
|
|
"HDR must anchor reference white via edrMetadata (the fix for 'too bright')")
|
|
|
|
// Mid-session HDR→SDR flip: the 8-bit path, EDR off, no metadata.
|
|
presenter.configure(hdr: false)
|
|
XCTAssertEqual(presenter.layer.pixelFormat, .bgra8Unorm, "SDR uses the plain 8-bit drawable")
|
|
XCTAssertFalse(presenter.layer.wantsExtendedDynamicRangeContent)
|
|
XCTAssertNil(presenter.layer.edrMetadata)
|
|
}
|
|
|
|
/// `render` with a freshly-allocated NV12 buffer must present without crashing or hanging — the
|
|
/// main-thread present path is the highest-risk part of the stage-2 rewrite. (A headless CI with no
|
|
/// display can still allocate a drawable from a CAMetalLayer; if it can't, render returns false,
|
|
/// which is also a valid non-crashing outcome.)
|
|
func testRenderDoesNotCrashOnNV12Frame() throws {
|
|
guard let presenter = MetalVideoPresenter.make() else {
|
|
throw XCTSkip("no Metal device available in this environment")
|
|
}
|
|
presenter.configure(hdr: false)
|
|
var pb: CVPixelBuffer?
|
|
let attrs: [CFString: Any] = [kCVPixelBufferMetalCompatibilityKey: true]
|
|
let status = CVPixelBufferCreate(
|
|
kCFAllocatorDefault, 256, 256, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
|
|
attrs as CFDictionary, &pb)
|
|
guard status == kCVReturnSuccess, let pixelBuffer = pb else {
|
|
throw XCTSkip("could not allocate a test pixel buffer")
|
|
}
|
|
// Just asserting it returns (true or false) without trapping — the layer may have no drawable
|
|
// source headless, so a false return is acceptable.
|
|
_ = presenter.render(pixelBuffer, isHDR: false)
|
|
}
|
|
}
|
|
#endif
|