EGLStream to custom DRM/KMS driver, how to change pixel memory layout?

Hi,

I have a display pipeline on Xavier that requires a custom DRM/KMS driver for GPU output, independent of the normal NVDC display pathway. I’ve created a driver that does this, and can successfully hook it up as an EGLStream consumer (EGL_EXT_device_drm) and move frames through the stream.

The trouble is that GPU is rendering the stream’s buffers in the Block Linear layout, but I need a Pitch Linear buffer. I can allocate the DRM framebuffer outside the driver using NvBufferLayout_Pitch layout memory with NvBufferCreateEx, but EGL ignores this. It sends its own buffers to my driver for displaying the stream. I can’t figure out how to indicate what the layout of those buffers ought to be.

Does the EGLStream producer on the Xavier communicate with the consumer to negotiate what pixel memory layout to use in any way and how? Is there some way to change that layout of data sent to the stream?

Thanks,

Kurt

Hi,
We have the implementation

tegra_multimedia_api\samples\common\classes\NvEglRenderer.cpp

It should be close to your usecase. Please share us a patch on either sample(maybe 00_video_decode) so that we can reproduce the issue for further checking.

I was able to get the same behavior using evdi. In this setup, a userspace program will raster the stream to the normal display.

### build test setup ###

# install build deps
sudo apt-get install -y cmake libboost-filesystem-dev libev-dev qtbase5-dev

# build module
cd ~
git clone https://github.com/kekiefer/evdi.git
cd evdi
make

# build monitor simulator
cd ~
git clone https://github.com/mlukaszek/evdipp.git
mkdir build
cd build
export DEST=$HOME/target
cmake -DCMAKE_INSTALL_PREFIX=$DEST -DCMAKE_INSTALL_RPATH=$DEST/lib ..
make install

# build eglstream test program
cd ~
git clone https://github.com/kekiefer/eglstreams-kms-example
cd eglstreams-kms-example
make

# grab an example edid
cd ~
wget https://raw.githubusercontent.com/akatrevorjay/edid-generator/master/3840x2160.bin
### run test setup ###

sudo systemctl stop gdm
sudo loginctl terminate-seat seat0
sudo pkill -9 X

# load evdi module
sudo insmod ~/evdi/module/evdi.ko

# start display simulator/viewer
sudo QT_QPA_EGLFS_INTEGRATION=eglfs_kms_egldevice ~/target/bin/monitorsim ~/3840x2160.bin -platform eglfs

# render to the virtual display through evdi
~/eglstreams-kms-example/eglstreams-kms-example

Hi,
Looks like the sample does not use any NvBuffer APIs. Since it is about NvBufferCreateEx(), please help to use tegra_multimedia_api samples for reproducing it.

I have updated the example to allocate the DRM FB with NvBuffer instead. As noted, it makes no difference, since EGL will commit its own color buffer for each page flip.

Unfortunately for the tegra_multimedia_api, the NvEglRenderer does not do the work required to support EGL_EXT_device_drm, so it would be a major overhaul.

Kurt

I’m still trying to figure this out.

Can you tell me if the closed tegra libdrm has any support for configuring linear buffers to be sent to nvdc? Or is there a way to configure EGL to use user-allocated NvBuffers for its front and back color render buffers?

Hi,
Below is the implementation in NvEglRenderer:

int
NvEglRenderer::renderInternal()
{
    EGLImageKHR hEglImage;
    bool frame_is_late = false;

    EGLSyncKHR egl_sync;
    int iErr;
    hEglImage = NvEGLImageFromFd(egl_display, render_fd);
    if (!hEglImage)
    {
        COMP_ERROR_MSG("Could not get EglImage from fd. Not rendering");
        return -1;
    }

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, hEglImage);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    iErr = glGetError();
    if (iErr != GL_NO_ERROR)
    {
        COMP_ERROR_MSG("glDrawArrays arrays failed:" << iErr);
        return -1;
    }
    egl_sync = eglCreateSyncKHR(egl_display, EGL_SYNC_FENCE_KHR, NULL);
    if (egl_sync == EGL_NO_SYNC_KHR)
    {
        COMP_ERROR_MSG("eglCreateSyncKHR() failed");
        return -1;
    }
    if (last_render_time.tv_sec != 0)
    {
        pthread_mutex_lock(&render_lock);
        last_render_time.tv_sec += render_time_sec;
        last_render_time.tv_nsec += render_time_nsec;
        last_render_time.tv_sec += last_render_time.tv_nsec / 1000000000UL;
        last_render_time.tv_nsec %= 1000000000UL;

        if (isProfilingEnabled())
        {
            struct timeval cur_time;
            gettimeofday(&cur_time, NULL);
            if ((cur_time.tv_sec * 1000000.0 + cur_time.tv_usec) >
                    (last_render_time.tv_sec * 1000000.0 +
                     last_render_time.tv_nsec / 1000.0))
            {
                frame_is_late = true;
            }
        }

        pthread_cond_timedwait(&render_cond, &render_lock,
                &last_render_time);

        pthread_mutex_unlock(&render_lock);
    }
    else
    {
        struct timeval now;

        gettimeofday(&now, NULL);
        last_render_time.tv_sec = now.tv_sec;
        last_render_time.tv_nsec = now.tv_usec * 1000L;
    }
    eglSwapBuffers(egl_display, egl_surface);
    if (eglGetError() != EGL_SUCCESS)
    {
        COMP_ERROR_MSG("Got Error in eglSwapBuffers " << eglGetError());
        return -1;
    }
    if (eglClientWaitSyncKHR (egl_display, egl_sync,
                EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR) == EGL_FALSE)
    {
        COMP_ERROR_MSG("eglClientWaitSyncKHR failed!");
    }

    if (eglDestroySyncKHR(egl_display, egl_sync) != EGL_TRUE)
    {
        COMP_ERROR_MSG("eglDestroySyncKHR failed!");
    }
    NvDestroyEGLImage(egl_display, hEglImage);

    if (strlen(overlay_str) != 0)
    {
        XSetForeground(x_display, gc,
                        BlackPixel(x_display, DefaultScreen(x_display)));
        XSetFont(x_display, gc, fontinfo->fid);
        XDrawString(x_display, x_window, gc, overlay_str_x_offset,
                    overlay_str_y_offset, overlay_str, strlen(overlay_str));
    }

    profiler.finishProcessing(0, frame_is_late);

    return 0;
}

Each NvBuffer has a fd and it can get EGLImage from the fd.

Hi Dane,

Thanks for the response.

The trouble is that EGL is creating its own front and back buffers when I set up a context to render to the DRM device. I understand that the NvBuffers are backed by this reference, as this is what is used during eglSwapBuffers to import the buffer into the DRM FB. That gets back to the original issue – the buffer is still laid out in a block linear format.

How can I control the format of these buffers as they are rendered? Reducing the block height to 1 or 2 GOBs (like TRM notes is optimal for scanout) instead of the default 16 GOBs, or simply rendering the surface as pitch linear?

Thanks for your continued help,

Kurt

Hi,
This looks similar to the post. The user has shared a test code, we will try to reproduce it first. Will update once we have new findings.

Hi,
We have clarified the other issue:
[url]How to render the EGL image (from fd) to a frame buffer? - Jetson Nano - NVIDIA Developer Forums
Please check if it is same rootcause as this one.

No, that’s not relevant to this issue. EGLStreams definitely rendering to a surface:

/*
     * Create an EGLSurface as the producer of the EGLStream.  Once
     * the stream's producer and consumer are defined, the stream is
     * ready to use.  eglSwapBuffers() calls for the EGLSurface will
     * deliver to the stream's consumer, i.e., the DRM KMS plane
     * corresponding to the EGLOutputLayer.
     */

    eglSurface = pEglCreateStreamProducerSurfaceKHR(eglDpy, eglConfig,
                                                    eglStream, surfaceAttribs);
    if (eglSurface == EGL_NO_SURFACE) {
        Fatal("Unable to create EGLSurface stream producer.\n");
    }

    /*
     * Make current to the EGLSurface, so that OpenGL rendering is
     * directed to it.
     */

    ret = eglMakeCurrent(eglDpy, eglSurface, eglSurface, eglContext);

The trouble is that the surface it renders is always block linear. I want to know how to either A) alter the block linear parameters to reduce the block height or B) just have it output a pitch linear surface. There must be a way to control this since the TRM indicates that smaller block heights are used for the drm-nvdc.

Hi,
Could you run 00_video_decode and check the surface after calling eglSwapBuffers(egl_display, egl_surface); in NvEglRenderer.cpp?

render_fd is in pitch linear and hEglImage should also be pitch linear. If you observe block linear format, probably the following functions make the conversion.

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, hEglImage);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
eglSwapBuffers(egl_display, egl_surface);

Surface handling in that example is not relevant to my use-case. NvEglRenderer backs its surfaces with X11 windows and a call to eglCreateWindowSurface. EglStreams uses the EglDevice extensions (as also used by QT and Weston), and EglStreamProducerSurface sets things up such that the EGL creates its own surfaces which get send to the display device on eglSwapBuffers. If I could allocate the backing surfaces myself as NvEglRenderer can do with eglCreateWindowSurface, then presumably I’d have the opportunity to change the surface format, but it doesn’t work like that. The attempt to allocate a backing surface by creating a DRM framebuffer backed by nvbuf gets ignored.

Hi,
Please check if below steps are working in your case:

Allocate one NvBuffer(src_buf) in RGBA/block linear
Copy your surface to the NVBuffer
Allocate another NvBuffer(dst_buf) in RGBA/pitch linear
Call NvBufferTransform(src_buf, dst_buf) to convert RGBA/block linear to RGBA/pitch linear

Yes, this is one of the first things I tried to verify that the surface was being rendered block linear. Following the TRM description of the format, I wrote a python script to do the conversion of a surface (looped back by the hardware), and it does result in a pitch linear surface. Copying that loopback data into a mapped block linear buffer and using NvBufferTransform also comes out correctly.

Hi,
With further discussion with core teams, it is confirmed ‘we don’t support pitch linear in GL/EGL APIs’.

OK, then how about any way at all to reduce the block height to 1 or 2 GOBs (like TRM notes is optimal for scanout)?

Hi,

Sorry for the late. We are carefully checking this. It is about the hardware design and may not be public. Will update once there is further information.

Hi,
We have confirmed the block height cannot be customized. Reducing the block height to 1 or 2 GOBs is not supported.

Thanks for the confirmation.

Please clarify if each of these two restrictions is because of the capabilities of the hardware, or if it is the software support.