mirror of
https://github.com/vanilla-wiiu/vanilla.git
synced 2026-10-28 09:43:51 +00:00
gui: implement VideoToolbox hardware decoding on macOS and iOS
This commit is contained in:
1 parent
4f9b302f6e
commit
1d4684d3bb
8 files changed
+407
-11
No files matched your search
+3
-3
@@ -14,9 +14,9 @@ if(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
# FFmpeg's configure step expects both compiler drivers even though Vanilla
|
||||
# itself is written in C.
|
||||
enable_language(CXX)
|
||||
# FFmpeg's configure step expects both compiler drivers. Objective-C is
|
||||
# used by the VideoToolbox-to-Metal renderer.
|
||||
enable_language(CXX OBJC)
|
||||
endif()
|
||||
|
||||
if(ANDROID OR MSVC OR IOS)
|
||||
|
||||
+20
-7
@@ -58,6 +58,13 @@ function(compile_ffmpeg _target_name _system_name _system_processor)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
list(APPEND FFMPEG_CONFIGURE_ARGS
|
||||
--enable-videotoolbox
|
||||
--enable-hwaccel=h264_videotoolbox
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT "${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "${_system_processor}"
|
||||
OR NOT "${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "${_system_name}")
|
||||
set(FFMPEG_TARGET_OS ${_system_name})
|
||||
@@ -88,7 +95,6 @@ function(compile_ffmpeg _target_name _system_name _system_processor)
|
||||
--strip=${CMAKE_STRIP}
|
||||
--host-cc=clang
|
||||
--host-ld=clang
|
||||
--enable-videotoolbox
|
||||
)
|
||||
elseif(APPLE)
|
||||
set(FFMPEG_TARGET_OS "darwin")
|
||||
@@ -336,6 +342,15 @@ if (ANDROID)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND VANILLA_GUI_SRC
|
||||
ui/ui_sdl_videotoolbox.m
|
||||
)
|
||||
set_source_files_properties(ui/ui_sdl_videotoolbox.m PROPERTIES
|
||||
COMPILE_OPTIONS "-fobjc-arc"
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
list(APPEND VANILLA_GUI_SRC
|
||||
ui/ui_sdl_linux.c
|
||||
@@ -446,10 +461,6 @@ elseif(IOS)
|
||||
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}"
|
||||
)
|
||||
|
||||
set_target_properties(vanilla PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
)
|
||||
|
||||
# Compile storyboard
|
||||
add_custom_command(
|
||||
TARGET vanilla POST_BUILD
|
||||
@@ -530,12 +541,14 @@ if (NOT VANILLA_BUILD_VENDORED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2_ttf REQUIRED)
|
||||
find_package(SDL2_image REQUIRED)
|
||||
set(_sdl_extension_libs SDL2_ttf::SDL2_ttf SDL2_image::SDL2_image)
|
||||
else()
|
||||
set(_sdl_extension_libs SDL2_ttf SDL2_image)
|
||||
endif()
|
||||
|
||||
target_link_libraries(vanillagui PRIVATE
|
||||
${_sdl_lib}
|
||||
SDL2_ttf
|
||||
SDL2_image
|
||||
${_sdl_extension_libs}
|
||||
libvanilla
|
||||
m
|
||||
)
|
||||
|
||||
+36
-1
@@ -21,6 +21,7 @@
|
||||
#include "menu_common.h"
|
||||
#include "menu_main.h"
|
||||
#include "ui/ui_anim.h"
|
||||
#include "ui/ui_sdl.h"
|
||||
#include "ui/ui_util.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
@@ -248,6 +249,20 @@ static enum AVPixelFormat drm_get_format(struct AVCodecContext *s, const enum AV
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
static enum AVPixelFormat videotoolbox_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt)
|
||||
{
|
||||
while (*fmt != AV_PIX_FMT_NONE) {
|
||||
if (*fmt == AV_PIX_FMT_VIDEOTOOLBOX) {
|
||||
return *fmt;
|
||||
}
|
||||
fmt++;
|
||||
}
|
||||
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
static enum AVPixelFormat mediacodec_get_format(struct AVCodecContext *s,
|
||||
const enum AVPixelFormat *fmt)
|
||||
@@ -272,6 +287,9 @@ typedef struct {
|
||||
enum HwDecoderType {
|
||||
#ifdef ANDROID
|
||||
HWDEC_TYPE_MEDIACODEC,
|
||||
#endif
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
HWDEC_TYPE_VIDEOTOOLBOX,
|
||||
#endif
|
||||
HWDEC_TYPE_NVDEC,
|
||||
HWDEC_TYPE_VAAPI,
|
||||
@@ -421,6 +439,12 @@ int vpi_decode_init(vpi_decode_state_t *s)
|
||||
decoders[HWDEC_TYPE_MEDIACODEC].get_format = mediacodec_get_format;
|
||||
#endif
|
||||
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
decoders[HWDEC_TYPE_VIDEOTOOLBOX].name = "VideoToolbox";
|
||||
decoders[HWDEC_TYPE_VIDEOTOOLBOX].codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
decoders[HWDEC_TYPE_VIDEOTOOLBOX].get_format = videotoolbox_get_format;
|
||||
#endif
|
||||
|
||||
decoders[HWDEC_TYPE_NVDEC].name = "NVDEC";
|
||||
decoders[HWDEC_TYPE_NVDEC].codec = avcodec_find_decoder_by_name("h264_cuvid");
|
||||
decoders[HWDEC_TYPE_NVDEC].get_format = nvdec_get_format;
|
||||
@@ -445,6 +469,17 @@ int vpi_decode_init(vpi_decode_state_t *s)
|
||||
int r = VANILLA_ERR_GENERIC;
|
||||
|
||||
if (!vpi_config.force_software_decode) {
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
if (r != VANILLA_SUCCESS && vui_sdl_videotoolbox_available(s->vui)) {
|
||||
vpi_decode_exit(s);
|
||||
ffmpeg_err = av_hwdevice_ctx_create(&s->hw_device_ctx, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, NULL, NULL, 0);
|
||||
if (ffmpeg_err >= 0) {
|
||||
r = open_decoder(s, &decoders[HWDEC_TYPE_VIDEOTOOLBOX]);
|
||||
} else {
|
||||
vpilog("Failed to create VideoToolbox device: %s\n", av_err2str(ffmpeg_err));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
// MediaCodec renders decoded buffers directly into the SurfaceTexture
|
||||
// owned by SDL's external OES texture.
|
||||
@@ -1017,9 +1052,9 @@ void *vpi_event_loop(void *arg)
|
||||
case VANILLA_EVENT_VIDEO:
|
||||
// If decoder context/thread is not set up, set up now
|
||||
if (!vpi_decode_alloc) {
|
||||
s.vui = vui;
|
||||
int ret = vpi_decode_init(&s);
|
||||
if (ret >= 0) {
|
||||
s.vui = vui;
|
||||
s.thread_running = 1;
|
||||
|
||||
if (pthread_create(&decode_thread, NULL, vpi_decode_loop, &s) == 0) {
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include "ui_sdl_android.h"
|
||||
#endif
|
||||
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
#include "ui_sdl_videotoolbox.h"
|
||||
#endif
|
||||
|
||||
#ifdef VANILLA_HAS_EGL
|
||||
#include <SDL2/SDL_egl.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
@@ -104,6 +108,9 @@ typedef struct {
|
||||
struct timeval toast_expiry;
|
||||
AVFrame *frame;
|
||||
AVFrame *held_frame; // keeps the displayed dmabuf alive until the next frame replaces it
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
vui_sdl_videotoolbox_context_t *videotoolbox;
|
||||
#endif
|
||||
uint64_t present_frame_sequence;
|
||||
Uint64 update_tick_origin;
|
||||
Uint64 update_tick_frequency;
|
||||
@@ -842,6 +849,13 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen)
|
||||
vpilog("EGL unavailable, VAAPI will be disabled\n");
|
||||
}
|
||||
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
sdl_ctx->videotoolbox = vui_sdl_videotoolbox_create(sdl_ctx->renderer);
|
||||
if (!sdl_ctx->videotoolbox) {
|
||||
vpilog("VideoToolbox zero-copy rendering unavailable; using software decoding\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Open audio output device
|
||||
SDL_AudioSpec desired = {0}, obtained;
|
||||
desired.freq = 48000;
|
||||
@@ -1051,6 +1065,10 @@ void vui_close_sdl(vui_context_t *ctx)
|
||||
|
||||
SDL_CloseAudioDevice(sdl_ctx->audio);
|
||||
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
vui_sdl_videotoolbox_destroy(&sdl_ctx->videotoolbox);
|
||||
#endif
|
||||
|
||||
SDL_DestroyRenderer(sdl_ctx->renderer);
|
||||
|
||||
SDL_DestroyWindow(sdl_ctx->window);
|
||||
@@ -1060,6 +1078,17 @@ void vui_close_sdl(vui_context_t *ctx)
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
int vui_sdl_videotoolbox_available(vui_context_t *ctx)
|
||||
{
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
if (ctx && ctx->platform_data) {
|
||||
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) ctx->platform_data;
|
||||
return sdl_ctx->videotoolbox != NULL;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
float lerp(float a, float b, float f)
|
||||
{
|
||||
return a * (1.0 - f) + (b * f);
|
||||
@@ -2217,6 +2246,10 @@ int vui_update_sdl(vui_context_t *vui)
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
get_texture_from_cpu_frame(sdl_ctx, sdl_ctx->frame);
|
||||
break;
|
||||
|
||||
// AV_PIX_FMT_VIDEOTOOLBOX does not need to be handled here because
|
||||
// it is handled further down by vui_sdl_videotoolbox_render
|
||||
|
||||
}
|
||||
|
||||
av_frame_unref(sdl_ctx->held_frame);
|
||||
@@ -2245,6 +2278,11 @@ int vui_update_sdl(vui_context_t *vui)
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
#ifdef VANILLA_VIDEOTOOLBOX_AVAILABLE
|
||||
if (sdl_ctx->held_frame->format == AV_PIX_FMT_VIDEOTOOLBOX) {
|
||||
vui_sdl_videotoolbox_render(sdl_ctx->videotoolbox, renderer, sdl_ctx->held_frame);
|
||||
} else
|
||||
#endif
|
||||
if (sdl_ctx->game_tex) {
|
||||
#ifdef ANDROID
|
||||
if (sdl_ctx->game_tex == sdl_ctx->android_video_tex) {
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
int vui_init_sdl(vui_context_t *ctx, int fullscreen);
|
||||
void vui_close_sdl(vui_context_t *ctx);
|
||||
|
||||
// Return whether direct VideoToolbox to display path is available (i.e. if Metal is being used)
|
||||
int vui_sdl_videotoolbox_available(vui_context_t *ctx);
|
||||
|
||||
/**
|
||||
* Main loop
|
||||
*
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef VANILLA_UI_SDL_VIDEOTOOLBOX_H
|
||||
#define VANILLA_UI_SDL_VIDEOTOOLBOX_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <libavutil/frame.h>
|
||||
|
||||
typedef struct vui_sdl_videotoolbox_context vui_sdl_videotoolbox_context_t;
|
||||
|
||||
vui_sdl_videotoolbox_context_t *vui_sdl_videotoolbox_create(SDL_Renderer *renderer);
|
||||
void vui_sdl_videotoolbox_destroy(vui_sdl_videotoolbox_context_t **context);
|
||||
int vui_sdl_videotoolbox_render(vui_sdl_videotoolbox_context_t *context,
|
||||
SDL_Renderer *renderer,
|
||||
const AVFrame *frame);
|
||||
|
||||
#endif // VANILLA_UI_SDL_VIDEOTOOLBOX_H
|
||||
@@ -0,0 +1,288 @@
|
||||
#include "ui_sdl_videotoolbox.h"
|
||||
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#import <simd/simd.h>
|
||||
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
typedef struct {
|
||||
vector_float4 red;
|
||||
vector_float4 green;
|
||||
vector_float4 blue;
|
||||
} vui_yuv_conversion_t;
|
||||
|
||||
@interface VUIVideoToolboxRenderer : NSObject
|
||||
@property(nonatomic, strong) id<MTLDevice> device;
|
||||
@property(nonatomic, strong) id<MTLRenderPipelineState> rgbaPipeline;
|
||||
@property(nonatomic, strong) id<MTLRenderPipelineState> bgraPipeline;
|
||||
@property(nonatomic, strong) id<MTLSamplerState> sampler;
|
||||
@property(nonatomic) CVMetalTextureCacheRef textureCache;
|
||||
@end
|
||||
|
||||
static NSString *const vuiVideoShaderSource =
|
||||
@"#include <metal_stdlib>\n"
|
||||
"using namespace metal;\n"
|
||||
"struct RasterizerData {\n"
|
||||
" float4 position [[position]];\n"
|
||||
" float2 texcoord;\n"
|
||||
"};\n"
|
||||
"struct YUVConversion {\n"
|
||||
" float4 red;\n"
|
||||
" float4 green;\n"
|
||||
" float4 blue;\n"
|
||||
"};\n"
|
||||
"vertex RasterizerData vanilla_video_vertex(uint vertexID [[vertex_id]]) {\n"
|
||||
" constexpr float2 positions[] = {\n"
|
||||
" float2(-1.0, 1.0), float2(1.0, 1.0),\n"
|
||||
" float2(-1.0, -1.0), float2(1.0, -1.0)\n"
|
||||
" };\n"
|
||||
" constexpr float2 texcoords[] = {\n"
|
||||
" float2(0.0, 0.0), float2(1.0, 0.0),\n"
|
||||
" float2(0.0, 1.0), float2(1.0, 1.0)\n"
|
||||
" };\n"
|
||||
" RasterizerData output;\n"
|
||||
" output.position = float4(positions[vertexID], 0.0, 1.0);\n"
|
||||
" output.texcoord = texcoords[vertexID];\n"
|
||||
" return output;\n"
|
||||
"}\n"
|
||||
"fragment float4 vanilla_video_fragment(\n"
|
||||
" RasterizerData input [[stage_in]],\n"
|
||||
" texture2d<float> luma [[texture(0)]],\n"
|
||||
" texture2d<float> chroma [[texture(1)]],\n"
|
||||
" sampler videoSampler [[sampler(0)]],\n"
|
||||
" constant YUVConversion &conversion [[buffer(0)]]) {\n"
|
||||
" const float y = luma.sample(videoSampler, input.texcoord).r;\n"
|
||||
" const float2 cbcr = chroma.sample(videoSampler, input.texcoord).rg;\n"
|
||||
" const float4 sample = float4(y, cbcr, 1.0);\n"
|
||||
" return float4(dot(conversion.red, sample),\n"
|
||||
" dot(conversion.green, sample),\n"
|
||||
" dot(conversion.blue, sample), 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
static id<MTLRenderPipelineState> vui_create_pipeline(id<MTLDevice> device, id<MTLLibrary> library, MTLPixelFormat pixelFormat)
|
||||
{
|
||||
MTLRenderPipelineDescriptor *descriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
descriptor.vertexFunction = [library newFunctionWithName:@"vanilla_video_vertex"];
|
||||
descriptor.fragmentFunction = [library newFunctionWithName:@"vanilla_video_fragment"];
|
||||
descriptor.colorAttachments[0].pixelFormat = pixelFormat;
|
||||
|
||||
NSError *error = nil;
|
||||
id<MTLRenderPipelineState> pipeline = [device newRenderPipelineStateWithDescriptor:descriptor error:&error];
|
||||
if (!pipeline) {
|
||||
vpilog("Failed to create VideoToolbox Metal pipeline: %s\n", error.localizedDescription.UTF8String);
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@implementation VUIVideoToolboxRenderer
|
||||
|
||||
- (instancetype)initWithSDLRenderer:(SDL_Renderer *)renderer
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
SDL_RendererInfo rendererInfo;
|
||||
if (SDL_GetRendererInfo(renderer, &rendererInfo) < 0 || !rendererInfo.name || strcmp(rendererInfo.name, "metal") != 0) {
|
||||
vpilog("VideoToolbox requires SDL's Metal renderer\n");
|
||||
return nil;
|
||||
}
|
||||
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *) SDL_RenderGetMetalLayer(renderer);
|
||||
self.device = layer.device;
|
||||
if (!self.device) {
|
||||
vpilog("SDL's Metal renderer did not provide a Metal device\n");
|
||||
return nil;
|
||||
}
|
||||
|
||||
CVReturn result = CVMetalTextureCacheCreate(kCFAllocatorDefault, NULL, self.device, NULL, &_textureCache);
|
||||
if (result != kCVReturnSuccess) {
|
||||
vpilog("Failed to create CoreVideo Metal texture cache: %d\n", result);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
id<MTLLibrary> library = [self.device newLibraryWithSource:vuiVideoShaderSource options:nil error:&error];
|
||||
if (!library) {
|
||||
vpilog("Failed to compile VideoToolbox Metal shader: %s\n", error.localizedDescription.UTF8String);
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.rgbaPipeline = vui_create_pipeline(self.device, library, MTLPixelFormatRGBA8Unorm);
|
||||
self.bgraPipeline = vui_create_pipeline(self.device, library, MTLPixelFormatBGRA8Unorm);
|
||||
if (!self.rgbaPipeline && !self.bgraPipeline) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
MTLSamplerDescriptor *samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
|
||||
samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
|
||||
samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
|
||||
samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
|
||||
samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
|
||||
self.sampler = [self.device newSamplerStateWithDescriptor:samplerDescriptor];
|
||||
if (!self.sampler) {
|
||||
vpilog("Failed to create VideoToolbox Metal sampler\n");
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_textureCache) {
|
||||
CFRelease(_textureCache);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static vui_yuv_conversion_t vui_yuv_conversion(const AVFrame *frame, OSType pixelFormat)
|
||||
{
|
||||
const int fullRange = pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange || frame->color_range == AVCOL_RANGE_JPEG;
|
||||
const int bt709 = frame->colorspace == AVCOL_SPC_BT709;
|
||||
const float kr = bt709 ? 0.2126f : 0.2990f;
|
||||
const float kb = bt709 ? 0.0722f : 0.1140f;
|
||||
const float kg = 1.0f - kr - kb;
|
||||
const float yOffset = fullRange ? 0.0f : 16.0f / 255.0f;
|
||||
const float chromaOffset = 128.0f / 255.0f;
|
||||
const float yScale = fullRange ? 1.0f : 255.0f / 219.0f;
|
||||
const float chromaScale = fullRange ? 1.0f : 255.0f / 224.0f;
|
||||
|
||||
const float redCr = 2.0f * (1.0f - kr) * chromaScale;
|
||||
const float blueCb = 2.0f * (1.0f - kb) * chromaScale;
|
||||
const float greenCb = -2.0f * kb * (1.0f - kb) / kg * chromaScale;
|
||||
const float greenCr = -2.0f * kr * (1.0f - kr) / kg * chromaScale;
|
||||
|
||||
vui_yuv_conversion_t conversion = {
|
||||
.red = { yScale, 0.0f, redCr, -yScale * yOffset - redCr * chromaOffset },
|
||||
.green = { yScale, greenCb, greenCr, -yScale * yOffset - (greenCb + greenCr) * chromaOffset },
|
||||
.blue = { yScale, blueCb, 0.0f, -yScale * yOffset - blueCb * chromaOffset }
|
||||
};
|
||||
return conversion;
|
||||
}
|
||||
|
||||
vui_sdl_videotoolbox_context_t *vui_sdl_videotoolbox_create(SDL_Renderer *renderer)
|
||||
{
|
||||
VUIVideoToolboxRenderer *context = [[VUIVideoToolboxRenderer alloc] initWithSDLRenderer:renderer];
|
||||
return (__bridge_retained vui_sdl_videotoolbox_context_t *) context;
|
||||
}
|
||||
|
||||
void vui_sdl_videotoolbox_destroy(vui_sdl_videotoolbox_context_t **context)
|
||||
{
|
||||
if (!context || !*context) {
|
||||
return;
|
||||
}
|
||||
CFBridgingRelease(*context);
|
||||
*context = NULL;
|
||||
}
|
||||
|
||||
int vui_sdl_videotoolbox_render(vui_sdl_videotoolbox_context_t *opaque, SDL_Renderer *renderer, const AVFrame *frame)
|
||||
{
|
||||
if (!opaque || !renderer || !frame || frame->format != AV_PIX_FMT_VIDEOTOOLBOX || !frame->data[3]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
VUIVideoToolboxRenderer *context = (__bridge VUIVideoToolboxRenderer *) opaque;
|
||||
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) frame->data[3];
|
||||
OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
|
||||
if (CVPixelBufferGetPlaneCount(pixelBuffer) != 2 || (pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange && pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
|
||||
vpilog("VideoToolbox returned unsupported pixel format: 0x%08x\n", pixelFormat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_Texture *target = SDL_GetRenderTarget(renderer);
|
||||
Uint32 targetFormat = 0;
|
||||
int targetWidth = 0;
|
||||
int targetHeight = 0;
|
||||
if (!target || SDL_QueryTexture(target, &targetFormat, NULL, &targetWidth, &targetHeight) < 0) {
|
||||
vpilog("VideoToolbox rendering requires an SDL texture target\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
id<MTLRenderPipelineState> pipeline = nil;
|
||||
if (targetFormat == SDL_PIXELFORMAT_RGBA32) {
|
||||
pipeline = context.rgbaPipeline;
|
||||
} else if (targetFormat == SDL_PIXELFORMAT_BGRA32) {
|
||||
pipeline = context.bgraPipeline;
|
||||
}
|
||||
if (!pipeline) {
|
||||
vpilog("VideoToolbox cannot render to SDL pixel format 0x%08x\n", targetFormat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CVMetalTextureRef lumaRef = NULL;
|
||||
CVMetalTextureRef chromaRef = NULL;
|
||||
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(
|
||||
kCFAllocatorDefault,
|
||||
context.textureCache,
|
||||
pixelBuffer,
|
||||
NULL,
|
||||
MTLPixelFormatR8Unorm,
|
||||
CVPixelBufferGetWidthOfPlane(pixelBuffer, 0),
|
||||
CVPixelBufferGetHeightOfPlane(pixelBuffer, 0),
|
||||
0,
|
||||
&lumaRef
|
||||
);
|
||||
if (result == kCVReturnSuccess) {
|
||||
result = CVMetalTextureCacheCreateTextureFromImage(
|
||||
kCFAllocatorDefault,
|
||||
context.textureCache,
|
||||
pixelBuffer,
|
||||
NULL,
|
||||
MTLPixelFormatRG8Unorm,
|
||||
CVPixelBufferGetWidthOfPlane(pixelBuffer, 1),
|
||||
CVPixelBufferGetHeightOfPlane(pixelBuffer, 1),
|
||||
1,
|
||||
&chromaRef
|
||||
);
|
||||
}
|
||||
|
||||
id<MTLTexture> luma = lumaRef ? CVMetalTextureGetTexture(lumaRef) : nil;
|
||||
id<MTLTexture> chroma = chromaRef ? CVMetalTextureGetTexture(chromaRef) : nil;
|
||||
if (result != kCVReturnSuccess || !luma || !chroma) {
|
||||
vpilog("Failed to expose VideoToolbox frame as Metal textures: %d\n", result);
|
||||
if (chromaRef) CFRelease(chromaRef);
|
||||
if (lumaRef) CFRelease(lumaRef);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (SDL_RenderFlush(renderer) < 0) {
|
||||
vpilog("Failed to flush SDL before VideoToolbox render: %s\n", SDL_GetError());
|
||||
CFRelease(chromaRef);
|
||||
CFRelease(lumaRef);
|
||||
return 0;
|
||||
}
|
||||
|
||||
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>) SDL_RenderGetMetalCommandEncoder(renderer);
|
||||
if (!encoder) {
|
||||
vpilog("SDL did not provide a Metal command encoder\n");
|
||||
CFRelease(chromaRef);
|
||||
CFRelease(lumaRef);
|
||||
return 0;
|
||||
}
|
||||
|
||||
MTLViewport viewport = { 0.0, 0.0, targetWidth, targetHeight, 0.0, 1.0 };
|
||||
MTLScissorRect scissor = { 0, 0, targetWidth, targetHeight };
|
||||
vui_yuv_conversion_t conversion = vui_yuv_conversion(frame, pixelFormat);
|
||||
|
||||
[encoder setViewport:viewport];
|
||||
[encoder setScissorRect:scissor];
|
||||
[encoder setRenderPipelineState:pipeline];
|
||||
[encoder setFragmentTexture:luma atIndex:0];
|
||||
[encoder setFragmentTexture:chroma atIndex:1];
|
||||
[encoder setFragmentSamplerState:context.sampler atIndex:0];
|
||||
[encoder setFragmentBytes:&conversion length:sizeof(conversion) atIndex:0];
|
||||
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||
|
||||
CFRelease(chromaRef);
|
||||
CFRelease(lumaRef);
|
||||
return 1;
|
||||
}
|
||||
@@ -180,7 +180,11 @@ void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp)
|
||||
|
||||
memset(video_segments, 0, sizeof(video_segments));
|
||||
|
||||
if (is_idr) {
|
||||
frame_decode_num = 0;
|
||||
} else {
|
||||
frame_decode_num++;
|
||||
}
|
||||
|
||||
if (!video_complete_frame && !is_idr) {
|
||||
send_idr_request_to_console(ctx->socket_msg);
|
||||
|
||||
Reference in new issue
Block a user