From 4617e2c45aee13d1558d9d1aa7f964f550705d63 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 23 Jul 2021 17:33:37 +0200 Subject: Add option to record a display or all displays. This requires nvfbc --- README.md | 3 +- TODO | 7 + include/NvFBC.h | 2006 ++++++++++++++++++++++++++++++++++++++++++++++ include/NvFBCLibrary.hpp | 245 ++++++ src/main.cpp | 509 +++++++----- 5 files changed, 2582 insertions(+), 188 deletions(-) create mode 100644 TODO create mode 100644 include/NvFBC.h create mode 100644 include/NvFBCLibrary.hpp diff --git a/README.md b/README.md index 55fb67f..135367d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ When recording a 4k game, fps drops from 30 to 7 when using OBS Studio, however the fps remains at 30. # Installation -gpu screen recorder can be built using [sibs](https://git.dec05eba.com/sibs) or if you are running Arch Linux, then you can find it on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`). +gpu screen recorder can be built using [sibs](https://git.dec05eba.com/sibs) or if you are running Arch Linux, then you can find it on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`).\ +Recording displays requires a gpu with NvFBC support. Normally only tesla and quadro gpus support this, but by using https://github.com/keylase/nvidia-patch you can do this on all gpus that support nvenc as well (gpus as old as the nvidia 600 series), provided you are not using outdated gpu drivers. # How to use Run `interactive.sh` or run gpu-screen-recorder directly, for example: `gpu-screen-recorder -w 0x1c00001 -c mp4 -f 60 -a bluez_sink.00_18_09_8A_07_93.a2dp_sink.monitor > test_video.mp4`\ diff --git a/TODO b/TODO new file mode 100644 index 0000000..c2af783 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +Check for reparent. +Only add window to list if its the window is a topmost window. +Use nvEncoder api directly? maybe with this we could copy the window opengl texture directly to the gpu which doesn't work right now for some reason. + Right now we are required to copy the opengl texture to another opengl texture first. +Load cuda at runtime with dlopen. +Track window damages and only update then. That is better for output file size. +Remove cuda to cuda copy when using nvFBC if possible. ffmpeg is getting in the way. \ No newline at end of file diff --git a/include/NvFBC.h b/include/NvFBC.h new file mode 100644 index 0000000..8990eea --- /dev/null +++ b/include/NvFBC.h @@ -0,0 +1,2006 @@ +/*! + * \file + * + * This file contains the interface constants, structure definitions and + * function prototypes defining the NvFBC API for Linux. + * + * Copyright (c) 2013-2020, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef _NVFBC_H_ +#define _NVFBC_H_ + +#include + +/*! + * \mainpage NVIDIA Framebuffer Capture (NvFBC) for Linux. + * + * NvFBC is a high performance, low latency API to capture the framebuffer of + * an X server screen. + * + * The output from NvFBC captures everything that would be visible if we were + * directly looking at the monitor. This includes window manager decoration, + * mouse cursor, overlay, etc. + * + * It is ideally suited to desktop or fullscreen application capture and + * remoting. + */ + +/*! + * \defgroup FBC_REQ Requirements + * + * The following requirements are provided by the regular NVIDIA Display Driver + * package: + * + * - OpenGL core >= 4.2: + * Required. NvFBC relies on OpenGL to perform frame capture and + * post-processing. + * + * - Vulkan 1.1: + * Required. + * + * - libcuda.so.1 >= 5.5: + * Optional. Used for capture to video memory with CUDA interop. + * + * The following requirements must be installed separately depending on the + * Linux distribution being used: + * + * - XRandR extension >= 1.2: + * Optional. Used for RandR output tracking. + * + * - libX11-xcb.so.1 >= 1.2: + * Required. NvFBC uses a mix of Xlib and XCB. Xlib is needed to use GLX, + * XCB is needed to make NvFBC more resilient against X server terminations + * while a capture session is active. + * + * - libxcb.so.1 >= 1.3: + * Required. See above. + * + * - xorg-server >= 1.3: + * Optional. Required for push model to work properly. + * + * Note that all optional dependencies are dlopen()'d at runtime. Failure to + * load an optional library is not fatal. + */ + +/*! + * \defgroup FBC_CHANGES ChangeLog + * + * NvFBC Linux API version 0.1 + * - Initial BETA release. + * + * NvFBC Linux API version 0.2 + * - Added 'bEnableMSE' field to NVFBC_H264_HW_ENC_CONFIG. + * - Added 'dwMSE' field to NVFBC_TOH264_GRAB_FRAME_PARAMS. + * - Added 'bEnableAQ' field to NVFBC_H264_HW_ENC_CONFIG. + * - Added 'NVFBC_H264_PRESET_LOSSLESS_HP' enum to NVFBC_H264_PRESET. + * - Added 'NVFBC_BUFFER_FORMAT_YUV444P' enum to NVFBC_BUFFER_FORMAT. + * - Added 'eInputBufferFormat' field to NVFBC_H264_HW_ENC_CONFIG. + * - Added '0' and '244' values for NVFBC_H264_HW_ENC_CONFIG::dwProfile. + * + * NvFBC Linux API version 0.3 + * - Improved multi-threaded support by implementing an API locking mechanism. + * - Added 'nvFBCBindContext' API entry point. + * - Added 'nvFBCReleaseContext' API entry point. + * + * NvFBC Linux API version 1.0 + * - Added codec agnostic interface for HW encoding. + * - Deprecated H.264 interface. + * - Added support for H.265/HEVC HW encoding. + * + * NvFBC Linux API version 1.1 + * - Added 'nvFBCToHwGetCaps' API entry point. + * - Added 'dwDiffMapScalingFactor' field to NVFBC_TOSYS_SETUP_PARAMS. + * + * NvFBC Linux API version 1.2 + * - Deprecated ToHwEnc interface. + * - Added ToGL interface that captures frames to an OpenGL texture in video + * memory. + * - Added 'bDisableAutoModesetRecovery' field to + * NVFBC_CREATE_CAPTURE_SESSION_PARAMS. + * - Added 'bExternallyManagedContext' field to NVFBC_CREATE_HANDLE_PARAMS. + * + * NvFBC Linux API version 1.3 + * - Added NVFBC_BUFFER_FORMAT_RGBA + * - Added 'dwTimeoutMs' field to NVFBC_TOSYS_GRAB_FRAME_PARAMS, + * NVFBC_TOCUDA_GRAB_FRAME_PARAMS, and NVFBC_TOGL_GRAB_FRAME_PARAMS. + * + * NvFBC Linux API version 1.4 + * - Clarified that NVFBC_BUFFER_FORMAT_{ARGB,RGB,RGBA} are byte-order formats. + * - Renamed NVFBC_BUFFER_FORMAT_YUV420P to NVFBC_BUFFER_FORMAT_NV12. + * - Added new requirements. + * - Made NvFBC more resilient against the X server terminating during an active + * capture session. See new comments for ::NVFBC_ERR_X. + * - Relaxed requirement that 'frameSize' must have a width being a multiple of + * 4 and a height being a multiple of 2. + * - Added 'bRoundFrameSize' field to NVFBC_CREATE_CAPTURE_SESSION_PARAMS. + * - Relaxed requirement that the scaling factor for differential maps must be + * a multiple of the size of the frame. + * - Added 'diffMapSize' field to NVFBC_TOSYS_SETUP_PARAMS and + * NVFBC_TOGL_SETUP_PARAMS. + * + * NvFBC Linux API version 1.5 + * - Added NVFBC_BUFFER_FORMAT_BGRA + * + * NvFBC Linux API version 1.6 + * - Added the 'NVFBC_TOSYS_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY', + * 'NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY', and + * 'NVFBC_TOGL_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY' capture flags. + * - Exposed debug and performance logs through the NVFBC_LOG_LEVEL environment + * variable. Setting it to "1" enables performance logs, setting it to "2" + * enables debugging logs, setting it to "3" enables both. + * - Logs are printed to stdout or to the file pointed by the NVFBC_LOG_FILE + * environment variable. + * - Added 'ulTimestampUs' to NVFBC_FRAME_GRAB_INFO. + * - Added 'dwSamplingRateMs' to NVFBC_CREATE_CAPTURE_SESSION_PARAMS. + * - Added 'bPushModel' to NVFBC_CREATE_CAPTURE_SESSION_PARAMS. + * + * NvFBC Linux API version 1.7 + * - Retired the NVFBC_CAPTURE_TO_HW_ENCODER interface. + * This interface has been deprecated since NvFBC 1.2 and has received no + * updates or new features since. We recommend using the NVIDIA Video Codec + * SDK to encode NvFBC frames. + * See: https://developer.nvidia.com/nvidia-video-codec-sdk + * - Added a 'Capture Modes' section to those headers. + * - Added a 'Post Processing' section to those headers. + * - Added an 'Environment Variables' section to those headers. + * - Added 'bInModeset' to NVFBC_GET_STATUS_PARAMS. + * - Added 'bAllowDirectCapture' to NVFBC_CREATE_CAPTURE_SESSION_PARAMS. + * - Added 'bDirectCaptured' to NVFBC_FRAME_GRAB_INFO. + * - Added 'bRequiredPostProcessing' to NVFBC_FRAME_GRAB_INFO. + */ + +/*! + * \defgroup FBC_MODES Capture Modes + * + * When creating a capture session, NvFBC instantiates a capture subsystem + * living in the NVIDIA X driver. + * + * This subsystem listens for damage events coming from applications then + * generates (composites) frames for NvFBC when new content is available. + * + * This capture server can operate on a timer where it periodically checks if + * there are any pending damage events, or it can generate frames as soon as it + * receives a new damage event. + * See NVFBC_CREATE_CAPTURE_SESSION_PARAMS::dwSamplingRateMs, + * and NVFBC_CREATE_CAPTURE_SESSION_PARAMS::bPushModel. + * + * NvFBC can also attach itself to a fullscreen unoccluded application and have + * it copy its frames directly into a buffer owned by NvFBC upon present. This + * mode bypasses the X server. + * See NVFBC_CREATE_CAPTURE_SESSION_PARAMS::bAllowDirectCapture. + * + * NvFBC is designed to capture frames with as few copies as possible. The + * NVIDIA X driver composites frames directly into the NvFBC buffers, and + * direct capture copies frames directly into these buffers as well. + * + * Depending on the configuration of a capture session, an extra copy (rendering + * pass) may be needed. See the 'Post Processing' section. + */ + +/*! + * \defgroup FBC_PP Post Processing + * + * Depending on the configuration of a capture session, NvFBC might require to + * do post processing on frames. + * + * Post processing is required for the following reasons: + * - NvFBC needs to do a pixel format conversion. + * - Diffmaps are requested. + * - Capture to system memory is requested. + * + * NvFBC needs to do a conversion if the requested pixel format does not match + * the native format. The native format is NVFBC_BUFFER_FORMAT_BGRA. + * + * Note: post processing is *not* required for frame scaling and frame cropping. + * + * Skipping post processing can reduce capture latency. An application can know + * whether post processing was required by checking + * NVFBC_FRAME_GRAB_INFO::bRequiredPostProcessing. + */ + +/*! + * \defgroup FBC_ENVVAR Environment Variables + * + * Below are the environment variables supported by NvFBC: + * + * - NVFBC_LOG_LEVEL + * Bitfield where the first bit enables debug logs and the second bit enables + * performance logs. Both can be enabled by setting this envvar to 3. + * + * - NVFBC_LOG_FILE + * Write all NvFBC logs to the given file. + * + * - NVFBC_FORCE_ALLOW_DIRECT_CAPTURE + * Used to override NVFBC_CREATE_CAPTURE_SESSION_PARAMS::bAllowDirectCapture. + * + * - NVFBC_FORCE_POST_PROCESSING + * Used to force the post processing step, even if it could be skipped. + * See the 'Post Processing' section. + */ + +/*! + * \defgroup FBC_STRUCT Structure Definition + * + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * Calling convention. + */ +#define NVFBCAPI + +/*! + * NvFBC API major version. + */ +#define NVFBC_VERSION_MAJOR 1 + +/*! + * NvFBC API minor version. + */ +#define NVFBC_VERSION_MINOR 7 + +/*! + * NvFBC API version. + */ +#define NVFBC_VERSION (uint32_t) (NVFBC_VERSION_MINOR | (NVFBC_VERSION_MAJOR << 8)) + +/*! + * Creates a version number for structure parameters. + */ +#define NVFBC_STRUCT_VERSION(typeName, ver) \ + (uint32_t) (sizeof(typeName) | ((ver) << 16) | (NVFBC_VERSION << 24)) + +/*! + * Defines error codes. + * + * \see NvFBCGetLastErrorStr + */ +typedef enum _NVFBCSTATUS +{ + /*! + * This indicates that the API call returned with no errors. + */ + NVFBC_SUCCESS = 0, + /*! + * This indicates that the API version between the client and the library + * is not compatible. + */ + NVFBC_ERR_API_VERSION = 1, + /*! + * An internal error occurred. + */ + NVFBC_ERR_INTERNAL = 2, + /*! + * This indicates that one or more of the parameter passed to the API call + * is invalid. + */ + NVFBC_ERR_INVALID_PARAM = 3, + /*! + * This indicates that one or more of the pointers passed to the API call + * is invalid. + */ + NVFBC_ERR_INVALID_PTR = 4, + /*! + * This indicates that the handle passed to the API call to identify the + * client is invalid. + */ + NVFBC_ERR_INVALID_HANDLE = 5, + /*! + * This indicates that the maximum number of threaded clients of the same + * process has been reached. The limit is 10 threads per process. + * There is no limit on the number of process. + */ + NVFBC_ERR_MAX_CLIENTS = 6, + /*! + * This indicates that the requested feature is not currently supported + * by the library. + */ + NVFBC_ERR_UNSUPPORTED = 7, + /*! + * This indicates that the API call failed because it was unable to allocate + * enough memory to perform the requested operation. + */ + NVFBC_ERR_OUT_OF_MEMORY = 8, + /*! + * This indicates that the API call was not expected. This happens when + * API calls are performed in a wrong order, such as trying to capture + * a frame prior to creating a new capture session; or trying to set up + * a capture to video memory although a capture session to system memory + * was created. + */ + NVFBC_ERR_BAD_REQUEST = 9, + /*! + * This indicates an X error, most likely meaning that the X server has + * been terminated. When this error is returned, the only resort is to + * create another FBC handle using NvFBCCreateHandle(). + * + * The previous handle should still be freed with NvFBCDestroyHandle(), but + * it might leak resources, in particular X, GLX, and GL resources since + * it is no longer possible to communicate with an X server to free them + * through the driver. + * + * The best course of action to eliminate this potential leak is to close + * the OpenGL driver, close the forked process running the capture, or + * restart the application. + */ + NVFBC_ERR_X = 10, + /*! + * This indicates a GLX error. + */ + NVFBC_ERR_GLX = 11, + /*! + * This indicates an OpenGL error. + */ + NVFBC_ERR_GL = 12, + /*! + * This indicates a CUDA error. + */ + NVFBC_ERR_CUDA = 13, + /*! + * This indicates a HW encoder error. + */ + NVFBC_ERR_ENCODER = 14, + /*! + * This indicates an NvFBC context error. + */ + NVFBC_ERR_CONTEXT = 15, + /*! + * This indicates that the application must recreate the capture session. + * + * This error can be returned if a modeset event occurred while capturing + * frames, and NVFBC_CREATE_HANDLE_PARAMS::bDisableAutoModesetRecovery + * was set to NVFBC_TRUE. + */ + NVFBC_ERR_MUST_RECREATE = 16, + /*! + * This indicates a Vulkan error. + */ + NVFBC_ERR_VULKAN = 17, +} NVFBCSTATUS; + +/*! + * Defines boolean values. + */ +typedef enum _NVFBC_BOOL +{ + /*! + * False value. + */ + NVFBC_FALSE = 0, + /*! + * True value. + */ + NVFBC_TRUE, +} NVFBC_BOOL; + +/*! + * Maximum size in bytes of an error string. + */ +#define NVFBC_ERR_STR_LEN 512 + +/*! + * Capture type. + */ +typedef enum _NVFBC_CAPTURE_TYPE +{ + /*! + * Capture frames to a buffer in system memory. + */ + NVFBC_CAPTURE_TO_SYS = 0, + /*! + * Capture frames to a CUDA device in video memory. + * + * Specifying this will dlopen() libcuda.so.1 and fail if not available. + */ + NVFBC_CAPTURE_SHARED_CUDA, + /*! + * Retired. Do not use. + */ + /* NVFBC_CAPTURE_TO_HW_ENCODER, */ + /*! + * Capture frames to an OpenGL buffer in video memory. + */ + NVFBC_CAPTURE_TO_GL = 3, +} NVFBC_CAPTURE_TYPE; + +/*! + * Tracking type. + * + * NvFBC can track a specific region of the framebuffer to capture. + * + * An X screen corresponds to the entire framebuffer. + * + * An RandR CRTC is a component of the GPU that reads pixels from a region of + * the X screen and sends them through a pipeline to an RandR output. + * A physical monitor can be connected to an RandR output. Tracking an RandR + * output captures the region of the X screen that the RandR CRTC is sending to + * the RandR output. + */ +typedef enum +{ + /*! + * By default, NvFBC tries to track a connected primary output. If none is + * found, then it tries to track the first connected output. If none is + * found then it tracks the entire X screen. + * + * If the XRandR extension is not available, this option has the same effect + * as ::NVFBC_TRACKING_SCREEN. + * + * This default behavior might be subject to changes in the future. + */ + NVFBC_TRACKING_DEFAULT = 0, + /*! + * Track an RandR output specified by its ID in the appropriate field. + * + * The list of connected outputs can be queried via NvFBCGetStatus(). + * This list can also be obtained using e.g., xrandr(1). + * + * If the XRandR extension is not available, setting this option returns an + * error. + */ + NVFBC_TRACKING_OUTPUT, + /*! + * Track the entire X screen. + */ + NVFBC_TRACKING_SCREEN, +} NVFBC_TRACKING_TYPE; + +/*! + * Buffer format. + */ +typedef enum _NVFBC_BUFFER_FORMAT +{ + /*! + * Data will be converted to ARGB8888 byte-order format. 32 bpp. + */ + NVFBC_BUFFER_FORMAT_ARGB = 0, + /*! + * Data will be converted to RGB888 byte-order format. 24 bpp. + */ + NVFBC_BUFFER_FORMAT_RGB, + /*! + * Data will be converted to NV12 format using HDTV weights + * according to ITU-R BT.709. 12 bpp. + */ + NVFBC_BUFFER_FORMAT_NV12, + /*! + * Data will be converted to YUV 444 planar format using HDTV weights + * according to ITU-R BT.709. 24 bpp + */ + NVFBC_BUFFER_FORMAT_YUV444P, + /*! + * Data will be converted to RGBA8888 byte-order format. 32 bpp. + */ + NVFBC_BUFFER_FORMAT_RGBA, + /*! + * Native format. No pixel conversion needed. + * BGRA8888 byte-order format. 32 bpp. + */ + NVFBC_BUFFER_FORMAT_BGRA, +} NVFBC_BUFFER_FORMAT; + +#define NVFBC_BUFFER_FORMAT_YUV420P NVFBC_BUFFER_FORMAT_NV12 + +/*! + * Handle used to identify an NvFBC session. + */ +typedef uint64_t NVFBC_SESSION_HANDLE; + +/*! + * Box used to describe an area of the tracked region to capture. + * + * The coordinates are relative to the tracked region. + * + * E.g., if the size of the X screen is 3520x1200 and the tracked RandR output + * scans a region of 1600x1200+1920+0, then setting a capture box of + * 800x600+100+50 effectively captures a region of 800x600+2020+50 relative to + * the X screen. + */ +typedef struct _NVFBC_BOX +{ + /*! + * [in] X offset of the box. + */ + uint32_t x; + /*! + * [in] Y offset of the box. + */ + uint32_t y; + /*! + * [in] Width of the box. + */ + uint32_t w; + /*! + * [in] Height of the box. + */ + uint32_t h; +} NVFBC_BOX; + +/*! + * Size used to describe the size of a frame. + */ +typedef struct _NVFBC_SIZE +{ + /*! + * [in] Width. + */ + uint32_t w; + /*! + * [in] Height. + */ + uint32_t h; +} NVFBC_SIZE; + +/*! + * Describes information about a captured frame. + */ +typedef struct _NVFBC_FRAME_GRAB_INFO +{ + /*! + * [out] Width of the captured frame. + */ + uint32_t dwWidth; + /*! + * [out] Height of the captured frame. + */ + uint32_t dwHeight; + /*! + * [out] Size of the frame in bytes. + */ + uint32_t dwByteSize; + /*! + * [out] Incremental ID of the current frame. + * + * This can be used to identify a frame. + */ + uint32_t dwCurrentFrame; + /*! + * [out] Whether the captured frame is a new frame. + * + * When using non blocking calls it is possible to capture a frame + * that was already captured before if the display server did not + * render a new frame in the meantime. In that case, this flag + * will be set to NVFBC_FALSE. + * + * When using blocking calls each captured frame will have + * this flag set to NVFBC_TRUE since the blocking mechanism waits for + * the display server to render a new frame. + * + * Note that this flag does not guarantee that the content of + * the frame will be different compared to the previous captured frame. + * + * In particular, some compositing managers report the entire + * framebuffer as damaged when an application refreshes its content. + * + * Consider a single X screen spanned across physical displays A and B + * and an NvFBC application tracking display A. Depending on the + * compositing manager, it is possible that an application refreshing + * itself on display B will trigger a frame capture on display A. + * + * Workarounds include: + * - Using separate X screens + * - Disabling the composite extension + * - Using a compositing manager that properly reports what regions + * are damaged + * - Using NvFBC's diffmaps to find out if the frame changed + */ + NVFBC_BOOL bIsNewFrame; + /*! + * [out] Frame timestamp + * + * Time in micro seconds when the display server started rendering the + * frame. + * + * This does not account for when the frame was captured. If capturing an + * old frame (e.g., bIsNewFrame is NVFBC_FALSE) the reported timestamp + * will reflect the time when the old frame was rendered by the display + * server. + */ + uint64_t ulTimestampUs; + /* + * [out] Number of frames generated since the last capture. + * + * This can help applications tell whether they missed frames or there + * were no frames generated by the server since the last capture. + */ + uint32_t dwMissedFrames; + /* + * [out] Whether the captured frame required post processing. + * + * See the 'Post Processing' section. + */ + NVFBC_BOOL bRequiredPostProcessing; + /* + * [out] Whether this frame was obtained via direct capture. + * + * See NVFBC_CREATE_CAPTURE_SESSION_PARAMS::bAllowDirectCapture. + */ + NVFBC_BOOL bDirectCapture; +} NVFBC_FRAME_GRAB_INFO; + +/*! + * Defines parameters for the CreateHandle() API call. + */ +typedef struct _NVFBC_CREATE_HANDLE_PARAMS +{ + /*! + * [in] Must be set to NVFBC_CREATE_HANDLE_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [in] Application specific private information passed to the NvFBC + * session. + */ + const void *privateData; + /*! + * [in] Size of the application specific private information passed to the + * NvFBC session. + */ + uint32_t privateDataSize; + /*! + * [in] Whether NvFBC should not create and manage its own graphics context + * + * NvFBC internally uses OpenGL to perfom graphics operations on the + * captured frames. By default, NvFBC will create and manage (e.g., make + * current, detect new threads, etc.) its own OpenGL context. + * + * If set to NVFBC_TRUE, NvFBC will use the application's context. It will + * be the application's responsibility to make sure that a context is + * current on the thread calling into the NvFBC API. + */ + NVFBC_BOOL bExternallyManagedContext; + /*! + * [in] GLX context + * + * GLX context that NvFBC should use internally to create pixmaps and + * make them current when creating a new capture session. + * + * Note: NvFBC expects a context created against a GLX_RGBA_TYPE render + * type. + */ + void *glxCtx; + /*! + * [in] GLX framebuffer configuration + * + * Framebuffer configuration that was used to create the GLX context, and + * that will be used to create pixmaps internally. + * + * Note: NvFBC expects a configuration having at least the following + * attributes: + * GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT + * GLX_BIND_TO_TEXTURE_RGBA_EXT, 1 + * GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT + */ + void *glxFBConfig; +} NVFBC_CREATE_HANDLE_PARAMS; + +/*! + * NVFBC_CREATE_HANDLE_PARAMS structure version. + */ +#define NVFBC_CREATE_HANDLE_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_CREATE_HANDLE_PARAMS, 2) + +/*! + * Defines parameters for the ::NvFBCDestroyHandle() API call. + */ +typedef struct _NVFBC_DESTROY_HANDLE_PARAMS +{ + /*! + * [in] Must be set to NVFBC_DESTROY_HANDLE_PARAMS_VER + */ + uint32_t dwVersion; +} NVFBC_DESTROY_HANDLE_PARAMS; + +/*! + * NVFBC_DESTROY_HANDLE_PARAMS structure version. + */ +#define NVFBC_DESTROY_HANDLE_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_DESTROY_HANDLE_PARAMS, 1) + +/*! + * Maximum number of connected RandR outputs to an X screen. + */ +#define NVFBC_OUTPUT_MAX 5 + +/*! + * Maximum size in bytes of an RandR output name. + */ +#define NVFBC_OUTPUT_NAME_LEN 128 + +/*! + * Describes an RandR output. + * + * Filling this structure relies on the XRandR extension. This feature cannot + * be used if the extension is missing or its version is below the requirements. + * + * \see Requirements + */ +typedef struct _NVFBC_OUTPUT +{ + /*! + * Identifier of the RandR output. + */ + uint32_t dwId; + /*! + * Name of the RandR output, as reported by tools such as xrandr(1). + * + * Example: "DVI-I-0" + */ + char name[NVFBC_OUTPUT_NAME_LEN]; + /*! + * Region of the X screen tracked by the RandR CRTC driving this RandR + * output. + */ + NVFBC_BOX trackedBox; +} NVFBC_RANDR_OUTPUT_INFO; + +/*! + * Defines parameters for the ::NvFBCGetStatus() API call. + */ +typedef struct _NVFBC_GET_STATUS_PARAMS +{ + /*! + * [in] Must be set to NVFBC_GET_STATUS_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [out] Whether or not framebuffer capture is supported by the graphics + * driver. + */ + NVFBC_BOOL bIsCapturePossible; + /*! + * [out] Whether or not there is already a capture session on this system. + */ + NVFBC_BOOL bCurrentlyCapturing; + /*! + * [out] Whether or not it is possible to create a capture session on this + * system. + */ + NVFBC_BOOL bCanCreateNow; + /*! + * [out] Size of the X screen (framebuffer). + */ + NVFBC_SIZE screenSize; + /*! + * [out] Whether the XRandR extension is available. + * + * If this extension is not available then it is not possible to have + * information about RandR outputs. + */ + NVFBC_BOOL bXRandRAvailable; + /*! + * [out] Array of outputs connected to the X screen. + * + * An application can track a specific output by specifying its ID when + * creating a capture session. + * + * Only if XRandR is available. + */ + NVFBC_RANDR_OUTPUT_INFO outputs[NVFBC_OUTPUT_MAX]; + /*! + * [out] Number of outputs connected to the X screen. + * + * This must be used to parse the array of connected outputs. + * + * Only if XRandR is available. + */ + uint32_t dwOutputNum; + /*! + * [out] Version of the NvFBC library running on this system. + */ + uint32_t dwNvFBCVersion; + /*! + * [out] Whether the X server is currently in modeset. + * + * When the X server is in modeset, it must give up all its video + * memory allocations. It is not possible to create a capture + * session until the modeset is over. + * + * Note that VT-switches are considered modesets. + */ + NVFBC_BOOL bInModeset; +} NVFBC_GET_STATUS_PARAMS; + +/*! + * NVFBC_GET_STATUS_PARAMS structure version. + */ +#define NVFBC_GET_STATUS_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_GET_STATUS_PARAMS, 2) + +/*! + * Defines parameters for the ::NvFBCCreateCaptureSession() API call. + */ +typedef struct _NVFBC_CREATE_CAPTURE_SESSION_PARAMS +{ + /*! + * [in] Must be set to NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [in] Desired capture type. + * + * Note that when specyfing ::NVFBC_CAPTURE_SHARED_CUDA NvFBC will try to + * dlopen() the corresponding libraries. This means that NvFBC can run on + * a system without the CUDA library since it does not link against them. + */ + NVFBC_CAPTURE_TYPE eCaptureType; + /*! + * [in] What region of the framebuffer should be tracked. + */ + NVFBC_TRACKING_TYPE eTrackingType; + /*! + * [in] ID of the output to track if eTrackingType is set to + * ::NVFBC_TRACKING_OUTPUT. + */ + uint32_t dwOutputId; + /*! + * [in] Crop the tracked region. + * + * The coordinates are relative to the tracked region. + * + * It can be set to 0 to capture the entire tracked region. + */ + NVFBC_BOX captureBox; + /*! + * [in] Desired size of the captured frame. + * + * This parameter allow to scale the captured frame. + * + * It can be set to 0 to disable frame resizing. + */ + NVFBC_SIZE frameSize; + /*! + * [in] Whether the mouse cursor should be composited to the frame. + * + * Disabling the cursor will not generate new frames when only the cursor + * is moved. + */ + NVFBC_BOOL bWithCursor; + /*! + * [in] Whether NvFBC should not attempt to recover from modesets. + * + * NvFBC is able to detect when a modeset event occured and can automatically + * re-create a capture session with the same settings as before, then resume + * its frame capture session transparently. + * + * This option allows to disable this behavior. NVFBC_ERR_MUST_RECREATE + * will be returned in that case. + * + * It can be useful in the cases when an application needs to do some work + * between setting up a capture and grabbing the first frame. + * + * For example: an application using the ToGL interface needs to register + * resources with EncodeAPI prior to encoding frames. + * + * Note that during modeset recovery, NvFBC will try to re-create the + * capture session every second until it succeeds. + */ + NVFBC_BOOL bDisableAutoModesetRecovery; + /*! + * [in] Whether NvFBC should round the requested frameSize. + * + * When disabled, NvFBC will not attempt to round the requested resolution. + * + * However, some pixel formats have resolution requirements. E.g., YUV/NV + * formats must have a width being a multiple of 4, and a height being a + * multiple of 2. RGB formats don't have such requirements. + * + * If the resolution doesn't meet the requirements of the format, then NvFBC + * will fail at setup time. + * + * When enabled, NvFBC will round the requested width to the next multiple + * of 4 and the requested height to the next multiple of 2. + * + * In this case, requesting any resolution will always work with every + * format. However, an NvFBC client must be prepared to handle the case + * where the requested resolution is different than the captured resolution. + * + * NVFBC_FRAME_GRAB_INFO::dwWidth and NVFBC_FRAME_GRAB_INFO::dwHeight should + * always be used for getting information about captured frames. + */ + NVFBC_BOOL bRoundFrameSize; + /*! + * [in] Rate in ms at which the display server generates new frames + * + * This controls the frequency at which the display server will generate + * new frames if new content is available. This effectively controls the + * capture rate when using blocking calls. + * + * Note that lower values will increase the CPU and GPU loads. + * + * The default value is 16ms (~ 60 Hz). + */ + uint32_t dwSamplingRateMs; + /*! + * [in] Enable push model for frame capture + * + * When set to NVFBC_TRUE, the display server will generate frames whenever + * it receives a damage event from applications. + * + * Setting this to NVFBC_TRUE will ignore ::dwSamplingRateMs. + * + * Using push model with the NVFBC_*_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY + * capture flag should guarantee the shortest amount of time between an + * application rendering a frame and an NvFBC client capturing it, provided + * that the NvFBC client is able to process the frames quickly enough. + * + * Note that applications running at high frame rates will increase CPU and + * GPU loads. + */ + NVFBC_BOOL bPushModel; + /*! + * [in] Allow direct capture + * + * Direct capture allows NvFBC to attach itself to a fullscreen graphics + * application. Whenever that application presents a frame, it makes a copy + * of it directly into a buffer owned by NvFBC thus bypassing the X server. + * + * When direct capture is *not* enabled, the NVIDIA X driver generates a + * frame for NvFBC when it receives a damage event from an application if push + * model is enabled, or periodically checks if there are any pending damage + * events otherwise (see NVFBC_CREATE_CAPTURE_SESSION_PARAMS::dwSamplingRateMs). + * + * Direct capture is possible under the following conditions: + * - Direct capture is allowed + * - Push model is enabled (see NVFBC_CREATE_CAPTURE_SESSION_PARAMS::bPushModel) + * - The mouse cursor is not composited (see NVFBC_CREATE_CAPTURE_SESSION_PARAMS::bWithCursor) + * - No viewport transformation is required. This happens when the remote + * desktop is e.g. rotated. + * + * When direct capture is possible, NvFBC will automatically attach itself + * to a fullscreen unoccluded application, if such exists. + * + * Notes: + * - This includes compositing desktops such as GNOME (e.g., gnome-shell + * is the fullscreen unoccluded application). + * - There can be only one fullscreen unoccluded application at a time. + * - The NVIDIA X driver monitors which application qualifies or no + * longer qualifies. + * + * For example, if a fullscreen application is launched in GNOME, NvFBC will + * detach from gnome-shell and attach to that application. + * + * Attaching and detaching happens automatically from the perspective of an + * NvFBC client. When detaching from an application, the X driver will + * transparently resume generating frames for NvFBC. + * + * An application can know whether a given frame was obtained through + * direct capture by checking NVFBC_FRAME_GRAB_INFO::bDirectCapture. + */ + NVFBC_BOOL bAllowDirectCapture; +} NVFBC_CREATE_CAPTURE_SESSION_PARAMS; + +/*! + * NVFBC_CREATE_CAPTURE_SESSION_PARAMS structure version. + */ +#define NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_CREATE_CAPTURE_SESSION_PARAMS, 6) + +/*! + * Defines parameters for the ::NvFBCDestroyCaptureSession() API call. + */ +typedef struct _NVFBC_DESTROY_CAPTURE_SESSION_PARAMS +{ + /*! + * [in] Must be set to NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER + */ + uint32_t dwVersion; +} NVFBC_DESTROY_CAPTURE_SESSION_PARAMS; + +/*! + * NVFBC_DESTROY_CAPTURE_SESSION_PARAMS structure version. + */ +#define NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_DESTROY_CAPTURE_SESSION_PARAMS, 1) + +/*! + * Defines parameters for the ::NvFBCBindContext() API call. + */ +typedef struct _NVFBC_BIND_CONTEXT_PARAMS +{ + /*! + * [in] Must be set to NVFBC_BIND_CONTEXT_PARAMS_VER + */ + uint32_t dwVersion; +} NVFBC_BIND_CONTEXT_PARAMS; + +/*! + * NVFBC_BIND_CONTEXT_PARAMS structure version. + */ +#define NVFBC_BIND_CONTEXT_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_BIND_CONTEXT_PARAMS, 1) + +/*! + * Defines parameters for the ::NvFBCReleaseContext() API call. + */ +typedef struct _NVFBC_RELEASE_CONTEXT_PARAMS +{ + /*! + * [in] Must be set to NVFBC_RELEASE_CONTEXT_PARAMS_VER + */ + uint32_t dwVersion; +} NVFBC_RELEASE_CONTEXT_PARAMS; + +/*! + * NVFBC_RELEASE_CONTEXT_PARAMS structure version. + */ +#define NVFBC_RELEASE_CONTEXT_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_RELEASE_CONTEXT_PARAMS, 1) + +/*! + * Defines flags that can be used when capturing to system memory. + */ +typedef enum +{ + /*! + * Default, capturing waits for a new frame or mouse move. + * + * The default behavior of blocking grabs is to wait for a new frame until + * after the call was made. But it's possible that there is a frame already + * ready that the client hasn't seen. + * \see NVFBC_TOSYS_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY + */ + NVFBC_TOSYS_GRAB_FLAGS_NOFLAGS = 0, + /*! + * Capturing does not wait for a new frame nor a mouse move. + * + * It is therefore possible to capture the same frame multiple times. + * When this occurs, the dwCurrentFrame parameter of the + * NVFBC_FRAME_GRAB_INFO structure is not incremented. + */ + NVFBC_TOSYS_GRAB_FLAGS_NOWAIT = (1 << 0), + /*! + * Forces the destination buffer to be refreshed even if the frame has not + * changed since previous capture. + * + * By default, if the captured frame is identical to the previous one, NvFBC + * will omit one copy and not update the destination buffer. + * + * Setting that flag will prevent this behavior. This can be useful e.g., + * if the application has modified the buffer in the meantime. + */ + NVFBC_TOSYS_GRAB_FLAGS_FORCE_REFRESH = (1 << 1), + /*! + * Similar to NVFBC_TOSYS_GRAB_FLAGS_NOFLAGS, except that the capture will + * not wait if there is already a frame available that the client has + * never seen yet. + */ + NVFBC_TOSYS_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY = (1 << 2), +} NVFBC_TOSYS_GRAB_FLAGS; + +/*! + * Defines parameters for the ::NvFBCToSysSetUp() API call. + */ +typedef struct _NVFBC_TOSYS_SETUP_PARAMS +{ + /*! + * [in] Must be set to NVFBC_TOSYS_SETUP_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [in] Desired buffer format. + */ + NVFBC_BUFFER_FORMAT eBufferFormat; + /*! + * [out] Pointer to a pointer to a buffer in system memory. + * + * This buffer contains the pixel value of the requested format. Refer to + * the description of the buffer formats to understand the memory layout. + * + * The application does not need to allocate memory for this buffer. It + * should not free this buffer either. This buffer is automatically + * re-allocated when needed (e.g., when the resolution changes). + * + * This buffer is allocated by the NvFBC library to the proper size. This + * size is returned in the dwByteSize field of the + * ::NVFBC_FRAME_GRAB_INFO structure. + */ + void **ppBuffer; + /*! + * [in] Whether differential maps should be generated. + */ + NVFBC_BOOL bWithDiffMap; + /*! + * [out] Pointer to a pointer to a buffer in system memory. + * + * This buffer contains the differential map of two frames. It must be read + * as an array of unsigned char. Each unsigned char is either 0 or + * non-zero. 0 means that the pixel value at the given location has not + * changed since the previous captured frame. Non-zero means that the pixel + * value has changed. + * + * The application does not need to allocate memory for this buffer. It + * should not free this buffer either. This buffer is automatically + * re-allocated when needed (e.g., when the resolution changes). + * + * This buffer is allocated by the NvFBC library to the proper size. The + * size of the differential map is returned in ::diffMapSize. + * + * This option is not compatible with the ::NVFBC_BUFFER_FORMAT_YUV420P and + * ::NVFBC_BUFFER_FORMAT_YUV444P buffer formats. + */ + void **ppDiffMap; + /*! + * [in] Scaling factor of the differential maps. + * + * For example, a scaling factor of 16 means that one pixel of the diffmap + * will represent 16x16 pixels of the original frames. + * + * If any of these 16x16 pixels is different between the current and the + * previous frame, then the corresponding pixel in the diffmap will be set + * to non-zero. + * + * The default scaling factor is 1. A dwDiffMapScalingFactor of 0 will be + * set to 1. + */ + uint32_t dwDiffMapScalingFactor; + /*! + * [out] Size of the differential map. + * + * Only set if bWithDiffMap is set to NVFBC_TRUE. + */ + NVFBC_SIZE diffMapSize; +} NVFBC_TOSYS_SETUP_PARAMS; + +/*! + * NVFBC_TOSYS_SETUP_PARAMS structure version. + */ +#define NVFBC_TOSYS_SETUP_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_TOSYS_SETUP_PARAMS, 3) + +/*! + * Defines parameters for the ::NvFBCToSysGrabFrame() API call. + */ +typedef struct _NVFBC_TOSYS_GRAB_FRAME_PARAMS +{ + /*! + * [in] Must be set to NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [in] Flags defining the behavior of this frame capture. + */ + uint32_t dwFlags; + /*! + * [out] Information about the captured frame. + * + * Can be NULL. + */ + NVFBC_FRAME_GRAB_INFO *pFrameGrabInfo; + /*! + * [in] Wait timeout in milliseconds. + * + * When capturing frames with the NVFBC_TOSYS_GRAB_FLAGS_NOFLAGS or + * NVFBC_TOSYS_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY flags, + * NvFBC will wait for a new frame or mouse move until the below timer + * expires. + * + * When timing out, the last captured frame will be returned. Note that as + * long as the NVFBC_TOSYS_GRAB_FLAGS_FORCE_REFRESH flag is not set, + * returning an old frame will incur no performance penalty. + * + * NvFBC clients can use the return value of the grab frame operation to + * find out whether a new frame was captured, or the timer expired. + * + * Note that the behavior of blocking calls is to wait for a new frame + * *after* the call has been made. When using timeouts, it is possible + * that NvFBC will return a new frame (e.g., it has never been captured + * before) even though no new frame was generated after the grab call. + * + * For the precise definition of what constitutes a new frame, see + * ::bIsNewFrame. + * + * Set to 0 to disable timeouts. + */ + uint32_t dwTimeoutMs; +} NVFBC_TOSYS_GRAB_FRAME_PARAMS; + +/*! + * NVFBC_TOSYS_GRAB_FRAME_PARAMS structure version. + */ +#define NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_TOSYS_GRAB_FRAME_PARAMS, 2) + +/*! + * Defines flags that can be used when capturing to a CUDA buffer in video memory. + */ +typedef enum +{ + /*! + * Default, capturing waits for a new frame or mouse move. + * + * The default behavior of blocking grabs is to wait for a new frame until + * after the call was made. But it's possible that there is a frame already + * ready that the client hasn't seen. + * \see NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY + */ + NVFBC_TOCUDA_GRAB_FLAGS_NOFLAGS = 0, + /*! + * Capturing does not wait for a new frame nor a mouse move. + * + * It is therefore possible to capture the same frame multiple times. + * When this occurs, the dwCurrentFrame parameter of the + * NVFBC_FRAME_GRAB_INFO structure is not incremented. + */ + NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT = (1 << 0), + /*! + * [in] Forces the destination buffer to be refreshed even if the frame + * has not changed since previous capture. + * + * By default, if the captured frame is identical to the previous one, NvFBC + * will omit one copy and not update the destination buffer. + * + * Setting that flag will prevent this behavior. This can be useful e.g., + * if the application has modified the buffer in the meantime. + */ + NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH = (1 << 1), + /*! + * Similar to NVFBC_TOCUDA_GRAB_FLAGS_NOFLAGS, except that the capture will + * not wait if there is already a frame available that the client has + * never seen yet. + */ + NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY = (1 << 2), +} NVFBC_TOCUDA_FLAGS; + +/*! + * Defines parameters for the ::NvFBCToCudaSetUp() API call. + */ +typedef struct _NVFBC_TOCUDA_SETUP_PARAMS +{ + /*! + * [in] Must be set to NVFBC_TOCUDA_SETUP_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [in] Desired buffer format. + */ + NVFBC_BUFFER_FORMAT eBufferFormat; +} NVFBC_TOCUDA_SETUP_PARAMS; + +/*! + * NVFBC_TOCUDA_SETUP_PARAMS structure version. + */ +#define NVFBC_TOCUDA_SETUP_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_TOCUDA_SETUP_PARAMS, 1) + +/*! + * Defines parameters for the ::NvFBCToCudaGrabFrame() API call. + */ +typedef struct _NVFBC_TOCUDA_GRAB_FRAME_PARAMS +{ + /*! + * [in] Must be set to NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER. + */ + uint32_t dwVersion; + /*! + * [in] Flags defining the behavior of this frame capture. + */ + uint32_t dwFlags; + /*! + * [out] Pointer to a ::CUdeviceptr + * + * The application does not need to allocate memory for this CUDA device. + * + * The application does need to create its own CUDA context to use this + * CUDA device. + * + * This ::CUdeviceptr will be mapped to a segment in video memory containing + * the frame. It is not possible to process a CUDA device while capturing + * a new frame. If the application wants to do so, it must copy the CUDA + * device using ::cuMemcpyDtoD or ::cuMemcpyDtoH beforehand. + */ + void *pCUDADeviceBuffer; + /*! + * [out] Information about the captured frame. + * + * Can be NULL. + */ + NVFBC_FRAME_GRAB_INFO *pFrameGrabInfo; + /*! + * [in] Wait timeout in milliseconds. + * + * When capturing frames with the NVFBC_TOCUDA_GRAB_FLAGS_NOFLAGS or + * NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY flags, + * NvFBC will wait for a new frame or mouse move until the below timer + * expires. + * + * When timing out, the last captured frame will be returned. Note that as + * long as the NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH flag is not set, + * returning an old frame will incur no performance penalty. + * + * NvFBC clients can use the return value of the grab frame operation to + * find out whether a new frame was captured, or the timer expired. + * + * Note that the behavior of blocking calls is to wait for a new frame + * *after* the call has been made. When using timeouts, it is possible + * that NvFBC will return a new frame (e.g., it has never been captured + * before) even though no new frame was generated after the grab call. + * + * For the precise definition of what constitutes a new frame, see + * ::bIsNewFrame. + * + * Set to 0 to disable timeouts. + */ + uint32_t dwTimeoutMs; +} NVFBC_TOCUDA_GRAB_FRAME_PARAMS; + +/*! + * NVFBC_TOCUDA_GRAB_FRAME_PARAMS structure version. + */ +#define NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_TOCUDA_GRAB_FRAME_PARAMS, 2) + +/*! + * Defines flags that can be used when capturing to an OpenGL buffer in video memory. + */ +typedef enum +{ + /*! + * Default, capturing waits for a new frame or mouse move. + * + * The default behavior of blocking grabs is to wait for a new frame until + * after the call was made. But it's possible that there is a frame already + * ready that the client hasn't seen. + * \see NVFBC_TOGL_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY + */ + NVFBC_TOGL_GRAB_FLAGS_NOFLAGS = 0, + /*! + * Capturing does not wait for a new frame nor a mouse move. + * + * It is therefore possible to capture the same frame multiple times. + * When this occurs, the dwCurrentFrame parameter of the + * NVFBC_FRAME_GRAB_INFO structure is not incremented. + */ + NVFBC_TOGL_GRAB_FLAGS_NOWAIT = (1 << 0), + /*! + * [in] Forces the destination buffer to be refreshed even if the frame + * has not changed since previous capture. + * + * By default, if the captured frame is identical to the previous one, NvFBC + * will omit one copy and not update the destination buffer. + * + * Setting that flag will prevent this behavior. This can be useful e.g., + * if the application has modified the buffer in the meantime. + */ + NVFBC_TOGL_GRAB_FLAGS_FORCE_REFRESH = (1 << 1), + /*! + * Similar to NVFBC_TOGL_GRAB_FLAGS_NOFLAGS, except that the capture will + * not wait if there is already a frame available that the client has + * never seen yet. + */ + NVFBC_TOGL_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY = (1 << 2), +} NVFBC_TOGL_FLAGS; + +/*! + * Maximum number of GL textures that can be used to store frames. + */ +#define NVFBC_TOGL_TEXTURES_MAX 2 + +/*! + * Defines parameters for the ::NvFBCToGLSetUp() API call. + */ +typedef struct _NVFBC_TOGL_SETUP_PARAMS +{ + /*! + * [in] Must be set to NVFBC_TOGL_SETUP_PARAMS_VER + */ + uint32_t dwVersion; + /*! + * [in] Desired buffer format. + */ + NVFBC_BUFFER_FORMAT eBufferFormat; + /*! + * [in] Whether differential maps should be generated. + */ + NVFBC_BOOL bWithDiffMap; + /*! + * [out] Pointer to a pointer to a buffer in system memory. + * + * \see NVFBC_TOSYS_SETUP_PARAMS::ppDiffMap + */ + void **ppDiffMap; + /*! + * [in] Scaling factor of the differential maps. + * + * \see NVFBC_TOSYS_SETUP_PARAMS::dwDiffMapScalingFactor + */ + uint32_t dwDiffMapScalingFactor; + /*! + * [out] List of GL textures that will store the captured frames. + * + * This array is 0 terminated. The number of textures varies depending on + * the capture settings (such as whether diffmaps are enabled). + * + * An application wishing to interop with, for example, EncodeAPI will need + * to register these textures prior to start encoding frames. + * + * After each frame capture, the texture holding the current frame will be + * returned in NVFBC_TOGL_GRAB_FRAME_PARAMS::dwTexture. + */ + uint32_t dwTextures[NVFBC_TOGL_TEXTURES_MAX]; + /*! + * [out] GL target to which the texture should be bound. + */ + uint32_t dwTexTarget; + /*! + * [out] GL format of the textures. + */ + uint32_t dwTexFormat; + /*! + * [out] GL type of the textures. + */ + uint32_t dwTexType; + /*! + * [out] Size of the differential map. + * + * Only set if bWithDiffMap is set to NVFBC_TRUE. + */ + NVFBC_SIZE diffMapSize; +} NVFBC_TOGL_SETUP_PARAMS; + +/*! + * NVFBC_TOGL_SETUP_PARAMS structure version. + */ +#define NVFBC_TOGL_SETUP_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_TOGL_SETUP_PARAMS, 2) + +/*! + * Defines parameters for the ::NvFBCToGLGrabFrame() API call. + */ +typedef struct _NVFBC_TOGL_GRAB_FRAME_PARAMS +{ + /*! + * [in] Must be set to NVFBC_TOGL_GRAB_FRAME_PARAMS_VER. + */ + uint32_t dwVersion; + /*! + * [in] Flags defining the behavior of this frame capture. + */ + uint32_t dwFlags; + /*! + * [out] Index of the texture storing the current frame. + * + * This is an index in the NVFBC_TOGL_SETUP_PARAMS::dwTextures array. + */ + uint32_t dwTextureIndex; + /*! + * [out] Information about the captured frame. + * + * Can be NULL. + */ + NVFBC_FRAME_GRAB_INFO *pFrameGrabInfo; + /*! + * [in] Wait timeout in milliseconds. + * + * When capturing frames with the NVFBC_TOGL_GRAB_FLAGS_NOFLAGS or + * NVFBC_TOGL_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY flags, + * NvFBC will wait for a new frame or mouse move until the below timer + * expires. + * + * When timing out, the last captured frame will be returned. Note that as + * long as the NVFBC_TOGL_GRAB_FLAGS_FORCE_REFRESH flag is not set, + * returning an old frame will incur no performance penalty. + * + * NvFBC clients can use the return value of the grab frame operation to + * find out whether a new frame was captured, or the timer expired. + * + * Note that the behavior of blocking calls is to wait for a new frame + * *after* the call has been made. When using timeouts, it is possible + * that NvFBC will return a new frame (e.g., it has never been captured + * before) even though no new frame was generated after the grab call. + * + * For the precise definition of what constitutes a new frame, see + * ::bIsNewFrame. + * + * Set to 0 to disable timeouts. + */ + uint32_t dwTimeoutMs; +} NVFBC_TOGL_GRAB_FRAME_PARAMS; + +/*! + * NVFBC_TOGL_GRAB_FRAME_PARAMS structure version. + */ +#define NVFBC_TOGL_GRAB_FRAME_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_TOGL_GRAB_FRAME_PARAMS, 2) + +/*! @} FBC_STRUCT */ + +/*! + * \defgroup FBC_FUNC API Entry Points + * + * Entry points are thread-safe and can be called concurrently. + * + * The locking model includes a global lock that protects session handle + * management (\see NvFBCCreateHandle, \see NvFBCDestroyHandle). + * + * Each NvFBC session uses a local lock to protect other entry points. Note + * that in certain cases, a thread can hold the local lock for an undefined + * amount of time, such as grabbing a frame using a blocking call. + * + * Note that a context is associated with each session. NvFBC clients wishing + * to share a session between different threads are expected to release and + * bind the context appropriately (\see NvFBCBindContext, + * \see NvFBCReleaseContext). This is not required when each thread uses its + * own NvFBC session. + * + * @{ + */ + +/*! + * Gets the last error message that got recorded for a client. + * + * When NvFBC returns an error, it will save an error message that can be + * queried through this API call. Only the last message is saved. + * The message and the return code should give enough information about + * what went wrong. + * + * \param [in] sessionHandle + * Handle to the NvFBC client. + * \return + * A NULL terminated error message, or an empty string. Its maximum length + * is NVFBC_ERROR_STR_LEN. + */ +const char* NVFBCAPI NvFBCGetLastErrorStr(const NVFBC_SESSION_HANDLE sessionHandle); + +/*! + * \brief Allocates a new handle for an NvFBC client. + * + * This function allocates a session handle used to identify an FBC client. + * + * This function implicitly calls NvFBCBindContext(). + * + * \param [out] pSessionHandle + * Pointer that will hold the allocated session handle. + * \param [in] pParams + * ::NVFBC_CREATE_HANDLE_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_PTR \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_OUT_OF_MEMORY \n + * ::NVFBC_ERR_MAX_CLIENTS \n + * ::NVFBC_ERR_X \n + * ::NVFBC_ERR_GLX \n + * ::NVFBC_ERR_GL + * + */ +NVFBCSTATUS NVFBCAPI NvFBCCreateHandle(NVFBC_SESSION_HANDLE *pSessionHandle, NVFBC_CREATE_HANDLE_PARAMS *pParams); + +/*! + * \brief Destroys the handle of an NvFBC client. + * + * This function uninitializes an FBC client. + * + * This function implicitly calls NvFBCReleaseContext(). + * + * After this fucntion returns, it is not possible to use this session handle + * for any further API call. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_DESTROY_HANDLE_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCDestroyHandle(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_HANDLE_PARAMS *pParams); + +/*! + * \brief Gets the current status of the display driver. + * + * This function queries the display driver for various information. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_GET_STATUS_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCGetStatus(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_GET_STATUS_PARAMS *pParams); + +/*! + * \brief Binds the FBC context to the calling thread. + * + * The NvFBC library internally relies on objects that must be bound to a + * thread. Such objects are OpenGL contexts and CUDA contexts. + * + * This function binds these objects to the calling thread. + * + * The FBC context must be bound to the calling thread for most NvFBC entry + * points, otherwise ::NVFBC_ERR_CONTEXT is returned. + * + * If the FBC context is already bound to a different thread, + * ::NVFBC_ERR_CONTEXT is returned. The other thread must release the context + * first by calling the ReleaseContext() entry point. + * + * If the FBC context is already bound to the current thread, this function has + * no effects. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_DESTROY_CAPTURE_SESSION_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCBindContext(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_BIND_CONTEXT_PARAMS *pParams); + +/*! + * \brief Releases the FBC context from the calling thread. + * + * If the FBC context is bound to a different thread, ::NVFBC_ERR_CONTEXT is + * returned. + * + * If the FBC context is already released, this functino has no effects. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCReleaseContext(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_RELEASE_CONTEXT_PARAMS *pParams); + +/*! + * \brief Creates a capture session for an FBC client. + * + * This function starts a capture session of the desired type (system memory, + * video memory with CUDA interop, or H.264 compressed frames in system memory). + * + * Not all types are supported on all systems. Also, it is possible to use + * NvFBC without having the CUDA library. In this case, requesting a capture + * session of the concerned type will return an error. + * + * After this function returns, the display driver will start generating frames + * that can be captured using the corresponding API call. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_CREATE_CAPTURE_SESSION_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INVALID_PARAM \n + * ::NVFBC_ERR_OUT_OF_MEMORY \n + * ::NVFBC_ERR_X \n + * ::NVFBC_ERR_GLX \n + * ::NVFBC_ERR_GL \n + * ::NVFBC_ERR_CUDA \n + * ::NVFBC_ERR_MUST_RECREATE \n + * ::NVFBC_ERR_INTERNAL + */ +NVFBCSTATUS NVFBCAPI NvFBCCreateCaptureSession(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_CREATE_CAPTURE_SESSION_PARAMS *pParams); + +/*! + * \brief Destroys a capture session for an FBC client. + * + * This function stops a capture session and frees allocated objects. + * + * After this function returns, it is possible to create another capture + * session using the corresponding API call. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_DESTROY_CAPTURE_SESSION_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCDestroyCaptureSession(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_CAPTURE_SESSION_PARAMS *pParams); + +/*! + * \brief Sets up a capture to system memory session. + * + * This function configures how the capture to system memory should behave. It + * can be called anytime and several times after the capture session has been + * created. However, it must be called at least once prior to start capturing + * frames. + * + * This function allocates the buffer that will contain the captured frame. + * The application does not need to free this buffer. The size of this buffer + * is returned in the ::NVFBC_FRAME_GRAB_INFO structure. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_TOSYS_SETUP_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_UNSUPPORTED \n + * ::NVFBC_ERR_INVALID_PTR \n + * ::NVFBC_ERR_INVALID_PARAM \n + * ::NVFBC_ERR_OUT_OF_MEMORY \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCToSysSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_SETUP_PARAMS *pParams); + +/*! + * \brief Captures a frame to a buffer in system memory. + * + * This function triggers a frame capture to a buffer in system memory that was + * registered with the ToSysSetUp() API call. + * + * Note that it is possible that the resolution of the desktop changes while + * capturing frames. This should be transparent for the application. + * + * When the resolution changes, the capture session is recreated using the same + * parameters, and necessary buffers are re-allocated. The frame counter is not + * reset. + * + * An application can detect that the resolution changed by comparing the + * dwByteSize member of the ::NVFBC_FRAME_GRAB_INFO against a previous + * frame and/or dwWidth and dwHeight. + * + * During a change of resolution the capture is paused even in asynchronous + * mode. + * + * \param [in] sessionHandle + * FBC session handle. + * \param [in] pParams + * ::NVFBC_TOSYS_GRAB_FRAME_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INVALID_PTR \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X \n + * ::NVFBC_ERR_MUST_RECREATE \n + * \see NvFBCCreateCaptureSession \n + * \see NvFBCToSysSetUp + */ +NVFBCSTATUS NVFBCAPI NvFBCToSysGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_GRAB_FRAME_PARAMS *pParams); + +/*! + * \brief Sets up a capture to video memory session. + * + * This function configures how the capture to video memory with CUDA interop + * should behave. It can be called anytime and several times after the capture + * session has been created. However, it must be called at least once prior + * to start capturing frames. + * + * \param [in] sessionHandle + * FBC session handle. + * + * \param [in] pParams + * ::NVFBC_TOCUDA_SETUP_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_UNSUPPORTED \n + * ::NVFBC_ERR_GL \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCToCudaSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_SETUP_PARAMS *pParams); + +/*! + * \brief Captures a frame to a CUDA device in video memory. + * + * This function triggers a frame capture to a CUDA device in video memory. + * + * Note about changes of resolution: \see NvFBCToSysGrabFrame + * + * \param [in] sessionHandle + * FBC session handle. + * + * \param [in] pParams + * ::NVFBC_TOCUDA_GRAB_FRAME_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INVALID_PTR \n + * ::NVFBC_ERR_CUDA \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X \n + * ::NVFBC_ERR_MUST_RECREATE \n + * \see NvFBCCreateCaptureSession \n + * \see NvFBCToCudaSetUp + */ +NVFBCSTATUS NVFBCAPI NvFBCToCudaGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_GRAB_FRAME_PARAMS *pParams); + +/*! + * \brief Sets up a capture to OpenGL buffer in video memory session. + * + * This function configures how the capture to video memory should behave. + * It can be called anytime and several times after the capture session has been + * created. However, it must be called at least once prior to start capturing + * frames. + * + * \param [in] sessionHandle + * FBC session handle. + * + * \param [in] pParams + * ::NVFBC_TOGL_SETUP_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_UNSUPPORTED \n + * ::NVFBC_ERR_GL \n + * ::NVFBC_ERR_X + */ +NVFBCSTATUS NVFBCAPI NvFBCToGLSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_SETUP_PARAMS *pParams); + +/*! + * \brief Captures a frame to an OpenGL buffer in video memory. + * + * This function triggers a frame capture to a selected resource in video memory. + * + * Note about changes of resolution: \see NvFBCToSysGrabFrame + * + * \param [in] sessionHandle + * FBC session handle. + * + * \param [in] pParams + * ::NVFBC_TOGL_GRAB_FRAME_PARAMS + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_HANDLE \n + * ::NVFBC_ERR_API_VERSION \n + * ::NVFBC_ERR_BAD_REQUEST \n + * ::NVFBC_ERR_CONTEXT \n + * ::NVFBC_ERR_INVALID_PTR \n + * ::NVFBC_ERR_INTERNAL \n + * ::NVFBC_ERR_X \n + * ::NVFBC_ERR_MUST_RECREATE \n + * \see NvFBCCreateCaptureSession \n + * \see NvFBCToCudaSetUp + */ +NVFBCSTATUS NVFBCAPI NvFBCToGLGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_GRAB_FRAME_PARAMS *pParams); + +/*! + * \cond FBC_PFN + * + * Defines API function pointers + */ +typedef const char* (NVFBCAPI* PNVFBCGETLASTERRORSTR)(const NVFBC_SESSION_HANDLE sessionHandle); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCCREATEHANDLE)(NVFBC_SESSION_HANDLE *pSessionHandle, NVFBC_CREATE_HANDLE_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCDESTROYHANDLE)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_HANDLE_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCBINDCONTEXT)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_BIND_CONTEXT_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCRELEASECONTEXT)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_RELEASE_CONTEXT_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCGETSTATUS)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_GET_STATUS_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCCREATECAPTURESESSION)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_CREATE_CAPTURE_SESSION_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCDESTROYCAPTURESESSION)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_CAPTURE_SESSION_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCTOSYSSETUP)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_SETUP_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCTOSYSGRABFRAME)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_GRAB_FRAME_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCTOCUDASETUP)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_SETUP_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCTOCUDAGRABFRAME)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_GRAB_FRAME_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCTOGLSETUP)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_SETUP_PARAMS *pParams); +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCTOGLGRABFRAME)(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_GRAB_FRAME_PARAMS *pParams); + +/// \endcond + +/*! @} FBC_FUNC */ + +/*! + * \ingroup FBC_STRUCT + * + * Structure populated with API function pointers. + */ +typedef struct +{ + uint32_t dwVersion; //!< [in] Must be set to NVFBC_VERSION. + PNVFBCGETLASTERRORSTR nvFBCGetLastErrorStr; //!< [out] Pointer to ::NvFBCGetLastErrorStr(). + PNVFBCCREATEHANDLE nvFBCCreateHandle; //!< [out] Pointer to ::NvFBCCreateHandle(). + PNVFBCDESTROYHANDLE nvFBCDestroyHandle; //!< [out] Pointer to ::NvFBCDestroyHandle(). + PNVFBCGETSTATUS nvFBCGetStatus; //!< [out] Pointer to ::NvFBCGetStatus(). + PNVFBCCREATECAPTURESESSION nvFBCCreateCaptureSession; //!< [out] Pointer to ::NvFBCCreateCaptureSession(). + PNVFBCDESTROYCAPTURESESSION nvFBCDestroyCaptureSession; //!< [out] Pointer to ::NvFBCDestroyCaptureSession(). + PNVFBCTOSYSSETUP nvFBCToSysSetUp; //!< [out] Pointer to ::NvFBCToSysSetUp(). + PNVFBCTOSYSGRABFRAME nvFBCToSysGrabFrame; //!< [out] Pointer to ::NvFBCToSysGrabFrame(). + PNVFBCTOCUDASETUP nvFBCToCudaSetUp; //!< [out] Pointer to ::NvFBCToCudaSetUp(). + PNVFBCTOCUDAGRABFRAME nvFBCToCudaGrabFrame; //!< [out] Pointer to ::NvFBCToCudaGrabFrame(). + void* pad1; //!< [out] Retired. Do not use. + void* pad2; //!< [out] Retired. Do not use. + void* pad3; //!< [out] Retired. Do not use. + PNVFBCBINDCONTEXT nvFBCBindContext; //!< [out] Pointer to ::NvFBCBindContext(). + PNVFBCRELEASECONTEXT nvFBCReleaseContext; //!< [out] Pointer to ::NvFBCReleaseContext(). + void* pad4; //!< [out] Retired. Do not use. + void* pad5; //!< [out] Retired. Do not use. + void* pad6; //!< [out] Retired. Do not use. + void* pad7; //!< [out] Retired. Do not use. + PNVFBCTOGLSETUP nvFBCToGLSetUp; //!< [out] Pointer to ::nvFBCToGLSetup(). + PNVFBCTOGLGRABFRAME nvFBCToGLGrabFrame; //!< [out] Pointer to ::nvFBCToGLGrabFrame(). +} NVFBC_API_FUNCTION_LIST; + +/*! + * \ingroup FBC_FUNC + * + * \brief Entry Points to the NvFBC interface. + * + * Creates an instance of the NvFBC interface, and populates the + * pFunctionList with function pointers to the API routines implemented by + * the NvFBC interface. + * + * \param [out] pFunctionList + * + * \return + * ::NVFBC_SUCCESS \n + * ::NVFBC_ERR_INVALID_PTR \n + * ::NVFBC_ERR_API_VERSION + */ +NVFBCSTATUS NVFBCAPI NvFBCCreateInstance(NVFBC_API_FUNCTION_LIST *pFunctionList); +/*! + * \ingroup FBC_FUNC + * + * Defines function pointer for the ::NvFBCCreateInstance() API call. + */ +typedef NVFBCSTATUS (NVFBCAPI* PNVFBCCREATEINSTANCE)(NVFBC_API_FUNCTION_LIST *pFunctionList); + +#ifdef __cplusplus +} +#endif + +#endif // _NVFBC_H_ diff --git a/include/NvFBCLibrary.hpp b/include/NvFBCLibrary.hpp new file mode 100644 index 0000000..a0bf045 --- /dev/null +++ b/include/NvFBCLibrary.hpp @@ -0,0 +1,245 @@ +#pragma once + +#include "NvFBC.h" +#include + +#include +#include +#include + +class NvFBCLibrary { +public: + ~NvFBCLibrary() { + if(fbc_handle_created) { + NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params; + memset(&destroy_capture_params, 0, sizeof(destroy_capture_params)); + destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER; + nv_fbc_function_list.nvFBCDestroyCaptureSession(nv_fbc_handle, &destroy_capture_params); + + NVFBC_DESTROY_HANDLE_PARAMS destroy_params; + memset(&destroy_params, 0, sizeof(destroy_params)); + destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER; + nv_fbc_function_list.nvFBCDestroyHandle(nv_fbc_handle, &destroy_params); + } + + if(library) + dlclose(library); + } + + bool load() { + if(library) + return true; + + dlerror(); // clear + void *lib = dlopen("libnvidia-fbc.so.1", RTLD_NOW); + if(!lib) { + fprintf(stderr, "Error: failed to load libnvidia-fbc.so.1, error: %s\n", dlerror()); + return false; + } + + nv_fbc_create_instance = (PNVFBCCREATEINSTANCE)dlsym(lib, "NvFBCCreateInstance"); + if(!nv_fbc_create_instance) { + fprintf(stderr, "Error: unable to resolve symbol 'NvFBCCreateInstance'\n"); + dlclose(lib); + return false; + } + + memset(&nv_fbc_function_list, 0, sizeof(nv_fbc_function_list)); + nv_fbc_function_list.dwVersion = NVFBC_VERSION; + NVFBCSTATUS status = nv_fbc_create_instance(&nv_fbc_function_list); + if(status != NVFBC_SUCCESS) { + fprintf(stderr, "Error: failed to create NvFBC instance (status: %d)\n", status); + dlclose(lib); + return false; + } + + library = lib; + return true; + } + + // If |display_to_capture| is "screen", then the entire x11 screen is captured (all displays) + bool create(const char *display_to_capture, uint32_t fps, /*out*/ uint32_t *display_width, /*out*/ uint32_t *display_height, uint32_t x = 0, uint32_t y = 0, uint32_t width = 0, uint32_t height = 0) { + if(!library || !display_to_capture || !display_width || !display_height || fbc_handle_created) + return false; + + const bool capture_region = (x > 0 && y > 0 && width > 0 && height > 0); + + NVFBCSTATUS status; + NVFBC_TRACKING_TYPE tracking_type; + bool capture_session_created = false; + uint32_t output_id = 0; + fbc_handle_created = false; + + NVFBC_CREATE_HANDLE_PARAMS create_params; + memset(&create_params, 0, sizeof(create_params)); + create_params.dwVersion = NVFBC_CREATE_HANDLE_PARAMS_VER; + + status = nv_fbc_function_list.nvFBCCreateHandle(&nv_fbc_handle, &create_params); + if(status != NVFBC_SUCCESS) { + fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle)); + return false; + } + fbc_handle_created = true; + + NVFBC_GET_STATUS_PARAMS status_params; + memset(&status_params, 0, sizeof(status_params)); + status_params.dwVersion = NVFBC_GET_STATUS_PARAMS_VER; + + status = nv_fbc_function_list.nvFBCGetStatus(nv_fbc_handle, &status_params); + if(status != NVFBC_SUCCESS) { + fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle)); + goto error_cleanup; + } + + if(status_params.bCanCreateNow == NVFBC_FALSE) { + fprintf(stderr, "Error: it's not possible to create a capture session on this system\n"); + goto error_cleanup; + } + + tracking_type = strcmp(display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT; + if(tracking_type == NVFBC_TRACKING_OUTPUT) { + if(!status_params.bXRandRAvailable) { + fprintf(stderr, "Error: the xrandr extension is not available\n"); + goto error_cleanup; + } + + if(status_params.bInModeset) { + fprintf(stderr, "Error: the x server is in modeset, unable to record\n"); + goto error_cleanup; + } + + output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, display_to_capture, display_width, display_height); + if(output_id == 0) { + fprintf(stderr, "Error: display '%s' not found\n", display_to_capture); + goto error_cleanup; + } + } else { + *display_width = status_params.screenSize.w; + *display_height = status_params.screenSize.h; + } + + NVFBC_CREATE_CAPTURE_SESSION_PARAMS create_capture_params; + memset(&create_capture_params, 0, sizeof(create_capture_params)); + create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER; + create_capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA; + create_capture_params.bWithCursor = NVFBC_TRUE; // This will need to be disabled when using bAllowDirectCapture + if(capture_region) { + create_capture_params.captureBox = { x, y, width, height }; + *display_width = width; + *display_height = height; + } + create_capture_params.eTrackingType = tracking_type; + //create_capture_params.dwSamplingRateMs = 1000 / fps; + if(tracking_type == NVFBC_TRACKING_OUTPUT) + create_capture_params.dwOutputId = output_id; + + // TODO: Use create_capture_params.bAllowDirectCapture and create_capture_params.bPushModel + status = nv_fbc_function_list.nvFBCCreateCaptureSession(nv_fbc_handle, &create_capture_params); + if(status != NVFBC_SUCCESS) { + fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle)); + goto error_cleanup; + } + capture_session_created = true; + + NVFBC_TOCUDA_SETUP_PARAMS setup_params; + memset(&setup_params, 0, sizeof(setup_params)); + setup_params.dwVersion = NVFBC_TOCUDA_SETUP_PARAMS_VER; + setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA; + + status = nv_fbc_function_list.nvFBCToCudaSetUp(nv_fbc_handle, &setup_params); + if(status != NVFBC_SUCCESS) { + fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle)); + goto error_cleanup; + } + + return true; + + error_cleanup: + if(fbc_handle_created) { + if(capture_session_created) { + NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params; + memset(&destroy_capture_params, 0, sizeof(destroy_capture_params)); + destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER; + nv_fbc_function_list.nvFBCDestroyCaptureSession(nv_fbc_handle, &destroy_capture_params); + } + + NVFBC_DESTROY_HANDLE_PARAMS destroy_params; + memset(&destroy_params, 0, sizeof(destroy_params)); + destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER; + nv_fbc_function_list.nvFBCDestroyHandle(nv_fbc_handle, &destroy_params); + fbc_handle_created = false; + } + output_id = 0; + return false; + } + + bool capture(/*out*/ CUdeviceptr *cu_device_ptr, uint32_t *byte_size) { + if(!library || !fbc_handle_created || !cu_device_ptr || !byte_size) + return false; + + NVFBCSTATUS status; + NVFBC_FRAME_GRAB_INFO frame_info; + memset(&frame_info, 0, sizeof(frame_info)); + + NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab_params; + memset(&grab_params, 0, sizeof(grab_params)); + grab_params.dwVersion = NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER; + grab_params.dwFlags = NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT; + grab_params.pFrameGrabInfo = &frame_info; + grab_params.pCUDADeviceBuffer = cu_device_ptr; + + status = nv_fbc_function_list.nvFBCToCudaGrabFrame(nv_fbc_handle, &grab_params); + if(status != NVFBC_SUCCESS) { + fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle)); + return false; + } + + *byte_size = frame_info.dwByteSize; + // TODO: Check bIsNewFrame + // TODO: Check dwWidth and dwHeight and update size in video output in ffmpeg. This can happen when xrandr is used to change monitor resolution + + return true; + } +private: + static char to_upper(char c) { + if(c >= 'a' && c <= 'z') + return c - 32; + else + return c; + } + + static bool strcase_equals(const char *str1, const char *str2) { + for(;;) { + char c1 = to_upper(*str1); + char c2 = to_upper(*str2); + if(c1 != c2) + return false; + if(c1 == '\0' || c2 == '\0') + return true; + ++str1; + ++str2; + } + } + + // Returns 0 on failure + uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs, uint32_t num_outputs, const char *display_name, uint32_t *display_width, uint32_t *display_height) { + if(!outputs) + return 0; + + for(uint32_t i = 0; i < num_outputs; ++i) { + if(strcase_equals(outputs[i].name, display_name)) { + *display_width = outputs[i].trackedBox.w; + *display_height = outputs[i].trackedBox.h; + return outputs[i].dwId; + } + } + + return 0; + } +private: + void *library = nullptr; + PNVFBCCREATEINSTANCE nv_fbc_create_instance = nullptr; + NVFBC_API_FUNCTION_LIST nv_fbc_function_list; + NVFBC_SESSION_HANDLE nv_fbc_handle; + bool fbc_handle_created = false; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 782e329..5bc69b8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,6 +52,8 @@ extern "C" { #include } +#include "../include/NvFBCLibrary.hpp" + #include //#include @@ -397,12 +399,13 @@ static AVStream *add_audio_stream(AVFormatContext *av_format_context, AVCodec ** static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec **codec, VideoQuality video_quality, - const WindowPixmap &window_pixmap, + int texture_width, int texture_height, int fps) { //*codec = avcodec_find_encoder(codec_id); - *codec = avcodec_find_encoder_by_name("h264_nvenc"); + bool using_hevc = true; + *codec = avcodec_find_encoder_by_name("hevc_nvenc"); if (!*codec) { - *codec = avcodec_find_encoder_by_name("nvenc_h264"); + *codec = avcodec_find_encoder_by_name("nvenc_hevc"); } if (!*codec) { fprintf( @@ -425,8 +428,8 @@ static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec ** assert((*codec)->type == AVMEDIA_TYPE_VIDEO); codec_context->codec_id = (*codec)->id; fprintf(stderr, "codec id: %d\n", (*codec)->id); - codec_context->width = window_pixmap.texture_width & ~1; - codec_context->height = window_pixmap.texture_height & ~1; + codec_context->width = texture_width & ~1; + codec_context->height = texture_height & ~1; codec_context->bit_rate = 7500000 + (codec_context->width * codec_context->height) / 2; // Timebase: This is the fundamental unit of time (in seconds) in terms // of which frame timestamps are represented. For fixed-fps content, @@ -439,7 +442,7 @@ static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec ** codec_context->sample_aspect_ratio.num = 0; codec_context->sample_aspect_ratio.den = 0; codec_context->gop_size = fps * 2; - codec_context->max_b_frames = 2; + codec_context->max_b_frames = using_hevc ? 0 : 2; codec_context->pix_fmt = AV_PIX_FMT_CUDA; codec_context->color_range = AVCOL_RANGE_JPEG; switch(video_quality) { @@ -577,24 +580,26 @@ static void open_video(AVCodec *codec, AVStream *stream, exit(1); } - CUresult res; - CUcontext old_ctx; - res = cuCtxPopCurrent(&old_ctx); - res = cuCtxPushCurrent(*cuda_context); - res = cuGraphicsGLRegisterImage( - cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D, - CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); - // cuGraphicsUnregisterResource(*cuda_graphics_resource); - if (res != CUDA_SUCCESS) { - const char *err_str; - cuGetErrorString(res, &err_str); - fprintf(stderr, - "Error: cuGraphicsGLRegisterImage failed, error %s, texture " - "id: %u\n", - err_str, window_pixmap.target_texture_id); - exit(1); + if(window_pixmap.target_texture_id != 0) { + CUresult res; + CUcontext old_ctx; + res = cuCtxPopCurrent(&old_ctx); + res = cuCtxPushCurrent(*cuda_context); + res = cuGraphicsGLRegisterImage( + cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D, + CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); + // cuGraphicsUnregisterResource(*cuda_graphics_resource); + if (res != CUDA_SUCCESS) { + const char *err_str; + cuGetErrorString(res, &err_str); + fprintf(stderr, + "Error: cuGraphicsGLRegisterImage failed, error %s, texture " + "id: %u\n", + err_str, window_pixmap.target_texture_id); + exit(1); + } + res = cuCtxPopCurrent(&old_ctx); } - res = cuCtxPopCurrent(&old_ctx); } static void close_video(AVStream *video_stream, AVFrame *frame) { @@ -605,9 +610,10 @@ static void close_video(AVStream *video_stream, AVFrame *frame) { static void usage() { fprintf(stderr, "usage: gpu-screen-recorder -w -c -f [-a ] [-q ] [-r ] [-o ]\n"); fprintf(stderr, "OPTIONS:\n"); - fprintf(stderr, " -w Window to record.\n"); + fprintf(stderr, " -w Window to record or a display or \"screen\". The display is the display name in xrandr and if \"screen\" is selected then all displays are recorded and they are recorded in h265 (aka hevc). Recording a display requires a gpu with NvFBC support.\n"); + //fprintf(stderr, " -s The screen region to capture in format WxH+X+Y. This is only applicable when -w is a display or \"screen\". Optional, the entire window/display/screen is recorded by default.\n"); fprintf(stderr, " -c Container format for output file, for example mp4, or flv.\n"); - fprintf(stderr, " -f Framerate to record at.\n"); + fprintf(stderr, " -f Framerate to record at. Clamped to [1,500].\n"); fprintf(stderr, " -a Audio device to record from (pulse audio device). Optional, disabled by default.\n"); fprintf(stderr, " -q Video quality. Should either be 'medium', 'high' or 'ultra'. Optional, set to 'medium' be default.\n"); fprintf(stderr, " -r Replay buffer size in seconds. If this is set, then only the last seconds as set by this option will be stored" @@ -628,17 +634,39 @@ struct Arg { bool optional; }; +static bool is_hex_num(char c) { + return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'); +} + +static bool contains_non_hex_number(const char *str) { + size_t len = strlen(str); + if(len >= 2 && memcmp(str, "0x", 2) == 0) { + str += 2; + len -= 2; + } + + for(size_t i = 0; i < len; ++i) { + char c = str[i]; + if(c == '\0') + return false; + if(!is_hex_num(c)) + return true; + } + return false; +} + int main(int argc, char **argv) { signal(SIGINT, int_handler); std::map args = { { "-w", Arg { nullptr, false } }, + //{ "-s", Arg { nullptr, true } }, { "-c", Arg { nullptr, false } }, { "-f", Arg { nullptr, false } }, { "-a", Arg { nullptr, true } }, { "-q", Arg { nullptr, true } }, { "-o", Arg { nullptr, true } }, - { "-r", Arg { nullptr, true} } + { "-r", Arg { nullptr, true } } }; for(int i = 1; i < argc - 1; i += 2) { @@ -657,13 +685,30 @@ int main(int argc, char **argv) { } } - Window src_window_id = strtol(args["-w"].value, nullptr, 0); + uint32_t region_x = 0; + uint32_t region_y = 0; + uint32_t region_width = 0; + uint32_t region_height = 0; + + /* + TODO: Fix this. Doesn't work for some reason + const char *screen_region = args["-s"].value; + if(screen_region) { + if(sscanf(screen_region, "%ux%u+%u+%u", ®ion_x, ®ion_y, ®ion_width, ®ion_height) != 4) { + fprintf(stderr, "Invalid value for -s '%s', expected a value in format WxH+X+Y\n", screen_region); + return 1; + } + } + */ + const char *container_format = args["-c"].value; int fps = atoi(args["-f"].value); - if(fps <= 0 || fps > 255) { - fprintf(stderr, "invalid fps argument: %s\n", args["-f"].value); + if(fps == 0) { + fprintf(stderr, "Invalid fps argument: %s\n", args["-f"].value); return 1; } + if(fps > 500) + fps = 500; const char *quality_str = args["-q"].value; if(!quality_str) @@ -681,12 +726,6 @@ int main(int argc, char **argv) { usage(); } - const char *filename = args["-o"].value; - if(!filename) - filename = "/dev/stdout"; - - const double target_fps = 1.0 / (double)fps; - int replay_buffer_size_secs = -1; const char *replay_buffer_size_secs_str = args["-r"].value; if(replay_buffer_size_secs_str) { @@ -698,70 +737,133 @@ int main(int argc, char **argv) { replay_buffer_size_secs += 5; // Add a few seconds to account of lost packets because of non-keyframe packets skipped } - Display *dpy = XOpenDisplay(nullptr); - if (!dpy) { - fprintf(stderr, "Error: Failed to open display\n"); - return 1; + CUresult res; + + res = cuInit(0); + if(res != CUDA_SUCCESS) { + fprintf(stderr, "Error: cuInit failed (result: %d)\n", res); + return {}; } - bool has_name_pixmap = x11_supports_composite_named_window_pixmap(dpy); - if (!has_name_pixmap) { - fprintf(stderr, "Error: XCompositeNameWindowPixmap is not supported by " - "your X11 server\n"); + CUdevice cu_dev; + res = cuDeviceGet(&cu_dev, 0); + if(res != CUDA_SUCCESS) { + fprintf(stderr, "Unable to get CUDA device (result: %d)\n", res); return 1; } - XWindowAttributes attr; - if (!XGetWindowAttributes(dpy, src_window_id, &attr)) { - fprintf(stderr, "Error: Invalid window id: %lu\n", src_window_id); + CUcontext cu_ctx; + res = cuCtxCreate_v2(&cu_ctx, CU_CTX_SCHED_AUTO, cu_dev); + if(res != CUDA_SUCCESS) { + fprintf(stderr, "Unable to create CUDA context (result: %d)\n", res); return 1; } - XCompositeRedirectWindow(dpy, src_window_id, CompositeRedirectAutomatic); + uint32_t window_width = 0; + uint32_t window_height = 0; - // glXMakeContextCurrent(Display *dpy, GLXDrawable draw, GLXDrawable read, - // GLXContext ctx) - if (!glfwInit()) { - fprintf(stderr, "Error: Failed to initialize glfw\n"); - return 1; - } + NvFBCLibrary nv_fbc_library; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + const char *window_str = args["-w"].value; + Window src_window_id = None; + if(contains_non_hex_number(window_str)) { + if(!nv_fbc_library.load()) + return 1; - GLFWwindow *window = glfwCreateWindow(1, 1, "gpu-screen-recorder", nullptr, nullptr); - if (!window) { - fprintf(stderr, "Error: Failed to create glfw window\n"); - glfwTerminate(); - return 1; + if(!nv_fbc_library.create(window_str, fps, &window_width, &window_height, region_x, region_y, region_width, region_height)) + return 1; + } else { + src_window_id = strtol(window_str, nullptr, 0); + if(src_window_id == None && errno == EINVAL) { + fprintf(stderr, "Invalid window number %s\n", window_str); + usage(); + } } - glfwMakeContextCurrent(window); - glfwSwapInterval(0); - glfwHideWindow(window); - -//#if defined(DEBUG) - XSetErrorHandler(x11_error_handler); - XSetIOErrorHandler(x11_io_error_handler); -//#endif + const char *filename = args["-o"].value; + if(!filename) + filename = "/dev/stdout"; - glewExperimental = GL_TRUE; - GLenum nGlewError = glewInit(); - if (nGlewError != GLEW_OK) { - fprintf(stderr, "%s - Error initializing GLEW! %s\n", __FUNCTION__, - glewGetErrorString(nGlewError)); - return 1; - } - glGetError(); // to clear the error caused deep in GLEW + const double target_fps = 1.0 / (double)fps; WindowPixmap window_pixmap; - if (!recreate_window_pixmap(dpy, src_window_id, window_pixmap)) { - fprintf(stderr, "Error: Failed to create glx pixmap for window: %lu\n", - src_window_id); - return 1; + Display *dpy = nullptr; + GLFWwindow *window = nullptr; + if(src_window_id) { + dpy = XOpenDisplay(nullptr); + if (!dpy) { + fprintf(stderr, "Error: Failed to open display\n"); + return 1; + } + + bool has_name_pixmap = x11_supports_composite_named_window_pixmap(dpy); + if (!has_name_pixmap) { + fprintf(stderr, "Error: XCompositeNameWindowPixmap is not supported by " + "your X11 server\n"); + return 1; + } + + XWindowAttributes attr; + if (!XGetWindowAttributes(dpy, src_window_id, &attr)) { + fprintf(stderr, "Error: Invalid window id: %lu\n", src_window_id); + return 1; + } + + XCompositeRedirectWindow(dpy, src_window_id, CompositeRedirectAutomatic); + + // glXMakeContextCurrent(Display *dpy, GLXDrawable draw, GLXDrawable read, + // GLXContext ctx) + if (!glfwInit()) { + fprintf(stderr, "Error: Failed to initialize glfw\n"); + return 1; + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + + window = glfwCreateWindow(1, 1, "gpu-screen-recorder", nullptr, nullptr); + if (!window) { + fprintf(stderr, "Error: Failed to create glfw window\n"); + glfwTerminate(); + return 1; + } + + glfwMakeContextCurrent(window); + glfwSwapInterval(0); + glfwHideWindow(window); + + //#if defined(DEBUG) + XSetErrorHandler(x11_error_handler); + XSetIOErrorHandler(x11_io_error_handler); + //#endif + + glewExperimental = GL_TRUE; + GLenum nGlewError = glewInit(); + if (nGlewError != GLEW_OK) { + fprintf(stderr, "%s - Error initializing GLEW! %s\n", __FUNCTION__, + glewGetErrorString(nGlewError)); + return 1; + } + glGetError(); // to clear the error caused deep in GLEW + + if (!recreate_window_pixmap(dpy, src_window_id, window_pixmap)) { + fprintf(stderr, "Error: Failed to create glx pixmap for window: %lu\n", + src_window_id); + return 1; + } + } else { + window_pixmap.texture_id = 0; + window_pixmap.target_texture_id = 0; + window_pixmap.texture_width = window_width; + window_pixmap.texture_height = window_height; + + if (!glfwInit()) { + fprintf(stderr, "Error: Failed to initialize glfw\n"); + return 1; + } } // Video start @@ -780,8 +882,7 @@ int main(int argc, char **argv) { AVCodec *video_codec; AVStream *video_stream = - add_video_stream(av_format_context, &video_codec, quality, - window_pixmap, fps); + add_video_stream(av_format_context, &video_codec, quality, window_pixmap.texture_width, window_pixmap.texture_height, fps); if (!video_stream) { fprintf(stderr, "Error: Failed to create video stream\n"); return 1; @@ -795,11 +896,6 @@ int main(int argc, char **argv) { return 1; } - if (cuInit(0) < 0) { - fprintf(stderr, "Error: cuInit failed\n"); - return {}; - } - AVBufferRef *device_ctx; CUgraphicsResource cuda_graphics_resource; open_video(video_codec, video_stream, window_pixmap, &device_ctx, @@ -841,7 +937,8 @@ int main(int argc, char **argv) { // av_frame_free(&rgb_frame); // avcodec_close(av_codec_context); - XSelectInput(dpy, src_window_id, StructureNotifyMask); + if(dpy) + XSelectInput(dpy, src_window_id, StructureNotifyMask); /* int damage_event; @@ -857,20 +954,21 @@ int main(int argc, char **argv) { int frame_count = 0; - CUresult res; CUcontext old_ctx; - res = cuCtxPopCurrent(&old_ctx); - res = cuCtxPushCurrent(*cuda_context); - - // Get texture - res = cuGraphicsResourceSetMapFlags( - cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); - res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0); - - // Map texture to cuda array CUarray mapped_array; - res = cuGraphicsSubResourceGetMappedArray(&mapped_array, - cuda_graphics_resource, 0, 0); + if(src_window_id) { + res = cuCtxPopCurrent(&old_ctx); + res = cuCtxPushCurrent(*cuda_context); + + // Get texture + res = cuGraphicsResourceSetMapFlags( + cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); + res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0); + + // Map texture to cuda array + res = cuGraphicsSubResourceGetMappedArray(&mapped_array, + cuda_graphics_resource, 0, 0); + } // Release texture // res = cuGraphicsUnmapResources(1, &cuda_graphics_resource, 0); @@ -896,11 +994,12 @@ int main(int argc, char **argv) { exit(1); } - XWindowAttributes xwa; - XGetWindowAttributes(dpy, src_window_id, &xwa); - int window_width = xwa.width; - int window_height = xwa.height; - + if(dpy) { + XWindowAttributes xwa; + XGetWindowAttributes(dpy, src_window_id, &xwa); + window_width = xwa.width; + window_height = xwa.height; + } int original_window_width = window_width; int original_window_height = window_height; @@ -999,67 +1098,70 @@ int main(int argc, char **argv) { while (running) { double frame_start = glfwGetTime(); glfwPollEvents(); - - glClear(GL_COLOR_BUFFER_BIT); - if (XCheckTypedWindowEvent(dpy, src_window_id, ConfigureNotify, &e) && e.xconfigure.window == src_window_id) { - // Window resize - if(e.xconfigure.width != window_width || e.xconfigure.height != window_height) { - window_width = e.xconfigure.width; - window_height = e.xconfigure.height; - window_resize_timer = glfwGetTime(); - window_resized = true; - } - } + if(window) + glClear(GL_COLOR_BUFFER_BIT); redraw = true; - const double window_resize_timeout = 1.0; // 1 second - if(window_resized && glfwGetTime() - window_resize_timer >= window_resize_timeout) { - window_resized = false; - fprintf(stderr, "Resize window!\n"); - recreate_window_pixmap(dpy, src_window_id, window_pixmap); - // Resolution must be a multiple of two - //video_stream->codec->width = window_pixmap.texture_width & ~1; - //video_stream->codec->height = window_pixmap.texture_height & ~1; - - cuGraphicsUnregisterResource(cuda_graphics_resource); - res = cuGraphicsGLRegisterImage( - &cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D, - CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); - if (res != CUDA_SUCCESS) { - const char *err_str; - cuGetErrorString(res, &err_str); - fprintf(stderr, - "Error: cuGraphicsGLRegisterImage failed, error %s, texture " - "id: %u\n", - err_str, window_pixmap.target_texture_id); - running = false; - break; + if(src_window_id) { + if (XCheckTypedWindowEvent(dpy, src_window_id, ConfigureNotify, &e) && e.xconfigure.window == src_window_id) { + // Window resize + if(e.xconfigure.width != window_width || e.xconfigure.height != window_height) { + window_width = e.xconfigure.width; + window_height = e.xconfigure.height; + window_resize_timer = glfwGetTime(); + window_resized = true; + } } - res = cuGraphicsResourceSetMapFlags( - cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); - res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0); - res = cuGraphicsSubResourceGetMappedArray(&mapped_array, cuda_graphics_resource, 0, 0); + const double window_resize_timeout = 1.0; // 1 second + if(window_resized && glfwGetTime() - window_resize_timer >= window_resize_timeout) { + window_resized = false; + fprintf(stderr, "Resize window!\n"); + recreate_window_pixmap(dpy, src_window_id, window_pixmap); + // Resolution must be a multiple of two + //video_stream->codec->width = window_pixmap.texture_width & ~1; + //video_stream->codec->height = window_pixmap.texture_height & ~1; + + cuGraphicsUnregisterResource(cuda_graphics_resource); + res = cuGraphicsGLRegisterImage( + &cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D, + CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY); + if (res != CUDA_SUCCESS) { + const char *err_str; + cuGetErrorString(res, &err_str); + fprintf(stderr, + "Error: cuGraphicsGLRegisterImage failed, error %s, texture " + "id: %u\n", + err_str, window_pixmap.target_texture_id); + running = false; + break; + } - av_frame_unref(frame); - if (av_hwframe_get_buffer(video_stream->codec->hw_frames_ctx, frame, 0) < 0) { - fprintf(stderr, "Error: av_hwframe_get_buffer failed\n"); - running = false; - break; - } + res = cuGraphicsResourceSetMapFlags( + cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); + res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0); + res = cuGraphicsSubResourceGetMappedArray(&mapped_array, cuda_graphics_resource, 0, 0); - frame->pts = frame_count; + av_frame_unref(frame); + if (av_hwframe_get_buffer(video_stream->codec->hw_frames_ctx, frame, 0) < 0) { + fprintf(stderr, "Error: av_hwframe_get_buffer failed\n"); + running = false; + break; + } - if(window_width < original_window_width) - frame->width = window_pixmap.texture_width & ~1; - else - frame->width = original_window_width; + frame->pts = frame_count; - if(window_height < original_window_height) - frame->height = window_pixmap.texture_height & ~1; - else - frame->height = original_window_height; + if(window_width < original_window_width) + frame->width = window_pixmap.texture_width & ~1; + else + frame->width = original_window_width; + + if(window_height < original_window_height) + frame->height = window_pixmap.texture_height & ~1; + else + frame->height = original_window_height; + } } ++fps_counter; @@ -1078,33 +1180,64 @@ int main(int argc, char **argv) { if (frame_time_overflow >= 0.0) { frame_timer_start = time_now - frame_time_overflow; + bool frame_captured = true; if(redraw) { redraw = false; - // TODO: Use a framebuffer instead. glCopyImageSubData requires - // opengl 4.2 - glCopyImageSubData( - window_pixmap.texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, - window_pixmap.target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, - window_pixmap.texture_width, window_pixmap.texture_height, 1); - glfwSwapBuffers(window); - // int err = glGetError(); - // fprintf(stderr, "error: %d\n", err); - - CUDA_MEMCPY2D memcpy_struct; - memcpy_struct.srcXInBytes = 0; - memcpy_struct.srcY = 0; - memcpy_struct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY; - - memcpy_struct.dstXInBytes = 0; - memcpy_struct.dstY = 0; - memcpy_struct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE; - - memcpy_struct.srcArray = mapped_array; - memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; - memcpy_struct.dstPitch = frame->linesize[0]; - memcpy_struct.WidthInBytes = frame->width * 4; - memcpy_struct.Height = frame->height; - cuMemcpy2D(&memcpy_struct); + if(src_window_id) { + // TODO: Use a framebuffer instead. glCopyImageSubData requires + // opengl 4.2 + glCopyImageSubData( + window_pixmap.texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, + window_pixmap.target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, + window_pixmap.texture_width, window_pixmap.texture_height, 1); + glfwSwapBuffers(window); + // int err = glGetError(); + // fprintf(stderr, "error: %d\n", err); + + CUDA_MEMCPY2D memcpy_struct; + memcpy_struct.srcXInBytes = 0; + memcpy_struct.srcY = 0; + memcpy_struct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY; + + memcpy_struct.dstXInBytes = 0; + memcpy_struct.dstY = 0; + memcpy_struct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE; + + memcpy_struct.srcArray = mapped_array; + memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; + memcpy_struct.dstPitch = frame->linesize[0]; + memcpy_struct.WidthInBytes = frame->width * 4; + memcpy_struct.Height = frame->height; + cuMemcpy2D(&memcpy_struct); + + frame_captured = true; + } else { + uint32_t byte_size; + CUdeviceptr src_cu_device_ptr; + frame_captured = nv_fbc_library.capture(&src_cu_device_ptr, &byte_size); + if(frame_captured) { + // TODO: Is it possible to bypass this copy? + /* + CUDA_MEMCPY2D memcpy_struct; + memcpy_struct.srcXInBytes = 0; + memcpy_struct.srcY = 0; + memcpy_struct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE; + + memcpy_struct.dstXInBytes = 0; + memcpy_struct.dstY = 0; + memcpy_struct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE; + + memcpy_struct.srcDevice = src_cu_device_ptr; + memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0]; + memcpy_struct.dstPitch = frame->linesize[0]; + memcpy_struct.WidthInBytes = frame->width * 4; + memcpy_struct.Height = frame->height; + cuMemcpy2D(&memcpy_struct); + */ + cuMemcpyDtoD((CUdeviceptr)frame->data[0], src_cu_device_ptr, byte_size); + //frame->data[0] = (uint8_t*)src_cu_device_ptr; + } + } // res = cuCtxPopCurrent(&old_ctx); } @@ -1196,6 +1329,8 @@ int main(int argc, char **argv) { // avformat_free_context(av_format_context); // cleanup_window_pixmap(dpy, window_pixmap); - XCompositeUnredirectWindow(dpy, src_window_id, CompositeRedirectAutomatic); - XCloseDisplay(dpy); + if(dpy) { + XCompositeUnredirectWindow(dpy, src_window_id, CompositeRedirectAutomatic); + XCloseDisplay(dpy); + } } -- cgit v1.2.3