Name KHR_stream_cross_process_fd Name Strings EGL_KHR_stream_cross_process_fd Contributors Acorn Pooley Ian Stewart Contacts Acorn Pooley, NVIDIA (apooley 'at' nvidia.com) Notice Copyright (c) 2011-2013 The Khronos Group Inc. Copyright terms at http://www.khronos.org/registry/speccopyright.html Status Complete. Approved by the EGL Working Group on June 6, 2012. Approved by the Khronos Board of Promoters on July 27, 2012. Version Version 8 - June 5, 2012 Number EGL Extension #41 Dependencies Requires EGL 1.2. Requires EGL_KHR_stream This extension is written based on the wording of the EGL 1.2 specification. This extension interacts with the following extensions if they are also present: EGL_KHR_stream_producer_eglsurface EGL_KHR_stream_consumer_gltexture EGL_KHR_stream_producer_aldatalocator EGL_KHR_stream_fifo Overview This extension allows an EGLStreamKHR object handle to be duplicated into another process so that the EGLStream producer can be in one process while the EGLStream consumer can be in another process. Duplicating the EGLStreamKHR object handle into another process is peformed in 3 steps 1) Get a file descriptor associated with the EGLStream. 2) Duplicate the file descriptor into another process. 3) Create an EGLStreamKHR from the duplicated file descriptor in the other process. The file descriptor is obtained by calling eglGetStreamFileDescriptorKHR(). Duplicating the file descriptor into another process is outside the scope of this extension. See issue #1 for an example of how to do this on a Linux system. The EGLStreamKHR object handle is created in the second process by passing the file descriptor to the eglCreateStreamFromFileDescriptorKHR() function. This must be done while the EGLStream is in the EGL_STREAM_STATE_CREATED_KHR state. Once the EGLStreamKHR object handle is created in the second process, it refers to the same EGLStream as the EGLStreamKHR object handle in the original process. A consumer can be associated with the EGLStream from either process. A producer can be associated with the EGLStream from either process. New Types Represents a native OS file descriptor. typedef int EGLNativeFileDescriptorKHR New Procedures and Functions EGLNativeFileDescriptorKHR eglGetStreamFileDescriptorKHR( EGLDisplay dpy, EGLStreamKHR stream); EGLStreamKHR eglCreateStreamFromFileDescriptorKHR( EGLDisplay dpy, EGLNativeFileDescriptorKHR file_descriptor); New Tokens Returned from eglGetStreamFileDescriptorKHR on error. #define EGL_NO_FILE_DESCRIPTOR_KHR ((EGLNativeFileDescriptorKHR)(-1)) Add a new section just after section "3.10.1 Creating an EGLStream" in the EGL_KHR_stream extension 3.10.1.1 Duplicating an EGLStream from a file descriptor Call EGLNativeFileDescriptorKHR eglGetStreamFileDescriptorKHR( EGLDisplay dpy, EGLStreamKHR stream); to create a file descriptor that refers to the EGLStream. must be an EGLStream in the EGL_STREAM_STATE_CREATED_KHR state. eglGetStreamFileDescriptorKHR may be called at most once for any . On success a file descriptor is returned which can be used to create a duplicate EGLStreamKHR handle which refers to the same underlying EGLStream as . This file descriptor and file descriptors duplicated from it should only be used in a call to eglCreateStreamFromFileDescriptorKHR() and/or a call to close(). In particular reads, writes, and other operations on the file descriptor result in undefined behavior. On failure the functions returns EGL_NO_FILE_DESCRIPTOR_KHR and generates an error - EGL_BAD_DISPLAY is generated if is not a valid initialized EGLDisplay - EGL_BAD_STREAM_KHR is generated if is not a valid EGLStreamKHR handle created for . - EGL_BAD_STATE_KHR is generated if is not in the EGL_STREAM_STATE_CREATED_KHR state or if eglGetStreamFileDescriptorKHR() has previously been called on this . - EGL_BAD_STATE_KHR is generated if was not created by eglCreateStreamKHR (e.g. if it was created by eglCreateStreamFromFileDescriptorKHR). The file descriptor returned by eglGetStreamFileDescriptorKHR can be duplicated into a different process address space using system specific mechanisms outside the scope of this specification. (For example, on a Linux system it can be sent over a UNIX domain socket using sendmsg/recvmsg.) Call EGLStreamKHR eglCreateStreamFromFileDescriptorKHR( EGLDisplay dpy, EGLNativeFileDescriptorKHR file_descriptor); to create an EGLStreamKHR handle. must be a file descriptor returned by eglGetStreamFileDescriptorKHR or a file descriptor duplicated from such a file descriptor (possibly in a different process). The EGLStream must be in the EGL_STREAM_STATE_CREATED_KHR or EGL_STREAM_STATE_CONNECTING_KHR state. On success an EGLStreamKHR handle is returned. This EGLStreamKHR handle refers to the same EGLStream which was used to create the or the file descriptor from which was duplicated. After the file descriptor is passed to eglCreateStreamFromFileDescriptorKHR it may no longer be used to create a new EGLStream. On failure EGL_NO_STREAM_KHR is returned and an error is generated. - EGL_BAD_DISPLAY is generated if is not a valid initialized EGLDisplay - EGL_BAD_ATTRIBUTE is generated if is EGL_NO_FILE_DESCRIPTOR_KHR. - EGL_BAD_ATTRIBUTE is generated if is not an open file descriptor referring to an EGLStream created on the same Native Display as . - EGL_BAD_ATTRIBUTE is generated if has already been used to create a stream handle via a previous call to eglCreateStreamFromFileDescriptorKHR. - EGL_BAD_STATE_KHR is generated if is not in the EGL_STREAM_STATE_CREATED_KHR or EGL_STREAM_STATE_CONNECTING_KHR state. The application should close the file descriptor and any file descriptors duplicated from it once eglCreateStreamFromFileDescriptorKHR has returned. Open file descriptors will consume resources until they are closed or until all processes that hold them open have terminated. Closing the file descriptors after calling eglCreateStreamFromFileDescriptorKHR will not affect the associated EGLStream. If an application calls eglGetStreamFileDescriptorKHR and then determines that the file descriptor and/or the EGLStream is no longer needed then it may (and should) close the file descriptor and destroy the EGLStream (this is not considered an error). If a process which has successfully connected a consumer or producer to the EGLStream terminates (normally or abnormally) then the EGLStream state becomes EGL_STREAM_STATE_DISCONNECTED_KHR. If a process has created an EGLStreamKHR handle either with eglCreateStreamKHR or eglCreateStreamFromFileDescriptorKHR but has not connected a producer or consumer to the stream, and this process terminates (normally or abnormally) then this has no effect on the EGLStream. Interactions with the EGL_KHR_stream_producer_eglsurface extension. The eglCreateStreamProducerSurfaceKHR() function can be called from either the process that created the original EGLStreamKHR, or from the process which called eglCreateStreamFromFileDescriptorKHR. Interactions with the EGL_KHR_stream_consumer_gltexture extension. The eglStreamConsumerGLTextureExternalKHR() function can be called from either the process that created the original EGLStreamKHR, or from the process which called eglCreateStreamFromFileDescriptorKHR. The eglStreamConsumerAcquireKHR() and eglStreamConsumerReleaseKHR() functions must be called from the same process that calls eglStreamConsumerGLTextureExternalKHR() (or else they will fail and generate an EGL_BAD_ACCESS error). Interactions with the EGL_KHR_stream_producer_aldatalocator extension. The CreateMediaPlayer() method can be called from either the process that created the original EGLStreamKHR, or from the process which called eglCreateStreamFromFileDescriptorKHR. Interactions with the EGL_KHR_stream_fifo extension. The queries for EGL_STREAM_FIFO_LENGTH_KHR, EGL_STREAM_TIME_NOW_KHR, EGL_STREAM_TIME_CONSUMER_KHR, and EGL_STREAM_TIME_PRODUCER_KHR can be made from either process. The time values returned by the EGL_STREAM_TIME_NOW_KHR query will be consistent between the two processes (i.e. if queried at the same time from both processes, the same value (plus or minus some margin of error) will be returned). Interactions with the EGL_NV_stream_cross_process_fd extension. These extensions may both exist on the same implementation and are functionally equivalent. Mixing and matching file descriptors from one extension with functions from the other is allowed. Interactions with the EGL_NV_stream_sync extension. The eglCreateStreamSyncNV() function may only be called from a process which has successfully connected a consumer to the EGLStream. Otherwise eglCreateStreamSyncNV generates a EGL_BAD_ACCESS error. Issues 1. How does the application transfer the file descriptor to another process? RESOLVED: This is outside the scope of this extension. The application can use existing operating system mechanisms for duplicating the file descriptor into another process. For example on Linux a file descriptor can be sent over a UNIX domain socket using the following code (call send_fd() to send the file descriptor, and receive_fd() in the other process to receive the file descriptor). (The following code is placed into the public domain by its author, Acorn Pooley) #include #include #include #include #include #include #define FATAL_ERROR() exit(1) #define SOCKET_NAME "/tmp/example_socket" /* Send (a file descriptor) to another process */ /* over a unix domain socket named . */ /* can be any nonexistant filename. */ void send_fd(const char *socket_name, int fd_to_send) { int sock_fd; struct sockaddr_un sock_addr; struct msghdr msg; struct iovec iov[1]; char ctrl_buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg = NULL; sock_fd = socket(PF_UNIX, SOCK_STREAM, 0); if (sock_fd < 0) FATAL_ERROR(); memset(&sock_addr, 0, sizeof(struct sockaddr_un)); sock_addr.sun_family = AF_UNIX; strncpy(sock_addr.sun_path, socket_name, sizeof(sock_addr.sun_path)-1); while (connect(sock_fd, (const struct sockaddr*)&sock_addr, sizeof(struct sockaddr_un))) { printf("Waiting for reciever\n"); sleep(1); } memset(&msg, 0, sizeof(msg)); iov[0].iov_len = 1; // must send at least 1 byte iov[0].iov_base = "x"; // any byte value (value ignored) msg.msg_iov = iov; msg.msg_iovlen = 1; memset(ctrl_buf, 0, sizeof(ctrl_buf)); msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); *((int *) CMSG_DATA(cmsg)) = fd_to_send; msg.msg_controllen = cmsg->cmsg_len; if (sendmsg(sock_fd, &msg, 0) <= 0) FATAL_ERROR(); close(sock_fd); } /* Listen on a unix domain socket named and */ /* receive a file descriptor from another process. */ /* Returns the file descriptor. Note: the integer value */ /* of the file descriptor may be different from the */ /* integer value in the other process, but the file */ /* descriptors in each process will refer to the same file */ /* object in the kernel. */ int receive_fd(const char *socket_name) { int listen_fd; struct sockaddr_un sock_addr; int connect_fd; struct sockaddr_un connect_addr; socklen_t connect_addr_len = 0; struct msghdr msg; struct iovec iov[1]; char msg_buf[1]; char ctrl_buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg; listen_fd = socket(PF_UNIX, SOCK_STREAM, 0); if (listen_fd < 0) FATAL_ERROR(); unlink(socket_name); memset(&sock_addr, 0, sizeof(struct sockaddr_un)); sock_addr.sun_family = AF_UNIX; strncpy(sock_addr.sun_path, socket_name, sizeof(sock_addr.sun_path)-1); if (bind(listen_fd, (const struct sockaddr*)&sock_addr, sizeof(struct sockaddr_un))) FATAL_ERROR(); if (listen(listen_fd, 1)) FATAL_ERROR(); connect_fd = accept( listen_fd, (struct sockaddr *)&connect_addr, &connect_addr_len); close(listen_fd); unlink(socket_name); if (connect_fd < 0) FATAL_ERROR(); memset(&msg, 0, sizeof(msg)); iov[0].iov_base = msg_buf; iov[0].iov_len = sizeof(msg_buf); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); if (recvmsg(connect_fd, &msg, 0) <= 0) FATAL_ERROR(); cmsg = CMSG_FIRSTHDR(&msg); if (!cmsg) FATAL_ERROR(); if (cmsg->cmsg_level != SOL_SOCKET) FATAL_ERROR(); if (cmsg->cmsg_type != SCM_RIGHTS) FATAL_ERROR(); return *(int *) CMSG_DATA(cmsg); } 2. Does this extension work with all consumers and all producers? RESOLVED: This extension is compatible with EGL_KHR_stream_producer_eglsurface EGL_KHR_stream_consumer_gltexture EGL_KHR_stream_producer_aldatalocator EGL_KHR_stream_fifo as described in the Interactions sections. Whether an EGLStream that has been duplicated into another process will work with other types of consumers and producers should be mentioned in the description of those consumers and producers. 3. Does EGL create a file descriptor for every EGLStream when the EGLStream is created, or is the file descriptor be created when eglGetStreamFileDescriptorKHR is called? RESOLVED: This is implementation dependent. However, recommended behavior is to create the file descriptor when eglGetStreamFileDescriptorKHR is called. This avoids polluting the file descriptor namespace (which may have a limited size on some systems) with descriptors for EGLStreams which will only be used inside a single process. The eglGetStreamFileDescriptorKHR function will fail and generate an EGL_BAD_ALLOC error if it is unable to allocate a file descriptor for the EGLStream. 4. Should the EGLStream be created from the file descriptor with the existing eglCreateStreamKHR function or with a new function dedicated to that purpose? The advantage of creating a new function is that a new parameter can be added with a specific type. This is not really necessary for this extension since a file descriptor is a small integer which can fit into the EGLint in the eglCreateStreamKHR attrib_list. However, other similar extensions may be invented that use other types of handles (not file descriptors) which may not fit into an EGLint. Creating a dedicated function allows these other extensions to use a similar function. RESOLVED: Use a different function. 5. How does this extension interact with the EGL_NV_stream_cross_process_fd extension? RESOLVED: These extensions may both exist on the same implementation and are functionally equivalent. Mixing and matching file descriptors from one extension with functions from the other is allowed. 6. Who should close the file descriptors and when? There is no way for the EGL implementation to safely close all the file descriptors associated with an EGLStream because some of them may have been created using OS specific duping mechanisms. Also, the app may need to close a descriptor if it runs into an error before it is able to call eglCreateStreamFromFileDescriptorKHR. Therefore the application will need to close at least some of the created file descriptors. To make things simple and clear it is therefore left up to the app to close all the file descriptors. The app is not *required* to do this, but not doing so will "leak" file descriptors which will consume resources until the process terminates. Allowing the app to close all file descriptors as soon as eglCreateStreamFromFileDescriptorKHR returns simplifies the app (no need to keep track of open file descriptors). RESOLVED: Application is responsible for closing all file descriptors. They can be safely closed as soon as eglCreateStreamFromFileDescriptorKHR returns. 7. What happens when an invalid file descriptor is passed to eglCreateStreamFromFileDescriptorKHR()? RESOLVED: The implementation must detect this and generate an error. If the file descriptor refers to a file then the implementation may not modify the file, change the seek location, or otherwise modify the file descriptor. 8. What happens if one process hangs or crashes? RESOLVED: If either the consumer's or producer's process terminates (normally or abnormally) the EGL implementation must notice this and place the EGLStream in EGL_STREAM_STATE_DISCONNECTED_KHR state. If the consumer is blocked in a eglStreamConsumerAcquireKHR() call, the call will generate an EGL_BAD_STATE_KHR message and return EGL_FALSE. If the consumer process has created a reusable sync object with eglCreateStreamSyncNV() and is blocking in a eglClientWaitSyncKHR() call, the call will block until the timeout runs out. If the producer process "hangs" (e.g. enters an infinite loop, blocks in a kernel call, etc) then the consumer process will continue to function. The consumer will continue to use the last frame that the producer produced. If the producer has not yet produced a frame then the EGLStream will be in EGL_STREAM_STATE_EMPTY_KHR state and no frame will be available. The consumer process can block in some situations: - If a EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR is set then eglStreamConsumerAcquireKHR() will block until the timeout runs out (or indefinitely if timeout is negative). - eglClientWaitSyncKHR() will block until the timeout runs out. If the consumer process "hangs" then the producer process will continue to function. If the EGLStream has had EGL_STREAM_FIFO_LENGTH_KHR set to a nonzero value then the producer will block indefinitely when it fills the fifo and tries to insert another frame. Otherwise the producer will not block (as new frames are inserted into the EGLStream old ones will be discarded). Revision History #8 (June 5, 2012) Acorn Pooley - rename from XXX to KHR #7 (June 5, 2012) Acorn Pooley - Add issue 8. - Better define EGLStream behavior when a process terminates. - Add Interactions with the EGL_NV_stream_sync extension. #6 (April 20, 2012) Ian Stewart - Fix extension/function names in interactions - Removed references to NV_stream_sync. - Changed interactions with NV_stream_cross_process_fd such that they are interchangeable. #5 (April 18, 2012) Acorn Pooley - Add issue 7 - define errors generated when passing invalid file descriptors #4 (January 29, 2012) Acorn Pooley - Fork EGL_XXX_stream_cross_process_fd.txt from EGL_NV_stream_cross_process_fd.txt to make changes suggested by working group. - add issues 4, 5, and 6. #3 (January 6, 2012) Acorn Pooley - fix typos (EGLImage -> EGLStream) #2 (December 7, 2011) Acorn Pooley - Upload to Khronos for review #1 (September 27, 2011) Acorn Pooley - Initial draft # vim:ai:ts=4:sts=4:expandtab:textwidth=70