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.
    <stream> must be an EGLStream in the EGL_STREAM_STATE_CREATED_KHR
    state.  eglGetStreamFileDescriptorKHR may be called at most once
    for any <stream>.

    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 <stream>.  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 <dpy> is not a valid
          initialized EGLDisplay

        - EGL_BAD_STREAM_KHR is generated if <stream> is not a valid
          EGLStreamKHR handle created for <dpy>.

        - EGL_BAD_STATE_KHR is generated if <stream> is not in the
          EGL_STREAM_STATE_CREATED_KHR state or if
          eglGetStreamFileDescriptorKHR() has previously been called
          on this <stream>.

        - EGL_BAD_STATE_KHR is generated if <stream> 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.  <file_descriptor> 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
    <file_descriptor> or the file descriptor from which
    <file_descriptor> 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 <dpy> is not a valid
          initialized EGLDisplay

        - EGL_BAD_ATTRIBUTE is generated if <file_descriptor> is
          EGL_NO_FILE_DESCRIPTOR_KHR.

        - EGL_BAD_ATTRIBUTE is generated if <file_descriptor> is
          not an open file descriptor referring to an EGLStream
          created on the same Native Display as <dpy>.

        - EGL_BAD_ATTRIBUTE is generated if <file_descriptor> has
          already been used to create a stream handle via a previous
          call to eglCreateStreamFromFileDescriptorKHR.

        - EGL_BAD_STATE_KHR is generated if <stream> 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 <stdio.h>
            #include <stdlib.h>
            #include <unistd.h>
            #include <sys/types.h>
            #include <sys/socket.h>
            #include <sys/un.h>

            #define FATAL_ERROR() exit(1)
            #define SOCKET_NAME "/tmp/example_socket"

            /* Send <fd_to_send> (a file descriptor) to another process */
            /* over a unix domain socket named <socket_name>.           */
            /* <socket_name> 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 <socket_name> 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
