DRM+EGL rendering: disable VSYNC

We are experimenting with low latency rendering on the TX2 using DRM+EGL. According to the docs, it seems like eglSwapBuffers() should return immediately if we configure mailbox mode by setting FIFO length to 0. However, what we observe is that eglSwapBuffers() blocks until roughly 16.7 ms has passed since it returned last time (we have a 60Hz monitor), which is consistent with a VSYNC behavior. We have also tried setting the swap interval to 0 by calling eglSwapInterval with argument 0, but that did not seem to have any effect. We also note that calling eglGetConfigAttrib with either EGL_MAX_SWAP_INTERVAL or EGL_MIN_SWAP_INTERVAL both return 0, suggesting that the swap interval is already in fact 0?

Can you confirm that it is possible to disable VSYNC on TX2? If so, then what are we doing wrong in our code?

Here is our code:

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl32.h>
#include <fcntl.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>

#define MAX_DEVICES 16

using namespace std::chrono_literals;

static PFNEGLQUERYDEVICESEXTPROC                eglQueryDevicesEXT                = NULL;
static PFNEGLQUERYDEVICESTRINGEXTPROC           eglQueryDeviceStringEXT           = NULL;
static PFNEGLGETPLATFORMDISPLAYEXTPROC          eglGetPlatformDisplayEXT          = NULL;
static PFNEGLGETOUTPUTLAYERSEXTPROC             eglGetOutputLayersEXT             = NULL;
static PFNEGLCREATESTREAMKHRPROC                eglCreateStreamKHR                = NULL;
static PFNEGLDESTROYSTREAMKHRPROC               eglDestroyStreamKHR               = NULL;
static PFNEGLSTREAMCONSUMEROUTPUTEXTPROC        eglStreamConsumerOutputEXT        = NULL;
static PFNEGLCREATESTREAMPRODUCERSURFACEKHRPROC eglCreateStreamProducerSurfaceKHR = NULL;

#define CHECK(cond)                                                                                                \
    do                                                                                                             \
    {                                                                                                              \
        if (!(cond))                                                                                               \
        {                                                                                                          \
            throw std::runtime_error(std::string("CHECK FAILED at ") + __FILE__ + ":" + std::to_string(__LINE__)); \
        }                                                                                                          \
    } while (0)

template <typename PtrType>
static void load_func(PtrType & func, const char * name)
{
    auto * addr = eglGetProcAddress(name);
    CHECK(addr);
    func = reinterpret_cast<PtrType>(addr);
    CHECK(func);
}

// Extension checking utility
static void CheckExtension(const char * exts, const char * ext)
{
    int          extLen = (int)strlen(ext);
    const char * end    = exts + strlen(exts);

    bool found = false;
    while (exts < end)
    {
        while (*exts == ' ') exts++;

        int n = strcspn(exts, " ");
        if ((extLen == n) && (strncmp(ext, exts, n) == 0))
        {
            found = true;
        };

        exts += n;
    }
    if (!found)
    {
        std::cout << "Missing required extension: " << ext << std::endl;
        CHECK(0);
    }
}

class DrmEglContext
{
public:
    DrmEglContext()
    {
        // setup DRM & EGL
        load_func(eglQueryDevicesEXT, "eglQueryDevicesEXT");
        load_func(eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT");
        load_func(eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT");
        load_func(eglGetOutputLayersEXT, "eglGetOutputLayersEXT");
        load_func(eglCreateStreamKHR, "eglCreateStreamKHR");
        load_func(eglDestroyStreamKHR, "eglDestroyStreamKHR");
        load_func(eglStreamConsumerOutputEXT, "eglStreamConsumerOutputEXT");
        load_func(eglCreateStreamProducerSurfaceKHR, "eglCreateStreamProducerSurfaceKHR");

        // Query device
        EGLDeviceEXT egl_devs[MAX_DEVICES];
        int          n;
        EGLBoolean   ret = eglQueryDevicesEXT(MAX_DEVICES, egl_devs, &n);
        CHECK(ret != EGL_FALSE && n > 0);

        _eglDev = egl_devs[0];

        //-----------------------------------------------------------------------------------------
        // setup DRM
        //-----------------------------------------------------------------------------------------

        // Obtain and open DRM device file
        const char * drm_name = eglQueryDeviceStringEXT(_eglDev, EGL_DRM_DEVICE_FILE_EXT);
        CHECK(drm_name);
        std::cout << "DRM device corresponding to egl device: " << drm_name << std::endl;

        if (drm_name[0] == '/')
        {
            _drmFd = open(drm_name, O_RDWR, NULL);
        }
        else
        {
            _drmFd = drmOpen(drm_name, NULL);
        }

        CHECK(_drmFd >= 0);

        // Obtain DRM-KMS resources
        _drmResources = drmModeGetResources(_drmFd);
        CHECK(_drmResources);

        // If a specific crtc was requested, make sure it exists
        CHECK(_drmResources->count_crtcs > 0);

        // Query info for requested connector
        for (int conn = 0; conn < _drmResources->count_connectors; ++conn)
        {
            _drmConnector = drmModeGetConnector(_drmFd, _drmResources->connectors[conn]);

            if (_drmConnector)
            {
                if (_drmConnector->connection == DRM_MODE_CONNECTED && _drmConnector->count_modes > 0) break;

                drmModeFreeConnector(_drmConnector);
                _drmConnector = NULL;
            }
        }
        CHECK(_drmConnector);

        // If there is already an encoder attached to the connector, choose
        //   it unless not compatible with crtc/plane
        _drmEncoder                  = drmModeGetEncoder(_drmFd, _drmConnector->encoder_id);
        const uint32_t all_crtc_mask = (1 << _drmResources->count_crtcs) - 1;
        if (_drmEncoder && !(_drmEncoder->possible_crtcs & all_crtc_mask))
        {
            // had an encoder but it was not good enough
            drmModeFreeEncoder(_drmEncoder);
            _drmEncoder = nullptr;
        }

        // If we didn't have a suitable encoder, find one
        if (!_drmEncoder)
        {
            for (int i = 0; i < _drmConnector->count_encoders; ++i)
            {
                _drmEncoder = drmModeGetEncoder(_drmFd, _drmConnector->encoders[i]);
                if (_drmEncoder)
                {
                    if (all_crtc_mask & _drmEncoder->possible_crtcs)
                    {
                        // we found a sufficiently good encoder, break and save intersection crtc set
                        break;
                    }
                    drmModeFreeEncoder(_drmEncoder);
                    _drmEncoder = NULL;
                }
            }
        }

        CHECK(_drmEncoder);
        const uint32_t crtc_mask = _drmEncoder->possible_crtcs & all_crtc_mask;
        CHECK(crtc_mask);

        int crtc_index = -1;

        // Select a suitable crtc.
        for (int i = 0; i < _drmResources->count_crtcs; ++i)
        {
            if (crtc_mask & (1 << i))
            {
                crtc_index   = i;
                _drm_crtc_id = _drmResources->crtcs[i];
                if (_drmResources->crtcs[i] == _drmEncoder->crtc_id)
                {
                    break;
                }
            }
        }

        CHECK(crtc_index >= 0);

        // Query info for crtc
        _drmCrtc = drmModeGetCrtc(_drmFd, _drm_crtc_id);
        CHECK(_drmCrtc);

        _width  = (int)_drmCrtc->mode.hdisplay;
        _height = (int)_drmCrtc->mode.vdisplay;

        // Setup plane
        drmModePlaneResPtr pPlaneRes = drmModeGetPlaneResources(_drmFd);
        CHECK(pPlaneRes != NULL);

        int plane_id{0};
        for (int i = 0; i < (int)pPlaneRes->count_planes; i++)
        {
            drmModePlanePtr pPlane = drmModeGetPlane(_drmFd, pPlaneRes->planes[i]);
            CHECK(pPlane);

            uint32_t crtcs = pPlane->possible_crtcs;
            drmModeFreePlane(pPlane);

            if (crtcs & (1 << crtc_index))
            {
                plane_id = pPlaneRes->planes[i];
                break;
            }
        }

        drmModeFreePlaneResources(pPlaneRes);

        CHECK(plane_id != 0);

        ret = drmModeSetPlane(_drmFd, plane_id, _drm_crtc_id, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1);
        CHECK(ret == 0);

        //---------------------------------------------------------------------------------------------
        // setup EGL
        //---------------------------------------------------------------------------------------------
        {
            // Obtain and initialize EGLDisplay
            _eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, (void *)_eglDev, NULL);
            CHECK(_eglDisplay != EGL_NO_DISPLAY);

            EGLint     major, minor;
            EGLBoolean ret = eglInitialize(_eglDisplay, &major, &minor);
            CHECK(ret != EGL_FALSE);

            std::cout << "Initialized EGL (version= " << major << "." << minor << ")" << std::endl;

            // Check for stream_consumer_egloutput + output_drm support
            const char * dpy_exts = eglQueryString(_eglDisplay, EGL_EXTENSIONS);
            const char * dev_exts = eglQueryDeviceStringEXT(_eglDev, EGL_EXTENSIONS);

            CheckExtension(dpy_exts, "EGL_EXT_output_base");
            CheckExtension(dev_exts, "EGL_EXT_device_drm");
            CheckExtension(dpy_exts, "EGL_EXT_output_drm");
            CheckExtension(dpy_exts, "EGL_EXT_stream_consumer_egloutput");

            // Choose a config and create a context
            EGLint cfg_attr[] = {
                EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_ALPHA_SIZE, 1,
                EGL_NONE};

            EGLConfig egl_cfg;
            int       n;
            ret = eglChooseConfig(_eglDisplay, cfg_attr, &egl_cfg, 1, &n);
            CHECK(ret != EGL_FALSE && n > 0);

            EGLint ctx_attr[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
            eglBindAPI(EGL_OPENGL_ES_API);

            _eglContext = eglCreateContext(_eglDisplay, egl_cfg, EGL_NO_CONTEXT, ctx_attr);
            CHECK(_eglContext != EGL_NO_CONTEXT);

            // Get the layer for this crtc/plane
            EGLAttrib layer_attr[] = {EGL_NONE, EGL_NONE, EGL_NONE};
            layer_attr[0]          = EGL_DRM_CRTC_EXT;
            layer_attr[1]          = (EGLAttrib)_drm_crtc_id;

            EGLOutputLayerEXT egl_lyr;
            ret = eglGetOutputLayersEXT(_eglDisplay, layer_attr, &egl_lyr, 1, &n);
            CHECK(ret != EGL_FALSE && n > 0);

            // Create a stream and connect to the output
            int          fifo          = 0;
            EGLint       stream_attr[] = {EGL_STREAM_FIFO_LENGTH_KHR, fifo, EGL_NONE};
            EGLStreamKHR egl_str       = eglCreateStreamKHR(_eglDisplay, stream_attr);
            CHECK(egl_str != EGL_NO_STREAM_KHR);

            ret = eglStreamConsumerOutputEXT(_eglDisplay, egl_str, egl_lyr);
            CHECK(ret != EGL_FALSE);

            // Create a surface to feed the stream
            EGLint srf_attr[] = {EGL_WIDTH, _width, EGL_HEIGHT, _height, EGL_NONE};
            _eglSurface       = eglCreateStreamProducerSurfaceKHR(_eglDisplay, egl_cfg, egl_str, srf_attr);

            CHECK(_eglSurface != EGL_NO_SURFACE);

            // Make current
            ret = eglMakeCurrent(_eglDisplay, _eglSurface, _eglSurface, _eglContext);
            CHECK(ret != EGL_FALSE);

            EGLint val;
            ret = eglGetConfigAttrib(_eglDisplay, egl_cfg, EGL_MAX_SWAP_INTERVAL, &val);
            if (ret)
            {
                std::cout << "EGL_MAX_SWAP_INTERVAL: " << val << std::endl;
            }
            CHECK(ret != EGL_FALSE);
            ret = eglGetConfigAttrib(_eglDisplay, egl_cfg, EGL_MIN_SWAP_INTERVAL, &val);
            if (ret)
            {
                std::cout << "EGL_MIN_SWAP_INTERVAL: " << val << std::endl;
            }
            CHECK(ret != EGL_FALSE);

            EGLint interval = 0;
            ret             = eglSwapInterval(_eglDisplay, interval);
            CHECK(ret != EGL_FALSE);
        }
    }
    void swapBuffers()
    {
        eglSwapBuffers(_eglDisplay, _eglSurface);
    }

private:
    EGLDeviceEXT       _eglDev;
    int                _drmFd;
    drmModeRes *       _drmResources{nullptr};
    drmModeConnector * _drmConnector{nullptr};
    drmModeEncoder *   _drmEncoder{nullptr};
    drmModeCrtc *      _drmCrtc{nullptr};
    uint32_t           _drm_crtc_id;

    int _width{};
    int _height{};

    EGLDisplay _eglDisplay{EGL_NO_DISPLAY};
    EGLSurface _eglSurface{EGL_NO_SURFACE};
    EGLContext _eglContext{EGL_NO_CONTEXT};

};  // END class

struct Stats
{
    Stats(const std::string & name) : _name(name)
    {
    }

    void add(unsigned long val_us)
    {
        sum_us += val_us;
        if (val_us < min_us)
        {
            min_us = val_us;
        }
        if (val_us > max_us)
        {
            max_us = val_us;
        }
        count++;
    }
    void procPrint()
    {
        if (count > 444)
        {
            std::cout << _name << " avg: " << float(sum_us) / count << " us\n"
                      << std::string(_name.size(), ' ') << " min: " << float(min_us) << " us\n"
                      << std::string(_name.size(), ' ') << " max: " << float(max_us) << " us" << std::endl;
            count  = 0;
            sum_us = 0;
            max_us = 0;
            min_us = 1UL << 31;
        }
    }
    std::string   _name;
    unsigned long count{0};
    unsigned long max_us{0};
    unsigned long min_us{1UL << 31};
    unsigned long sum_us{0};
};

int main()
{
    DrmEglContext context;

    bool white = false;

    using Clk = std::chrono::high_resolution_clock;
    Stats           intervalStats{"Interval"};
    Stats           elapsedStats{"Elapsed "};
    Clk::time_point last_stop = Clk::now();

    while (true)
    {
        float value = white ? 1.0f : 0.0f;
        glClearColor(value, value, value, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        auto start = Clk::now();
        context.swapBuffers();
        auto stop = Clk::now();

        intervalStats.add(std::chrono::duration_cast<std::chrono::microseconds>(stop - last_stop).count());
        last_stop = stop;

        elapsedStats.add(std::chrono::duration_cast<std::chrono::microseconds>(stop - start).count());

        intervalStats.procPrint();
        elapsedStats.procPrint();

        white = !white;

        std::this_thread::sleep_for(8ms);
    }

    return 0;
}

We are using L4T 32.7.3.

Hi,
The mode is not supported. eglSwapBuffers() is synchronized with VSYNC and disabling it is not supported.

That is a pity. Thanks for clarifying though!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.