From b0bbc31002fa0668ec97e626466ea2311d2013f1 Mon Sep 17 00:00:00 2001 From: MattKC <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 30 Nov 2025 20:56:52 -0800 Subject: [PATCH] Various optimizations and refactoring (#259) * optimizations * Update menu_game.h * try new code * use cached EGLImages * try drm cache on pi * nani? * huh? * require opengl egl * find EGL * find EGL separately * logging * add support for versions of ffmpeg less than 6.0 * disable drm cache for now * Update ui_sdl.c * use old code for now * disable avcc hdr for now * restore error message * try disabling format modifiers? * tell me what the layer is * pi decoder actually produces frames in yuv420p * print more debug info * try this * switch to faster decode path * switch to old code * Update ui_sdl.c * add more timing profiling * more printing * enable some optimizations * draw menus when not in game mode * call render present * Update ui_sdl.c * Update ui_sdl.c * remove unused code * Update CMakeLists.txt * incorporate direct DRM code * Update CMakeLists.txt * free drm context and test old decoding code * move SDL event loop to separate thread so controls are not limited by display rate * add a wait to reduce CPU thrashing * fix double free * separate video into recv and consume threads * Update ui_sdl_drm.c * Update ui_sdl_drm.c * Update gamepad.c * further optimizations * anti-alias buttons more * fix some compiler errors * only include DRM on linux * fix windows compile issues * fix includes * Update CMakeLists.txt * add htobe32 define for macOS * Update video.c * Update gamepad.c --- .editorconfig | 2 +- cmake/FindVAAPI.cmake | 92 ++++ gui/CMakeLists.txt | 50 +- gui/drm.h | 26 -- gui/game/game_decode.c | 615 ------------------------ gui/game/game_decode.h | 25 - gui/game/game_main.c | 132 ------ gui/game/game_main.h | 17 - gui/game/game_record.c | 2 - gui/game/game_record.h | 8 - gui/main.c | 1 - gui/main_old.c | 564 ---------------------- gui/menu/menu.c | 7 +- gui/menu/menu_game.c | 780 +++++++++++++++++++++++++++++-- gui/menu/menu_game.h | 22 +- gui/pipemgmt.c | 3 + gui/ui/ui.c | 2 +- gui/ui/ui_priv.h | 2 +- gui/ui/ui_sdl.c | 828 +++++++++++++++++++-------------- gui/{drm.c => ui/ui_sdl_drm.c} | 385 +++++++-------- gui/ui/ui_sdl_drm.h | 13 + lib/gamepad/gamepad.c | 243 +++++----- lib/gamepad/gamepad.h | 29 +- lib/gamepad/input.c | 20 +- lib/gamepad/video.c | 437 ++++++++++++----- lib/gamepad/video.h | 3 +- lib/vanilla.c | 5 + lib/vanilla.h | 7 +- pipe/linux/wpa.c | 58 ++- 29 files changed, 2123 insertions(+), 2255 deletions(-) create mode 100644 cmake/FindVAAPI.cmake delete mode 100644 gui/drm.h delete mode 100644 gui/game/game_decode.c delete mode 100644 gui/game/game_decode.h delete mode 100644 gui/game/game_main.c delete mode 100644 gui/game/game_main.h delete mode 100644 gui/game/game_record.c delete mode 100644 gui/game/game_record.h delete mode 100644 gui/main_old.c rename gui/{drm.c => ui/ui_sdl_drm.c} (56%) create mode 100644 gui/ui/ui_sdl_drm.h diff --git a/.editorconfig b/.editorconfig index 1b83267..3c44241 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,7 @@ root = true [*] -indent_style = tab +indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 diff --git a/cmake/FindVAAPI.cmake b/cmake/FindVAAPI.cmake new file mode 100644 index 0000000..b1170dc --- /dev/null +++ b/cmake/FindVAAPI.cmake @@ -0,0 +1,92 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +find_package(PkgConfig QUIET) + +function(qt_internal_multimedia_set_va_outputs component include_dir lib_path) + if ("${component}" STREQUAL "VA") + set(VAAPI_INCLUDE_DIR "${include_dir}" CACHE INTERNAL "") + get_filename_component(lib_realpath "${lib_path}" REALPATH) + + string(REGEX MATCH "[0-9]+(\\.[0-9]+)*$" VAAPI_SUFFIX "${lib_realpath}") + set(VAAPI_SUFFIX "${VAAPI_SUFFIX}" CACHE INTERNAL "") + + mark_as_advanced(VAAPI_SUFFIX VAAPI_INCLUDE_DIR) + endif() +endfunction() + +function(find_component component prefix header library) + if(NOT TARGET VAAPI::${component}) + string(TOUPPER ${component} upper) + pkg_search_module(PC_VAAPI_${upper} ${prefix} IMPORTED_TARGET) + if(TARGET PkgConfig::PC_VAAPI_${upper}) + add_library(VAAPI::${component} INTERFACE IMPORTED) + target_link_libraries(VAAPI::${component} INTERFACE PkgConfig::PC_VAAPI_${upper}) + + if (NOT PC_VAAPI_${upper}_LINK_LIBRARIES) + get_target_property(PC_VAAPI_${upper}_LINK_LIBRARIES PkgConfig::PC_VAAPI_${upper} INTERFACE_LINK_LIBRARIES) + message(STATUS "PC_VAAPI_${upper}_LINK_LIBRARIES is not defined by PkgConfig; " + "Get the value from target properties: ${PC_VAAPI_${upper}_LINK_LIBRARIES}") + endif() + + foreach (lib_path ${PC_VAAPI_${upper}_LINK_LIBRARIES}) + get_filename_component(lib_name "${lib_path}" NAME_WLE) + if (${lib_name} STREQUAL ${prefix}) + qt_internal_multimedia_set_va_outputs(${component} + "${PC_VAAPI_${upper}_INCLUDEDIR}" "${lib_path}") + break() + endif() + endforeach() + else() + find_path(VAAPI_${component}_INCLUDE_DIR + NAMES ${header} + PATH_SUFFIXES VAAPI-1.0 + ) + find_library(VAAPI_${component}_LIBRARY + NAMES ${library} + ) + if(VAAPI_${component}_LIBRARY AND VAAPI_${component}_INCLUDE_DIR) + add_library(VAAPI::${component} INTERFACE IMPORTED) + target_include_directories(VAAPI::${component} INTERFACE ${VAAPI_${component}_INCLUDE_DIR}) + target_link_libraries(VAAPI::${component} INTERFACE ${VAAPI_${component}_LIBRARY}) + endif() + mark_as_advanced(VAAPI_${component}_INCLUDE_DIR VAAPI_${component}_LIBRARY) + + qt_internal_multimedia_set_va_outputs(${component} + "${VAAPI_${component}_INCLUDE_DIR}" "${VAAPI_${component}_LIBRARY}") + endif() + endif() + + if(TARGET VAAPI::${component}) + set(VAAPI_${component}_FOUND TRUE PARENT_SCOPE) + endif() +endfunction() + +find_component(VA libva va/va.h libva) +find_component(DRM libva-drm va/va-drm.h libva-drm) + +if(TARGET VAAPI::VA) + target_link_libraries(VAAPI::VA) +endif() +if(TARGET VAAPI::VA AND TARGET VAAPI::DRM) + target_link_libraries(VAAPI::DRM INTERFACE VAAPI::VA) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(VAAPI + REQUIRED_VARS + VAAPI_VA_FOUND + VAAPI_DRM_FOUND + VAAPI_INCLUDE_DIR + VAAPI_SUFFIX + HANDLE_COMPONENTS +) + +if(VAAPI_FOUND AND NOT TARGET VAAPI::VAAPI) + add_library(VAAPI::VAAPI INTERFACE IMPORTED) + target_link_libraries(VAAPI::VAAPI INTERFACE + VAAPI::VA + VAAPI::DRM + ) +endif() diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 33e3d35..801b356 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -247,8 +247,8 @@ list(APPEND VANILLA_GUI_SRC # drm.c lang.c #game/game_decode_ffmpeg.c - game/game_decode.c - game/game_main.c + # game/game_decode.c + # game/game_main.c main.c menu/menu.c menu/menu_common.c @@ -271,6 +271,18 @@ list(APPEND VANILLA_GUI_SRC ui/ui_util.h ) +if (LINUX) + # Link with DRM if available + find_package(DRM) + if (DRM_FOUND) + list(APPEND VANILLA_GUI_SRC + ui/ui_sdl_drm.c + ) + endif() + + find_package(VAAPI) +endif() + if (VANILLA_BUILD_PIPE) OPTION(VANILLA_BUILD_USE_POLKIT "Enable Polkit-based authentication" ON) endif() @@ -346,30 +358,30 @@ endif() if (VANILLA_BUILD_USE_POLKIT) # Find Polkit and GLIB libraries - find_package(PkgConfig) + find_package(PkgConfig REQUIRED) pkg_check_modules(PC_POLKIT polkit-agent-1 REQUIRED IMPORTED_TARGET) target_include_directories(vanilla PRIVATE ${PC_POLKIT_INCLUDE_DIRS}) target_link_libraries(vanilla PRIVATE ${PC_POLKIT_LIBRARIES}) target_compile_definitions(vanilla PRIVATE VANILLA_POLKIT_AVAILABLE) endif() -if (LINUX) - # Link with DRM if available - find_package(DRM) - if (DRM_FOUND) - target_link_libraries(vanilla PRIVATE ${DRM_LIBRARIES}) - target_include_directories(vanilla PRIVATE ${DRM_INCLUDE_DIRS}) - endif() +if (VAAPI_FOUND) + target_link_libraries(vanilla PRIVATE VAAPI::VAAPI VAAPI::VA VAAPI::DRM va-x11 X11) +endif() - # TODO: A more comprehensive "Find" for libva - OPTION(VANILLA_BUILD_USE_VAAPI "Enable VAAPI hardware decoding" ON) - if (VANILLA_BUILD_USE_VAAPI) - find_package(OpenGL COMPONENTS EGL) - if (OpenGL_EGL_FOUND) - target_link_libraries(vanilla PRIVATE OpenGL::EGL va va-drm va-x11 X11) - target_compile_definitions(vanilla PRIVATE VANILLA_HAS_EGL) - endif() - endif() +if (DRM_FOUND) + target_link_libraries(vanilla PRIVATE ${DRM_LIBRARIES}) + target_compile_definitions(vanilla PRIVATE VANILLA_DRM_AVAILABLE) + target_include_directories(vanilla PRIVATE ${DRM_INCLUDE_DIRS}) +endif() + +if (VAAPI_FOUND OR DRM_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(EGL REQUIRED egl) + target_include_directories(vanilla PRIVATE ${EGL_INCLUDE_DIRS}) + target_link_directories(vanilla PRIVATE ${EGL_LIBRARY_DIRS}) + target_link_libraries(vanilla PRIVATE ${EGL_LIBRARIES}) + target_compile_definitions(vanilla PRIVATE VANILLA_HAS_EGL) endif() # Find libxml2 (used for storing/parsing user config) diff --git a/gui/drm.h b/gui/drm.h deleted file mode 100644 index 729a566..0000000 --- a/gui/drm.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef VANILLA_PI_DRM_H -#define VANILLA_PI_DRM_H - -#include -#include -#include -#include - -typedef struct { - int fd; - uint32_t crtc; - int crtc_index; - uint32_t plane_id; - int got_plane; - uint32_t fb_id; - int got_fb; - uint32_t handles[AV_DRM_MAX_PLANES]; - int got_handles; -} vanilla_drm_ctx_t; - -int set_tty(int mode); -int initialize_drm(vanilla_drm_ctx_t *ctx); -int free_drm(vanilla_drm_ctx_t *ctx); -int display_drm(vanilla_drm_ctx_t *ctx, AVFrame *frame); - -#endif // VANILLA_PI_DRM_H diff --git a/gui/game/game_decode.c b/gui/game/game_decode.c deleted file mode 100644 index 0ef890f..0000000 --- a/gui/game/game_decode.c +++ /dev/null @@ -1,615 +0,0 @@ -#include "game_decode.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "def.h" -#include "game_main.h" -#include "lang.h" -#include "platform.h" -#include "ui/ui_util.h" - -pthread_mutex_t vpi_decoding_mutex = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t decoding_wait_cond = PTHREAD_COND_INITIALIZER; -pthread_mutex_t vpi_decode_loop_mutex = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t vpi_decode_loop_cond = PTHREAD_COND_INITIALIZER; -int vpi_decode_loop_running = 0; -uint8_t vpi_decode_ready = 0; -uint8_t vpi_decode_data[65536]; -size_t vpi_decode_size = 0; - -AVFrame *vpi_present_frame = 0; -static AVFrame *decoding_frame; -static AVCodecContext *video_codec_ctx; -static AVPacket *video_packet; - -static pthread_mutex_t screenshot_mutex = PTHREAD_MUTEX_INITIALIZER; -static char screenshot_buf[4096] = {0}; - -static pthread_mutex_t recording_mutex = PTHREAD_MUTEX_INITIALIZER; -static AVFormatContext *recording_fmt_ctx = 0; -static AVStream *recording_vstr; -static AVStream *recording_astr; -static struct timeval recording_start; - -static const int VIDEO_STREAM_INDEX = 0; -static const int AUDIO_STREAM_INDEX = 1; - -enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt) -{ - while (*pix_fmt != AV_PIX_FMT_NONE) { - if (*pix_fmt == AV_PIX_FMT_DRM_PRIME) { - return AV_PIX_FMT_DRM_PRIME; - } - pix_fmt++; - } - - return AV_PIX_FMT_NONE; -} - -int dump_frame_to_file(const AVFrame *frame, const char *filename) -{ - AVFormatContext *ofmt; - int ret = avformat_alloc_output_context2(&ofmt, 0, 0, filename); - if (ret < 0) { - vpilog("Failed to allocate new AVFormatContext for screenshot\n"); - goto exit; - } - - enum AVCodecID codec_id = AV_CODEC_ID_PNG; - const AVCodec *codec = avcodec_find_encoder(codec_id); - if (!codec) { - vpilog("Failed to find encoder for screenshot\n"); - ret = AVERROR_ENCODER_NOT_FOUND; - goto exit_and_free_avformatcontext; - } - - AVStream *s = avformat_new_stream(ofmt, 0); - if (!s) { - vpilog("Failed to create new AVStream for screenshot\n"); - ret = AVERROR(ENOMEM); - goto exit_and_free_avformatcontext; - } - - s->id = 0; - - AVCodecContext *ocodec_ctx = avcodec_alloc_context3(codec); - if (!ocodec_ctx) { - vpilog("Failed to create new AVCodecContext for screenshot\n"); - ret = AVERROR(ENOMEM); - goto exit_and_free_avformatcontext; - } - - enum AVPixelFormat opix_fmt = AV_PIX_FMT_RGB24; - - ocodec_ctx->width = frame->width; - ocodec_ctx->height = frame->height; - ocodec_ctx->pix_fmt = opix_fmt; - ocodec_ctx->codec_id = codec_id; - ocodec_ctx->time_base = (AVRational){1, 1}; - ocodec_ctx->frame_num = 1; - - if (ofmt->flags & AVFMT_GLOBALHEADER) { - ocodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - } - - AVPacket *opkt = av_packet_alloc(); - if (!opkt) { - vpilog("Failed to create new AVPacket for screenshot\n"); - ret = AVERROR(ENOMEM); - goto exit_and_free_avcodeccontext; - } - - ret = avcodec_open2(ocodec_ctx, codec, 0); - if (ret < 0) { - vpilog("Failed to open encoder for screenshot\n"); - goto exit_and_free_avpacket; - } - - ret = avcodec_parameters_from_context(s->codecpar, ocodec_ctx); - if (ret < 0) { - vpilog("Failed to copy params from context for screenshot\n"); - goto exit_and_free_avpacket; - } - - ret = avformat_write_header(ofmt, 0); - if (ret < 0) { - vpilog("Failed to write header for screenshot\n"); - goto exit_and_free_avpacket; - } - - AVFrame *swframe = 0; - if (frame->format != AV_PIX_FMT_YUV420P) { - // Assume this frame is hardware accelerated and we need to download it - // to CPU memory - swframe = av_frame_alloc(); - if (!swframe) { - vpilog("Failed to allocate CPU frame for screenshot\n"); - ret = AVERROR(EINVAL); - goto exit_and_free_avpacket; - } - - if (av_hwframe_transfer_data(swframe, frame, 0) < 0) { - vpilog("Failed to transfer data from hardware for screenshot\n"); - ret = AVERROR(EINVAL); - goto exit_and_free_swframe; - } - - // Use this CPU frame instead from now on - frame = swframe; - } - - struct SwsContext *osws_ctx = sws_getContext( - frame->width, frame->height, frame->format, - frame->width, frame->height, opix_fmt, - 0, 0, 0, 0); - if (!osws_ctx) { - vpilog("Failed to get SwsContext for screenshot\n"); - ret = AVERROR(EINVAL); - goto exit_and_free_avpacket; - } - - AVFrame *oframe = av_frame_alloc(); - if (!oframe) { - vpilog("Failed to allocate AVFrame for screenshot\n"); - ret = AVERROR(ENOMEM); - goto exit_and_free_swscontext; - } - - oframe->width = frame->width; - oframe->height = frame->height; - oframe->format = opix_fmt; - - ret = av_frame_get_buffer(oframe, 0); - if (ret < 0) { - vpilog("Failed to get AVFrame buffer for screenshot\n"); - goto exit_and_free_frame; - } - - ret = sws_scale(osws_ctx, (const uint8_t **) frame->data, (const int *) frame->linesize, 0, frame->height, oframe->data, oframe->linesize); - if (ret < 0) { - vpilog("Failed to do swscale for screenshot\n"); - goto exit_and_free_frame; - } - - ret = avcodec_send_frame(ocodec_ctx, oframe); - if (ret < 0) { - vpilog("Failed to send frame to encoder for screenshot\n"); - goto exit_and_free_frame; - } - - ret = avcodec_receive_packet(ocodec_ctx, opkt); - if (ret < 0) { - vpilog("Failed to receive packet from encoder for screenshot\n"); - goto exit_and_free_frame; - } - - ret = av_write_frame(ofmt, opkt); - if (ret < 0) { - vpilog("Failed to write frame for screenshot\n"); - goto exit_and_free_frame; - } - - ret = av_write_trailer(ofmt); - if (ret < 0) { - vpilog("Failed to write trailer for screenshot\n"); - goto exit_and_free_frame; - } - -exit_and_free_frame: - av_frame_free(&oframe); - -exit_and_free_swscontext: - sws_freeContext(osws_ctx); - -exit_and_free_swframe: - if (swframe) { - av_frame_free(&swframe); - } - -exit_and_free_avpacket: - av_packet_free(&opkt); - -exit_and_free_avcodeccontext: - avcodec_free_context(&ocodec_ctx); - -exit_and_free_avformatcontext: - avformat_free_context(ofmt); - -exit: - if (ret >= 0) { - vpilog("Saved screenshot to \"%s\"\n", filename); - - char buf[VPI_TOAST_MAX_LEN]; - snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_SUCCESS), filename); - vpi_show_toast(buf); - } else { - char buf[VPI_TOAST_MAX_LEN]; - snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_ERROR), ret); - vpi_show_toast(buf); - } - - return ret; -} - -int64_t get_recording_timestamp(AVRational timebase) -{ - struct timeval tv; - gettimeofday(&tv, 0); - - int64_t us = (tv.tv_sec - recording_start.tv_sec) * 1000000 + (tv.tv_usec - recording_start.tv_usec); - return av_rescale_q(us, (AVRational) {1, 1000000}, timebase); -} - -void vpi_decode_send_audio(const void *data, size_t size) -{ - pthread_mutex_lock(&recording_mutex); - - if (recording_fmt_ctx) { - int ret; - - AVPacket *pkt = av_packet_alloc(); - - pkt->data = (uint8_t *) data; - pkt->size = size; - - int64_t ts = get_recording_timestamp(recording_astr->time_base); - pkt->stream_index = AUDIO_STREAM_INDEX; - pkt->dts = ts; - pkt->pts = ts; - - av_interleaved_write_frame(recording_fmt_ctx, pkt); - - av_packet_free(&pkt); - } - - pthread_mutex_unlock(&recording_mutex); -} - -int vpi_decode_is_recording() -{ - int i; - pthread_mutex_lock(&recording_mutex); - i = (recording_fmt_ctx != 0); - pthread_mutex_unlock(&recording_mutex); - return i; -} - -int vpi_decode_record(const char *filename) -{ - pthread_mutex_lock(&recording_mutex); - - int r = avformat_alloc_output_context2(&recording_fmt_ctx, 0, 0, filename); - if (r < 0) { - vpilog("Failed to allocate output context for recording\n"); - goto exit; - } - - recording_vstr = avformat_new_stream(recording_fmt_ctx, 0); - if (!recording_vstr) { - vpilog("Failed to allocate video stream for recording\n"); - goto exit_and_free_context; - } - - recording_vstr->id = VIDEO_STREAM_INDEX; - recording_vstr->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - recording_vstr->codecpar->width = 854; - recording_vstr->codecpar->height = 480; - recording_vstr->codecpar->format = AV_PIX_FMT_YUV420P; - recording_vstr->time_base = (AVRational) {1, 60}; - recording_vstr->codecpar->codec_id = AV_CODEC_ID_H264; - - recording_vstr->codecpar->extradata = av_malloc(200); - recording_vstr->codecpar->extradata_size = vanilla_generate_h264_header(recording_vstr->codecpar->extradata, 200); - - recording_astr = avformat_new_stream(recording_fmt_ctx, 0); - if (!recording_astr) { - vpilog("Failed to allocate audio stream for recording\n"); - goto exit_and_free_context; - } - - recording_astr->id = AUDIO_STREAM_INDEX; - recording_astr->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; - recording_astr->codecpar->sample_rate = 48000; -#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57,24,100) - recording_astr->codecpar->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; -#else - recording_astr->codecpar->channel_layout = AV_CH_LAYOUT_STEREO; -#endif - recording_astr->codecpar->format = AV_SAMPLE_FMT_S16; - recording_astr->time_base = (AVRational) {1, 48000}; - recording_astr->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE; - - r = avio_open2(&recording_fmt_ctx->pb, filename, AVIO_FLAG_WRITE, 0, 0); - if (r < 0) { - vpilog("Failed to open AVIO for recording\n"); - goto exit_and_free_context; - } - - r = avformat_write_header(recording_fmt_ctx, 0); - if (r < 0) { - vpilog("Failed to write header for recording\n"); - goto exit_and_free_context; - } - - vanilla_request_idr(); - - gettimeofday(&recording_start, 0); - - char buf[VPI_TOAST_MAX_LEN]; - snprintf(buf, sizeof(buf), lang(VPI_LANG_RECORDING_START), filename); - vpi_show_toast(buf); - vpilog("Started recording to \"%s\"\n", filename); - - goto exit; - -exit_and_free_context: - avformat_free_context(recording_fmt_ctx); - recording_fmt_ctx = 0; - -exit: - pthread_mutex_unlock(&recording_mutex); - - return r; -} - -void vpi_decode_record_stop() -{ - pthread_mutex_lock(&recording_mutex); - - if (recording_fmt_ctx) { - int r = av_write_trailer(recording_fmt_ctx); - if (r < 0) { - vpilog("Failed to write trailer for recording\n"); - } - - avio_closep(&recording_fmt_ctx->pb); - - avformat_free_context(recording_fmt_ctx); - recording_fmt_ctx = 0; - - vpilog("Finished recording\n"); - vpi_show_toast(lang(VPI_LANG_RECORDING_FINISH)); - } - - pthread_mutex_unlock(&recording_mutex); -} - -int decode() -{ - int err; - - int ret = 1; - - // Retrieve frame from decoder - while (1) { - err = avcodec_receive_frame(video_codec_ctx, decoding_frame); - if (err == AVERROR(EAGAIN)) { - // Decoder wants another packet before it can output a frame. Silently exit. - break; - } else if (err < 0) { - vpilog("Failed to receive frame from decoder: %i\n", err); - ret = 0; - break; - } else { - pthread_mutex_lock(&vpi_decoding_mutex); - - // Swap refs from decoding_frame to present_frame - av_frame_unref(vpi_present_frame); - av_frame_move_ref(vpi_present_frame, decoding_frame); - - pthread_mutex_lock(&screenshot_mutex); - if (screenshot_buf[0] != 0) { - // Dump this frame into file - dump_frame_to_file(vpi_present_frame, screenshot_buf); - screenshot_buf[0] = 0; - } - pthread_mutex_unlock(&screenshot_mutex); - - pthread_cond_broadcast(&decoding_wait_cond); - pthread_mutex_unlock(&vpi_decoding_mutex); - } - } - - return ret; -} - -void vpi_decode_screenshot(const char *filename) -{ - // Grab copy of filename - pthread_mutex_lock(&screenshot_mutex); - vui_strncpy(screenshot_buf, filename, sizeof(screenshot_buf)); - pthread_mutex_unlock(&screenshot_mutex); -} - -static enum AVPixelFormat vaapi_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt) -{ - while (*fmt != AV_PIX_FMT_NONE) { - if (*fmt == AV_PIX_FMT_VAAPI) { - return *fmt; - } - fmt++; - } - - return AV_PIX_FMT_NONE; -} - -static enum AVPixelFormat drm_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt) -{ - while (*fmt != AV_PIX_FMT_NONE) { - if (*fmt == AV_PIX_FMT_DRM_PRIME) { - return *fmt; - } - fmt++; - } - - return AV_PIX_FMT_NONE; -} - -void *vpi_decode_loop(void *) -{ - int ret = VANILLA_PI_ERR_DECODER; - int ffmpeg_err; - - // Initialize FFmpeg - const AVCodec *codec = 0; - enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt); - - // Test for VAAPI - AVBufferRef *hw_device_ctx = 0; - if ((ffmpeg_err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, 0, 0, 0)) >= 0) { - vpilog("Decoding: VAAPI\n"); - codec = avcodec_find_decoder(AV_CODEC_ID_H264); - get_format = vaapi_get_format; - } else if ((ffmpeg_err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", 0, 0)) >= 0) { - vpilog("Decoding: DRM\n"); - codec = avcodec_find_decoder_by_name("h264_v4l2m2m"); - get_format = drm_get_format; - } - - // If we couldn't find a decoder (regardless of if we found hwaccel), fallback to software - if (!codec) { - if (hw_device_ctx) { - av_buffer_unref(&hw_device_ctx); - } - vpilog("Decoding: Software\n"); - codec = avcodec_find_decoder(AV_CODEC_ID_H264); - get_format = 0; - } - - // If we couldn't find a hardware or software decoder, fail - if (!codec) { - vpilog("No decoder was available\n"); - goto free_hwctx; - } - - video_codec_ctx = avcodec_alloc_context3(codec); - if (!video_codec_ctx) { - vpilog("Failed to allocate codec context\n"); - goto free_context; - } - - if (hw_device_ctx) { - video_codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); - } - - if (get_format) { - video_codec_ctx->get_format = get_format; - } - - ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL); - if (ffmpeg_err < 0) { - vpilog("Failed to open decoder: %i\n", ffmpeg_err); - goto free_context; - } - - decoding_frame = av_frame_alloc(); - if (!decoding_frame) { - vpilog("Failed to allocate AVFrame\n"); - goto free_context; - } - - vpi_present_frame = av_frame_alloc(); - if (!vpi_present_frame) { - vpilog("Failed to allocate AVFrame\n"); - goto free_decode_frame; - } - - video_packet = av_packet_alloc(); - if (!video_packet) { - vpilog("Failed to allocate AVPacket\n"); - goto free_present_frame; - } - - AVFrame *frame = av_frame_alloc(); - - pthread_mutex_lock(&vpi_decode_loop_mutex); - while (vpi_decode_loop_running) { - while (vpi_decode_loop_running && !vpi_decode_ready) { - pthread_cond_wait(&vpi_decode_loop_cond, &vpi_decode_loop_mutex); - } - - if (!vpi_decode_loop_running) { - break; - } - - // Send packet to decoder - video_packet->data = vpi_decode_data; - video_packet->size = vpi_decode_size; - vpi_decode_ready = 0; - - pthread_mutex_lock(&recording_mutex); - if (recording_fmt_ctx) { - video_packet->stream_index = VIDEO_STREAM_INDEX; - - int64_t ts = get_recording_timestamp(recording_vstr->time_base); - - video_packet->dts = ts; - video_packet->pts = ts; - - av_interleaved_write_frame(recording_fmt_ctx, video_packet); - - // av_interleaved_write_frame() eventually calls av_packet_unref(), - // so we must put the references back in - video_packet->data = vpi_decode_data; - video_packet->size = vpi_decode_size; - } - pthread_mutex_unlock(&recording_mutex); - - int err = avcodec_send_packet(video_codec_ctx, video_packet); - - pthread_mutex_unlock(&vpi_decode_loop_mutex); - - if (err < 0) { - vpilog("Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err); - // return 0; - - vanilla_request_idr(); - } else { - decode(); - } - - pthread_mutex_lock(&vpi_decode_loop_mutex); - } - pthread_mutex_unlock(&vpi_decode_loop_mutex); - - ret = VANILLA_SUCCESS; - - // TEMP: Signals to DRM thread - // pthread_mutex_lock(&decoding_mutex); - // running = 0; - // pthread_cond_broadcast(&decoding_wait_cond); - // pthread_mutex_unlock(&decoding_mutex); - - av_frame_free(&frame); - -free_packet: - av_packet_free(&video_packet); - -free_present_frame: - av_frame_free(&vpi_present_frame); - vpi_present_frame = 0; - -free_decode_frame: - av_frame_free(&decoding_frame); - -free_context: - avcodec_free_context(&video_codec_ctx); - -free_hwctx: - if (hw_device_ctx) { - av_buffer_unref(&hw_device_ctx); - } - -exit: - if (ret != VANILLA_SUCCESS) { - vpi_game_set_error(ret); - } - - return 0; -} diff --git a/gui/game/game_decode.h b/gui/game/game_decode.h deleted file mode 100644 index 1de60f9..0000000 --- a/gui/game/game_decode.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef VPI_GAME_DECODE_H -#define VPI_GAME_DECODE_H - -#include -#include - -extern pthread_mutex_t vpi_decode_loop_mutex; -extern pthread_cond_t vpi_decode_loop_cond; -extern int vpi_decode_loop_running; -extern uint8_t vpi_decode_data[65536]; -extern uint8_t vpi_decode_ready; -extern size_t vpi_decode_size; -extern AVFrame *vpi_present_frame; -extern pthread_mutex_t vpi_decoding_mutex; -extern pthread_cond_t decoding_wait_cond; - -void *vpi_decode_loop(void *); - -void vpi_decode_screenshot(const char *filename); -int vpi_decode_is_recording(); -int vpi_decode_record(const char *filename); -void vpi_decode_record_stop(); -void vpi_decode_send_audio(const void *data, size_t size); - -#endif // VPI_GAME_DECODE_H diff --git a/gui/game/game_main.c b/gui/game/game_main.c deleted file mode 100644 index b2ebeca..0000000 --- a/gui/game/game_main.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "game_main.h" - -#include -#include -#include - -#include "game_decode.h" -#include "platform.h" -#include "ui/ui.h" -#include "ui/ui_util.h" - -static pthread_t vpi_event_handler_thread; -static int vpi_game_error_value; -static pthread_mutex_t vpi_error_mutex = PTHREAD_MUTEX_INITIALIZER; - -static pthread_mutex_t vpi_toast_mutex = PTHREAD_MUTEX_INITIALIZER; -static char vpi_toast_string[VPI_TOAST_MAX_LEN]; -static struct timeval vpi_toast_expiry; -static int vpi_toast_number = 0; - -int vpi_game_error() -{ - int r; - pthread_mutex_lock(&vpi_error_mutex); - r = vpi_game_error_value; - pthread_mutex_unlock(&vpi_error_mutex); - return r; -} - -void vpi_game_set_error(int r) -{ - pthread_mutex_lock(&vpi_error_mutex); - vpi_game_error_value = r; - pthread_mutex_unlock(&vpi_error_mutex); -} - -void *vpi_event_handler(void *data) -{ - vanilla_event_t event; - - vpi_decode_loop_running = 1; - - pthread_t decode_thread; - pthread_create(&decode_thread, 0, vpi_decode_loop, 0); - - vui_context_t *vui = (vui_context_t *) data; - - while (vanilla_wait_event(&event)) { - int stop = 0; - - switch (event.type) { - case VANILLA_EVENT_VIDEO: - pthread_mutex_lock(&vpi_decode_loop_mutex); - - vpi_decode_size = event.size; - memcpy(vpi_decode_data, event.data, event.size); - vpi_decode_ready = 1; - - pthread_cond_broadcast(&vpi_decode_loop_cond); - pthread_mutex_unlock(&vpi_decode_loop_mutex); - break; - case VANILLA_EVENT_AUDIO: - vui_audio_push(vui, event.data, event.size); - - // We send audio to vpi_decode, but not actually for decoding since - // the audio is already uncompressed. We send it in case vpi_decode - // is recording, so the audio can be written to the file. - vpi_decode_send_audio(event.data, event.size); - break; - case VANILLA_EVENT_VIBRATE: - vui_vibrate_set(vui, event.data[0]); - break; - case VANILLA_EVENT_ERROR: - vpi_game_set_error(*(int *)event.data); - break; - case VANILLA_EVENT_MIC: - vui_mic_enabled_set(vui, event.data[0]); - break; - } - - vanilla_free_event(&event); - } - - pthread_mutex_lock(&vpi_decode_loop_mutex); - vpi_decode_loop_running = 0; - pthread_cond_broadcast(&vpi_decode_loop_cond); - pthread_mutex_unlock(&vpi_decode_loop_mutex); - - pthread_join(decode_thread, 0); - - return 0; -} - -void vpi_game_start(vui_context_t *vui) -{ - vpi_game_error_value = VANILLA_SUCCESS; - pthread_create(&vpi_event_handler_thread, 0, vpi_event_handler, vui); -} - -void vpi_show_toast(const char *message) -{ - pthread_mutex_lock(&vpi_toast_mutex); - - vui_strncpy(vpi_toast_string, message, sizeof(vpi_toast_string)); - - gettimeofday(&vpi_toast_expiry, 0); - - // Toast lasts for 2 seconds - vpi_toast_expiry.tv_sec += 2; - - vpi_toast_number++; - - pthread_mutex_unlock(&vpi_toast_mutex); -} - -void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time) -{ - pthread_mutex_lock(&vpi_toast_mutex); - - if (number) - *number = vpi_toast_number; - - if (output && output_size) { - vui_strncpy(output, vpi_toast_string, output_size); - } - - if (expiry_time) { - *expiry_time = vpi_toast_expiry; - } - - pthread_mutex_unlock(&vpi_toast_mutex); -} diff --git a/gui/game/game_main.h b/gui/game/game_main.h deleted file mode 100644 index b0728a5..0000000 --- a/gui/game/game_main.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef VPI_GAME_MAIN_H -#define VPI_GAME_MAIN_H - -#include - -#include "ui/ui.h" - -#define VPI_TOAST_MAX_LEN 1024 - -void vpi_game_start(vui_context_t *vui); -int vpi_game_error(); -void vpi_game_set_error(int r); - -void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time); -void vpi_show_toast(const char *message); - -#endif // VPI_GAME_MAIN_H \ No newline at end of file diff --git a/gui/game/game_record.c b/gui/game/game_record.c deleted file mode 100644 index e0346b7..0000000 --- a/gui/game/game_record.c +++ /dev/null @@ -1,2 +0,0 @@ -#include "game_record.h" - diff --git a/gui/game/game_record.h b/gui/game/game_record.h deleted file mode 100644 index 1b7c6a6..0000000 --- a/gui/game/game_record.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef VPI_GAME_RECORD_H -#define VPI_GAME_RECORD_H - -void game_take_screenshot(const char *filename); -void game_start_recording(const char *filename); -void game_stop_recording(); - -#endif // VPI_GAME_RECORD_H \ No newline at end of file diff --git a/gui/main.c b/gui/main.c index e79653d..50029e8 100644 --- a/gui/main.c +++ b/gui/main.c @@ -15,7 +15,6 @@ #define SDL_main main #endif -#include "game/game_main.h" #include "pipemgmt.h" void display_cli_help(const char **argv); diff --git a/gui/main_old.c b/gui/main_old.c deleted file mode 100644 index 97edb53..0000000 --- a/gui/main_old.c +++ /dev/null @@ -1,564 +0,0 @@ -#define SCREEN_WIDTH 854 -#define SCREEN_HEIGHT 480 - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "drm.h" -#include "ui.h" - -AVFrame *present_frame; -AVFrame *decoding_frame; -pthread_mutex_t decoding_mutex; -pthread_cond_t decoding_wait_cond; -AVCodecContext *video_codec_ctx; -AVPacket *video_packet; -int running = 0; - -static int button_map[SDL_CONTROLLER_BUTTON_MAX] = {-1}; -static int axis_map[SDL_CONTROLLER_AXIS_MAX] = {-1}; -static int vibrate = 0; - -// HACK: Easy macro to test between desktop and RPi (even though ARM doesn't necessarily mean RPi) -#ifdef __arm__ -#define RASPBERRY_PI -#endif - -enum AVPixelFormat get_format(AVCodecContext* ctx, const enum AVPixelFormat *pix_fmt) -{ - while (*pix_fmt != AV_PIX_FMT_NONE) { - if (*pix_fmt == AV_PIX_FMT_DRM_PRIME) { - return AV_PIX_FMT_DRM_PRIME; - } - pix_fmt++; - } - - return AV_PIX_FMT_NONE; -} - -int decode(void *data, size_t size) -{ - int err; - - // Parse this data for packets - err = av_packet_from_data(video_packet, (uint8_t *) data, size); - if (err < 0) { - fprintf(stderr, "Failed to initialize AVPacket from data: %s (%i)\n", av_err2str(err), err); - av_free(data); - return 0; - } - - // Send packet to decoder - err = avcodec_send_packet(video_codec_ctx, video_packet); - av_packet_unref(video_packet); - if (err < 0) { - fprintf(stderr, "Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err); - return 0; - } - - int ret = 1; - - // Retrieve frame from decoder - while (1) { - err = avcodec_receive_frame(video_codec_ctx, decoding_frame); - if (err == AVERROR(EAGAIN)) { - // Decoder wants another packet before it can output a frame. Silently exit. - break; - } else if (err < 0) { - fprintf(stderr, "Failed to receive frame from decoder: %i\n", err); - ret = 0; - break; - } else if (!decoding_frame->data[0]) { - fprintf(stderr, "WE GOT A NULL DATA[0] STRAIGHT FROM THE DECODER?????\n"); - abort(); - } else if ((decoding_frame->flags & AV_FRAME_FLAG_CORRUPT) != 0) { - fprintf(stderr, "GOT A CORRUPT FRAME??????\n"); - abort(); - } else { - pthread_mutex_lock(&decoding_mutex); - - // Swap refs from decoding_frame to present_frame - av_frame_unref(present_frame); - av_frame_move_ref(present_frame, decoding_frame); - - pthread_cond_broadcast(&decoding_wait_cond); - pthread_mutex_unlock(&decoding_mutex); - } - } - - return ret; -} - -static SDL_mutex *decode_loop_mutex; -static SDL_cond *decode_loop_cond; -static int decode_loop_running = 0; -static uint8_t *decode_data = NULL; -static size_t decode_size = 0; -int decode_loop(void *) -{ - SDL_LockMutex(decode_loop_mutex); - while (decode_loop_running) { - while (decode_loop_running && !decode_data) { - SDL_CondWait(decode_loop_cond, decode_loop_mutex); - } - - if (!decode_loop_running) { - break; - } - - // Copy into av_malloc buffer - uint8_t *our_data = decode_data; - size_t our_size = decode_size; - decode_data = NULL; - decode_size = 0; - - SDL_UnlockMutex(decode_loop_mutex); - - decode(our_data, our_size); - - SDL_LockMutex(decode_loop_mutex); - } - SDL_UnlockMutex(decode_loop_mutex); -} - -int run_backend(void *data) -{ - SDL_AudioSpec desired = {0}; - desired.freq = 48000; - desired.format = AUDIO_S16LSB; - desired.channels = 2; - - SDL_AudioDeviceID audio = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0); - if (audio) { - SDL_PauseAudioDevice(audio, 0); - } else { - printf("Failed to open audio device\n"); - } - - vanilla_event_t event; - - decode_loop_mutex = SDL_CreateMutex(); - decode_loop_cond = SDL_CreateCond(); - decode_loop_running = 1; - - SDL_Thread *decode_thread = SDL_CreateThread(decode_loop, "vanilla-decode", NULL); - - while (vanilla_wait_event(&event)) { - if (event.type == VANILLA_EVENT_VIDEO) { - SDL_LockMutex(decode_loop_mutex); - - if (decode_data) { - av_free(decode_data); - decode_data = NULL; - } - decode_size = event.size; - decode_data = av_malloc(event.size); - memcpy(decode_data, event.data, event.size); - - SDL_CondBroadcast(decode_loop_cond); - SDL_UnlockMutex(decode_loop_mutex); - } else if (event.type == VANILLA_EVENT_AUDIO) { - if (audio) { - if (SDL_QueueAudio(audio, event.data, event.size) < 0) { - printf("Failed to queue audio\n", event.size); - } - } - } else if (event.type == VANILLA_EVENT_VIBRATE) { - vibrate = event.data[0]; - } - - vanilla_free_event(&event); - } - - SDL_LockMutex(decode_loop_mutex); - decode_loop_running = 0; - SDL_CondBroadcast(decode_loop_cond); - SDL_UnlockMutex(decode_loop_mutex); - - SDL_WaitThread(decode_thread, NULL); - SDL_DestroyCond(decode_loop_cond); - SDL_DestroyMutex(decode_loop_mutex); - - SDL_CloseAudioDevice(audio); - - return 0; -} - -void logger(const char *s, va_list args) -{ - vfprintf(stderr, s, args); -} - -int display_loop(void *data) -{ - vanilla_drm_ctx_t *drm_ctx = (vanilla_drm_ctx_t *) data; - - Uint32 start_ticks = SDL_GetTicks(); - - #define TICK_MAX 30 - Uint32 tick_deltas[TICK_MAX]; - size_t tick_delta_index = 0; - - AVFrame *frame = av_frame_alloc(); - - pthread_mutex_lock(&decoding_mutex); - - while (running) { - while (running && !present_frame->data[0]) { - pthread_cond_wait(&decoding_wait_cond, &decoding_mutex); - } - - if (!running) { - break; - } - - // If a frame is available, present it here - av_frame_unref(frame); - av_frame_move_ref(frame, present_frame); - - pthread_mutex_unlock(&decoding_mutex); - - display_drm(drm_ctx, frame); - - // FPS counter stuff - Uint32 now = SDL_GetTicks(); - tick_deltas[tick_delta_index] = (now - start_ticks); - start_ticks = now; - tick_delta_index++; - - if (tick_delta_index == TICK_MAX) { - Uint32 total = 0; - for (int i = 0; i < TICK_MAX; i++) { - total += tick_deltas[i]; - } - - fprintf(stderr, "AVERAGE FPS: %.2f\n", 1000.0f / (total / (float) TICK_MAX)); - - tick_delta_index = 0; - } - - pthread_mutex_lock(&decoding_mutex); - } - - pthread_mutex_unlock(&decoding_mutex); - - av_frame_free(&frame); - - return 0; -} - -SDL_GameController *find_valid_controller() -{ - for (int i = 0; i < SDL_NumJoysticks(); i++) { - SDL_GameController *c = SDL_GameControllerOpen(i); - if (c) { - return c; - } - } - - return NULL; -} - -void sigint_handler(int signum) -{ - printf("Received SIGINT...\n"); - running = 0; - - SDL_QuitEvent ev; - ev.type = SDL_QUIT; - ev.timestamp = SDL_GetTicks(); - SDL_PushEvent((SDL_Event *) &ev); -} - -void init_gamepad() -{ - vibrate = 0; - - button_map[SDL_CONTROLLER_BUTTON_A] = VANILLA_BTN_A; - button_map[SDL_CONTROLLER_BUTTON_B] = VANILLA_BTN_B; - button_map[SDL_CONTROLLER_BUTTON_X] = VANILLA_BTN_X; - button_map[SDL_CONTROLLER_BUTTON_Y] = VANILLA_BTN_Y; - button_map[SDL_CONTROLLER_BUTTON_BACK] = VANILLA_BTN_MINUS; - button_map[SDL_CONTROLLER_BUTTON_GUIDE] = VANILLA_BTN_HOME; - button_map[SDL_CONTROLLER_BUTTON_MISC1] = VANILLA_BTN_TV; - button_map[SDL_CONTROLLER_BUTTON_START] = VANILLA_BTN_PLUS; - button_map[SDL_CONTROLLER_BUTTON_LEFTSTICK] = VANILLA_BTN_L3; - button_map[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = VANILLA_BTN_R3; - button_map[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = VANILLA_BTN_L; - button_map[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = VANILLA_BTN_R; - button_map[SDL_CONTROLLER_BUTTON_DPAD_UP] = VANILLA_BTN_UP; - button_map[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = VANILLA_BTN_DOWN; - button_map[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = VANILLA_BTN_LEFT; - button_map[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = VANILLA_BTN_RIGHT; - axis_map[SDL_CONTROLLER_AXIS_LEFTX] = VANILLA_AXIS_L_X; - axis_map[SDL_CONTROLLER_AXIS_LEFTY] = VANILLA_AXIS_L_Y; - axis_map[SDL_CONTROLLER_AXIS_RIGHTX] = VANILLA_AXIS_R_X; - axis_map[SDL_CONTROLLER_AXIS_RIGHTY] = VANILLA_AXIS_R_Y; - axis_map[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = VANILLA_BTN_ZL; - axis_map[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = VANILLA_BTN_ZR; -} - -int32_t pack_float(float f) -{ - int32_t x; - memcpy(&x, &f, sizeof(int32_t)); - return x; -} - -int main(int argc, const char **argv) -{ - vanilla_drm_ctx_t drm_ctx; - Uint32 sdl_flags = SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER; -#ifdef RASPBERRY_PI - if (!initialize_drm(&drm_ctx)) { - fprintf(stderr, "Failed to find DRM output\n"); - //return 1; - } -#else - sdl_flags |= SDL_INIT_VIDEO; -#endif - - // Initialize SDL2 for audio and input - if (SDL_Init(sdl_flags) < 0) { - fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError()); - return 1; - } - - // Initialize FFmpeg -#ifdef RASPBERRY_PI - const AVCodec *codec = avcodec_find_decoder_by_name("h264_v4l2m2m"); -#else - const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); -#endif - if (!codec) { - fprintf(stderr, "No decoder was available\n"); - return 1; - } - - video_codec_ctx = avcodec_alloc_context3(codec); - if (!video_codec_ctx) { - fprintf(stderr, "Failed to allocate codec context\n"); - return 1; - } - - int ffmpeg_err; - -#ifdef RASPBERRY_PI - ffmpeg_err = av_hwdevice_ctx_create(&video_codec_ctx->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", NULL, 0); - if (ffmpeg_err < 0) { - fprintf(stderr, "Failed to create hwdevice context: %s (%i)\n", av_err2str(ffmpeg_err), ffmpeg_err); - return 1; - } - - // MAKE SURE WE GET DRM PRIME FRAMES BY OVERRIDING THE GET_FORMAT FUNCTION - video_codec_ctx->get_format = get_format; -#endif - - ffmpeg_err = avcodec_open2(video_codec_ctx, codec, NULL); - if (ffmpeg_err < 0) { - fprintf(stderr, "Failed to open decoder: %i\n", ffmpeg_err); - return 1; - } - - decoding_frame = av_frame_alloc(); - present_frame = av_frame_alloc(); - if (!decoding_frame || !present_frame) { - fprintf(stderr, "Failed to allocate AVFrame\n"); - return 1; - } - - video_packet = av_packet_alloc(); - if (!video_packet) { - fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError()); - return 1; - } - - // Install logging debugger - vanilla_install_logger(logger); - - // Start Vanilla -#ifdef RASPBERRY_PI - vanilla_start(0); -#else - vanilla_start(ntohl(inet_addr("127.0.0.1"))); -#endif - - // Initialize gamepad lookup tables - init_gamepad(); - - // Create variable for background threads to run - running = 1; - signal(SIGINT, sigint_handler); - - pthread_mutex_init(&decoding_mutex, NULL); - pthread_cond_init(&decoding_wait_cond, NULL); - - // Start background threads - SDL_Thread *backend_thread = SDL_CreateThread(run_backend, "vanilla-pi-backend", NULL); - -#ifdef RASPBERRY_PI - SDL_Thread *display_thread = SDL_CreateThread(display_loop, "vanilla-pi-display", &drm_ctx); -#else - SDL_Window *window = SDL_CreateWindow("vanilla-pi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_RESIZABLE); - SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT); - - AVFrame *frame = av_frame_alloc(); -#endif - - SDL_GameController *controller = find_valid_controller(); - - vui_context_t *vui = vui_alloc(); - - Uint32 ticks = SDL_GetTicks(); - while (running) { - vui_reset(vui); - - int sync_btn = vui_draw_button(vui, 100, 100, 100, 100, "Sync"); - int connect_btn = vui_draw_button(vui, 300, 100, 100, 100, "Connect"); - - SDL_Event event; -#ifdef RASPBERRY_PI - if (SDL_WaitEvent(&event)) { -#else - while (SDL_PollEvent(&event)) { -#endif - switch (event.type) { - case SDL_QUIT: - // Exit loop - running = 0; - break; - case SDL_CONTROLLERDEVICEADDED: - // Attempt to find controller if one doesn't exist already - if (!controller) { - controller = find_valid_controller(); - } - break; - case SDL_CONTROLLERDEVICEREMOVED: - // Handle current controller being removed - if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) { - SDL_GameControllerClose(controller); - controller = find_valid_controller(); - } - break; - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) { - int vanilla_btn = button_map[event.cbutton.button]; - if (vanilla_btn != -1) { - vanilla_set_button(vanilla_btn, event.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0); - } - } - break; - case SDL_CONTROLLERAXISMOTION: - if (controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) { - int vanilla_axis = axis_map[event.caxis.axis]; - Sint16 axis_value = event.caxis.value; - if (vanilla_axis != -1) { - vanilla_set_button(vanilla_axis, axis_value); - } - } - break; - case SDL_CONTROLLERSENSORUPDATE: - if (event.csensor.sensor == SDL_SENSOR_ACCEL) { - vanilla_set_button(VANILLA_SENSOR_ACCEL_X, pack_float(event.csensor.data[0])); - vanilla_set_button(VANILLA_SENSOR_ACCEL_Y, pack_float(event.csensor.data[1])); - vanilla_set_button(VANILLA_SENSOR_ACCEL_Z, pack_float(event.csensor.data[2])); - } else if (event.csensor.sensor == SDL_SENSOR_GYRO) { - vanilla_set_button(VANILLA_SENSOR_GYRO_PITCH, pack_float(event.csensor.data[0])); - vanilla_set_button(VANILLA_SENSOR_GYRO_YAW, pack_float(event.csensor.data[1])); - vanilla_set_button(VANILLA_SENSOR_GYRO_ROLL, pack_float(event.csensor.data[2])); - } - break; - case SDL_KEYDOWN: - if (event.key.keysym.sym == SDLK_ESCAPE) { - running = 0; - } - break; - case SDL_MOUSEBUTTONDOWN: - { - int btn = vui_process_click(vui, event.button.x, event.button.y); - if (btn != -1) { - if (btn == sync_btn) { - // Go to sync menu - } else if (btn == connect_btn) { - // Go to connect menu - } - } - break; - } - } - - if (controller) { - uint16_t amount = vibrate ? 0xFFFF : 0; - SDL_GameControllerRumble(controller, amount, amount, 0); - } - } - -#ifndef RASPBERRY_PI - pthread_mutex_lock(&decoding_mutex); - if (present_frame->data[0]) { - av_frame_move_ref(frame, present_frame); - } - pthread_mutex_unlock(&decoding_mutex); - - if (frame->data[0]) { - SDL_UpdateYUVTexture(texture, NULL, - frame->data[0], frame->linesize[0], - frame->data[1], frame->linesize[1], - frame->data[2], frame->linesize[2]); - av_frame_unref(frame); - SDL_RenderCopy(renderer, texture, NULL, NULL); - } - SDL_RenderPresent(renderer); -#endif - } - - vui_free(vui); - - // Terminate background threads and wait for them to end gracefully - vanilla_stop(); - - pthread_mutex_lock(&decoding_mutex); - running = 0; - pthread_cond_broadcast(&decoding_wait_cond); - pthread_mutex_unlock(&decoding_mutex); - - SDL_WaitThread(backend_thread, NULL); - -#ifdef RASPBERRY_PI - SDL_WaitThread(display_thread, NULL); -#else - av_frame_free(&frame); - - SDL_DestroyTexture(texture); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); -#endif - - pthread_cond_destroy(&decoding_wait_cond); - pthread_mutex_destroy(&decoding_mutex); - - av_frame_free(&present_frame); - av_frame_free(&decoding_frame); - av_packet_free(&video_packet); - avcodec_free_context(&video_codec_ctx); - - SDL_Quit(); - - free_drm(&drm_ctx); - - return 0; -} \ No newline at end of file diff --git a/gui/menu/menu.c b/gui/menu/menu.c index 5cd7c13..60642c0 100644 --- a/gui/menu/menu.c +++ b/gui/menu/menu.c @@ -7,9 +7,8 @@ #include #include -#include "game/game_decode.h" -#include "game/game_main.h" #include "menu_common.h" +#include "menu_game.h" #include "menu_main.h" #include "platform.h" @@ -63,8 +62,8 @@ void vpi_menu_action(vui_context_t *vui, vpi_extra_action_t action) case VPI_ACTION_DISCONNECT: { if (vui_game_mode_get(vui)) { - // Simulate shutdown - vpi_game_set_error(VANILLA_ERR_SHUTDOWN); + // Send shutdown signal + vpi_game_shutdown(); } else { // Quit Vanilla entirely vpi_menu_quit_vanilla(vui); diff --git a/gui/menu/menu_game.c b/gui/menu/menu_game.c index eabbd03..f399535 100644 --- a/gui/menu/menu_game.c +++ b/gui/menu/menu_game.c @@ -1,21 +1,46 @@ #include "menu_game.h" +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include "config.h" #include "lang.h" -#include "game/game_decode.h" -#include "game/game_main.h" #include "menu_common.h" #include "menu_main.h" #include "ui/ui_anim.h" +#include "ui/ui_util.h" + +static char vpi_toast_string[VPI_TOAST_MAX_LEN]; +static struct timeval vpi_toast_expiry; +static int vpi_toast_number = 0; + +AVFrame *vpi_present_frame = 0; +pthread_mutex_t vpi_present_frame_mutex = PTHREAD_MUTEX_INITIALIZER; + +static AVFormatContext *recording_fmt_ctx = 0; +static AVStream *recording_vstr; +static AVStream *recording_astr; +static struct timeval recording_start; +static char screenshot_buf[4096] = {0}; + +static const int VIDEO_STREAM_INDEX = 0; +static const int AUDIO_STREAM_INDEX = 1; static int status_lbl; static struct { int64_t last_power_time; } menu_game_ctx; +static _Atomic int vpi_game_queued_error = VANILLA_SUCCESS; + void back_to_main_menu(vui_context_t *vui, int button, void *v) { int layer = (intptr_t) v; @@ -90,36 +115,579 @@ static void update_battery_information(vui_context_t *vui, int64_t time) } } +static inline void be16(uint8_t *p, uint16_t v) { p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)v; } + +size_t build_avcc(uint8_t *dst, size_t cap, + const uint8_t * const *sps_list, const uint16_t *sps_len, int sps_count, + const uint8_t * const *pps_list, const uint16_t *pps_len, int pps_count, + int nal_length_size /* 1,2,4 */) +{ + assert(nal_length_size==1 || nal_length_size==2 || nal_length_size==4); + + // Derive profile/compat/level from the first SPS (bytes after NAL header) + // SPS layout starts with: [nal_header=0x67][profile_idc][profile_compat][level_idc]... + if (sps_count < 1 || sps_len[0] < 4) return 0; + const uint8_t *sps0 = sps_list[0]; + uint8_t AVCProfileIndication = sps0[1]; + uint8_t profile_compatibility = sps0[2]; + uint8_t AVCLevelIndication = sps0[3]; + + uint8_t *p = dst; + uint8_t *e = dst + cap; + + // configurationVersion + if (p+1 > e) return 0; *p++ = 1; + // profile/compat/level + if (p+3 > e) return 0; + *p++ = AVCProfileIndication; + *p++ = profile_compatibility; + *p++ = AVCLevelIndication; + + // lengthSizeMinusOne (low 2 bits), upper 6 bits all 1s + if (p+1 > e) return 0; + uint8_t lengthSizeMinusOne = (uint8_t)(nal_length_size - 1); // 0→1B,1→2B,3→4B + *p++ = (uint8_t)(0xFC | (lengthSizeMinusOne & 0x03)); + + // numOfSPS (low 5 bits), upper 3 bits all 1s + if (sps_count > 31) return 0; + if (p+1 > e) return 0; + *p++ = (uint8_t)(0xE0 | (sps_count & 0x1F)); + + // Write each SPS: [2B len][bytes...] + for (int i = 0; i < sps_count; ++i) { + if (p+2+sps_len[i] > e) return 0; + be16(p, sps_len[i]); p += 2; + memcpy(p, sps_list[i], sps_len[i]); p += sps_len[i]; + } + + // numOfPPS + if (pps_count > 255) return 0; + if (p+1 > e) return 0; + *p++ = (uint8_t)pps_count; + + // Write each PPS: [2B len][bytes...] + for (int i = 0; i < pps_count; ++i) { + if (p+2+pps_len[i] > e) return 0; + be16(p, pps_len[i]); p += 2; + memcpy(p, pps_list[i], pps_len[i]); p += pps_len[i]; + } + + return (size_t)(p - dst); +} + +static enum AVPixelFormat vaapi_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt) +{ + while (*fmt != AV_PIX_FMT_NONE) { + if (*fmt == AV_PIX_FMT_VAAPI) { + return *fmt; + } + fmt++; + } + + return AV_PIX_FMT_NONE; +} + +static enum AVPixelFormat drm_get_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt) +{ + while (*fmt != AV_PIX_FMT_NONE) { + if (*fmt == AV_PIX_FMT_DRM_PRIME) { + return *fmt; + } + fmt++; + } + + return AV_PIX_FMT_NONE; +} + +typedef struct { + AVCodecContext *codec_ctx; + AVPacket *pkt; + AVFrame *frame; + AVBufferRef *hw_device_ctx; +} vpi_decode_state_t; + +int vpi_decode_init(vpi_decode_state_t *s) +{ + int ffmpeg_err; + + // Initialize FFmpeg + const AVCodec *codec = 0; + enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt); + + // Test for VAAPI + if ((ffmpeg_err = av_hwdevice_ctx_create(&s->hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, 0, 0, 0)) >= 0) { + vpilog("Decoding: VAAPI\n"); + codec = avcodec_find_decoder(AV_CODEC_ID_H264); + get_format = vaapi_get_format; + } else if ((ffmpeg_err = av_hwdevice_ctx_create(&s->hw_device_ctx, AV_HWDEVICE_TYPE_DRM, "/dev/dri/card0", 0, 0)) >= 0) { + vpilog("Decoding: DRM\n"); + codec = avcodec_find_decoder_by_name("h264_v4l2m2m"); + get_format = drm_get_format; + } + + // If we couldn't find a decoder (regardless of if we found hwaccel), fallback to software + if (!codec) { + if (s->hw_device_ctx) { + av_buffer_unref(&s->hw_device_ctx); + } + vpilog("Decoding: Software\n"); + codec = avcodec_find_decoder(AV_CODEC_ID_H264); + get_format = 0; + } + + // If we couldn't find a hardware or software decoder, fail + if (!codec) { + vpilog("No decoder was available\n"); + return VANILLA_ERR_GENERIC; + } + + s->codec_ctx = avcodec_alloc_context3(codec); + if (!s->codec_ctx) { + vpilog("Failed to allocate codec context\n"); + return VANILLA_ERR_GENERIC; + } + + if (s->hw_device_ctx) { + s->codec_ctx->hw_device_ctx = av_buffer_ref(s->hw_device_ctx); + } + + if (get_format) { + s->codec_ctx->get_format = get_format; + } + + const int BUILD_AVCC = 0; + if (BUILD_AVCC) { + uint8_t sps[100], pps[100]; + uint16_t sps_size = vanilla_generate_sps_params(sps, sizeof(sps)); + uint16_t pps_size = vanilla_generate_pps_params(pps, sizeof(pps)); + + const uint8_t *p_sps = sps; + const uint8_t *p_pps = pps; + + s->codec_ctx->extradata = av_malloc(1024); + s->codec_ctx->extradata_size = build_avcc( + s->codec_ctx->extradata, 1024, + &p_sps, &sps_size, 1, + &p_pps, &pps_size, 1, + 4 + ); + } + + ffmpeg_err = avcodec_open2(s->codec_ctx, codec, NULL); + if (ffmpeg_err < 0) { + vpilog("Failed to open decoder: %i\n", ffmpeg_err); + return VANILLA_ERR_GENERIC; + } + + AVFrame *decoding_frame = av_frame_alloc(); + if (!decoding_frame) { + vpilog("Failed to allocate AVFrame\n"); + return VANILLA_ERR_GENERIC; + } + + vpi_present_frame = av_frame_alloc(); + if (!vpi_present_frame) { + vpilog("Failed to allocate AVFrame\n"); + return VANILLA_ERR_GENERIC; + } + + s->pkt = av_packet_alloc(); + if (!s->pkt) { + vpilog("Failed to allocate AVPacket\n"); + return VANILLA_ERR_GENERIC; + } + + s->frame = av_frame_alloc(); + + vpilog("initialized state!\n"); + + return VANILLA_SUCCESS; +} + +void vpi_decode_exit(vpi_decode_state_t *s) +{ + if (s->frame) + av_frame_free(&s->frame); + + if (s->pkt) + av_packet_free(&s->pkt); + + if (vpi_present_frame) + av_frame_free(&vpi_present_frame); + + if (s->codec_ctx) + avcodec_free_context(&s->codec_ctx); + + if (s->hw_device_ctx) + av_buffer_unref(&s->hw_device_ctx); +} + +int64_t get_recording_timestamp(AVRational timebase) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + int64_t us = (tv.tv_sec - recording_start.tv_sec) * 1000000 + (tv.tv_usec - recording_start.tv_usec); + return av_rescale_q(us, (AVRational) {1, 1000000}, timebase); +} + +int dump_frame_to_file(const AVFrame *frame, const char *filename) +{ + AVFormatContext *ofmt; + int ret = avformat_alloc_output_context2(&ofmt, 0, 0, filename); + if (ret < 0) { + vpilog("Failed to allocate new AVFormatContext for screenshot\n"); + goto exit; + } + + enum AVCodecID codec_id = AV_CODEC_ID_PNG; + const AVCodec *codec = avcodec_find_encoder(codec_id); + if (!codec) { + vpilog("Failed to find encoder for screenshot\n"); + ret = AVERROR_ENCODER_NOT_FOUND; + goto exit_and_free_avformatcontext; + } + + AVStream *s = avformat_new_stream(ofmt, 0); + if (!s) { + vpilog("Failed to create new AVStream for screenshot\n"); + ret = AVERROR(ENOMEM); + goto exit_and_free_avformatcontext; + } + + s->id = 0; + + AVCodecContext *ocodec_ctx = avcodec_alloc_context3(codec); + if (!ocodec_ctx) { + vpilog("Failed to create new AVCodecContext for screenshot\n"); + ret = AVERROR(ENOMEM); + goto exit_and_free_avformatcontext; + } + + enum AVPixelFormat opix_fmt = AV_PIX_FMT_RGB24; + + ocodec_ctx->width = frame->width; + ocodec_ctx->height = frame->height; + ocodec_ctx->pix_fmt = opix_fmt; + ocodec_ctx->codec_id = codec_id; + ocodec_ctx->time_base = (AVRational){1, 1}; + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 2, 100) + ocodec_ctx->frame_num = 1; +#else + ocodec_ctx->frame_number = 1; +#endif + + if (ofmt->flags & AVFMT_GLOBALHEADER) { + ocodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + AVPacket *opkt = av_packet_alloc(); + if (!opkt) { + vpilog("Failed to create new AVPacket for screenshot\n"); + ret = AVERROR(ENOMEM); + goto exit_and_free_avcodeccontext; + } + + ret = avcodec_open2(ocodec_ctx, codec, 0); + if (ret < 0) { + vpilog("Failed to open encoder for screenshot\n"); + goto exit_and_free_avpacket; + } + + ret = avcodec_parameters_from_context(s->codecpar, ocodec_ctx); + if (ret < 0) { + vpilog("Failed to copy params from context for screenshot\n"); + goto exit_and_free_avpacket; + } + + ret = avformat_write_header(ofmt, 0); + if (ret < 0) { + vpilog("Failed to write header for screenshot\n"); + goto exit_and_free_avpacket; + } + + AVFrame *swframe = 0; + if (frame->format != AV_PIX_FMT_YUV420P) { + // Assume this frame is hardware accelerated and we need to download it + // to CPU memory + swframe = av_frame_alloc(); + if (!swframe) { + vpilog("Failed to allocate CPU frame for screenshot\n"); + ret = AVERROR(EINVAL); + goto exit_and_free_avpacket; + } + + if (av_hwframe_transfer_data(swframe, frame, 0) < 0) { + vpilog("Failed to transfer data from hardware for screenshot\n"); + ret = AVERROR(EINVAL); + goto exit_and_free_swframe; + } + + // Use this CPU frame instead from now on + frame = swframe; + } + + struct SwsContext *osws_ctx = sws_getContext( + frame->width, frame->height, frame->format, + frame->width, frame->height, opix_fmt, + 0, 0, 0, 0); + if (!osws_ctx) { + vpilog("Failed to get SwsContext for screenshot\n"); + ret = AVERROR(EINVAL); + goto exit_and_free_avpacket; + } + + AVFrame *oframe = av_frame_alloc(); + if (!oframe) { + vpilog("Failed to allocate AVFrame for screenshot\n"); + ret = AVERROR(ENOMEM); + goto exit_and_free_swscontext; + } + + oframe->width = frame->width; + oframe->height = frame->height; + oframe->format = opix_fmt; + + ret = av_frame_get_buffer(oframe, 0); + if (ret < 0) { + vpilog("Failed to get AVFrame buffer for screenshot\n"); + goto exit_and_free_frame; + } + + ret = sws_scale(osws_ctx, (const uint8_t **) frame->data, (const int *) frame->linesize, 0, frame->height, oframe->data, oframe->linesize); + if (ret < 0) { + vpilog("Failed to do swscale for screenshot\n"); + goto exit_and_free_frame; + } + + ret = avcodec_send_frame(ocodec_ctx, oframe); + if (ret < 0) { + vpilog("Failed to send frame to encoder for screenshot\n"); + goto exit_and_free_frame; + } + + ret = avcodec_receive_packet(ocodec_ctx, opkt); + if (ret < 0) { + vpilog("Failed to receive packet from encoder for screenshot\n"); + goto exit_and_free_frame; + } + + ret = av_write_frame(ofmt, opkt); + if (ret < 0) { + vpilog("Failed to write frame for screenshot\n"); + goto exit_and_free_frame; + } + + ret = av_write_trailer(ofmt); + if (ret < 0) { + vpilog("Failed to write trailer for screenshot\n"); + goto exit_and_free_frame; + } + +exit_and_free_frame: + av_frame_free(&oframe); + +exit_and_free_swscontext: + sws_freeContext(osws_ctx); + +exit_and_free_swframe: + if (swframe) { + av_frame_free(&swframe); + } + +exit_and_free_avpacket: + av_packet_free(&opkt); + +exit_and_free_avcodeccontext: + avcodec_free_context(&ocodec_ctx); + +exit_and_free_avformatcontext: + avformat_free_context(ofmt); + +exit: + if (ret >= 0) { + vpilog("Saved screenshot to \"%s\"\n", filename); + + char buf[VPI_TOAST_MAX_LEN]; + snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_SUCCESS), filename); + vpi_show_toast(buf); + } else { + char buf[VPI_TOAST_MAX_LEN]; + snprintf(buf, sizeof(buf), lang(VPI_LANG_SCREENSHOT_ERROR), ret); + vpi_show_toast(buf); + } + + return ret; +} + +void vpi_game_shutdown() +{ + vpi_game_queued_error = VANILLA_ERR_SHUTDOWN; +} + +static pthread_t vpi_event_thread; + +void *vpi_event_loop(void *arg) +{ + vui_context_t *vui = (vui_context_t *) arg; + static int vpi_decode_alloc = 0; + + static vpi_decode_state_t s; + vanilla_event_t event; + while (vpi_game_queued_error == VANILLA_SUCCESS && vanilla_wait_event(&event)) { + int stop = 0; + + switch (event.type) { + case VANILLA_EVENT_VIDEO: + if (!vpi_decode_alloc) { + int ret = vpi_decode_init(&s); + if (ret >= 0) { + vpi_decode_alloc = 1; + } + } + + if (vpi_decode_alloc) { + AVPacket *pkt = s.pkt; + + // Send packet to decoder + pkt->data = event.data; + pkt->size = event.size; + + if (recording_fmt_ctx) { + pkt->stream_index = VIDEO_STREAM_INDEX; + + int64_t ts = get_recording_timestamp(recording_vstr->time_base); + + pkt->dts = ts; + pkt->pts = ts; + + av_interleaved_write_frame(recording_fmt_ctx, pkt); + + // av_interleaved_write_frame() eventually calls av_packet_unref(), + // so we must put the references back in + pkt->data = event.data; + pkt->size = event.size; + } + + int err = avcodec_send_packet(s.codec_ctx, pkt); + + if (err < 0) { + vpilog("Failed to send packet to decoder: %s (%i)\n", av_err2str(err), err); + // return 0; + + vanilla_request_idr(); + } else { + int err; + + int ret = 1; + + // Retrieve frame from decoder + while (1) { + err = avcodec_receive_frame(s.codec_ctx, s.frame); + if (err == AVERROR(EAGAIN)) { + // Decoder wants another packet before it can output a frame. Silently exit. + break; + } else if (err < 0) { + vpilog("Failed to receive frame from decoder: %i\n", err); + ret = 0; + break; + } else { + pthread_mutex_lock(&vpi_present_frame_mutex); + + // Swap refs from decoding_frame to present_frame + av_frame_unref(vpi_present_frame); + av_frame_move_ref(vpi_present_frame, s.frame); + vpi_present_frame->pts = s.frame->pts; + + if (screenshot_buf[0] != 0) { + // Dump this frame into file + dump_frame_to_file(vpi_present_frame, screenshot_buf); + screenshot_buf[0] = 0; + } + + pthread_mutex_unlock(&vpi_present_frame_mutex); + + // Not thread safe? + if (!vui_game_mode_get(vui)) { + vui_game_mode_set(vui, 1); + vui_audio_set_enabled(vui, 1); + } + } + } + + // return ret; + } + } + break; + case VANILLA_EVENT_AUDIO: + vui_audio_push(vui, event.data, event.size); + + // We send audio to vpi_decode, but not actually for decoding since + // the audio is already uncompressed. We send it in case vpi_decode + // is recording, so the audio can be written to the file. + vpi_decode_send_audio(event.data, event.size); + break; + case VANILLA_EVENT_VIBRATE: + vui_vibrate_set(vui, event.data[0]); + break; + case VANILLA_EVENT_ERROR: + { + int backend_err = *(int *)event.data; + switch (backend_err) { + case VANILLA_SUCCESS: + case VANILLA_ERR_CONNECTED: + // Do nothing + break; + case VANILLA_ERR_DISCONNECTED: + case VANILLA_ERR_SHUTDOWN: + vpi_game_queued_error = VANILLA_ERR_SHUTDOWN; + break; + default: + vpi_game_queued_error = backend_err; + }; + break; + } + case VANILLA_EVENT_MIC: + vui_mic_enabled_set(vui, event.data[0]); + break; + } + + vanilla_free_event(&event); + } + + if (vpi_decode_alloc) { + vpi_decode_exit(&s); + vpi_decode_alloc = 0; + } + + return NULL; +} + void vpi_display_update(vui_context_t *vui, int64_t time, void *v) { - int backend_err = vpi_game_error(); - if (backend_err != VANILLA_SUCCESS) { - switch (backend_err) { - case VANILLA_ERR_CONNECTED: - // Wait for frame before enabling "game mode" - pthread_mutex_lock(&vpi_decode_loop_mutex); - if (vpi_present_frame && vpi_present_frame->format != -1) { - vui_game_mode_set(vui, 1); - vui_audio_set_enabled(vui, 1); - vpi_game_set_error(VANILLA_SUCCESS); - } - pthread_mutex_unlock(&vpi_decode_loop_mutex); - break; - case VANILLA_ERR_DISCONNECTED: - vui_label_update_text(vui, status_lbl, lang(VPI_LANG_DISCONNECTED)); - vui_audio_set_enabled(vui, 0); - vui_game_mode_set(vui, 0); - break; - case VANILLA_ERR_SHUTDOWN: - vanilla_stop(); + update_battery_information(vui, time); + + switch ((int)vpi_game_queued_error) { + case VANILLA_SUCCESS: + // All good, do nothing + break; + case VANILLA_ERR_DISCONNECTED: + // Tell user connection has terminated and try to re-establish + vui_label_update_text(vui, status_lbl, lang(VPI_LANG_DISCONNECTED)); + vui_audio_set_enabled(vui, 0); + vui_game_mode_set(vui, 0); + break; + default: + // Something went wrong, assume we must fail and report to user + pthread_join(vpi_event_thread, 0); + vanilla_stop(); + if (vpi_game_queued_error != VANILLA_ERR_SHUTDOWN) { + show_error(vui, (void*)(intptr_t) vpi_game_queued_error); + } else { vpi_menu_main(vui, 0); - break; - default: - vanilla_stop(); - show_error(vui, (void*)(intptr_t) backend_err); - }; - } else { - update_battery_information(vui, time); + } + vpi_game_queued_error = VANILLA_SUCCESS; } } @@ -150,12 +718,14 @@ void vpi_menu_game_start(vui_context_t *vui, void *v) vui_get_screen_size(vui, &scrw, &scrh); status_lbl = vui_label_create(vui, 0, scrh * 2 / 5, scrw, scrh, buf, vui_color_create(0.5f,0.5f,0.5f,1), VUI_FONT_SIZE_NORMAL, fglayer); + // TODO: `state` does not get freed if const int cancel_btn_w = BTN_SZ*3; int cancel_btn = vui_button_create(vui, scrw/2 - cancel_btn_w/2, scrh * 3 / 5, cancel_btn_w, BTN_SZ, lang(VPI_LANG_CANCEL_BTN), 0, VUI_BUTTON_STYLE_BUTTON, fglayer, cancel_connect, (void *) (intptr_t) fglayer); vui_transition_fade_layer_in(vui, fglayer, 0, 0); - vpi_game_start(vui); + vpi_game_queued_error = VANILLA_SUCCESS; + pthread_create(&vpi_event_thread, 0, vpi_event_loop, vui); vui_start_passive_animation(vui, vpi_display_update, 0); } @@ -165,3 +735,153 @@ void vpi_menu_game(vui_context_t *vui, void *v) { vpi_menu_start_pipe(vui, 0, vpi_menu_game_start, v, 0, 0); } + +void vpi_show_toast(const char *message) +{ + vui_strncpy(vpi_toast_string, message, sizeof(vpi_toast_string)); + + gettimeofday(&vpi_toast_expiry, 0); + + // Toast lasts for 2 seconds + vpi_toast_expiry.tv_sec += 2; + + vpi_toast_number++; +} + +void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time) +{ + if (number) + *number = vpi_toast_number; + + if (output && output_size) { + vui_strncpy(output, vpi_toast_string, output_size); + } + + if (expiry_time) { + *expiry_time = vpi_toast_expiry; + } +} + +void vpi_decode_send_audio(const void *data, size_t size) +{ + if (recording_fmt_ctx) { + int ret; + + AVPacket *pkt = av_packet_alloc(); + + pkt->data = (uint8_t *) data; + pkt->size = size; + + int64_t ts = get_recording_timestamp(recording_astr->time_base); + pkt->stream_index = AUDIO_STREAM_INDEX; + pkt->dts = ts; + pkt->pts = ts; + + av_interleaved_write_frame(recording_fmt_ctx, pkt); + + av_packet_free(&pkt); + } +} + +int vpi_decode_is_recording() +{ + return (recording_fmt_ctx != 0); +} + +int vpi_decode_record(const char *filename) +{ + int r = avformat_alloc_output_context2(&recording_fmt_ctx, 0, 0, filename); + if (r < 0) { + vpilog("Failed to allocate output context for recording\n"); + goto exit; + } + + recording_vstr = avformat_new_stream(recording_fmt_ctx, 0); + if (!recording_vstr) { + vpilog("Failed to allocate video stream for recording\n"); + goto exit_and_free_context; + } + + recording_vstr->id = VIDEO_STREAM_INDEX; + recording_vstr->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + recording_vstr->codecpar->width = 854; + recording_vstr->codecpar->height = 480; + recording_vstr->codecpar->format = AV_PIX_FMT_YUV420P; + recording_vstr->time_base = (AVRational) {1, 60}; + recording_vstr->codecpar->codec_id = AV_CODEC_ID_H264; + + recording_vstr->codecpar->extradata = av_malloc(200); + recording_vstr->codecpar->extradata_size = vanilla_generate_h264_header(recording_vstr->codecpar->extradata, 200); + + recording_astr = avformat_new_stream(recording_fmt_ctx, 0); + if (!recording_astr) { + vpilog("Failed to allocate audio stream for recording\n"); + goto exit_and_free_context; + } + + recording_astr->id = AUDIO_STREAM_INDEX; + recording_astr->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + recording_astr->codecpar->sample_rate = 48000; +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57,24,100) + recording_astr->codecpar->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; +#else + recording_astr->codecpar->channel_layout = AV_CH_LAYOUT_STEREO; +#endif + recording_astr->codecpar->format = AV_SAMPLE_FMT_S16; + recording_astr->time_base = (AVRational) {1, 48000}; + recording_astr->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE; + + r = avio_open2(&recording_fmt_ctx->pb, filename, AVIO_FLAG_WRITE, 0, 0); + if (r < 0) { + vpilog("Failed to open AVIO for recording\n"); + goto exit_and_free_context; + } + + r = avformat_write_header(recording_fmt_ctx, 0); + if (r < 0) { + vpilog("Failed to write header for recording\n"); + goto exit_and_free_context; + } + + vanilla_request_idr(); + + gettimeofday(&recording_start, 0); + + char buf[VPI_TOAST_MAX_LEN]; + snprintf(buf, sizeof(buf), lang(VPI_LANG_RECORDING_START), filename); + vpi_show_toast(buf); + vpilog("Started recording to \"%s\"\n", filename); + + goto exit; + +exit_and_free_context: + avformat_free_context(recording_fmt_ctx); + recording_fmt_ctx = 0; + +exit: + return r; +} + +void vpi_decode_record_stop() +{ + if (recording_fmt_ctx) { + int r = av_write_trailer(recording_fmt_ctx); + if (r < 0) { + vpilog("Failed to write trailer for recording\n"); + } + + avio_closep(&recording_fmt_ctx->pb); + + avformat_free_context(recording_fmt_ctx); + recording_fmt_ctx = 0; + + vpilog("Finished recording\n"); + vpi_show_toast(lang(VPI_LANG_RECORDING_FINISH)); + } +} + +void vpi_decode_screenshot(const char *filename) +{ + // Grab copy of filename + vui_strncpy(screenshot_buf, filename, sizeof(screenshot_buf)); +} diff --git a/gui/menu/menu_game.h b/gui/menu/menu_game.h index c469507..2a3d409 100644 --- a/gui/menu/menu_game.h +++ b/gui/menu/menu_game.h @@ -1,8 +1,28 @@ #ifndef VANILLA_PI_MENU_GAME_H #define VANILLA_PI_MENU_GAME_H +#include +#include +#include + #include "ui/ui.h" +#define VPI_TOAST_MAX_LEN 1024 + +extern AVFrame *vpi_present_frame; +extern pthread_mutex_t vpi_present_frame_mutex; + void vpi_menu_game(vui_context_t *vui, void *v); -#endif // VANILLA_PI_MENU_GAME_H \ No newline at end of file +void vpi_game_shutdown(); + +void vpi_get_toast(int *number, char *output, size_t output_size, struct timeval *expiry_time); +void vpi_show_toast(const char *message); + +void vpi_decode_screenshot(const char *filename); +int vpi_decode_is_recording(); +int vpi_decode_record(const char *filename); +void vpi_decode_record_stop(); +void vpi_decode_send_audio(const void *data, size_t size); + +#endif // VANILLA_PI_MENU_GAME_H diff --git a/gui/pipemgmt.c b/gui/pipemgmt.c index bb1f7ef..b513224 100644 --- a/gui/pipemgmt.c +++ b/gui/pipemgmt.c @@ -468,6 +468,9 @@ int vpi_start_pipe() } #endif + // Tell lib our wireless interface + vanilla_set_wireless_interface(vpi_config.wireless_interface); + return ret; } } diff --git a/gui/ui/ui.c b/gui/ui/ui.c index b3e5ce4..5ef163c 100644 --- a/gui/ui/ui.c +++ b/gui/ui/ui.c @@ -923,7 +923,7 @@ void vui_process_keyup(vui_context_t *ctx, int button) vui_power_state_t vui_power_state_get(vui_context_t *ctx, int *percent) { if (ctx->power_state_handler) { - return ctx->power_state_handler(percent); + return ctx->power_state_handler(ctx, percent); } else { if (percent) { *percent = -1; diff --git a/gui/ui/ui_priv.h b/gui/ui/ui_priv.h index b81f36d..0be06cf 100644 --- a/gui/ui/ui_priv.h +++ b/gui/ui/ui_priv.h @@ -99,7 +99,7 @@ typedef void (*vui_audio_handler_t)(const void *data, size_t size, void *userdat typedef void (*vui_vibrate_handler_t)(uint8_t vibrate, void *userdata); typedef int (*vui_font_height_handler_t)(vui_font_size_t size, void *userdata); typedef void (*vui_text_open_handler_t)(vui_context_t *ctx, int textedit, int open, void *userdata); -typedef vui_power_state_t (*vui_power_state_handler_t)(int *percent); +typedef vui_power_state_t (*vui_power_state_handler_t)(vui_context_t *ctx, int *percent); typedef void (*vui_audio_enabled_handler_t)(vui_context_t *ctx, int enabled, void *userdata); typedef struct vui_context_t { diff --git a/gui/ui/ui_sdl.c b/gui/ui/ui_sdl.c index 08f039e..2580a55 100644 --- a/gui/ui/ui_sdl.c +++ b/gui/ui/ui_sdl.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #ifdef VANILLA_HAS_EGL #include @@ -17,9 +19,13 @@ #include #endif -#include "game/game_decode.h" -#include "game/game_main.h" +#ifdef VANILLA_DRM_AVAILABLE +#include +#include "ui_sdl_drm.h" +#endif + #include "menu/menu.h" +#include "menu/menu_game.h" #include "platform.h" #include "ui_priv.h" #include "ui_util.h" @@ -65,6 +71,14 @@ typedef struct { size_t audio_buffer_start; size_t audio_buffer_end; pthread_mutex_t audio_buffer_mutex; + + Uint32 last_power_state_check; + vui_power_state_t last_power_state; + + uint16_t last_vibration_state; + + SDL_Thread *event_thread; + SDL_mutex *display_mutex; } vui_sdl_context_t; static int button_map[SDL_CONTROLLER_BUTTON_MAX]; @@ -193,7 +207,10 @@ void vui_sdl_vibrate_handler(uint8_t vibrate, void *userdata) vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) userdata; if (sdl_ctx->controller) { uint16_t amount = vibrate ? 0xFFFF : 0; - SDL_GameControllerRumble(sdl_ctx->controller, amount, amount, 0); + if (sdl_ctx->last_vibration_state != amount) { + SDL_GameControllerRumble(sdl_ctx->controller, amount, amount, 0); + sdl_ctx->last_vibration_state = amount; + } } } @@ -237,9 +254,19 @@ void vui_sdl_text_open_handler(vui_context_t *ctx, int textedit, int open, void } } -vui_power_state_t vui_sdl_power_state_handler(int *percent) +vui_power_state_t vui_sdl_power_state_handler(vui_context_t *ctx, int *percent) { - return (vui_power_state_t) SDL_GetPowerInfo(NULL, percent); + vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) ctx->platform_data; + + // Turns out checking the power state is an expensive operation. Let's + // limit checks to once per minute and cache the result. + Uint32 now = SDL_GetTicks(); + if (now >= sdl_ctx->last_power_state_check + 60000) { + sdl_ctx->last_power_state = (vui_power_state_t) SDL_GetPowerInfo(NULL, percent); + sdl_ctx->last_power_state_check = now; + } + + return sdl_ctx->last_power_state; } void vui_sdl_audio_set_enabled(vui_context_t *ctx, int enabled, void *userdata) @@ -264,7 +291,7 @@ void audio_callback(void *userdata, Uint8 *stream, int len) if ((sdl_ctx->audio_buffer_end - sdl_ctx->audio_buffer_start) >= len) { for (size_t i = 0; i < len; ) { - size_t phys = sdl_ctx->audio_buffer_start % sdl_ctx->audio_buffer_size; + size_t phys = sdl_ctx->audio_buffer_start % sdl_ctx->audio_buffer_size; size_t max_write = MIN(sdl_ctx->audio_buffer_size - phys, len - i); memcpy(stream + i, sdl_ctx->audio_buffer + phys, max_write); i += max_write; @@ -285,6 +312,222 @@ void mic_callback(void *userdata, Uint8 *stream, int len) ctx->mic_callback(ctx->mic_callback_data, stream, len); } +int32_t pack_float(float f) +{ + int32_t x; + memcpy(&x, &f, sizeof(int32_t)); + return x; +} + +int vui_sdl_event_thread(void *data) +{ + vui_context_t *vui = (vui_context_t *) data; + vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) vui->platform_data; + + SDL_Event ev; + while (!vui->quit) { + while (SDL_WaitEventTimeout(&ev, 100)) { + SDL_LockMutex(sdl_ctx->display_mutex); + + switch (ev.type) { + case SDL_QUIT: + vanilla_stop(); + vui_quit(vui); + SDL_UnlockMutex(sdl_ctx->display_mutex); + return 0; + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + // Ensure dst_rect is initialized + SDL_Rect *dst_rect = &sdl_ctx->dst_rect; + if (dst_rect->w > 0 && dst_rect->h > 0) { + // Translate screen coords to logical coords + int tr_x, tr_y; + tr_x = (ev.button.x - dst_rect->x) * vui->screen_width / dst_rect->w; + tr_y = (ev.button.y - dst_rect->y) * vui->screen_height / dst_rect->h; + + if (vui->game_mode) { + // In game mode, pass clicks directly to Vanilla + int x, y; + if (ev.button.button == SDL_BUTTON_LEFT && (ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEMOTION)) { + x = tr_x; + y = tr_y; + } else { + x = -1; + y = -1; + } + vanilla_set_touch(x, y); + } else { + // Otherwise, handle ourselves + if (ev.type == SDL_MOUSEBUTTONDOWN) + vui_process_mousedown(vui, tr_x, tr_y); + else if (ev.type == SDL_MOUSEBUTTONUP) + vui_process_mouseup(vui, tr_x, tr_y); + + if (vui->active_textedit != -1 && (ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEMOTION) && ev.button.button == SDL_BUTTON_LEFT) { + // Put cursor in correct position + vui_textedit_t *edit = &vui->textedits[vui->active_textedit]; + TTF_Font *font = get_font(sdl_ctx, edit->size); + + // Determine best location for new cursor + int rel_x = tr_x - edit->x; + char tmp[MAX_BUTTON_TEXT]; + int new_cursor = 0; + int diff = rel_x; + size_t len = 0; + char *src = edit->text; + while (*src != 0) { + src = vui_utf8_advance(src); + + size_t len = src - edit->text; + strncpy(tmp, edit->text, len); + tmp[len] = 0; + + int tw; + TTF_SizeUTF8(font, tmp, &tw, 0); + + int this_diff = abs(tw - rel_x); + if (this_diff < diff) { + diff = this_diff; + new_cursor++; + } else { + break; + } + } + + vui_textedit_set_cursor(vui, vui->active_textedit, new_cursor); + } + } + } + break; + } + case SDL_CONTROLLERDEVICEADDED: + // Attempt to find controller if one doesn't exist already + if (!sdl_ctx->controller) { + sdl_ctx->controller = find_valid_controller(); + } + break; + case SDL_CONTROLLERDEVICEREMOVED: + // Handle current controller being removed + if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { + SDL_GameControllerClose(sdl_ctx->controller); + sdl_ctx->controller = find_valid_controller(); + } + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { + int vanilla_btn = button_map[ev.cbutton.button]; + if (vanilla_btn > VPI_ACTION_START_INDEX) { + if (ev.type == SDL_CONTROLLERBUTTONDOWN) + vpi_menu_action(vui, (vpi_extra_action_t) vanilla_btn); + } else if (vanilla_btn != -1) { + if (vui->game_mode) { + vanilla_set_button(vanilla_btn, ev.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0); + } else if (ev.type == SDL_CONTROLLERBUTTONDOWN) { + vui_process_keydown(vui, vanilla_btn); + } else { + vui_process_keyup(vui, vanilla_btn); + } + } + } + break; + case SDL_CONTROLLERAXISMOTION: + if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { + int vanilla_axis = axis_map[ev.caxis.axis]; + Sint16 axis_value = ev.caxis.value; + if (vanilla_axis != -1) { + if (vui->game_mode) { + vanilla_set_button(vanilla_axis, axis_value); + } else if (vanilla_axis == SDL_CONTROLLER_AXIS_LEFTX) { + if (axis_value < 0) + vui_process_keydown(vui, VANILLA_AXIS_L_LEFT); + else if (axis_value > 0) + vui_process_keydown(vui, VANILLA_AXIS_L_RIGHT); + else { + vui_process_keyup(vui, VANILLA_AXIS_L_LEFT); + vui_process_keyup(vui, VANILLA_AXIS_L_RIGHT); + } + } else if (vanilla_axis == SDL_CONTROLLER_AXIS_LEFTY) { + if (axis_value < 0) + vui_process_keydown(vui, VANILLA_AXIS_L_UP); + else if (axis_value > 0) + vui_process_keydown(vui, VANILLA_AXIS_L_DOWN); + else { + vui_process_keyup(vui, VANILLA_AXIS_L_UP); + vui_process_keyup(vui, VANILLA_AXIS_L_DOWN); + } + } + } + } + break; + case SDL_CONTROLLERSENSORUPDATE: + if (ev.csensor.sensor == SDL_SENSOR_ACCEL) { + vanilla_set_button(VANILLA_SENSOR_ACCEL_X, pack_float(ev.csensor.data[0])); + vanilla_set_button(VANILLA_SENSOR_ACCEL_Y, pack_float(ev.csensor.data[1])); + vanilla_set_button(VANILLA_SENSOR_ACCEL_Z, pack_float(ev.csensor.data[2])); + } else if (ev.csensor.sensor == SDL_SENSOR_GYRO) { + vanilla_set_button(VANILLA_SENSOR_GYRO_PITCH, pack_float(ev.csensor.data[0])); + vanilla_set_button(VANILLA_SENSOR_GYRO_YAW, pack_float(ev.csensor.data[1])); + vanilla_set_button(VANILLA_SENSOR_GYRO_ROLL, pack_float(ev.csensor.data[2])); + } + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + { + if (vui->active_textedit == -1) { + int vanilla_btn = key_map[ev.key.keysym.scancode]; + if (vanilla_btn > VPI_ACTION_START_INDEX) { + if (ev.type == SDL_KEYDOWN) + vpi_menu_action(vui, (vpi_extra_action_t) vanilla_btn); + } else if (vanilla_btn != -1) { + if (vui->game_mode) { + vanilla_set_button(vanilla_btn, ev.type == SDL_KEYDOWN ? INT16_MAX : 0); + } else if (ev.type == SDL_KEYDOWN) { + vui_process_keydown(vui, vanilla_btn); + } else { + vui_process_keyup(vui, vanilla_btn); + } + } + } else if (ev.type == SDL_KEYDOWN) { + switch (ev.key.keysym.scancode) { + case SDL_SCANCODE_BACKSPACE: + vui_textedit_backspace(vui, vui->active_textedit); + break; + case SDL_SCANCODE_LEFT: + vui_textedit_move_cursor(vui, vui->active_textedit, -1); + break; + case SDL_SCANCODE_RIGHT: + vui_textedit_move_cursor(vui, vui->active_textedit, 1); + break; + case SDL_SCANCODE_HOME: + vui_textedit_move_cursor(vui, vui->active_textedit, -MAX_BUTTON_TEXT); + break; + case SDL_SCANCODE_END: + vui_textedit_move_cursor(vui, vui->active_textedit, MAX_BUTTON_TEXT); + break; + case SDL_SCANCODE_DELETE: + vui_textedit_del(vui, vui->active_textedit); + break; + } + } + break; + } + case SDL_TEXTINPUT: + vui_textedit_input(vui, vui->active_textedit, ev.text.text); + break; + case SDL_TEXTEDITING: + vpilog("text editing!\n"); + break; + } + SDL_UnlockMutex(sdl_ctx->display_mutex); + } + } + + return 0; +} + int vui_init_sdl(vui_context_t *ctx, int fullscreen) { // Enable Steam Deck gyroscopes even while Steam is open and in gaming mode @@ -297,6 +540,9 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) // Force EGL when using X11 so that VAAPI works SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); + // Enable linear filtering + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + // By default, SDL2 uses X11, even on Wayland systems. This probably isn't // a big deal, but just in case we want it to prefer Wayland, this is how // we can do it. @@ -309,10 +555,9 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) } vui_sdl_context_t *sdl_ctx = malloc(sizeof(vui_sdl_context_t)); + memset(sdl_ctx, 0, sizeof(vui_sdl_context_t)); ctx->platform_data = sdl_ctx; - memset(&sdl_ctx->dst_rect, 0, sizeof(SDL_Rect)); - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); SDL_WindowFlags window_flags; @@ -346,8 +591,7 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) sdl_ctx->audio = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); if (sdl_ctx->audio) { // Set up buffers - size_t buffer_size = obtained.samples * 4; // * 2 for 16-bit, * 2 for stereo - buffer_size += 2048; // Pad buffer for maximum size of Wii U packet + size_t buffer_size = obtained.samples * 4 * 3; // * 2 for 16-bit, * 2 for stereo, * 3 so there's plenty of buffer sdl_ctx->audio_buffer_size = buffer_size; sdl_ctx->audio_buffer = malloc(buffer_size); sdl_ctx->audio_buffer_start = 0; @@ -382,6 +626,7 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) ctx->vibrate_handler = vui_sdl_vibrate_handler; ctx->vibrate_handler_data = sdl_ctx; + sdl_ctx->last_vibration_state = 0; ctx->font_height_handler = vui_sdl_font_height_handler; ctx->font_height_handler_data = sdl_ctx; @@ -389,6 +634,8 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) ctx->text_open_handler = vui_sdl_text_open_handler; ctx->power_state_handler = vui_sdl_power_state_handler; + sdl_ctx->last_power_state_check = 0; + sdl_ctx->last_power_state = VUI_POWERSTATE_UNKNOWN; if (TTF_Init()) { vpilog("Failed to TTF_Init\n"); @@ -435,6 +682,9 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen) sdl_ctx->frame = av_frame_alloc(); + sdl_ctx->event_thread = SDL_CreateThread(vui_sdl_event_thread, "vanilla-event", ctx); + sdl_ctx->display_mutex = SDL_CreateMutex(); + // Initialize gamepad lookup tables init_gamepad(); @@ -448,6 +698,14 @@ void vui_close_sdl(vui_context_t *ctx) return; } + if (sdl_ctx->event_thread) { + SDL_WaitThread(sdl_ctx->event_thread, 0); + } + + if (sdl_ctx->display_mutex) { + SDL_DestroyMutex(sdl_ctx->display_mutex); + } + av_frame_free(&sdl_ctx->frame); if (sdl_ctx->controller) { @@ -567,7 +825,7 @@ void determine_color_for_gradient(int y, int h, uint8_t *r, uint8_t *g, uint8_t } } -void vui_sdl_draw_button(vui_context_t *vui, vui_sdl_context_t *sdl_ctx, vui_button_t *btn) +void vui_sdl_draw_button(vui_context_t *vui, vui_sdl_context_t *sdl_ctx, vui_button_t *btn, int msaa) { SDL_SetRenderDrawColor(sdl_ctx->renderer, 0, 0, 0, 0); SDL_RenderClear(sdl_ctx->renderer); @@ -577,8 +835,8 @@ void vui_sdl_draw_button(vui_context_t *vui, vui_sdl_context_t *sdl_ctx, vui_but SDL_Rect rect; rect.x = 0; rect.y = 0; - rect.w = btn->w; - rect.h = btn->h; + rect.w = btn->w * msaa; + rect.h = btn->h * msaa; // static const float button_x_gradient_positions[] = { // 0.0f, @@ -712,8 +970,8 @@ void vui_sdl_draw_button(vui_context_t *vui, vui_sdl_context_t *sdl_ctx, vui_but text_rect.h = surface->h; const int btn_padding = 8; - text_rect.x = rect.x + rect.w / 2 - text_rect.w / 2; - text_rect.y = rect.y + rect.h / 2 - text_rect.h / 2; + text_rect.x = rect.x + rect.w / 2 - (text_rect.w * msaa) / 2; + text_rect.y = rect.y + rect.h / 2 - (text_rect.h * msaa) / 2; // Adjust text rect if icon exists if (icon) { @@ -743,6 +1001,8 @@ void vui_sdl_draw_button(vui_context_t *vui, vui_sdl_context_t *sdl_ctx, vui_but } } + text_rect.w *= msaa; + text_rect.h *= msaa; SDL_RenderCopy(sdl_ctx->renderer, texture, NULL, &text_rect); SDL_DestroyTexture(texture); @@ -1007,13 +1267,15 @@ void vui_draw_sdl(vui_context_t *ctx, SDL_Renderer *renderer) btn_tex->texture = 0; } + const int MSAA = 2; + if (!btn_tex->texture) { - btn_tex->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, btn->w, btn->h); + btn_tex->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, btn->w * MSAA, btn->h * MSAA); } SDL_Texture *old_target = SDL_GetRenderTarget(renderer); SDL_SetRenderTarget(renderer, btn_tex->texture); - vui_sdl_draw_button(ctx, sdl_ctx, btn); + vui_sdl_draw_button(ctx, sdl_ctx, btn, MSAA); SDL_SetRenderTarget(renderer, old_target); btn_tex->w = btn->w; @@ -1118,13 +1380,6 @@ void vui_draw_sdl(vui_context_t *ctx, SDL_Renderer *renderer) } } -int32_t pack_float(float f) -{ - int32_t x; - memcpy(&x, &f, sizeof(int32_t)); - return x; -} - int get_texture_from_cpu_frame(vui_sdl_context_t *sdl_ctx, AVFrame *f) { if (!sdl_ctx->game_tex) { @@ -1179,13 +1434,13 @@ int check_has_EGL_EXT_image_dma_buf_import() do { if (SDL_strcmp(token, "EGL_EXT_image_dma_buf_import") == 0 || SDL_strcmp(token, "EGL_EXT_image_dma_buf_import_modifiers") == 0) { - ret = 1; + ret++; } } while ((token = SDL_strtokr(NULL, " ", &saveptr)) != NULL); SDL_free(extensions); - return ret; + return ret == 2; // if (SDL_GL_ExtensionSupported("GL_OES_EGL_image")) { // glEGLImageTargetTexture2DOESFunc = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); @@ -1204,66 +1459,86 @@ int check_has_EGL_EXT_image_dma_buf_import() int get_texture_from_drm_prime_frame(vui_sdl_context_t *sdl_ctx, AVFrame *f) { #ifdef VANILLA_HAS_EGL - if (!sdl_ctx->game_tex) { - sdl_ctx->game_tex = SDL_CreateTexture( - sdl_ctx->renderer, - SDL_PIXELFORMAT_NV12, - SDL_TEXTUREACCESS_STATIC, - f->width, - f->height - ); - - if (!sdl_ctx->game_tex) { - vpilog("Failed to create texture for DRM PRIME frame\n"); - return 0; - } - } - - static int has_EGL_EXT_image_dma_buf_import = -1; - if (has_EGL_EXT_image_dma_buf_import == -1) { - has_EGL_EXT_image_dma_buf_import = check_has_EGL_EXT_image_dma_buf_import(); - } - - const AVDRMFrameDescriptor *desc = (const AVDRMFrameDescriptor *)f->data[0]; - PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = SDL_GL_GetProcAddress("glActiveTextureARB"); - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); - EGLDisplay display = eglGetCurrentDisplay(); + static PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL; + if (!glActiveTextureARB) { + glActiveTextureARB = SDL_GL_GetProcAddress("glActiveTextureARB"); + } + + static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = NULL; + if (!glEGLImageTargetTexture2DOES) { + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + } int image_index = 0; + EGLDisplay display = eglGetCurrentDisplay(); for (int i = 0; i < desc->nb_layers; i++) { const AVDRMLayerDescriptor *layer = &desc->layers[i]; + for (int j = 0; j < layer->nb_planes; j++) { const AVDRMPlaneDescriptor *plane = &layer->planes[j]; const AVDRMObjectDescriptor *object = &desc->objects[plane->object_index]; - const EGLAttrib EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT_OR_NONE = has_EGL_EXT_image_dma_buf_import ? EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT : EGL_NONE; + static int has_EGL_EXT_image_dma_buf_import = -1; + if (has_EGL_EXT_image_dma_buf_import == -1) { + has_EGL_EXT_image_dma_buf_import = check_has_EGL_EXT_image_dma_buf_import(); + } - EGLAttrib attr[] = { - EGL_LINUX_DRM_FOURCC_EXT, layer->format, - EGL_WIDTH, f->width / (image_index + 1), - EGL_HEIGHT, f->height / (image_index + 1), - EGL_DMA_BUF_PLANE0_FD_EXT, object->fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, plane->offset, - EGL_DMA_BUF_PLANE0_PITCH_EXT, plane->pitch, - EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT_OR_NONE, (object->format_modifier >> 0) & 0xFFFFFFFF, - EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (object->format_modifier >> 32) & 0xFFFFFFFF, - EGL_NONE - }; + const EGLAttrib EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT_OR_NONE = + has_EGL_EXT_image_dma_buf_import && object->format_modifier != DRM_FORMAT_MOD_INVALID ? + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT : EGL_NONE; - EGLImage image = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, 0, attr); - if (image == EGL_NO_IMAGE) { - vpilog("Failed to create EGLImage: 0x%x (display: %p)\n", eglGetError(), display); - return 0; - } + if (!sdl_ctx->game_tex) { + sdl_ctx->game_tex = SDL_CreateTexture( + sdl_ctx->renderer, + layer->format == DRM_FORMAT_YUV420 ? SDL_PIXELFORMAT_IYUV : SDL_PIXELFORMAT_NV12, + SDL_TEXTUREACCESS_STATIC, + f->width, + f->height + ); + + if (!sdl_ctx->game_tex) { + vpilog("Failed to create texture for DRM PRIME frame\n"); + return 0; + } + } + + EGLAttrib use_fmt = layer->format == DRM_FORMAT_YUV420 ? DRM_FORMAT_R8 : layer->format; + + int w = f->width; + int h = f->height; + if (image_index > 0) { + w /= 2; + h /= 2; + } + + EGLAttrib attr[] = { + EGL_LINUX_DRM_FOURCC_EXT, use_fmt, + EGL_WIDTH, w, + EGL_HEIGHT, h, + EGL_DMA_BUF_PLANE0_FD_EXT, object->fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, plane->offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, plane->pitch, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT_OR_NONE, (object->format_modifier >> 0) & 0xFFFFFFFF, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (object->format_modifier >> 32) & 0xFFFFFFFF, + EGL_NONE + }; + + EGLImage image = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, 0, attr); + if (image == EGL_NO_IMAGE) { + vpilog("Failed to create EGLImage: 0x%x (display: %p, layer %i, plane %i)\n", eglGetError(), display, i, j); + return 0; + } SDL_GL_BindTexture(sdl_ctx->game_tex, 0, 0); glActiveTextureARB(GL_TEXTURE0_ARB + image_index); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); image_index++; SDL_GL_UnbindTexture(sdl_ctx->game_tex); + + eglDestroyImage(display, image); } } @@ -1274,210 +1549,34 @@ int get_texture_from_drm_prime_frame(vui_sdl_context_t *sdl_ctx, AVFrame *f) #endif } +// Rendering/main thread int vui_update_sdl(vui_context_t *vui) { vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) vui->platform_data; + SDL_LockMutex(sdl_ctx->display_mutex); + SDL_Rect *dst_rect = &sdl_ctx->dst_rect; SDL_Renderer *renderer = sdl_ctx->renderer; - SDL_Event ev; - while (SDL_PollEvent(&ev)) { - switch (ev.type) { - case SDL_QUIT: - vanilla_stop(); - return 0; - case SDL_MOUSEMOTION: - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - // Ensure dst_rect is initialized - if (dst_rect->w > 0 && dst_rect->h > 0) { - // Translate screen coords to logical coords - int tr_x, tr_y; - tr_x = (ev.button.x - dst_rect->x) * vui->screen_width / dst_rect->w; - tr_y = (ev.button.y - dst_rect->y) * vui->screen_height / dst_rect->h; - - if (vui->game_mode) { - // In game mode, pass clicks directly to Vanilla - int x, y; - if (ev.button.button == SDL_BUTTON_LEFT && (ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEMOTION)) { - x = tr_x; - y = tr_y; - } else { - x = -1; - y = -1; - } - vanilla_set_touch(x, y); - } else { - // Otherwise, handle ourselves - if (ev.type == SDL_MOUSEBUTTONDOWN) - vui_process_mousedown(vui, tr_x, tr_y); - else if (ev.type == SDL_MOUSEBUTTONUP) - vui_process_mouseup(vui, tr_x, tr_y); - - if (vui->active_textedit != -1 && (ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEMOTION) && ev.button.button == SDL_BUTTON_LEFT) { - // Put cursor in correct position - vui_textedit_t *edit = &vui->textedits[vui->active_textedit]; - TTF_Font *font = get_font(sdl_ctx, edit->size); - - // Determine best location for new cursor - int rel_x = tr_x - edit->x; - char tmp[MAX_BUTTON_TEXT]; - int new_cursor = 0; - int diff = rel_x; - size_t len = 0; - char *src = edit->text; - while (*src != 0) { - src = vui_utf8_advance(src); - - size_t len = src - edit->text; - strncpy(tmp, edit->text, len); - tmp[len] = 0; - - int tw; - TTF_SizeUTF8(font, tmp, &tw, 0); - - int this_diff = abs(tw - rel_x); - if (this_diff < diff) { - diff = this_diff; - new_cursor++; - } else { - break; - } - } - - vui_textedit_set_cursor(vui, vui->active_textedit, new_cursor); - } - } - } - break; - case SDL_CONTROLLERDEVICEADDED: - // Attempt to find controller if one doesn't exist already - if (!sdl_ctx->controller) { - sdl_ctx->controller = find_valid_controller(); - } - break; - case SDL_CONTROLLERDEVICEREMOVED: - // Handle current controller being removed - if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { - SDL_GameControllerClose(sdl_ctx->controller); - sdl_ctx->controller = find_valid_controller(); - } - break; - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { - int vanilla_btn = button_map[ev.cbutton.button]; - if (vanilla_btn > VPI_ACTION_START_INDEX) { - if (ev.type == SDL_CONTROLLERBUTTONDOWN) - vpi_menu_action(vui, (vpi_extra_action_t) vanilla_btn); - } else if (vanilla_btn != -1) { - if (vui->game_mode) { - vanilla_set_button(vanilla_btn, ev.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0); - } else if (ev.type == SDL_CONTROLLERBUTTONDOWN) { - vui_process_keydown(vui, vanilla_btn); - } else { - vui_process_keyup(vui, vanilla_btn); - } - } - } - break; - case SDL_CONTROLLERAXISMOTION: - if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { - int vanilla_axis = axis_map[ev.caxis.axis]; - Sint16 axis_value = ev.caxis.value; - if (vanilla_axis != -1) { - if (vui->game_mode) { - vanilla_set_button(vanilla_axis, axis_value); - } else if (vanilla_axis == SDL_CONTROLLER_AXIS_LEFTX) { - if (axis_value < 0) - vui_process_keydown(vui, VANILLA_AXIS_L_LEFT); - else if (axis_value > 0) - vui_process_keydown(vui, VANILLA_AXIS_L_RIGHT); - else { - vui_process_keyup(vui, VANILLA_AXIS_L_LEFT); - vui_process_keyup(vui, VANILLA_AXIS_L_RIGHT); - } - } else if (vanilla_axis == SDL_CONTROLLER_AXIS_LEFTY) { - if (axis_value < 0) - vui_process_keydown(vui, VANILLA_AXIS_L_UP); - else if (axis_value > 0) - vui_process_keydown(vui, VANILLA_AXIS_L_DOWN); - else { - vui_process_keyup(vui, VANILLA_AXIS_L_UP); - vui_process_keyup(vui, VANILLA_AXIS_L_DOWN); - } - } - } - } - break; - case SDL_CONTROLLERSENSORUPDATE: - if (ev.csensor.sensor == SDL_SENSOR_ACCEL) { - vanilla_set_button(VANILLA_SENSOR_ACCEL_X, pack_float(ev.csensor.data[0])); - vanilla_set_button(VANILLA_SENSOR_ACCEL_Y, pack_float(ev.csensor.data[1])); - vanilla_set_button(VANILLA_SENSOR_ACCEL_Z, pack_float(ev.csensor.data[2])); - } else if (ev.csensor.sensor == SDL_SENSOR_GYRO) { - vanilla_set_button(VANILLA_SENSOR_GYRO_PITCH, pack_float(ev.csensor.data[0])); - vanilla_set_button(VANILLA_SENSOR_GYRO_YAW, pack_float(ev.csensor.data[1])); - vanilla_set_button(VANILLA_SENSOR_GYRO_ROLL, pack_float(ev.csensor.data[2])); - } - break; - case SDL_KEYDOWN: - case SDL_KEYUP: - { - if (vui->active_textedit == -1) { - int vanilla_btn = key_map[ev.key.keysym.scancode]; - if (vanilla_btn > VPI_ACTION_START_INDEX) { - if (ev.type == SDL_KEYDOWN) - vpi_menu_action(vui, (vpi_extra_action_t) vanilla_btn); - } else if (vanilla_btn != -1) { - if (vui->game_mode) { - vanilla_set_button(vanilla_btn, ev.type == SDL_KEYDOWN ? INT16_MAX : 0); - } else if (ev.type == SDL_KEYDOWN) { - vui_process_keydown(vui, vanilla_btn); - } else { - vui_process_keyup(vui, vanilla_btn); - } - } - } else if (ev.type == SDL_KEYDOWN) { - switch (ev.key.keysym.scancode) { - case SDL_SCANCODE_BACKSPACE: - vui_textedit_backspace(vui, vui->active_textedit); - break; - case SDL_SCANCODE_LEFT: - vui_textedit_move_cursor(vui, vui->active_textedit, -1); - break; - case SDL_SCANCODE_RIGHT: - vui_textedit_move_cursor(vui, vui->active_textedit, 1); - break; - case SDL_SCANCODE_HOME: - vui_textedit_move_cursor(vui, vui->active_textedit, -MAX_BUTTON_TEXT); - break; - case SDL_SCANCODE_END: - vui_textedit_move_cursor(vui, vui->active_textedit, MAX_BUTTON_TEXT); - break; - case SDL_SCANCODE_DELETE: - vui_textedit_del(vui, vui->active_textedit); - break; - } - } - break; - } - case SDL_TEXTINPUT: - vui_textedit_input(vui, vui->active_textedit, ev.text.text); - break; - case SDL_TEXTEDITING: - vpilog("text editing!\n"); - break; - } - } - vui_update(vui); SDL_Texture *main_tex; +#ifdef VANILLA_DRM_AVAILABLE + static vanilla_drm_ctx_t *drm_ctx = NULL; +#endif // VANILLA_DRM_AVAILABLE + + int handle_final_blit = 1; if (!vui->game_mode) { + +#ifdef VANILLA_DRM_AVAILABLE + if (drm_ctx) { + vui_sdl_drm_free(&drm_ctx); // will set to null + } +#endif // VANILLA_DRM_AVAILABLE + // Draw vui to a custom texture vui_draw_sdl(vui, renderer); @@ -1503,17 +1602,28 @@ int vui_update_sdl(vui_context_t *vui) main_tex = sdl_ctx->layer_data[0]; } else { - pthread_mutex_lock(&vpi_decoding_mutex); + pthread_mutex_lock(&vpi_present_frame_mutex); if (vpi_present_frame && vpi_present_frame->format != -1) { av_frame_move_ref(sdl_ctx->frame, vpi_present_frame); + sdl_ctx->frame->pts = vpi_present_frame->pts; } - pthread_mutex_unlock(&vpi_decoding_mutex); + pthread_mutex_unlock(&vpi_present_frame_mutex); if (sdl_ctx->frame->format != -1) { switch (sdl_ctx->frame->format) { case AV_PIX_FMT_DRM_PRIME: - get_texture_from_drm_prime_frame(sdl_ctx, sdl_ctx->frame); + { + // get_texture_from_drm_prime_frame(sdl_ctx, sdl_ctx->frame); + +#ifdef VANILLA_DRM_AVAILABLE + if (!drm_ctx) { + vui_sdl_drm_initialize(&drm_ctx, sdl_ctx->window); + } + vui_sdl_drm_present(drm_ctx, sdl_ctx->frame); + handle_final_blit = 0; +#endif // VANILLA_DRM_AVAILABLE break; + } case AV_PIX_FMT_VAAPI: { AVFrame *drm = av_frame_alloc(); @@ -1537,113 +1647,121 @@ int vui_update_sdl(vui_context_t *vui) av_frame_unref(sdl_ctx->frame); } - if (sdl_ctx->game_tex) { - main_tex = sdl_ctx->layer_data[0]; - SDL_SetRenderTarget(renderer, main_tex); - SDL_RenderCopy(renderer, sdl_ctx->game_tex, 0, 0); + if (sdl_ctx->game_tex) { + // main_tex = sdl_ctx->layer_data[0]; + // SDL_SetRenderTarget(renderer, main_tex); + // SDL_RenderCopy(renderer, sdl_ctx->game_tex, 0, 0); + main_tex = sdl_ctx->game_tex; } } - // Draw toast on screen if necessary - const int TOAST_PADDING = 12; - int cur_toast; - vpi_get_toast(&cur_toast, 0, 0, 0); - if (cur_toast != sdl_ctx->last_shown_toast) { - // Get toast information - char toast_str[VPI_TOAST_MAX_LEN]; - vpi_get_toast(&cur_toast, toast_str, sizeof(toast_str), &sdl_ctx->toast_expiry); + if (handle_final_blit) { + // Draw toast on screen if necessary + const int TOAST_PADDING = 12; + int cur_toast; + vpi_get_toast(&cur_toast, 0, 0, 0); + if (cur_toast != sdl_ctx->last_shown_toast) { + // Get toast information + char toast_str[VPI_TOAST_MAX_LEN]; + vpi_get_toast(&cur_toast, toast_str, sizeof(toast_str), &sdl_ctx->toast_expiry); - sdl_ctx->last_shown_toast = cur_toast; + sdl_ctx->last_shown_toast = cur_toast; - const int toast_w = vui->screen_width/2; + const int toast_w = vui->screen_width/2; - SDL_Color c; - c.r = c.g = c.b = 0x40; - c.a = 0xFF; + SDL_Color c; + c.r = c.g = c.b = 0x40; + c.a = 0xFF; - SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped(sdl_ctx->sysfont_small, toast_str, c, toast_w - TOAST_PADDING - TOAST_PADDING); - SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped(sdl_ctx->sysfont_small, toast_str, c, toast_w - TOAST_PADDING - TOAST_PADDING); + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); - const int toast_h = surface->h + TOAST_PADDING + TOAST_PADDING; - sdl_ctx->toast_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, toast_w, toast_h); - SDL_SetTextureBlendMode(sdl_ctx->toast_tex, SDL_BLENDMODE_BLEND); + const int toast_h = surface->h + TOAST_PADDING + TOAST_PADDING; + sdl_ctx->toast_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, toast_w, toast_h); + SDL_SetTextureBlendMode(sdl_ctx->toast_tex, SDL_BLENDMODE_BLEND); - SDL_SetRenderTarget(renderer, sdl_ctx->toast_tex); + SDL_SetRenderTarget(renderer, sdl_ctx->toast_tex); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xF0); + const int TOAST_BORDER_RADIUS = TOAST_PADDING; + for (int y = 0; y < toast_h; y++) { + const int x_inset = calculate_x_inset(y, toast_h, TOAST_BORDER_RADIUS); + SDL_RenderDrawLine(renderer, x_inset, y, toast_w - x_inset, y); + } + + SDL_Rect dst_rect; + dst_rect.x = dst_rect.y = TOAST_PADDING; + dst_rect.w = surface->w; + dst_rect.h = surface->h; + SDL_RenderCopy(renderer, texture, 0, &dst_rect); + + SDL_DestroyTexture(texture); + SDL_FreeSurface(surface); + } + if (sdl_ctx->toast_tex) { + // Draw toast to screen + SDL_SetRenderTarget(renderer, main_tex); + + // Get texture size + int toast_w, toast_h; + SDL_QueryTexture(sdl_ctx->toast_tex, 0, 0, &toast_w, &toast_h); + + // Calculate dst rect + SDL_Rect dst_rect; + dst_rect.w = toast_w; + dst_rect.h = toast_h; + dst_rect.y = vui->screen_height - dst_rect.h - TOAST_PADDING - TOAST_PADDING; + dst_rect.x = vui->screen_width/2 - toast_w/2; + + SDL_RenderCopy(renderer, sdl_ctx->toast_tex, 0, &dst_rect); + + // Handle expiry + struct timeval now; + gettimeofday(&now, 0); + if (now.tv_sec*1000000+now.tv_usec >= sdl_ctx->toast_expiry.tv_sec*1000000+sdl_ctx->toast_expiry.tv_usec) { + SDL_DestroyTexture(sdl_ctx->toast_tex); + sdl_ctx->toast_tex = 0; + } + } + + // Calculate our destination rectangle + int win_w, win_h; + SDL_GetWindowSize(sdl_ctx->window, &win_w, &win_h); + if (win_w == vui->screen_width && win_h == vui->screen_height) { + dst_rect->x = 0; + dst_rect->y = 0; + dst_rect->w = vui->screen_width; + dst_rect->h = vui->screen_height; + } else if (win_w * 100 / win_h > vui->screen_width * 100 / vui->screen_height) { + // Window is wider than texture, scale by height + dst_rect->h = win_h; + dst_rect->y = 0; + dst_rect->w = win_h * vui->screen_width / vui->screen_height; + dst_rect->x = win_w / 2 - dst_rect->w / 2; + } else { + // Window is taller than texture, scale by width + dst_rect->w = win_w; + dst_rect->x = 0; + dst_rect->h = win_w * vui->screen_height / vui->screen_width; + dst_rect->y = win_h / 2 - dst_rect->h / 2; + } + } + + // We don't touch the struct anymore after this + SDL_UnlockMutex(sdl_ctx->display_mutex); + + if (handle_final_blit) { + // Copy texture to window + SDL_SetRenderTarget(renderer, NULL); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, main_tex, NULL, dst_rect); - SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xF0); - const int TOAST_BORDER_RADIUS = TOAST_PADDING; - for (int y = 0; y < toast_h; y++) { - const int x_inset = calculate_x_inset(y, toast_h, TOAST_BORDER_RADIUS); - SDL_RenderDrawLine(renderer, x_inset, y, toast_w - x_inset, y); - } - - SDL_Rect dst_rect; - dst_rect.x = dst_rect.y = TOAST_PADDING; - dst_rect.w = surface->w; - dst_rect.h = surface->h; - SDL_RenderCopy(renderer, texture, 0, &dst_rect); - - SDL_DestroyTexture(texture); - SDL_FreeSurface(surface); + // Flip surfaces + SDL_RenderPresent(renderer); } - if (sdl_ctx->toast_tex) { - // Draw toast to screen - SDL_SetRenderTarget(renderer, main_tex); - - // Get texture size - int toast_w, toast_h; - SDL_QueryTexture(sdl_ctx->toast_tex, 0, 0, &toast_w, &toast_h); - - // Calculate dst rect - SDL_Rect dst_rect; - dst_rect.w = toast_w; - dst_rect.h = toast_h; - dst_rect.y = vui->screen_height - dst_rect.h - TOAST_PADDING - TOAST_PADDING; - dst_rect.x = vui->screen_width/2 - toast_w/2; - - SDL_RenderCopy(renderer, sdl_ctx->toast_tex, 0, &dst_rect); - - // Handle expiry - struct timeval now; - gettimeofday(&now, 0); - if (now.tv_sec*1000000+now.tv_usec >= sdl_ctx->toast_expiry.tv_sec*1000000+sdl_ctx->toast_expiry.tv_usec) { - SDL_DestroyTexture(sdl_ctx->toast_tex); - sdl_ctx->toast_tex = 0; - } - } - - // Calculate our destination rectangle - int win_w, win_h; - SDL_GetWindowSize(sdl_ctx->window, &win_w, &win_h); - if (win_w == vui->screen_width && win_h == vui->screen_height) { - dst_rect->x = 0; - dst_rect->y = 0; - dst_rect->w = vui->screen_width; - dst_rect->h = vui->screen_height; - } else if (win_w * 100 / win_h > vui->screen_width * 100 / vui->screen_height) { - // Window is wider than texture, scale by height - dst_rect->h = win_h; - dst_rect->y = 0; - dst_rect->w = win_h * vui->screen_width / vui->screen_height; - dst_rect->x = win_w / 2 - dst_rect->w / 2; - } else { - // Window is taller than texture, scale by width - dst_rect->w = win_w; - dst_rect->x = 0; - dst_rect->h = win_w * vui->screen_height / vui->screen_width; - dst_rect->y = win_h / 2 - dst_rect->h / 2; - } - - // Copy texture to window - SDL_SetRenderTarget(renderer, NULL); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, main_tex, NULL, dst_rect); - - // Flip surfaces - SDL_RenderPresent(renderer); return !vui->quit; } diff --git a/gui/drm.c b/gui/ui/ui_sdl_drm.c similarity index 56% rename from gui/drm.c rename to gui/ui/ui_sdl_drm.c index dd2e7dc..45633ef 100644 --- a/gui/drm.c +++ b/gui/ui/ui_sdl_drm.c @@ -1,23 +1,184 @@ -#include "drm.h" +#include "ui_sdl_drm.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include #include #include +#include +#include +#include +#include -#define ALIGN(x, a) ((x) + (a - 1)) & (~(a - 1)) +#include "platform.h" -int initialize_drm(vanilla_drm_ctx_t *ctx) +#define MAX_HANDLE_CACHE 32 +typedef struct { + int fd; + uint32_t handle; +} vanilla_drm_handle_t; + +typedef struct vanilla_drm_ctx_t { + int fd; + uint32_t crtc; + int crtc_index; + uint32_t plane_id; + int got_plane; + uint32_t fb_id; + int got_fb; + vanilla_drm_handle_t handle_cache[MAX_HANDLE_CACHE]; + size_t handle_cache_count; +} vanilla_drm_ctx_t; + +static int find_plane(const int drmfd, const int crtcidx, const uint32_t format, uint32_t *const pplane_id) { - // Open DRM - ctx->fd = drmOpen("vc4", 0); - if (ctx->fd == -1) { - return 0; - } + drmModePlaneResPtr planes; + drmModePlanePtr plane; + unsigned int i; + unsigned int j; + int ret = 0; + + planes = drmModeGetPlaneResources(drmfd); + if (!planes) { + vpilog("drmModeGetPlaneResources failed: %s\n", strerror(errno)); + return -1; + } + + for (i = 0; i < planes->count_planes; ++i) { + plane = drmModeGetPlane(drmfd, planes->planes[i]); + if (!plane) { + vpilog("drmModeGetPlane failed: %s\n", strerror(errno)); + break; + } + + if (!(plane->possible_crtcs & (1 << crtcidx))) { + drmModeFreePlane(plane); + continue; + } + + for (j = 0; j < plane->count_formats; ++j) { + if (plane->formats[j] == format) break; + } + + if (j == plane->count_formats) { + drmModeFreePlane(plane); + continue; + } + + *pplane_id = plane->plane_id; + drmModeFreePlane(plane); + break; + } + + if (i == planes->count_planes) ret = -1; + + drmModeFreePlaneResources(planes); + return ret; +} + +int vui_sdl_drm_present(vanilla_drm_ctx_t *ctx, AVFrame *frame) +{ + const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *) frame->data[0]; + const uint32_t format = desc->layers[0].format; + + if (!ctx->got_plane) { + if (find_plane(ctx->fd, ctx->crtc_index, format, &ctx->plane_id) < 0) { + vpilog("Failed to find plane for format: %x\n", format); + return 0; + } else { + ctx->got_plane = 1; + } + } + + uint32_t pitches[AV_DRM_MAX_PLANES] = {0}; + uint32_t offsets[AV_DRM_MAX_PLANES] = {0}; + uint32_t bo_handles[AV_DRM_MAX_PLANES] = {0}; + uint64_t modifiers[AV_DRM_MAX_PLANES] = {0}; + uint32_t handles[AV_DRM_MAX_PLANES]; + + for (int i = 0; i < desc->nb_objects; i++) { + int fd = desc->objects[i].fd; + + uint32_t handle = 0; + for (size_t i = 0; i < ctx->handle_cache_count; i++) { + vanilla_drm_handle_t *h = &ctx->handle_cache[i]; + if (h->fd == fd) { + handle = h->handle; + break; + } + } + + if (!handle) { + vpilog("(re)opening DRM handle because fd 0x%x was not found\n", fd); + + vanilla_drm_handle_t *h = &ctx->handle_cache[ctx->handle_cache_count]; + if (drmPrimeFDToHandle(ctx->fd, fd, &handle) != 0) { + vpilog("Failed to get handle from file descriptor: %s\n", strerror(errno)); + return 0; + } + h->handle = handle; + h->fd = fd; + ctx->handle_cache_count++; + } + + handles[i] = handle; + } + + int n = 0; + for (int i = 0; i < desc->nb_layers; i++) { + const AVDRMLayerDescriptor *layer = &desc->layers[i]; + for (int j = 0; j < layer->nb_planes; j++) { + const AVDRMPlaneDescriptor *plane = &layer->planes[j]; + const AVDRMObjectDescriptor *obj = &desc->objects[plane->object_index]; + + pitches[n] = plane->pitch; + offsets[n] = plane->offset; + modifiers[n] = obj->format_modifier; + bo_handles[n] = handles[plane->object_index]; + + n++; + } + } + + uint32_t new_fb; + if (drmModeAddFB2WithModifiers(ctx->fd, + frame->width, frame->height, desc->layers[0].format, + bo_handles, pitches, offsets, modifiers, + &new_fb, DRM_MODE_FB_MODIFIERS) != 0) { + vpilog("Failed to create framebuffer: %s\n", strerror(errno)); + return 0; + } + + if (drmModeSetPlane(ctx->fd, ctx->plane_id, ctx->crtc, new_fb, 0, + 0, 0, frame->width, frame->height, + 0, 0, frame->width << 16, frame->height << 16) != 0) { + vpilog("Failed to set plane: %s\n", strerror(errno)); + return 0; + } + + // Free old framebuffer + if (ctx->got_fb) { + drmModeRmFB(ctx->fd, ctx->fb_id); + } + ctx->fb_id = new_fb; + ctx->got_fb = 1; + + return 1; +} + +int vui_sdl_drm_initialize(vanilla_drm_ctx_t **c, SDL_Window *window) +{ + vanilla_drm_ctx_t *ctx = (vanilla_drm_ctx_t *) malloc(sizeof(vanilla_drm_ctx_t)); + *c = ctx; + + memset(ctx, 0, sizeof(vanilla_drm_ctx_t)); + + SDL_SysWMinfo wmi; + SDL_VERSION(&wmi.version); + if (!SDL_GetWindowWMInfo(window, &wmi)) return 0; + if (wmi.subsystem != SDL_SYSWM_KMSDRM) return 0; + + ctx->fd = wmi.info.kmsdrm.drm_fd; int ret = 0; @@ -49,194 +210,46 @@ int initialize_drm(vanilla_drm_ctx_t *ctx) } drmModeFreeConnector(c); } - + // Free DRM resources drmModeFreeResources(res); ctx->got_plane = 0; ctx->got_fb = 0; - ctx->got_handles = 0; - memset(ctx->handles, 0, sizeof(ctx->handles)); - - return ret; -} - -int free_drm(vanilla_drm_ctx_t *ctx) -{ - if (ctx->got_fb) { - drmModeRmFB(ctx->fd, ctx->fb_id); + for (size_t i = 0; i < MAX_HANDLE_CACHE; i++) { + vanilla_drm_handle_t *h = &ctx->handle_cache[i]; + h->fd = -1; + h->handle = 0; } - // Close DRM - drmClose(ctx->fd); -} - -static int find_plane(const int drmfd, const int crtcidx, const uint32_t format, - uint32_t *const pplane_id) -{ - drmModePlaneResPtr planes; - drmModePlanePtr plane; - unsigned int i; - unsigned int j; - int ret = 0; - - planes = drmModeGetPlaneResources(drmfd); - if (!planes) { - fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", strerror(errno)); - return -1; - } - - for (i = 0; i < planes->count_planes; ++i) { - plane = drmModeGetPlane(drmfd, planes->planes[i]); - if (!plane) { - fprintf(stderr, "drmModeGetPlane failed: %s\n", strerror(errno)); - break; - } - - if (!(plane->possible_crtcs & (1 << crtcidx))) { - drmModeFreePlane(plane); - continue; - } - - for (j = 0; j < plane->count_formats; ++j) { - if (plane->formats[j] == format) break; - } - - if (j == plane->count_formats) { - drmModeFreePlane(plane); - continue; - } - - *pplane_id = plane->plane_id; - drmModeFreePlane(plane); - break; - } - - if (i == planes->count_planes) ret = -1; - - drmModeFreePlaneResources(planes); return ret; } -extern int running; -int display_drm(vanilla_drm_ctx_t *ctx, AVFrame *frame) +int vui_sdl_drm_free(vanilla_drm_ctx_t **c) { - const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *) frame->data[0]; - const uint32_t format = desc->layers[0].format; + vanilla_drm_ctx_t *ctx = *c; + *c = NULL; - if (!ctx->got_plane) { - if (find_plane(ctx->fd, ctx->crtc_index, format, &ctx->plane_id) < 0) { - fprintf(stderr, "Failed to find plane for format: %x\n", format, DRM_FORMAT_YUV420, frame->format); - return 0; - } else { - ctx->got_plane = 1; - } + struct drm_gem_close gem_close = {0}; + for (size_t i = 0; i < ctx->handle_cache_count; i++) { + vanilla_drm_handle_t *h = &ctx->handle_cache[i]; + gem_close.handle = h->handle; + drmIoctl(ctx->fd, DRM_IOCTL_GEM_CLOSE, &gem_close); + h->handle = 0; + h->fd = -1; } - /*{ - drmVBlank vbl; - vbl.request.type = DRM_VBLANK_RELATIVE; - vbl.request.sequence = 0; - while (running && drmWaitVBlank(ctx->fd, &vbl)) { - // Not sure what this does, stole it from hello_drmprime - if (errno != EINTR) { - break; - } - } - } - - if (!running) { - return 1; - }*/ - - uint32_t pitches[AV_DRM_MAX_PLANES] = {0}; - uint32_t offsets[AV_DRM_MAX_PLANES] = {0}; - uint32_t bo_handles[AV_DRM_MAX_PLANES] = {0}; - uint64_t modifiers[AV_DRM_MAX_PLANES] = {0}; - - uint32_t new_handles[AV_DRM_MAX_PLANES] = {0}; - - for (int i = 0; i < desc->nb_objects; i++) { - if (drmPrimeFDToHandle(ctx->fd, desc->objects[i].fd, &new_handles[i]) != 0) { - fprintf(stderr, "Failed to get handle from file descriptor: %s\n", strerror(errno)); - return 0; - } - } - - int n = 0; - for (int i = 0; i < desc->nb_layers; i++) { - const AVDRMLayerDescriptor *layer = &desc->layers[i]; - for (int j = 0; j < layer->nb_planes; j++) { - const AVDRMPlaneDescriptor *plane = &layer->planes[j]; - const AVDRMObjectDescriptor *obj = &desc->objects[plane->object_index]; - - pitches[n] = plane->pitch; - offsets[n] = plane->offset; - modifiers[n] = obj->format_modifier; - bo_handles[n] = new_handles[plane->object_index]; - - n++; - } - } - - /*fprintf(stderr, "%dx%d, fmt: %x, boh=%d,%d,%d,%d, pitch=%d,%d,%d,%d," - " offset=%d,%d,%d,%d, mod=%llx,%llx,%llx,%llx\n", - frame->width, - frame->height, - desc->layers[0].format, - bo_handles[0], - bo_handles[1], - bo_handles[2], - bo_handles[3], - pitches[0], - pitches[1], - pitches[2], - pitches[3], - offsets[0], - offsets[1], - offsets[2], - offsets[3], - (long long)modifiers[0], - (long long)modifiers[1], - (long long)modifiers[2], - (long long)modifiers[3] - );*/ - - uint32_t new_fb; - if (drmModeAddFB2WithModifiers(ctx->fd, - frame->width, frame->height, desc->layers[0].format, - bo_handles, pitches, offsets, modifiers, - &new_fb, DRM_MODE_FB_MODIFIERS) != 0) { - fprintf(stderr, "Failed to create framebuffer: %s\n", strerror(errno)); - return 0; - } - - if (drmModeSetPlane(ctx->fd, ctx->plane_id, ctx->crtc, new_fb, 0, - 0, 0, frame->width, frame->height, - 0, 0, frame->width << 16, frame->height << 16) != 0) { - fprintf(stderr, "Failed to set plane: %s\n", strerror(errno)); - return 0; - } - - // Free old framebuffer if (ctx->got_fb) { drmModeRmFB(ctx->fd, ctx->fb_id); + ctx->got_fb = 0; } - ctx->fb_id = new_fb; - ctx->got_fb = 1; - if (ctx->got_handles) { - struct drm_gem_close gem_close = {0}; - for (int i = 0; i < desc->nb_objects; i++) { - if (ctx->handles[i]) { - gem_close.handle = ctx->handles[i]; - drmIoctl(ctx->fd, DRM_IOCTL_GEM_CLOSE, &gem_close); - ctx->handles[i] = 0; - } - } - } - memcpy(ctx->handles, new_handles, sizeof(ctx->handles)); - ctx->got_handles = 1; + ctx->got_plane = 0; - return 1; -} \ No newline at end of file + // Close DRM (now owned by SDL so don't do this) + // drmClose(ctx->fd); + + free(ctx); + + return 0; +} diff --git a/gui/ui/ui_sdl_drm.h b/gui/ui/ui_sdl_drm.h new file mode 100644 index 0000000..ab42005 --- /dev/null +++ b/gui/ui/ui_sdl_drm.h @@ -0,0 +1,13 @@ +#ifndef VPI_UI_DRM_H +#define VPI_UI_DRM_H + +#include +#include + +typedef struct vanilla_drm_ctx_t vanilla_drm_ctx_t; + +int vui_sdl_drm_initialize(vanilla_drm_ctx_t **ctx, SDL_Window *window); +int vui_sdl_drm_present(vanilla_drm_ctx_t *ctx, AVFrame *frame); +int vui_sdl_drm_free(vanilla_drm_ctx_t **ctx); + +#endif // VPI_UI_DRM_H diff --git a/lib/gamepad/gamepad.c b/lib/gamepad/gamepad.c index 17db87d..d0b1305 100644 --- a/lib/gamepad/gamepad.c +++ b/lib/gamepad/gamepad.c @@ -2,16 +2,6 @@ #include "gamepad.h" -#ifdef _WIN32 -#include -typedef uint32_t in_addr_t; -typedef uint16_t in_port_t; -#else -#include -#include -#include -#endif - #include #include #include @@ -31,18 +21,13 @@ typedef uint16_t in_port_t; static uint32_t SERVER_ADDRESS = 0; static const int MAX_PIPE_RETRY = 5; +char wireless_interface[128]; + #define EVENT_BUFFER_SIZE 65536 #define EVENT_BUFFER_ARENA_SIZE VANILLA_MAX_EVENT_COUNT * 2 uint8_t *EVENT_BUFFER_ARENA[EVENT_BUFFER_ARENA_SIZE] = {0}; pthread_mutex_t event_buffer_mutex = PTHREAD_MUTEX_INITIALIZER; -typedef union { - struct sockaddr_in in; -#ifndef _WIN32 - struct sockaddr_un un; -#endif -} sockaddr_u; - static inline int skterr() { #ifdef _WIN32 @@ -52,24 +37,10 @@ static inline int skterr() #endif } -in_addr_t get_real_server_address() -{ - if (SERVER_ADDRESS == VANILLA_ADDRESS_DIRECT) { - // The Wii U always places itself at this address - return inet_addr("192.168.1.10"); - } else if (SERVER_ADDRESS != VANILLA_ADDRESS_LOCAL) { - // If there's a remote pipe, we send to that - return SERVER_ADDRESS; - } - - // Must be local, in which case we don't care - return INADDR_ANY; -} - -void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port, int delete) +void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t port, int local, int delete) { #ifndef _WIN32 - if (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) { + if (local) { memset(&addr->un, 0, sizeof(addr->un)); addr->un.sun_family = AF_UNIX; snprintf(addr->un.sun_path, sizeof(addr->un.sun_path) - 1, VANILLA_PIPE_LOCAL_SOCKET, port); @@ -90,6 +61,24 @@ void create_sockaddr(sockaddr_u *addr, size_t *size, in_addr_t inaddr, uint16_t #endif } +void create_server_sockaddr(sockaddr_u *addr, size_t *size, uint16_t port, int delete) +{ + // The Wii U always places itself at this address + in_addr_t ip = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? inet_addr("192.168.1.10") : SERVER_ADDRESS; + return create_sockaddr(addr, size, ip, port, 0, delete); +} + +void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr_u *sockaddr, size_t sockaddr_size) +{ + ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) sockaddr, sockaddr_size); + if (sent == -1) { + int err = skterr(); + if (err != 111) { // 111 is connection refused, occurs if we lose connection, but we'll already know that for other reasons so we don't need to spam the console with this error + vanilla_log("Failed to send to Wii U socket: fd: %d, errno: %i", fd, skterr()); + } + } +} + void send_to_console(int fd, const void *data, size_t data_size, uint16_t port) { sockaddr_u addr; @@ -97,15 +86,9 @@ void send_to_console(int fd, const void *data, size_t data_size, uint16_t port) in_port_t console_port = port - 100; - create_sockaddr(&addr, &addr_size, get_real_server_address(), console_port, 0); + create_server_sockaddr(&addr, &addr_size, console_port, 0); - ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) &addr, addr_size); - if (sent == -1) { - int err = skterr(); - if (err != 111) { // 111 is connection refused, occurs if we lose connection, but we'll already know that for other reasons so we don't need to spam the console with this error - vanilla_log("Failed to send to Wii U socket: fd: %d, port: %d, errno: %i", fd, console_port, skterr()); - } - } + send_to_sockaddr(fd, data, data_size, &addr, addr_size); } void set_socket_rcvtimeo(int skt, uint64_t microseconds) @@ -121,14 +104,15 @@ void set_socket_rcvtimeo(int skt, uint64_t microseconds) #endif // _WIN32 } -int create_socket(int *socket_out, in_port_t port) +int create_socket(int *socket_out, in_port_t port, int pipe) { sockaddr_u addr; size_t addr_size; - create_sockaddr(&addr, &addr_size, INADDR_ANY, port, 1); + int is_pipe_and_local = (pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL); + int domain = is_pipe_and_local ? AF_UNIX : AF_INET; - int domain = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? AF_UNIX : AF_INET; + create_sockaddr(&addr, &addr_size, INADDR_ANY, port, is_pipe_and_local, 1); int skt = socket(domain, SOCK_DGRAM, 0); if (skt == -1) { @@ -148,6 +132,22 @@ int create_socket(int *socket_out, in_port_t port) set_socket_rcvtimeo(skt, 250000); + // int buf_sz = 16777216; + // setsockopt(skt, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)); + // setsockopt(skt, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz)); + +#if !defined(_WIN32) && !defined(__APPLE__) + if (!pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) { + // Bind to wireless device + setsockopt(skt, SOL_SOCKET, SO_BINDTODEVICE, wireless_interface, strlen(wireless_interface)); + } +#endif + + // int val = 1000; // microseconds to busy-poll per recv() before sleeping + // if (setsockopt(skt, SOL_SOCKET, SO_BUSY_POLL, &val, sizeof(val)) < 0) { + // perror("setsockopt SO_BUSY_POLL"); + // } + return VANILLA_SUCCESS; } @@ -156,7 +156,10 @@ int send_pipe_cc(int skt, vanilla_pipe_command_t *cmd, size_t cmd_size, int wait sockaddr_u addr; size_t addr_size; - create_sockaddr(&addr, &addr_size, get_real_server_address(), VANILLA_PIPE_CMD_SERVER_PORT, 0); + int pipe_is_local = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL); + in_addr_t pipe_addr = pipe_is_local ? INADDR_ANY : SERVER_ADDRESS; + + create_sockaddr(&addr, &addr_size, pipe_addr, VANILLA_PIPE_CMD_SERVER_PORT, pipe_is_local, 0); ssize_t read_size; uint8_t recv_cc; @@ -195,7 +198,7 @@ int connect_to_backend(int *socket, vanilla_pipe_command_t *cmd, size_t cmd_size { // Try to bind with backend int pipe_cc_skt = -1; - int ret = create_socket(&pipe_cc_skt, VANILLA_PIPE_CMD_CLIENT_PORT); + int ret = create_socket(&pipe_cc_skt, VANILLA_PIPE_CMD_CLIENT_PORT, 1); if (ret != VANILLA_SUCCESS) { return ret; } @@ -288,16 +291,14 @@ int install_polkit_internal(thread_data_t *data, int install) int ret = VANILLA_ERR_GENERIC; int pipe_cc_skt = -1; - if (SERVER_ADDRESS != VANILLA_ADDRESS_DIRECT) { - vanilla_pipe_command_t cmd; - cmd.control_code = install ? VANILLA_PIPE_CC_INSTALL_POLKIT : VANILLA_PIPE_CC_UNINSTALL_POLKIT; + vanilla_pipe_command_t cmd; + cmd.control_code = install ? VANILLA_PIPE_CC_INSTALL_POLKIT : VANILLA_PIPE_CC_UNINSTALL_POLKIT; - // Connect to backend pipe - ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection)); + // Connect to backend pipe + ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection)); - // No interrupt is required here because VANILLA_PIPE_CC_INSTALL_POLKIT - // does not start a thread in the pipe - } + // No interrupt is required here because VANILLA_PIPE_CC_INSTALL_POLKIT + // does not start a thread in the pipe if (pipe_cc_skt != -1) { // Disconnect from pipe if necessary @@ -320,47 +321,45 @@ void connect_as_gamepad_internal(thread_data_t *data) int ret = VANILLA_SUCCESS; // Open all required sockets - if (create_socket(&info.socket_vid, PORT_VID) != VANILLA_SUCCESS) goto exit_pipe; - if (create_socket(&info.socket_msg, PORT_MSG) != VANILLA_SUCCESS) goto exit_vid; - if (create_socket(&info.socket_hid, PORT_HID) != VANILLA_SUCCESS) goto exit_msg; - if (create_socket(&info.socket_aud, PORT_AUD) != VANILLA_SUCCESS) goto exit_hid; - if (create_socket(&info.socket_cmd, PORT_CMD) != VANILLA_SUCCESS) goto exit_aud; + if (create_socket(&info.socket_vid, PORT_VID, 0) != VANILLA_SUCCESS) goto exit_pipe; + if (create_socket(&info.socket_msg, PORT_MSG, 0) != VANILLA_SUCCESS) goto exit_vid; + if (create_socket(&info.socket_hid, PORT_HID, 0) != VANILLA_SUCCESS) goto exit_msg; + if (create_socket(&info.socket_aud, PORT_AUD, 0) != VANILLA_SUCCESS) goto exit_hid; + if (create_socket(&info.socket_cmd, PORT_CMD, 0) != VANILLA_SUCCESS) goto exit_aud; int pipe_cc_skt = -1; - if (SERVER_ADDRESS != VANILLA_ADDRESS_DIRECT) { - vanilla_pipe_command_t cmd; - cmd.control_code = VANILLA_PIPE_CC_CONNECT; + vanilla_pipe_command_t cmd; + cmd.control_code = VANILLA_PIPE_CC_CONNECT; - cmd.connection.bssid = data->bssid; - cmd.connection.psk = data->psk; + cmd.connection.bssid = data->bssid; + cmd.connection.psk = data->psk; - // Connect to backend pipe - ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection)); - if (ret == VANILLA_SUCCESS) { - // Wait for backend to be available - vanilla_pipe_command_t connected_state; - ret = VANILLA_ERR_NO_CONNECTION; - while (!is_interrupted()) { - ssize_t read_size = recv(pipe_cc_skt, (char *) &connected_state, sizeof(connected_state), 0); - if (read_size < 0) { - int r = skterr(); + // Connect to backend pipe + ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection)); + if (ret == VANILLA_SUCCESS) { + // Wait for backend to be available + vanilla_pipe_command_t connected_state; + ret = VANILLA_ERR_NO_CONNECTION; + while (!is_interrupted()) { + ssize_t read_size = recv(pipe_cc_skt, (char *) &connected_state, sizeof(connected_state), 0); + if (read_size < 0) { + int r = skterr(); #ifdef _WIN32 - if (r != WSAETIMEDOUT) { + if (r != WSAETIMEDOUT) { #else - if (r != EAGAIN) { + if (r != EAGAIN) { #endif - vanilla_log("FAILED TO GET CONNECTED STATE: %i", r); - ret = VANILLA_ERR_PIPE_UNRESPONSIVE; - break; - } - } else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) { - ret = VANILLA_SUCCESS; - sleep(1); + vanilla_log("FAILED TO GET CONNECTED STATE: %i", r); + ret = VANILLA_ERR_PIPE_UNRESPONSIVE; break; } - - vanilla_log("STILL WAITING FOR CONNECTED STATE"); + } else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) { + ret = VANILLA_SUCCESS; + sleep(1); + break; } + + vanilla_log("STILL WAITING FOR CONNECTED STATE"); } } @@ -439,47 +438,67 @@ exit: wait_for_interrupt(); } -int push_event(event_loop_t *loop, int type, const void *data, size_t size) +int acquire_event(event_loop_t *loop, vanilla_event_t **event) { - int ret = VANILLA_SUCCESS; + int ret = VANILLA_SUCCESS; pthread_mutex_lock(&loop->mutex); - if (size <= EVENT_BUFFER_SIZE) { - // Prevent rollover by skipping oldest event if necessary - if (loop->new_index == loop->used_index + VANILLA_MAX_EVENT_COUNT) { - vanilla_free_event(&loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT]); - vanilla_log("SKIPPED EVENT TO PREVENT ROLLOVER (%lu > %lu + %lu)", loop->new_index, loop->used_index, VANILLA_MAX_EVENT_COUNT); - loop->used_index++; - } + // Prevent rollover by skipping oldest event if necessary + if (loop->new_index == loop->used_index + VANILLA_MAX_EVENT_COUNT) { + vanilla_free_event(&loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT]); + vanilla_log("SKIPPED EVENT TO PREVENT ROLLOVER (%lu > %lu + %lu)", loop->new_index, loop->used_index, VANILLA_MAX_EVENT_COUNT); + loop->used_index++; + } - vanilla_event_t *ev = &loop->events[loop->new_index % VANILLA_MAX_EVENT_COUNT]; + vanilla_event_t *ev = &loop->events[loop->new_index % VANILLA_MAX_EVENT_COUNT]; - assert(!ev->data); + assert(!ev->data); - ev->data = get_event_buffer(); - if (!ev->data) { - vanilla_log("OUT OF MEMORY FOR NEW EVENTS"); - ret = VANILLA_ERR_OUT_OF_MEMORY; - goto exit; - } + ev->data = get_event_buffer(); + // if (!ev->data) { + // vanilla_log("OUT OF MEMORY FOR NEW EVENTS"); + // ret = VANILLA_ERR_OUT_OF_MEMORY; + // } - ev->type = type; - memcpy(ev->data, data, size); - ev->size = size; + *event = ev; - loop->new_index++; + return ret; +} - pthread_cond_broadcast(&loop->waitcond); - } else { - vanilla_log("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.", size, EVENT_BUFFER_SIZE); - ret = VANILLA_ERR_INVALID_ARGUMENT; - } +int release_event(event_loop_t *loop) +{ + loop->new_index++; + + pthread_cond_broadcast(&loop->waitcond); + + // } else { + // vanilla_log("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.", size, EVENT_BUFFER_SIZE); + // ret = VANILLA_ERR_INVALID_ARGUMENT; + // } exit: pthread_mutex_unlock(&loop->mutex); - return ret; + return VANILLA_SUCCESS; +} + +int push_event(event_loop_t *loop, int type, const void *data, size_t size) +{ + vanilla_event_t *ev; + + int ret = acquire_event(loop, &ev); + if (ret != VANILLA_SUCCESS) { + return ret; + } + + ev->type = type; + memcpy(ev->data, data, size); + ev->size = size; + + ret = release_event(loop); + + return ret; } int get_event(event_loop_t *loop, vanilla_event_t *event, int wait) diff --git a/lib/gamepad/gamepad.h b/lib/gamepad/gamepad.h index be68dc4..5d03d0f 100644 --- a/lib/gamepad/gamepad.h +++ b/lib/gamepad/gamepad.h @@ -1,16 +1,28 @@ #ifndef VANILLA_GAMEPAD_H #define VANILLA_GAMEPAD_H -#include "vanilla.h" - #include #include +#ifdef _WIN32 +#include +typedef uint32_t in_addr_t; +typedef uint16_t in_port_t; +#else +#include +#include +#include +#endif + +#include "vanilla.h" + #include "../pipe/ports.h" struct wpa_ctrl; -#define VANILLA_MAX_EVENT_COUNT 20 +extern char wireless_interface[]; + +#define VANILLA_MAX_EVENT_COUNT 100 typedef struct { vanilla_event_t events[VANILLA_MAX_EVENT_COUNT]; @@ -43,12 +55,23 @@ typedef struct thread_data_t vanilla_psk_t psk; } thread_data_t; +typedef union { + struct sockaddr_in in; +#ifndef _WIN32 + struct sockaddr_un un; +#endif +} sockaddr_u; + void sync_internal(thread_data_t *data); void connect_as_gamepad_internal(thread_data_t *data); int install_polkit_internal(thread_data_t *data, int install); +void create_server_sockaddr(sockaddr_u *addr, size_t *size, uint16_t port, int delete); +void send_to_sockaddr(int fd, const void *data, size_t data_size, const sockaddr_u *sockaddr, size_t sockaddr_size); void send_to_console(int fd, const void *data, size_t data_size, uint16_t port); int push_event(event_loop_t *loop, int type, const void *data, size_t size); int get_event(event_loop_t *loop, vanilla_event_t *event, int wait); +int acquire_event(event_loop_t *loop, vanilla_event_t **event); +int release_event(event_loop_t *loop); void init_event_buffer_arena(); void free_event_buffer_arena(); diff --git a/lib/gamepad/input.c b/lib/gamepad/input.c index db6fed7..77ecf49 100644 --- a/lib/gamepad/input.c +++ b/lib/gamepad/input.c @@ -112,7 +112,7 @@ uint16_t resolve_axis_value(float axis, float neg, float pos, int flip) if (flip) { val = -val; } - + return ((int) (val * 1024)) + 2048; } @@ -160,7 +160,7 @@ void set_battery_status(int status) pthread_mutex_unlock(&button_mtx); } -void send_input(int socket_hid) +void send_input(int socket_hid, const sockaddr_u *addr, size_t addr_size) { InputPacket ip; memset(&ip, 0, sizeof(ip)); @@ -213,7 +213,7 @@ void send_input(int socket_hid) ip.buttons = htons(button_mask); button_mask = 0; - + if (current_buttons[VANILLA_BTN_L3]) button_mask |= 0x80; if (current_buttons[VANILLA_BTN_R3]) button_mask |= 0x40; if (current_buttons[VANILLA_BTN_TV]) button_mask |= 0x20; @@ -242,7 +242,7 @@ void send_input(int socket_hid) ip.fw_version_neg = 215; - send_to_console(socket_hid, &ip, sizeof(ip), PORT_HID); + send_to_sockaddr(socket_hid, &ip, sizeof(ip), addr, addr_size); } void *listen_input(void *x) @@ -251,14 +251,18 @@ void *listen_input(void *x) pthread_mutex_init(&button_mtx, NULL); + sockaddr_u addr; + size_t addr_size; + create_server_sockaddr(&addr, &addr_size, PORT_HID - 100, 0); + do { - send_input(info->socket_hid); - usleep(5 * 1000); // Produces 200Hz input, probably no need to go higher for the Wii U (supposedly the real gamepad is 180Hz) + send_input(info->socket_hid, &addr, addr_size); + usleep(5555); // Roughly 180Hz, same as the original gamepad } while (!is_interrupted()); pthread_mutex_destroy(&button_mtx); pthread_exit(NULL); - + return NULL; -} \ No newline at end of file +} diff --git a/lib/gamepad/video.c b/lib/gamepad/video.c index 53ad207..129f0e1 100644 --- a/lib/gamepad/video.c +++ b/lib/gamepad/video.c @@ -20,6 +20,12 @@ #include "vanilla.h" #include "util.h" +#ifdef __APPLE__ +// macOS has no htobe32 +#include +#define htobe32(x) OSSwapHostToBigInt32(x) +#endif // __APPLE__ + typedef struct { unsigned magic : 4; @@ -36,15 +42,15 @@ typedef struct uint8_t payload[2048]; } VideoPacket; -static pthread_mutex_t video_mutex; +static pthread_mutex_t idr_mutex; static int idr_is_queued = 0; -#define VIDEO_PACKET_CACHE_MAX 1024 -static VideoPacket video_packet_cache[VIDEO_PACKET_CACHE_MAX]; -static size_t video_packet_cache_index = 0; -static uint8_t video_packet[100000]; - -static size_t generated_sps_params_size = 0; +#define VIDEO_PACKET_QUEUE_MAX 1024 +static VideoPacket video_packet_queue[VIDEO_PACKET_QUEUE_MAX]; +static size_t video_packet_min = 0; +static size_t video_packet_max = 0; +static pthread_mutex_t video_packet_mutex; +static pthread_cond_t video_packet_cond; static const uint8_t VANILLA_PPS_PARAMS[] = { 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x06, 0x0c, 0xe8 @@ -52,9 +58,9 @@ static const uint8_t VANILLA_PPS_PARAMS[] = { void request_idr() { - pthread_mutex_lock(&video_mutex); + pthread_mutex_lock(&idr_mutex); idr_is_queued = 1; - pthread_mutex_unlock(&video_mutex); + pthread_mutex_unlock(&idr_mutex); } void send_idr_request_to_console(int socket_msg) @@ -65,7 +71,80 @@ void send_idr_request_to_console(int socket_msg) send_to_console(socket_msg, idr_request, sizeof(idr_request), PORT_MSG); } -void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp, size_t size, int socket_msg) +static uint8_t *write_slice_nal(int is_idr, int frame_decode_num, uint8_t *out) +{ + uint32_t slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((frame_decode_num & 0xff) << 13)); + *((uint32_t *)out) = htobe32(slice_header); + return out + sizeof(uint32_t); + + static uint8_t idr_num = 0; + + uint8_t slice_nal[200]; + size_t bit_index = 0; + + // forbidden_zero_bit + write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); + + // nal_ref_idc + write_bits(slice_nal, sizeof(slice_nal), &bit_index, is_idr ? 3 : 1, 2); + + // nal_unit_type + write_bits(slice_nal, sizeof(slice_nal), &bit_index, is_idr ? 5 : 1, 5); + + // first_mb_in_slice + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); + + // slice_type + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, is_idr ? 2 : 0); + + // pic_parameter_set_id + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); + + // frame_num + const int fn_bits = 8; + write_bits(slice_nal, sizeof(slice_nal), &bit_index, frame_decode_num, fn_bits); + + if (is_idr) { + // idr_pic_id + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, idr_num); + idr_num++; + + // dec_ref_pic_marking() for IDR: + write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); // no_output_of_prior_pics_flag + write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); // long_term_reference_flag + } else { + // num_ref_idx_active_override_flag + write_bits(slice_nal, sizeof(slice_nal), &bit_index, 1, 1); + + // num_ref_idx_l0_active_minus1 + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); + + // ref_pic_list_modification_flag_l0 = 0 (no modifications) + write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); + + // dec_ref_pic_marking() for non-IDR: + write_bits(slice_nal, sizeof(slice_nal), &bit_index, 0, 1); // adaptive_ref_pic_marking_mode_flag = 0 + + // CABAC: entropy_coding_mode_flag==1 and P-slice → must write cabac_init_idc + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // cabac_init_idc = 0 (good default) + } + + // slice_qp_delta + write_signed_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); + + // deblocking_filter_control_present_flag == 1 → must write + write_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // disable_deblocking_filter_idc = 0 + write_signed_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // slice_alpha_c0_offset_div2 + write_signed_exp_golomb(slice_nal, sizeof(slice_nal), &bit_index, 0); // slice_beta_offset_div2 + + size_t bytes = (bit_index + 7) >> 3; + memcpy(out, slice_nal, bytes); + out += bytes; + + return out; +} + +void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp) { // // === IMPORTANT NOTE! === @@ -88,123 +167,211 @@ void handle_video_packet(gamepad_context_t *ctx, VideoPacket *vp, size_t size, i int is_idr = 0; for (int i = 0; i < sizeof(vp->extended_header); i++) { if (vp->extended_header[i] == 0x80) { - // vanilla_log("GOT IDR"); - is_idr = 1; - - // pthread_mutex_lock(&video_mutex); - // if (idr_is_queued) { - // idr_is_queued = 0; - // } - // pthread_mutex_unlock(&video_mutex); break; } } - // Check seq ID - static int seq_id_expected = -1; - int seq_matched = 1; - if (seq_id_expected == -1) { - seq_id_expected = vp->seq_id; - } else if (seq_id_expected != vp->seq_id) { - seq_matched = 0; - } - seq_id_expected = (vp->seq_id + 1) & 0x3ff; // 10 bit number - static int is_streaming = 0; - if (!seq_matched) { - is_streaming = 0; - } - // Check if this is the beginning of the packet - static VideoPacket *video_segments[1024]; + static VideoPacket *video_segments[VIDEO_PACKET_QUEUE_MAX]; static int video_packet_seq = -1; static int video_packet_seq_end = -1; + static int video_complete_frame = 0; + + static uint8_t frame_decode_num = 0; if (vp->frame_begin) { video_packet_seq = vp->seq_id; video_packet_seq_end = -1; - if (!is_streaming) { - if (is_idr) { - is_streaming = 1; - } else { - send_idr_request_to_console(socket_msg); - return; - } + memset(video_segments, 0, sizeof(video_segments)); + + frame_decode_num++; + + if (!video_complete_frame && !is_idr) { + send_idr_request_to_console(ctx->socket_msg); + return; } + + video_complete_frame = 0; } - pthread_mutex_lock(&video_mutex); + pthread_mutex_lock(&idr_mutex); if (idr_is_queued) { - send_idr_request_to_console(socket_msg); + send_idr_request_to_console(ctx->socket_msg); idr_is_queued = 0; } - pthread_mutex_unlock(&video_mutex); + pthread_mutex_unlock(&idr_mutex); + // vanilla_log("set seq_id %i = %p", vp->seq_id, vp); video_segments[vp->seq_id] = vp; - if (vp->frame_end) { + if (vp->frame_end) video_packet_seq_end = vp->seq_id; - } - if (video_packet_seq_end != -1) { - // int complete_frame = 1; - if (is_streaming) { - // Encapsulate packet data into NAL unit - static int frame_decode_num = 0; - uint8_t *nals_current = video_packet + generated_sps_params_size; - - int slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((frame_decode_num & 0xff) << 13)); - - // begin slice nalu - uint8_t slice[] = {0x00, 0x00, 0x00, 0x01, - (uint8_t) ((slice_header >> 24) & 0xff), - (uint8_t) ((slice_header >> 16) & 0xff), - (uint8_t) ((slice_header >> 8) & 0xff), - (uint8_t) (slice_header & 0xff) - }; - memcpy(nals_current, slice, sizeof(slice)); - nals_current += sizeof(slice); - - // Get pointer to first packet's payload + if (video_packet_seq != -1 && video_packet_seq_end != -1) { + int complete_frame = 1; + { int current_index = video_packet_seq; - uint8_t *from = video_segments[current_index]->payload; - - memcpy(nals_current, from, 2); - nals_current += 2; - - // Escape codes - int byte = 2; while (1) { - uint8_t *data = video_segments[current_index]->payload; - size_t pkt_size = video_segments[current_index]->payload_size; - while (byte < pkt_size) { - if (data[byte] <= 3 && *(nals_current - 2) == 0 && *(nals_current - 1) == 0) { - *nals_current = 3; - nals_current++; - } - *nals_current = data[byte]; - nals_current++; - byte++; + if (!video_segments[current_index]) { + complete_frame = 0; + vanilla_log("damn, incomplete frame (missing %i)", current_index); + break; } if (current_index == video_packet_seq_end) { break; } - byte = 0; - current_index = (current_index + 1) % VIDEO_PACKET_CACHE_MAX; + current_index = (current_index + 1) % VIDEO_PACKET_QUEUE_MAX; } + } - // Skip IDR parameters if not an IDR frame - uint8_t *nals = (is_idr) ? video_packet : (video_packet + generated_sps_params_size); - push_event(ctx->event_loop, VANILLA_EVENT_VIDEO, nals, (nals_current - nals)); + if (complete_frame) { + video_complete_frame = 1; + + // Encapsulate packet data into NAL unit + vanilla_event_t *event; + int ret = acquire_event(ctx->event_loop, &event); + + event->type = VANILLA_EVENT_VIDEO; + + uint8_t *video_packet = event->data; + + const int OLD_CODE = 1; + if (OLD_CODE) { + uint8_t *nals_current = video_packet; + static const char *frame_start_word = "\x00\x00\x00\x01"; + + if (is_idr) { + uint8_t sps[200], pps[200]; + size_t sps_size = generate_sps_params(sps, sizeof(sps)); + size_t pps_size = generate_pps_params(pps, sizeof(pps)); + + memcpy(nals_current, frame_start_word, 4); + nals_current += 4; + + memcpy(nals_current, sps, sps_size); + nals_current += sps_size; + + memcpy(nals_current, pps, pps_size); + nals_current += pps_size; + } + + memcpy(nals_current, frame_start_word, 4); + nals_current += 4; + + nals_current = write_slice_nal(is_idr, frame_decode_num, nals_current); + + // Get pointer to first packet's payload + int current_index = video_packet_seq; + uint8_t *from = video_segments[current_index]->payload; + + memcpy(nals_current, from, 2); + nals_current += 2; + + // Escape codes + int byte = 2; + while (1) { + uint8_t *data = video_segments[current_index]->payload; + size_t pkt_size = video_segments[current_index]->payload_size; + while (byte < pkt_size) { + if (data[byte] <= 3 && *(nals_current - 2) == 0 && *(nals_current - 1) == 0) { + *nals_current = 3; + nals_current++; + } + *nals_current = data[byte]; + nals_current++; + byte++; + } + + if (current_index == video_packet_seq_end) { + break; + } + + byte = 0; + current_index = (current_index + 1) % VIDEO_PACKET_QUEUE_MAX; + } + + event->size = (nals_current - video_packet); + } else { + uint8_t *out = video_packet; + out += 4; // Make room for size + + out = write_slice_nal(is_idr, frame_decode_num, out); + + // vanilla_log_no_newline("generated slice NAL header:"); + // for (size_t i = 0; i < bytes; i++) { + // vanilla_log_no_newline(" %02x", slice_nal[i] & 0xFF); + // } + // vanilla_log_no_newline("\n"); + + // uint32_t slice_header = is_idr ? 0x25b804ff : (0x21e003ff | ((0 & 0xff) << 13)); + // *(uint32_t*)out = htobe32(slice_header); + // out += sizeof(uint32_t); + + int i = video_packet_seq; + + size_t offset = 0; + while (1) { + uint8_t *data = video_segments[i]->payload; + size_t sz = video_segments[i]->payload_size - offset; + + memcpy(out, data + offset, sz); + out += sz; + + offset = 0; + + if (i == video_packet_seq_end) { + break; + } + + i = (i + 1) % VIDEO_PACKET_QUEUE_MAX; + } + + // Go back and write size + *((uint32_t *)video_packet) = htobe32(out - (video_packet + 4)); + + event->size = (out - video_packet); + } + + // vanilla_log_no_newline("a few bytes from the packet:"); + // for (size_t i = 0; i < 64; i++) { + // // vanilla_log_no_newline(" %02x", video_segments[video_packet_seq]->payload[i] & 0xFF); + // vanilla_log_no_newline(" %02x", video_packet[i] & 0xFF); + // } + // vanilla_log_no_newline("\n"); + + release_event(ctx->event_loop); } else { // We didn't receive the complete frame so we'll skip it here } } } +void *consume_video_packets(void *data) +{ + gamepad_context_t *ctx = (gamepad_context_t *) data; + + pthread_mutex_lock(&video_packet_mutex); + + while (!is_interrupted()) { + while (video_packet_min < video_packet_max) { + VideoPacket *vp = &video_packet_queue[video_packet_min % VIDEO_PACKET_QUEUE_MAX]; + + pthread_mutex_unlock(&video_packet_mutex); + handle_video_packet(ctx, vp); + pthread_mutex_lock(&video_packet_mutex); + + video_packet_min++; + } + pthread_cond_wait(&video_packet_cond, &video_packet_mutex); + } + + pthread_mutex_unlock(&video_packet_mutex); +} + size_t generate_h264_header(void *data, size_t size) { uint8_t *output = (uint8_t *) data; @@ -228,36 +395,33 @@ void *listen_video(void *x) gamepad_context_t *info = (gamepad_context_t *) x; ssize_t size; - // Set up IDR nals on video_packet - uint8_t *nals_current = video_packet; + pthread_mutex_init(&idr_mutex, NULL); + pthread_mutex_init(&video_packet_mutex, NULL); + pthread_cond_init(&video_packet_cond, NULL); - static const char *frame_start_word = "\x00\x00\x00\x01"; - memcpy(nals_current, frame_start_word, 4); - nals_current += 4; - generated_sps_params_size += 4; - - size_t generated_sps = generate_sps_params(nals_current, sizeof(video_packet)); - nals_current += generated_sps; - generated_sps_params_size += generated_sps; - - memcpy(nals_current, VANILLA_PPS_PARAMS, sizeof(VANILLA_PPS_PARAMS)); - nals_current += sizeof(VANILLA_PPS_PARAMS); - generated_sps_params_size += sizeof(VANILLA_PPS_PARAMS); - - pthread_mutex_init(&video_mutex, NULL); + pthread_t video_consumer_thread; + pthread_create(&video_consumer_thread, 0, consume_video_packets, info); do { - VideoPacket *vp = &video_packet_cache[video_packet_cache_index % VIDEO_PACKET_CACHE_MAX]; - video_packet_cache_index++; - void *data = vp; - - size = recv(info->socket_vid, data, sizeof(VideoPacket), 0); + VideoPacket *vp = &video_packet_queue[video_packet_max % VIDEO_PACKET_QUEUE_MAX]; + size = recv(info->socket_vid, (void *) vp, sizeof(VideoPacket), 0); if (size > 0) { - handle_video_packet(info, vp, size, info->socket_msg); + pthread_mutex_lock(&video_packet_mutex); + video_packet_max++; + if (video_packet_max == video_packet_min + VIDEO_PACKET_QUEUE_MAX) { + vanilla_log("WARNING: ROLLED OVER VIDEO PACKET QUEUE"); + } + pthread_cond_broadcast(&video_packet_cond); + pthread_mutex_unlock(&video_packet_mutex); + // (info, vp, size, info->socket_msg); } } while (!is_interrupted()); - pthread_mutex_destroy(&video_mutex); + pthread_join(video_consumer_thread, 0); + + pthread_cond_destroy(&video_packet_cond); + pthread_mutex_destroy(&video_packet_mutex); + pthread_mutex_destroy(&idr_mutex); pthread_exit(NULL); @@ -269,7 +433,7 @@ void write_bits(void *data, size_t buffer_size, size_t *bit_index, uint8_t value const size_t size_of_byte = 8; assert(bit_width <= size_of_byte); - + // Calculate offsets size_t offset = *bit_index; uint8_t *bytes = (uint8_t *) data; @@ -325,7 +489,7 @@ void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_ int bit_width = ((sizeof(value) * size_of_byte) - leading_zeros); int exp_golomb_leading_zeros = bit_width - 1; - + for (int i = 0; i < exp_golomb_leading_zeros; i += size_of_byte) { write_bits(data, buffer_size, bit_index, 0, MIN(size_of_byte, exp_golomb_leading_zeros - i)); } @@ -348,8 +512,31 @@ void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_ } } +// void write_signed_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, int64_t value) +// { +// uint64_t codeNum = (value <= 0) ? (uint64_t)(-value*2) : (uint64_t)(value*2-1); +// write_exp_golomb(data, buffer_size, bit_index, codeNum-1); +// } + +void write_signed_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, int64_t value) +{ + uint64_t codeNum; + + if (value > 0) { + codeNum = ((uint64_t)value << 1) - 1; // 2*v - 1 + } else { + // value <= 0 + codeNum = (uint64_t)(-value) << 1; // -2*v + } + + write_exp_golomb(data, buffer_size, bit_index, codeNum); +} + size_t generate_sps_params(void *data, size_t size) { + // memcpy(data, VANILLA_SPS_PARAMS, MIN(sizeof(VANILLA_SPS_PARAMS), size)); + // return sizeof(VANILLA_SPS_PARAMS); + // // Reference: https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set // @@ -409,7 +596,7 @@ size_t generate_sps_params(void *data, size_t size) // seq_scaling_matrix_present_flag write_bits(data, size, &bit_index, 0, 1); - + // log2_max_frame_num_minus4 write_exp_golomb(data, size, &bit_index, 4); @@ -420,7 +607,7 @@ size_t generate_sps_params(void *data, size_t size) write_exp_golomb(data, size, &bit_index, 1); // gaps_in_frame_num_value_allowed_flag - write_bits(data, size, &bit_index, 0, 1); + write_bits(data, size, &bit_index, 1, 1); // pic_width_in_mbs_minus1 write_exp_golomb(data, size, &bit_index, 53); @@ -508,7 +695,7 @@ size_t generate_sps_params(void *data, size_t size) // RBSP trailing stop bit write_bits(data, size, &bit_index, 1, 1); - + // Alignment const int align = 8; if (bit_index % align != 0) { @@ -522,4 +709,24 @@ size_t generate_pps_params(void *data, size_t size) { memcpy(data, VANILLA_PPS_PARAMS, MIN(sizeof(VANILLA_PPS_PARAMS), size)); return sizeof(VANILLA_PPS_PARAMS); + + // size_t bit_index = 0; + + // // forbidden_zero_bit + // write_bits(data, size, &bit_index, 0, 1); + + // // nal_ref_idc = 3 (important) + // write_bits(data, size, &bit_index, 3, 2); + + // // nal_unit_type = 8 (PPS) + // write_bits(data, size, &bit_index, 7, 5); + + // // pic_parameter_set_id + // write_exp_golomb(data, size, &bit_index, 0); + + // // seq_parameter_set_id + // write_exp_golomb(data, size, &bit_index, 0); + + // // entropy_coding_mode_flag + // write_exp_golomb(data, size, &bit_index, 0); } diff --git a/lib/gamepad/video.h b/lib/gamepad/video.h index 4ae5b35..e14f79f 100644 --- a/lib/gamepad/video.h +++ b/lib/gamepad/video.h @@ -11,5 +11,6 @@ size_t generate_pps_params(void *data, size_t size); size_t generate_h264_header(void *data, size_t size); void write_bits(void *data, size_t size, size_t *bit_index, uint8_t value, size_t bit_width); void write_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, uint64_t value); +void write_signed_exp_golomb(void *data, size_t buffer_size, size_t *bit_index, int64_t value); -#endif // GAMEPAD_VIDEO_H \ No newline at end of file +#endif // GAMEPAD_VIDEO_H diff --git a/lib/vanilla.c b/lib/vanilla.c index 7a7d654..63b91cd 100644 --- a/lib/vanilla.c +++ b/lib/vanilla.c @@ -255,3 +255,8 @@ void vanilla_send_audio(const void *data, size_t size) { send_audio_packet(data, size); } + +void vanilla_set_wireless_interface(const char *intf) +{ + strcpy(wireless_interface, intf); +} diff --git a/lib/vanilla.h b/lib/vanilla.h index 4cf567b..769329f 100644 --- a/lib/vanilla.h +++ b/lib/vanilla.h @@ -23,7 +23,6 @@ extern "C" { #define VANILLA_ERR_DISCONNECTED -11 static const uint32_t VANILLA_ADDRESS_LOCAL = 0xFFFFFFFF; -static const uint32_t VANILLA_ADDRESS_DIRECT = 0x0; enum VanillaGamepadButtons { @@ -125,6 +124,8 @@ typedef struct { int vanilla_start(uint32_t server_address, vanilla_bssid_t bssid, vanilla_psk_t psk); int vanilla_sync(uint16_t code, uint32_t server_address); +void vanilla_set_wireless_interface(const char *intf); + int vanilla_install_polkit(uint32_t server_address); int vanilla_uninstall_polkit(uint32_t server_address); @@ -134,8 +135,6 @@ int vanilla_free_event(vanilla_event_t *event); /** * Attempt to stop the current action - * - * This can be called from another thread to safely exit a blocking call to vanilla_start(). */ void vanilla_stop(); @@ -195,6 +194,8 @@ void vanilla_set_battery_status(int battery_status); /** * Retrieve SPS/PPS parameters for H.264 packeting */ +size_t vanilla_generate_sps_params(void *data, size_t data_size); +size_t vanilla_generate_pps_params(void *data, size_t data_size); size_t vanilla_generate_h264_header(void *data, size_t size); /** diff --git a/pipe/linux/wpa.c b/pipe/linux/wpa.c index fb71dea..08c4211 100644 --- a/pipe/linux/wpa.c +++ b/pipe/linux/wpa.c @@ -666,28 +666,31 @@ close: void create_all_relays(struct sync_args *args) { pthread_t vid_thread, aud_thread, msg_thread, cmd_thread, hid_thread; - struct relay_info vid_info, aud_info, msg_info, cmd_info, hid_info; - // Set common info for all - vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = args->wireless_interface; - vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = args->local; - vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = args->client; - vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = args->client_size; + if (!args->local) { + struct relay_info vid_info, aud_info, msg_info, cmd_info, hid_info; - vid_info.port = PORT_VID; - aud_info.port = PORT_AUD; - msg_info.port = PORT_MSG; - cmd_info.port = PORT_CMD; - hid_info.port = PORT_HID; + // Set common info for all + vid_info.wireless_interface = aud_info.wireless_interface = msg_info.wireless_interface = cmd_info.wireless_interface = hid_info.wireless_interface = args->wireless_interface; + vid_info.local = aud_info.local = msg_info.local = cmd_info.local = hid_info.local = args->local; + vid_info.client = aud_info.client = msg_info.client = cmd_info.client = hid_info.client = args->client; + vid_info.client_size = aud_info.client_size = msg_info.client_size = cmd_info.client_size = hid_info.client_size = args->client_size; - // Enable relays - relay_running = 1; + vid_info.port = PORT_VID; + aud_info.port = PORT_AUD; + msg_info.port = PORT_MSG; + cmd_info.port = PORT_CMD; + hid_info.port = PORT_HID; - pthread_create(&vid_thread, NULL, open_relay, &vid_info); - pthread_create(&aud_thread, NULL, open_relay, &aud_info); - pthread_create(&msg_thread, NULL, open_relay, &msg_info); - pthread_create(&cmd_thread, NULL, open_relay, &cmd_info); - pthread_create(&hid_thread, NULL, open_relay, &hid_info); + // Enable relays + relay_running = 1; + + pthread_create(&vid_thread, NULL, open_relay, &vid_info); + pthread_create(&aud_thread, NULL, open_relay, &aud_info); + pthread_create(&msg_thread, NULL, open_relay, &msg_info); + pthread_create(&cmd_thread, NULL, open_relay, &cmd_info); + pthread_create(&hid_thread, NULL, open_relay, &hid_info); + } // Notify client that we are connected vanilla_pipe_command_t cmd; @@ -708,16 +711,21 @@ void create_all_relays(struct sync_args *args) break; } } + + // wpa_ctrl_recv is non-blocking, so reduce thrashing by sleeping here + sleep(1); } - // Stop all relays - interrupt_relays(); + if (!args->local) { + // Stop all relays + interrupt_relays(); - pthread_join(vid_thread, NULL); - pthread_join(aud_thread, NULL); - pthread_join(msg_thread, NULL); - pthread_join(cmd_thread, NULL); - pthread_join(hid_thread, NULL); + pthread_join(vid_thread, NULL); + pthread_join(aud_thread, NULL); + pthread_join(msg_thread, NULL); + pthread_join(cmd_thread, NULL); + pthread_join(hid_thread, NULL); + } } void *thread_handler(void *data)