mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-12 13:08:24 -10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09b678a61f | ||
|
|
a31bdf288f | ||
|
|
0e3284a884 | ||
|
|
7f106a3736 | ||
|
|
c7dc2e57b2 |
No files matched your search
@@ -81,6 +81,10 @@ on:
|
||||
- 'libretro-common/include/retro_atomic.h'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ios_sdk_only:
|
||||
description: 'Audit production VideoToolbox declarations against real iOS SDKs'
|
||||
type: boolean
|
||||
default: false
|
||||
native_aac_only:
|
||||
description: 'Run native AudioToolbox AAC acceptance without GPU/frontend jobs'
|
||||
type: boolean
|
||||
@@ -93,7 +97,7 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
stream-native:
|
||||
if: ${{ !inputs.native_aac_only && !inputs.frontend_stream_only }}
|
||||
if: ${{ !inputs.ios_sdk_only && !inputs.native_aac_only && !inputs.frontend_stream_only }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
@@ -152,7 +156,7 @@ jobs:
|
||||
${{ runner.temp }}/vt-metal-dispatch-mrc
|
||||
if-no-files-found: ignore
|
||||
aac-native:
|
||||
if: ${{ inputs.native_aac_only || !inputs.frontend_stream_only }}
|
||||
if: ${{ !inputs.ios_sdk_only && (inputs.native_aac_only || !inputs.frontend_stream_only) }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
@@ -184,7 +188,7 @@ jobs:
|
||||
path: ${{ runner.temp }}/audiotoolbox-src-evidence
|
||||
if-no-files-found: ignore
|
||||
encode:
|
||||
if: ${{ !inputs.native_aac_only && !inputs.frontend_stream_only }}
|
||||
if: ${{ !inputs.ios_sdk_only && !inputs.native_aac_only && !inputs.frontend_stream_only }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
@@ -285,7 +289,7 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
|
||||
frontend-build:
|
||||
if: ${{ !inputs.native_aac_only && !inputs.frontend_stream_only }}
|
||||
if: ${{ !inputs.ios_sdk_only && !inputs.native_aac_only && !inputs.frontend_stream_only }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
@@ -322,7 +326,7 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
|
||||
frontend-stream-build:
|
||||
if: ${{ !inputs.native_aac_only }}
|
||||
if: ${{ !inputs.ios_sdk_only && !inputs.native_aac_only }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
@@ -372,9 +376,46 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
|
||||
frontend-contracts:
|
||||
if: ${{ !inputs.native_aac_only }}
|
||||
if: ${{ !inputs.ios_sdk_only && !inputs.native_aac_only }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Source-clock capture and backpressure under sanitizers
|
||||
run: make -C samples/record/metal_frontend check SANITIZER=address,undefined
|
||||
|
||||
ios-sdk:
|
||||
if: ${{ inputs.ios_sdk_only }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Compile production VideoToolbox headers for iOS and simulator
|
||||
run: python3 samples/record/videotoolbox_encode/ios_sdk_check.py --output-dir "${{ runner.temp }}/vt-ios-sdk"
|
||||
- name: Save actual SDK diagnostics
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vt-ios-sdk
|
||||
path: ${{ runner.temp }}/vt-ios-sdk
|
||||
if-no-files-found: error
|
||||
|
||||
ios-app:
|
||||
if: ${{ inputs.ios_sdk_only }}
|
||||
needs: ios-sdk
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build and link actual iOS application with native recording
|
||||
run: python3 samples/record/videotoolbox_encode/ios_app_check.py --output-dir "${{ runner.temp }}/vt-ios-app"
|
||||
- name: Save full application build evidence
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vt-ios-app
|
||||
path: |
|
||||
${{ runner.temp }}/vt-ios-app/*.json
|
||||
${{ runner.temp }}/vt-ios-app/*.log
|
||||
${{ runner.temp }}/vt-ios-app/*.txt
|
||||
${{ runner.temp }}/vt-ios-app/build.xcresult
|
||||
if-no-files-found: error
|
||||
@@ -9,6 +9,8 @@
|
||||
#else
|
||||
#define VT_ENCODE_AVAILABLE
|
||||
#endif
|
||||
#include "videotoolbox_encode_hardware.h"
|
||||
|
||||
typedef struct videotoolbox_encode_config
|
||||
{
|
||||
unsigned width, height, slots, bitrate, key_interval, timescale, bit_depth;
|
||||
@@ -57,7 +59,15 @@ static INLINE VT_ENCODE_AVAILABLE videotoolbox_encode_t *videotoolbox_encode_new
|
||||
videotoolbox_encode_t *encoder = NULL;
|
||||
CFMutableDictionaryRef spec = NULL, attrs = NULL, props = NULL;
|
||||
CFTypeRef hardware = NULL, selected_id = NULL;
|
||||
CFStringRef admitted_id = NULL;
|
||||
#ifdef VT_ENCODE_MOBILE
|
||||
bool query_hardware = false;
|
||||
#endif
|
||||
OSStatus status = kVTParameterErr;
|
||||
#ifdef VT_ENCODE_MOBILE
|
||||
if (VT_ENCODE_HARDWARE_KEYS_AVAILABLE)
|
||||
query_hardware = true;
|
||||
#endif
|
||||
if (error) *error = status;
|
||||
if ((encoder_id && !CFStringGetLength(encoder_id)) || !config || !config->width || !config->height || (config->width & 1)
|
||||
|| (config->height & 1) || config->width > 16384 || config->height > 16384
|
||||
@@ -68,6 +78,20 @@ static INLINE VT_ENCODE_AVAILABLE videotoolbox_encode_t *videotoolbox_encode_new
|
||||
|| (config->bit_depth != 8 && config->bit_depth != 10)
|
||||
|| (config->bit_depth == 10 && !config->hevc)
|
||||
|| (config->hdr && config->bit_depth != 10)) return NULL;
|
||||
#ifdef VT_ENCODE_MOBILE
|
||||
if (!query_hardware)
|
||||
{
|
||||
admitted_id = videotoolbox_encode_hardware_id(encoder_id,
|
||||
config->hevc ? kCMVideoCodecType_HEVC : kCMVideoCodecType_H264);
|
||||
if (!admitted_id)
|
||||
{
|
||||
if (error)
|
||||
*error = kVTCouldNotFindVideoEncoderErr;
|
||||
return NULL;
|
||||
}
|
||||
encoder_id = admitted_id;
|
||||
}
|
||||
#endif
|
||||
encoder = (videotoolbox_encode_t*)calloc(1, sizeof(*encoder));
|
||||
spec = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
attrs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
@@ -76,7 +100,10 @@ static INLINE VT_ENCODE_AVAILABLE videotoolbox_encode_t *videotoolbox_encode_new
|
||||
encoder->config = *config;
|
||||
encoder->pool = videotoolbox_encode_slots_new(config->slots);
|
||||
if (!encoder->pool) { status = kVTAllocationFailedErr; goto done; }
|
||||
CFDictionarySetValue(spec, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, kCFBooleanTrue);
|
||||
#ifdef VT_ENCODE_HAS_HARDWARE_KEYS
|
||||
if (VT_ENCODE_HARDWARE_KEYS_AVAILABLE)
|
||||
CFDictionarySetValue(spec, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, kCFBooleanTrue);
|
||||
#endif
|
||||
if (encoder_id) CFDictionarySetValue(spec,kVTVideoEncoderSpecification_EncoderID,encoder_id);
|
||||
if (gpu_registry_id)
|
||||
{
|
||||
@@ -132,10 +159,15 @@ static INLINE VT_ENCODE_AVAILABLE videotoolbox_encode_t *videotoolbox_encode_new
|
||||
if (status) goto done;
|
||||
status = VTCompressionSessionPrepareToEncodeFrames(encoder->session);
|
||||
if (status) goto done;
|
||||
status = VTSessionCopyProperty(encoder->session,
|
||||
kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder, NULL, &hardware);
|
||||
if (!status && (!hardware || CFGetTypeID(hardware) != CFBooleanGetTypeID()
|
||||
|| !CFBooleanGetValue((CFBooleanRef)hardware))) status = kVTCouldNotFindVideoEncoderErr;
|
||||
#ifdef VT_ENCODE_HAS_HARDWARE_KEYS
|
||||
if (VT_ENCODE_HARDWARE_KEYS_AVAILABLE)
|
||||
{
|
||||
status = VTSessionCopyProperty(encoder->session,
|
||||
kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder, NULL, &hardware);
|
||||
if (!status && (!hardware || CFGetTypeID(hardware) != CFBooleanGetTypeID()
|
||||
|| !CFBooleanGetValue((CFBooleanRef)hardware))) status = kVTCouldNotFindVideoEncoderErr;
|
||||
}
|
||||
#endif
|
||||
if (!status && encoder_id)
|
||||
{
|
||||
status=VTSessionCopyProperty(encoder->session,kVTCompressionPropertyKey_EncoderID,NULL,&selected_id);
|
||||
@@ -143,6 +175,7 @@ static INLINE VT_ENCODE_AVAILABLE videotoolbox_encode_t *videotoolbox_encode_new
|
||||
|| !CFEqual(selected_id,encoder_id))) status=kVTCouldNotFindVideoEncoderErr;
|
||||
}
|
||||
done:
|
||||
if (admitted_id) CFRelease((CFTypeRef)admitted_id);
|
||||
if (selected_id) CFRelease(selected_id);
|
||||
if (hardware) CFRelease(hardware);
|
||||
if (props) CFRelease(props);
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_VIDEOTOOLBOX_ENCODE_HARDWARE_H
|
||||
#define RETROARCH_VIDEOTOOLBOX_ENCODE_HARDWARE_H
|
||||
|
||||
#if defined(__APPLE__) && !defined(VIDEOTOOLBOX_ENCODE_TEST_TYPES)
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#define VT_ENCODE_MOBILE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(VT_ENCODE_MOBILE) \
|
||||
|| (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400) \
|
||||
|| (defined(__TV_OS_VERSION_MAX_ALLOWED) && __TV_OS_VERSION_MAX_ALLOWED >= 170400) \
|
||||
|| (defined(__VISION_OS_VERSION_MAX_ALLOWED) && __VISION_OS_VERSION_MAX_ALLOWED >= 10100)
|
||||
#define VT_ENCODE_HAS_HARDWARE_KEYS
|
||||
#endif
|
||||
|
||||
#if defined(VT_ENCODE_MOBILE) && defined(VT_ENCODE_HAS_HARDWARE_KEYS)
|
||||
#define VT_ENCODE_HARDWARE_KEYS_AVAILABLE \
|
||||
__builtin_available(iOS 17.4, tvOS 17.4, macCatalyst 17.4, visionOS 1.1, *)
|
||||
#elif defined(VT_ENCODE_HAS_HARDWARE_KEYS)
|
||||
#define VT_ENCODE_HARDWARE_KEYS_AVAILABLE true
|
||||
#else
|
||||
#define VT_ENCODE_HARDWARE_KEYS_AVAILABLE false
|
||||
#endif
|
||||
|
||||
#if defined(VT_ENCODE_MOBILE) || defined(VIDEOTOOLBOX_ENCODE_HARDWARE_LIST_TEST)
|
||||
/* Cold admission only. Return a retained, advertised hardware encoder ID;
|
||||
* the caller must independently confirm the session selected that same ID. */
|
||||
static INLINE CFStringRef videotoolbox_encode_hardware_id(
|
||||
CFStringRef requested, uint32_t codec_type)
|
||||
{
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_HARDWARE_LIST_TEST) \
|
||||
|| (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) \
|
||||
|| (defined(__TV_OS_VERSION_MAX_ALLOWED) && __TV_OS_VERSION_MAX_ALLOWED >= 130000) \
|
||||
|| defined(__VISION_OS_VERSION_MAX_ALLOWED)
|
||||
#ifndef VIDEOTOOLBOX_ENCODE_HARDWARE_LIST_TEST
|
||||
if (__builtin_available(iOS 13.0, tvOS 13.0, *))
|
||||
#endif
|
||||
{
|
||||
CFArrayRef list = NULL;
|
||||
CFStringRef selected = NULL;
|
||||
CFIndex i, count;
|
||||
OSStatus status = VTCopyVideoEncoderList(NULL, &list);
|
||||
if (status || !list)
|
||||
{
|
||||
if (list)
|
||||
CFRelease(list);
|
||||
return NULL;
|
||||
}
|
||||
count = CFArrayGetCount(list);
|
||||
if (count >= 0 && count <= 4096)
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
CFDictionaryRef entry = (CFDictionaryRef)CFArrayGetValueAtIndex(list, i);
|
||||
CFTypeRef hardware, codec, identifier;
|
||||
int64_t value = 0;
|
||||
if (!entry || CFGetTypeID(entry) != CFDictionaryGetTypeID())
|
||||
continue;
|
||||
hardware = CFDictionaryGetValue(entry, kVTVideoEncoderList_IsHardwareAccelerated);
|
||||
codec = CFDictionaryGetValue(entry, kVTVideoEncoderList_CodecType);
|
||||
identifier = CFDictionaryGetValue(entry, kVTVideoEncoderList_EncoderID);
|
||||
if (!hardware || CFGetTypeID(hardware) != CFBooleanGetTypeID()
|
||||
|| !CFBooleanGetValue((CFBooleanRef)hardware)
|
||||
|| !codec || CFGetTypeID(codec) != CFNumberGetTypeID()
|
||||
|| !CFNumberGetValue((CFNumberRef)codec, kCFNumberSInt64Type, &value)
|
||||
|| value != (int64_t)codec_type
|
||||
|| !identifier || CFGetTypeID(identifier) != CFStringGetTypeID()
|
||||
|| !CFStringGetLength((CFStringRef)identifier)
|
||||
|| (requested && !CFEqual(identifier, requested)))
|
||||
continue;
|
||||
selected = (CFStringRef)CFRetain(identifier);
|
||||
break;
|
||||
}
|
||||
CFRelease(list);
|
||||
return selected;
|
||||
}
|
||||
#endif
|
||||
(void)requested;
|
||||
(void)codec_type;
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -7,7 +7,7 @@
|
||||
// The cleanroom network stack, as on the other platforms: the crypto,
|
||||
// the encrypted keychain, the TLS 1.2/1.3 client and the SMB2/3 client
|
||||
// with Kerberos, plus the NFSv3/v4 client.
|
||||
NETWORK_STACK_FLAGS = -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS
|
||||
NETWORK_STACK_FLAGS = -DHAVE_RTMP -DHAVE_RFLV -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS
|
||||
|
||||
OTHER_CFLAGS = $(inherited) $(NETWORK_STACK_FLAGS)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#define RECORD_METAL_H
|
||||
#include "../record_driver.h"
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_OSX && defined(HAVE_METAL) && defined(HAVE_AVF) \
|
||||
#include "../record_metal_platform.h"
|
||||
#if defined(RECORD_METAL_NATIVE_SDK) && defined(HAVE_METAL) && defined(HAVE_AVF) \
|
||||
&& defined(HAVE_THREADS) && defined(HAVE_CONFIGFILE)
|
||||
#define HAVE_RECORD_METAL
|
||||
#endif
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
#undef RECORD_METAL_DISPATCH_UNDEF_INLINE
|
||||
#endif
|
||||
#include "record_gpu_dispatch_metal.h"
|
||||
#include "record_metal_platform.h"
|
||||
#include <retro_atomic.h>
|
||||
#include <defines/cocoa_defines.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#if TARGET_OS_OSX && defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
#if defined(RECORD_METAL_NATIVE_SDK) && defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
#include <pthread.h>
|
||||
#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__<199901L) && !defined(inline)
|
||||
#define inline __inline__
|
||||
@@ -447,6 +448,11 @@ static record_gpu_dispatch_metal_t *record_metal_dispatch_new(void *device,
|
||||
id<MTLDevice> metal=(__bridge id<MTLDevice>)device;
|
||||
size_t length;
|
||||
int error;
|
||||
if (RECORD_METAL_NATIVE_RUNTIME)
|
||||
{
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
if (!metal || !config || (!output && !stream)
|
||||
|| (output && output->video_format) || !path || !*path) return NULL;
|
||||
#ifndef RECORD_METAL_STREAM
|
||||
|
||||
@@ -17,16 +17,22 @@
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include "record_gpu_session_metal.h"
|
||||
#include "record_metal_platform.h"
|
||||
#include "record_avfoundation_session_native.h"
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && TARGET_OS_OSX \
|
||||
&& defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED>=101400
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && defined(RECORD_METAL_NATIVE_SDK)
|
||||
/* Enumerated IDs prevent silent default/software/other-GPU selection. A
|
||||
* hardware encoder with no advertised GPU is a separate explicit category,
|
||||
* as used by the hosted paravirtualized media engines. It is not evidence of
|
||||
* physical same-GPU encoding; CVPixelBuffer/Metal interop remains gated. */
|
||||
static __attribute__((availability(macos,introduced=10.14))) videotoolbox_encode_t *record_metal_select_encoder(
|
||||
static RECORD_METAL_NATIVE_AVAILABLE videotoolbox_encode_t *record_metal_select_encoder(
|
||||
const videotoolbox_encode_config_t *config,uint64_t renderer_gpu,OSStatus *error)
|
||||
{
|
||||
#if TARGET_OS_IPHONE
|
||||
/* Mobile admission verifies hardware and the selected ID in the VT adapter.
|
||||
* CVPixelBuffer/Metal compatibility is still checked by capture creation. */
|
||||
(void)renderer_gpu;
|
||||
return videotoolbox_encode_new(config, error);
|
||||
#else
|
||||
CFArrayRef list=NULL;
|
||||
CFIndex i,count;
|
||||
unsigned priority;
|
||||
@@ -62,6 +68,7 @@ static __attribute__((availability(macos,introduced=10.14))) videotoolbox_encode
|
||||
}
|
||||
done:
|
||||
CFRelease(list); *error=status; return encoder;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
record_avfoundation_session_t *record_gpu_session_metal_new_output(void *device,
|
||||
@@ -73,8 +80,8 @@ record_avfoundation_session_t *record_gpu_session_metal_new_output(void *device,
|
||||
if (queue_out) *queue_out=NULL;
|
||||
if (stage) *stage=RECORD_METAL_STARTUP_UNSUPPORTED;
|
||||
if (native_status) *native_status=0;
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && TARGET_OS_OSX \
|
||||
&& defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED>=101400
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && defined(RECORD_METAL_NATIVE_SDK)
|
||||
if (RECORD_METAL_NATIVE_RUNTIME)
|
||||
@autoreleasepool
|
||||
{
|
||||
videotoolbox_encode_config_t config;
|
||||
@@ -94,8 +101,12 @@ record_avfoundation_session_t *record_gpu_session_metal_new_output(void *device,
|
||||
|| !c->width || !c->height || c->width>16384 || c->height>16384
|
||||
|| ((c->width|c->height)&1) || c->slots<2 || c->slots>VIDEOTOOLBOX_ENCODE_SLOTS
|
||||
|| c->mode>2 || (c->mode && c->bit_depth!=10)) goto done;
|
||||
#if TARGET_OS_IPHONE
|
||||
gpu = 0;
|
||||
#else
|
||||
gpu=[(__bridge id<MTLDevice>)device registryID];
|
||||
if (!gpu) goto done;
|
||||
#endif
|
||||
config.width=c->width; config.height=c->height; config.slots=c->slots;
|
||||
config.bitrate=c->bitrate; config.key_interval=c->key_interval; config.timescale=c->timescale;
|
||||
config.bit_depth=c->bit_depth; config.hevc=c->hevc; config.hdr=c->mode!=0;
|
||||
@@ -128,20 +139,20 @@ done:
|
||||
if (native_status) *native_status=(int32_t)status;
|
||||
return session;
|
||||
}
|
||||
return NULL;
|
||||
#else
|
||||
(void)device; (void)c; (void)bind; (void)context;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && TARGET_OS_OSX \
|
||||
&& defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED>=101400
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && defined(RECORD_METAL_NATIVE_SDK)
|
||||
typedef struct record_metal_file_binding
|
||||
{
|
||||
const record_mux_avfoundation_config_t *output;
|
||||
const char *path;
|
||||
size_t audio_capacity;
|
||||
} record_metal_file_binding_t;
|
||||
static __attribute__((availability(macos,introduced=10.14))) record_avfoundation_session_t *record_metal_file_bind(
|
||||
static RECORD_METAL_NATIVE_AVAILABLE record_avfoundation_session_t *record_metal_file_bind(
|
||||
void *data,record_gpu_queue_t *queue,record_gpu_capture_metal_t *capture,
|
||||
struct videotoolbox_encode *encoder,unsigned width,unsigned height)
|
||||
{
|
||||
@@ -159,8 +170,8 @@ record_avfoundation_session_t *record_gpu_session_metal_new(void *device,
|
||||
if (queue_out) *queue_out=NULL;
|
||||
if (stage) *stage=RECORD_METAL_STARTUP_UNSUPPORTED;
|
||||
if (native_status) *native_status=0;
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && TARGET_OS_OSX \
|
||||
&& defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED>=101400
|
||||
#if defined(VIDEOTOOLBOX_ENCODE_SLOTS) && defined(RECORD_METAL_NATIVE_SDK)
|
||||
if (RECORD_METAL_NATIVE_RUNTIME)
|
||||
{
|
||||
record_metal_file_binding_t binding;
|
||||
if (!c || !output || output->video_format || c->timescale!=output->timescale || !path || !*path)
|
||||
@@ -172,6 +183,7 @@ record_avfoundation_session_t *record_gpu_session_metal_new(void *device,
|
||||
return record_gpu_session_metal_new_output(device,c,record_metal_file_bind,&binding,
|
||||
capture_out,queue_out,stage,native_status);
|
||||
}
|
||||
return NULL;
|
||||
#else
|
||||
(void)device; (void)c; (void)output; (void)audio_capacity_bytes; (void)path;
|
||||
return NULL;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RECORD_METAL_PLATFORM_H
|
||||
#define RECORD_METAL_PLATFORM_H
|
||||
#include <TargetConditionals.h>
|
||||
#include <Availability.h>
|
||||
|
||||
#if (TARGET_OS_OSX && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) \
|
||||
&& __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) \
|
||||
|| (TARGET_OS_IPHONE && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) \
|
||||
&& __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) \
|
||||
|| (TARGET_OS_TV && defined(__TV_OS_VERSION_MAX_ALLOWED) \
|
||||
&& __TV_OS_VERSION_MAX_ALLOWED >= 130000)
|
||||
#define RECORD_METAL_NATIVE_SDK
|
||||
#endif
|
||||
|
||||
#define RECORD_METAL_NATIVE_AVAILABLE \
|
||||
__attribute__((availability(macos,introduced=10.14), \
|
||||
availability(ios,introduced=13.0), availability(tvos,introduced=13.0)))
|
||||
#define RECORD_METAL_NATIVE_RUNTIME \
|
||||
__builtin_available(macOS 10.14, iOS 13.0, tvOS 13.0, *)
|
||||
#endif
|
||||
@@ -26,3 +26,6 @@
|
||||
|
||||
/session_gpu_refusal_test
|
||||
/session_gpu_refusal_test.exe
|
||||
|
||||
/hardware_id_test
|
||||
/hardware_id_test.exe
|
||||
@@ -9,7 +9,7 @@ ifneq ($(SANITIZER),)
|
||||
CFLAGS += -fsanitize=$(SANITIZER) -fno-omit-frame-pointer
|
||||
LDFLAGS += -fsanitize=$(SANITIZER)
|
||||
endif
|
||||
all: av_output_test$(EXE) audio_lend_test$(EXE) packet_test$(EXE) session_gpu_refusal_test$(EXE) av_session_test$(EXE) crop_test$(EXE) av_worker_test$(EXE) audio_queue_test$(EXE) slots_test$(EXE) session_test$(EXE) worker_test$(EXE) audio_rate_test$(EXE) audio_rate_native_test$(EXE)
|
||||
all: hardware_id_test$(EXE) av_output_test$(EXE) audio_lend_test$(EXE) packet_test$(EXE) session_gpu_refusal_test$(EXE) av_session_test$(EXE) crop_test$(EXE) av_worker_test$(EXE) audio_queue_test$(EXE) slots_test$(EXE) session_test$(EXE) worker_test$(EXE) audio_rate_test$(EXE) audio_rate_native_test$(EXE)
|
||||
av_output_test$(EXE): av_output_test.c av_worker_test.c $(RA)/record/record_avfoundation_session.c $(RA)/record/record_avfoundation_session.h
|
||||
$(CC) $(CFLAGS) -Wno-unused-function -o $@ av_output_test.c $(RA)/record/record_avfoundation_session.c $(RA)/record/record_avfoundation_worker.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_queue.c $(RA)/record/record_audio_queue.c $(RA)/libretro-common/queues/retro_spsc.c $(LDFLAGS)
|
||||
audio_lend_test$(EXE): audio_lend_test.c $(RA)/record/record_audio_queue.c $(RA)/record/record_audio_queue.h
|
||||
@@ -34,9 +34,11 @@ crop_test$(EXE): crop_test.c $(RA)/libretro-common/formats/h265/rh265_sps_crop.c
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
worker_test$(EXE): worker_test.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_worker_vt.h $(RA)/record/record_gpu_queue.c
|
||||
$(CC) $(CFLAGS) -o $@ worker_test.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_queue.c $(RA)/libretro-common/queues/retro_spsc.c $(LDFLAGS)
|
||||
session_gpu_refusal_test$(EXE): session_test.c $(RA)/gfx/common/videotoolbox_encode.h
|
||||
session_gpu_refusal_test$(EXE): session_test.c $(RA)/gfx/common/videotoolbox_encode.h $(RA)/gfx/common/videotoolbox_encode_hardware.h
|
||||
$(CC) $(CFLAGS) -DVIDEOTOOLBOX_ENCODE_TEST_NO_GPU_SELECTION -o $@ $< $(LDFLAGS)
|
||||
session_test$(EXE): session_test.c $(RA)/gfx/common/videotoolbox_encode.h $(RA)/gfx/common/videotoolbox_encode_slots.h
|
||||
session_test$(EXE): session_test.c $(RA)/gfx/common/videotoolbox_encode.h $(RA)/gfx/common/videotoolbox_encode_slots.h $(RA)/gfx/common/videotoolbox_encode_hardware.h
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
hardware_id_test$(EXE): hardware_id_test.c $(RA)/gfx/common/videotoolbox_encode_hardware.h
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
slots_test$(EXE): slots_test.c $(RA)/gfx/common/videotoolbox_encode_slots.h
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
@@ -58,6 +60,7 @@ check: all legacy-check
|
||||
$(RUNNER) ./worker_test$(EXE)
|
||||
$(RUNNER) ./audio_rate_test$(EXE)
|
||||
clean:
|
||||
rm -f hardware_id_test hardware_id_test.exe
|
||||
rm -f av_output_test av_output_test.exe
|
||||
rm -f audio_lend_test audio_lend_test.exe
|
||||
rm -f packet_test packet_test.exe
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_inline.h>
|
||||
|
||||
typedef struct object object_t;
|
||||
typedef object_t *CFTypeRef;
|
||||
typedef object_t *CFArrayRef;
|
||||
typedef object_t *CFDictionaryRef;
|
||||
typedef object_t *CFStringRef;
|
||||
typedef object_t *CFNumberRef;
|
||||
typedef object_t *CFBooleanRef;
|
||||
typedef long CFIndex;
|
||||
typedef int OSStatus;
|
||||
struct object
|
||||
{
|
||||
unsigned type, refs;
|
||||
int64_t number;
|
||||
const char *text;
|
||||
object_t *values[3];
|
||||
};
|
||||
#define kVTVideoEncoderList_IsHardwareAccelerated 0
|
||||
#define kVTVideoEncoderList_CodecType 1
|
||||
#define kVTVideoEncoderList_EncoderID 2
|
||||
#define kCFNumberSInt64Type 1
|
||||
static object_t list, entry, hardware, codec, identifier, requested;
|
||||
static CFIndex count = 1;
|
||||
static OSStatus list_status;
|
||||
static unsigned visits;
|
||||
static CFTypeRef CFRetain(CFTypeRef object)
|
||||
{
|
||||
assert(object && object->refs);
|
||||
object->refs++;
|
||||
return object;
|
||||
}
|
||||
static void CFRelease(CFTypeRef object)
|
||||
{
|
||||
assert(object && object->refs > 1);
|
||||
object->refs--;
|
||||
}
|
||||
static OSStatus VTCopyVideoEncoderList(void *options, CFArrayRef *output)
|
||||
{
|
||||
(void)options;
|
||||
*output = CFRetain(&list);
|
||||
return list_status;
|
||||
}
|
||||
static CFIndex CFArrayGetCount(CFArrayRef array)
|
||||
{
|
||||
assert(array == &list);
|
||||
return count;
|
||||
}
|
||||
static CFTypeRef CFArrayGetValueAtIndex(CFArrayRef array, CFIndex index)
|
||||
{
|
||||
assert(array == &list && index == 0);
|
||||
visits++;
|
||||
return &entry;
|
||||
}
|
||||
static unsigned CFGetTypeID(CFTypeRef object)
|
||||
{
|
||||
return object->type;
|
||||
}
|
||||
static unsigned CFDictionaryGetTypeID(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
static unsigned CFBooleanGetTypeID(void)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
static unsigned CFNumberGetTypeID(void)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
static unsigned CFStringGetTypeID(void)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
static CFTypeRef CFDictionaryGetValue(CFDictionaryRef object, unsigned key)
|
||||
{
|
||||
assert(object == &entry && key < 3);
|
||||
return object->values[key];
|
||||
}
|
||||
static bool CFBooleanGetValue(CFBooleanRef value)
|
||||
{
|
||||
return value->number != 0;
|
||||
}
|
||||
static bool CFNumberGetValue(CFNumberRef value, unsigned type, void *output)
|
||||
{
|
||||
assert(type == kCFNumberSInt64Type);
|
||||
*(int64_t*)output = value->number;
|
||||
return true;
|
||||
}
|
||||
static unsigned CFStringGetLength(CFStringRef value)
|
||||
{
|
||||
return (unsigned)strlen(value->text);
|
||||
}
|
||||
static bool CFEqual(CFTypeRef first, CFStringRef second)
|
||||
{
|
||||
return !strcmp(first->text, second->text);
|
||||
}
|
||||
#define VIDEOTOOLBOX_ENCODE_HARDWARE_LIST_TEST
|
||||
#include "../../../gfx/common/videotoolbox_encode_hardware.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
CFStringRef selected;
|
||||
list.refs = entry.refs = hardware.refs = codec.refs = identifier.refs = requested.refs = 1;
|
||||
entry.type = 1; hardware.type = 2; codec.type = 3; identifier.type = requested.type = 4;
|
||||
hardware.number = 1; codec.number = 7;
|
||||
identifier.text = requested.text = "hardware-encoder";
|
||||
entry.values[0] = &hardware;
|
||||
entry.values[1] = &codec;
|
||||
entry.values[2] = &identifier;
|
||||
selected = videotoolbox_encode_hardware_id(NULL, 7);
|
||||
assert(selected == &identifier && identifier.refs == 2 && list.refs == 1);
|
||||
CFRelease(selected);
|
||||
selected = videotoolbox_encode_hardware_id(&requested, 7);
|
||||
assert(selected == &identifier);
|
||||
CFRelease(selected);
|
||||
requested.text = "other-encoder";
|
||||
assert(!videotoolbox_encode_hardware_id(&requested, 7));
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 8));
|
||||
hardware.number = 0;
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7));
|
||||
hardware.number = 1;
|
||||
hardware.type = 3;
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7));
|
||||
hardware.type = 2;
|
||||
identifier.text = "";
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7));
|
||||
identifier.text = "hardware-encoder";
|
||||
entry.values[2] = NULL;
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7));
|
||||
entry.values[2] = &identifier;
|
||||
list_status = -1;
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7) && list.refs == 1);
|
||||
list_status = 0;
|
||||
visits = 0;
|
||||
count = 4097;
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7) && !visits);
|
||||
count = -1;
|
||||
assert(!videotoolbox_encode_hardware_id(NULL, 7) && !visits);
|
||||
assert(identifier.refs == 1 && list.refs == 1);
|
||||
puts("PASS advertised hardware encoder ID, codec admission, malformed lists and retained ownership");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build and link the production iOS application; never report device acceptance."""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output-dir', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
if sys.platform != 'darwin':
|
||||
parser.error('full iOS application linking requires macOS/Xcode')
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
output = args.output_dir.resolve()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
common = ['xcodebuild', '-project', 'pkg/apple/RetroArch_iOS13.xcodeproj',
|
||||
'-scheme', 'RetroArch iOS Release', '-configuration', 'Release',
|
||||
'-destination', 'generic/platform=iOS', '-xcconfig', 'pkg/apple/GitHubCI.xcconfig',
|
||||
'-derivedDataPath', str(output / 'derived'),
|
||||
'CODE_SIGNING_ALLOWED=NO', 'CODE_SIGNING_REQUIRED=NO',
|
||||
'CODE_SIGN_IDENTITY=', 'PROVISIONING_PROFILE_SPECIFIER=']
|
||||
settings = subprocess.run(common + ['-showBuildSettings', '-json'], cwd=root,
|
||||
check=True, capture_output=True, text=True, timeout=180)
|
||||
(output / 'build-settings.json').write_text(settings.stdout)
|
||||
rows = json.loads(settings.stdout)
|
||||
main_target = next(row['buildSettings'] for row in rows if row['target'] == 'RetroArchiOS')
|
||||
flags = shlex.split(main_target['OTHER_CFLAGS'])
|
||||
defines = {flag[2:].split('=', 1)[0] for flag in flags if flag.startswith('-D')}
|
||||
required = {'HAVE_METAL', 'HAVE_AVF', 'HAVE_THREADS', 'HAVE_CONFIGFILE',
|
||||
'HAVE_NETWORKING', 'HAVE_CRYPTO', 'HAVE_RTMP', 'HAVE_RFLV'}
|
||||
assert required <= defines, sorted(required - defines)
|
||||
assert 'HAVE_FFMPEG' not in defines
|
||||
command = common + ['-resultBundlePath', str(output / 'build.xcresult'), 'build']
|
||||
report = dict(command=command, required_defines=sorted(required),
|
||||
scope='Full unsigned device application build/link only; no physical playback or recording acceptance',
|
||||
result='FAIL')
|
||||
(output / 'results.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
with (output / 'xcodebuild.log').open('w') as log:
|
||||
result = subprocess.run(command, cwd=root, stdout=log, stderr=subprocess.STDOUT, timeout=3000)
|
||||
report['exit_code'] = result.returncode
|
||||
if not result.returncode:
|
||||
executable = output / 'derived/Build/Products/Release-iphoneos/RetroArch.app/RetroArch'
|
||||
assert executable.is_file() and executable.stat().st_size > 0
|
||||
linked = subprocess.run(['otool', '-L', str(executable)], check=True,
|
||||
capture_output=True, text=True, timeout=60).stdout
|
||||
(output / 'linked-libraries.txt').write_text(linked)
|
||||
assert not any(name in linked.lower() for name in ('libavcodec', 'libavformat', 'libavutil', 'libswscale'))
|
||||
report.update(result='PASS', executable_bytes=executable.stat().st_size)
|
||||
(output / 'results.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
print(report['result'], report['scope'], flush=True)
|
||||
return int(bool(result.returncode))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile production VT headers against real iOS SDKs; no device acceptance claim."""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def check(sdk, minimum, output):
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
sdk_path = subprocess.check_output(['xcrun', '--sdk', sdk, '--show-sdk-path'], text=True).strip()
|
||||
clang = subprocess.check_output(['xcrun', '--sdk', sdk, '--find', 'clang'], text=True).strip()
|
||||
target = 'arm64-apple-ios' + minimum + ('-simulator' if sdk == 'iphonesimulator' else '')
|
||||
with tempfile.TemporaryDirectory(prefix='vt-ios-sdk-') as tmp:
|
||||
probe = Path(tmp) / 'probe.m'
|
||||
probe.write_text('#include "gfx/common/videotoolbox_encode.h"\n'
|
||||
'#include "record/drivers/record_metal.h"\n'
|
||||
'#ifndef HAVE_RECORD_METAL\n#error native frontend unavailable\n#endif\n'
|
||||
'#ifndef RECORD_METAL_NATIVE_SDK\n#error mobile recording compiled as a stub\n#endif\n'
|
||||
'#ifndef VIDEOTOOLBOX_ENCODE_SLOTS\n#error native encoding slots unavailable\n#endif\n'
|
||||
'VT_ENCODE_AVAILABLE void *record_vt_ios_sdk_probe(\n'
|
||||
' const videotoolbox_encode_config_t *config, OSStatus *status)\n'
|
||||
'{\n return videotoolbox_encode_new(config, status);\n}\n')
|
||||
command = [clang, '-target', target, '-isysroot', sdk_path, '-std=gnu89',
|
||||
'-DHAVE_THREADS', '-DHAVE_METAL', '-DHAVE_AVF', '-DHAVE_CONFIGFILE', '-DHAVE_NETWORKING',
|
||||
'-DHAVE_RTMP', '-DHAVE_RFLV', '-DHAVE_CRYPTO', '-fblocks', '-fobjc-arc', '-I' + str(root), '-I' + str(root / 'libretro-common/include'),
|
||||
'-Wall', '-Wextra', '-Werror', '-Wunguarded-availability-new',
|
||||
'-Wno-unused-function', '-fsyntax-only', str(probe)]
|
||||
units = [probe] + [root / ('record/' + name) for name in (
|
||||
'record_gpu_capture_metal.m', 'record_gpu_session_metal.m',
|
||||
'record_gpu_dispatch_metal.m', 'record_mux_avfoundation.m', 'drivers/record_metal.c')]
|
||||
results = []
|
||||
for unit in units:
|
||||
invocation = command[:-1] + [str(unit)]
|
||||
if unit.suffix == '.c':
|
||||
invocation.remove('-fobjc-arc')
|
||||
result = subprocess.run(invocation, text=True, capture_output=True, timeout=120)
|
||||
name = sdk + '-' + minimum + '-' + unit.stem
|
||||
(output / (name + '.log')).write_text(result.stdout + result.stderr)
|
||||
results.append(dict(sdk=sdk, minimum=minimum, target=target, unit=unit.name,
|
||||
command=invocation, result='PASS' if not result.returncode else 'FAIL',
|
||||
exit_code=result.returncode))
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output-dir', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
if sys.platform != 'darwin':
|
||||
parser.error('real Apple SDK compilation requires macOS/Xcode; no local pass is reported')
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
results = [case for sdk in ('iphoneos', 'iphonesimulator')
|
||||
for minimum in ('13.0', '17.4')
|
||||
for case in check(sdk, minimum, args.output_dir)]
|
||||
(args.output_dir / 'results.json').write_text(json.dumps(dict(
|
||||
scope='SDK production unit compilation only; no full application link or device acceptance',
|
||||
cases=results), indent=2) + '\n')
|
||||
for result in results:
|
||||
print(result['result'], result['target'], result['unit'], flush=True)
|
||||
return int(any(result['exit_code'] for result in results))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user