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.