How to use the GPU of the NXP Layerscape LS1028A with Yocto
How to use the GPU of the NXP Layerscape LS1028A with Yocto
Yocto Project Configuration
The most modern and NXP-recommended method for developing with the LS1028A is the Layerscape Linux Distribution POC (LLDP) based on Yocto. Unlike the LSDK (which uses Flexbuild), LLDP uses Yocto/Bitbake and is the long-term supported path.
1 Host Requirements
Requirement
Details
Operating System
Ubuntu 20.04 LTS (Focal) — official recommendation
RAM
Minimum 8 GB (16 GB+ recommended)
Disk Space
Minimum 100 GB free
CPU
Minimum 4 cores (more cores = faster builds)
Tools
git, repo, python3, wget, curl, build-essential
Install dependencies on Ubuntu:
sudo apt-get update && sudo apt-get install -y gawk wget git diffstat unzip texinfo gcc-multilib \
build-essential chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping \
python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev pylint xterm rsync curl locales zstd liblz4-tool \
repo ca-certificates
# Configure git (required by repo)
git config --global user.name "Your Name"
git config --global user.email "
[email protected]"
2 Download the LLDP Repository (Yocto for LS1028A)
# Create working directory
mkdir ~/lldp-ls1028a && cd ~/lldp-ls1028a
# Initialize repo with the LLDP manifest (Kirkstone, kernel 5.15)
repo init -u https://github.com/nxp-qoriq/yocto-sdk.git \
-b kirkstone \
-m ls-5.15.71-2.2.0_distro.xml
# Sync all repositories (may tak e 30-60 minutes)
repo sync
Note: For the latest version (LLDP L6.1.1, kernel 6.1), check the updated manifest at: https://github.com/nxp-qoriq/yocto-sdk
3 Set Up the Build Environment for LS1028A
# Initialize Yocto environment for the LS1028A
# (run from the lldp-ls1028a/ directory)
DISTRO=fsl-qoriq-distro MACHINE=ls1028ardb source distro-setup-env
# This automatically creates and enters the build directory
# You are now in: ~/lldp-ls1028a/build_ls1028ardb/
4 local.conf File — GPU Configuration
The conf/local.conf file inside the build directory controls compilation options. For the LS1028A with GPU, verify or add:
# Edit the configuration file
nano conf/local.conf
Key parameters for GPU and desktop:
# Target machine
MACHINE = "ls1028ardb"
# Distribution with GPU and Wayland support
DISTRO = "fsl-qoriq-distro"
# Enable display and GPU features
DISTRO_FEATURES:append = " wayland opengl"
# GPU driver: use Etnaviv (open-source) for LS1028A
# DO NOT use imx-gpu-viv (i.MX only, requires ARCH_MXC)
PREFERRED_PROVIDER_virtual/libgl = "mesa"
PREFERRED_PROVIDER_virtual/libgles1 = "mesa"
PREFERRED_PROVIDER_virtual/libgles2 = "mesa"
PREFERRED_PROVIDER_virtual/egl = "mesa"
# Enable OpenCL support via Vivante GPU
IMAGE_INSTALL:append = " clinfo"
# Accept NXP proprietary licenses (required for GPU firmware)
LICENSE_FLAGS_ACCEPTED = "nxp-proprietary"
# Parallel build threads (adjust to your host PC)
BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j 8"
# (Optional) Use ccache to speed up recompilations
INHERIT += "ccache"
5 Required Yocto Layers (bblayers.conf)
Verify that conf/bblayers.conf includes these layers:
cat conf/bblayers.conf
It must contain at least:
BBLAYERS ?= " \
${BSPDIR}/sources/poky/meta \
${BSPDIR}/sources/poky/meta-poky \
${BSPDIR}/sources/meta-openembedded/meta-oe \
${BSPDIR}/sources/meta-openembedded/meta-multimedia \
${BSPDIR}/sources/meta-openembedded/meta-python \
${BSPDIR}/sources/meta-openembedded/meta-networking \
${BSPDIR}/sources/meta-freescale \
${BSPDIR}/sources/meta-qoriq \
${BSPDIR}/sources/meta-nxp-desktop \
"
The meta-nxp-desktop layer is what provides GPU support for the LS1028A with the ls-image-desktop image.
6 Build the Image with GPU Support
# Recommended: Desktop image with full GPU support (LS1028A only)
# Includes: GNOME desktop, Weston/Wayland, Vivante GPU drivers, OpenCL
bitbake ls-image-desktop
# Alternative: download all packages first before building
# (useful for catching network errors before the long build)
bitbake ls-image-desktop --runall fetch
bitbake ls-image-desktop
# Minimal: main image without desktop (no GPU by default)
bitbake ls-image-main
# Lite: minimal image
bitbake ls-image-lite
Estimated build time: Between 4 and 8 hours on the first build (depending on host hardware). Incremental builds are much faster.
7 Install the Image to the SD Card
After compilation, output files are located in: tmp/deploy/images/ls1028ardb/
# Identify the SD card (verify with lsblk)
lsblk
# Install image using flex-installer (included in the SDK)
flex-installer \
-b tmp/deploy/images/ls1028ardb/boot_ls1028ardb.tgz \
-f tmp/deploy/images/ls1028ardb/firmware_ls1028ardb_sdboot.img \
-r tmp/deploy/images/ls1028ardb/ls-image-desktop-ls1028ardb.tar.zst \
-d /dev/sdX # replace with your SD device
Practical Example: OpenCL Application on LS1028A
This example shows how to compile and run a program that uses the GC7000UL GPU to add two vectors with OpenCL.
8.1 Source Code: vector_add.cl (OpenCL Kernel)
Create the kernel file on the LS1028A board:
# On the LS1028A (via serial or SSH)
cat > /home/root/vector_add.cl << 'EOF'
__kernel void vector_add(
__global const float* a,
__global const float* b,
__global float* c,
const int n)
{
int gid = get_global_id(0);
if (gid < n) {
c[gid] = a[gid] + b[gid];
}
}
8.2 Source Code: vector_add.c (OpenCL Host)
cat > /home/root/vector_add.c << 'EOF'
#include
#include
#include
#include
#define VECTOR_SIZE 1024
int main() {
cl_platform_id platform;
cl_device_id device;
cl_context context;
cl_command_queue queue;
cl_program program;
cl_kernel kernel;
cl_mem buf_a, buf_b, buf_c;
cl_int err;
// 1. Get Vivante GPU platform and device
err = clGetPlatformIDs(1, &platform, NULL);
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
// Print device name
char device_name[128];
clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(device_name), device_name, NULL);
printf("GPU detected: %s\n", device_name);
// 2. Create context and command queue
context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
queue = clCreateCommandQueue(context, device, 0, &err);
// 3. Read kernel source from file
FILE* f = fopen("vector_add.cl", "r");
fseek(f, 0, SEEK_END);
size_t src_size = ftell(f);
rewind(f);
char* src=(char*)malloc(src_size + 1);
fread(src, 1, src_size, f);
src[src_size] = '\0';
fclose(f);
// 4. Compile OpenCL program
program = clCreateProgramWithSource(context, 1, (const char**)&src, &src_size, &err);
err = clBuildProgram(program, 1, &device, NULL, NULL, NULL);
if (err != CL_SUCCESS) {
char log[2048];
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
sizeof(log), log, NULL);
printf("Compilation error:\n%s\n", log);
return 1;
}
kernel = clCreateKernel(program, "vector_add", &err);
// 5. Prepare input data
float* h_a = (float*)malloc(VECTOR_SIZE * sizeof(float));
float* h_b = (float*)malloc(VECTOR_SIZE * sizeof(float));
float* h_c = (float*)malloc(VECTOR_SIZE * sizeof(float));
for (int i = 0; i < VECTOR_SIZE; i++) {
h_a[i] = (float)i;
h_b[i] = (float)(VECTOR_SIZE - i);
}
// 6. Create GPU buffers
buf_a = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
VECTOR_SIZE * sizeof(float), h_a, &err);
buf_b = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
VECTOR_SIZE * sizeof(float), h_b, &err);
buf_c = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &err);
// 7. Set kernel arguments and execute on GPU
int n = VECTOR_SIZE;
clSetKernelArg(kernel, 0, sizeof(cl_mem), &buf_a);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &buf_b);
clSetKernelArg(kernel, 2, sizeof(cl_mem), &buf_c);
clSetKernelArg(kernel, 3, sizeof(int), &n);
size_t global_size = VECTOR_SIZE;
err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
&global_size, NULL, 0, NULL, NULL);
clFinish(queue);
// 8. Read result
clEnqueueReadBuffer(queue, buf_c, CL_TRUE, 0,
VECTOR_SIZE * sizeof(float), h_c, 0, NULL, NULL);
// 9. Verify result (each element should equal VECTOR_SIZE = 1024)
int ok = 1;
for (int i = 0; i < VECTOR_SIZE; i++) {
if (h_c[i] != (float)VECTOR_SIZE) { ok = 0; break; }
}
printf("Result: %s\n", ok ? "CORRECT - GPU works!" : "CALCULATION ERROR");
printf("Example: a[0]=%.0f + b[0]=%.0f = c[0]=%.0f\n",
h_a[0], h_b[0], h_c[0]);
// Free resources
clReleaseMemObject(buf_a); clReleaseMemObject(buf_b); clReleaseMemObject(buf_c);
clReleaseKernel(kernel); clReleaseProgram(program);
clReleaseCommandQueue(queue); clReleaseContext(context);
free(h_a); free(h_b); free(h_c); free(src);
return 0;
}
8.3 Compile and Run on the LS1028A
On the LS1028A board (connected via serial or SSH):
# Install build tools and OpenCL headers
apt-get install -y gcc clinfo ocl-icd-libopencl1 opencl-headers
# Compile
gcc -o vector_add vector_add.c -lOpenCL -I/usr/include
# Run
./vector_add
Expected output:
GPU detected: Vivante OpenCL Device GC7000UL.6202.0000
Result: CORRECT - GPU works!
Example: a[0]=0 + b[0]=1024 = c[0]=1024
12.4 Yocto Recipe to Include the Example in the Image
To include the example directly in the Yocto image, create a recipe in your custom layer:
mkdir -p ~/lldp-ls1028a/sources/meta-my-layer/recipes-examples/opencl-vector/files
cp vector_add.c vector_add.cl \
~/lldp-ls1028a/sources/meta-my-layer/recipes-examples/opencl-vector/files/
Recipe file opencl-vector_1.0.bb:
SUMMARY = "OpenCL vector addition example for LS1028A GPU"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://vector_add.c \
file://vector_add.cl"
DEPENDS = "virtual/opencl-icd opencl-headers"
S = "${WORKDIR}"
do_compile() {
${CC} ${CFLAGS} -o vector_add vector_add.c -lOpenCL ${LDFLAGS}
}
do_install() {
install -d ${D}${bindir}
install -m 0755 vector_add ${D}${bindir}/
install -d ${D}/home/root
install -m 0644 vector_add.cl ${D}/home/root/
}
FILES:${PN} += "/home/root/vector_add.cl"
Add to local.conf and rebuild:
IMAGE_INSTALL:append = " opencl-vector"
bitbake ls-image-desktop
Practical Example: OpenGL ES Rendering with Wayland/Weston
This example shows how to render an animated triangle using OpenGL ES 2.0 with EGL on the Weston (Wayland) compositor — the standard "Hello World" of embedded graphics on the GC7000UL GPU.
9.1 Graphics Stack Diagram
C Application
↓
OpenGL ES 2.0 (libGLESv2.so — Vivante GC7000UL)
↓
EGL 1.4 (libEGL.so — interface between GLES and Wayland)
↓
Wayland Client (libwayland-client, libwayland-egl)
↓
Weston Compositor (DRM/KMS + Mali-DP500)
↓
DisplayPort → 4K Monitor
9.2 Install Dependencies
On the LS1028A (with ls-image-desktop):
apt-get install -y libgles2-mesa-dev libegl1-mesa-dev libwayland-dev libwayland-egl-backend-dev gcc pkg-config
9.3 Source Code: triangle_gles.c
cat > /home/root/triangle_gles.c << 'EOF'
/**
* OpenGL ES 2.0 + EGL + Wayland example
* Renders a colored spinning triangle on the Vivante GC7000UL GPU of the LS1028A
* Compile: gcc -o triangle_gles triangle_gles.c -lwayland-client -lwayland-egl -lEGL -lGLESv2 -lm
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define WIDTH 800
#define HEIGHT 600
static struct wl_display *wl_display = NULL;
static struct wl_compositor *wl_compositor = NULL;
static struct wl_shell *wl_shell = NULL;
static struct wl_surface *wl_surface = NULL;
static struct wl_shell_surface *shell_surface = NULL;
static struct wl_egl_window *egl_window = NULL;
static EGLDisplay egl_display;
static EGLContext egl_context;
static EGLSurface egl_surface;
static const char *vertex_shader_src=
"attribute vec2 a_position; \n"
"attribute vec3 a_color; \n"
"varying vec3 v_color; \n"
"uniform float u_angle; \n"
"void main() { \n"
" float c = cos(u_angle); \n"
" float s = sin(u_angle); \n"
" vec2 rot = vec2( \n"
" a_position.x*c - a_position.y*s,\n"
" a_position.x*s + a_position.y*c \n"
" ); \n"
" gl_Position = vec4(rot, 0.0, 1.0);\n"
" v_color = a_color; \n"
"} \n";
static const char *fragment_shader_src=
"precision mediump float; \n"
"varying vec3 v_color; \n"
"void main() { \n"
" gl_FragColor = vec4(v_color, 1.0);\n"
"} \n";
/* Vertices: position (x,y) + color (r,g,b) */
static const float vertices[] = {
0.0f, 0.8f, 1.0f, 0.0f, 0.0f, /* Top - Red */
-0.7f, -0.5f, 0.0f, 1.0f, 0.0f, /* Left - Green */
0.7f, -0.5f, 0.0f, 0.0f, 1.0f, /* Right - Blue */
};
static void registry_global(void *data, struct wl_registry *reg,
uint32_t name, const char *iface, uint32_t ver) {
if (strcmp(iface, "wl_compositor") == 0)
wl_compositor = wl_registry_bind(reg, name, &wl_compositor_interface, 1);
else if (strcmp(iface, "wl_shell") == 0)
wl_shell = wl_registry_bind(reg, name, &wl_shell_interface, 1);
}
static void registry_global_remove(void *d, struct wl_registry *r, uint32_t n) {}
static const struct wl_registry_listener registry_listener = {
registry_global, registry_global_remove
};
static GLuint compile_shader(GLenum type, const char *src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512]; glGetShaderInfoLog(shader, 512, NULL, log);
printf("Shader error: %s\n", log); exit(1);
}
return shader;
}
int main() {
wl_display = wl_display_connect(NULL);
if (!wl_display) { printf("Error: could not connect to Wayland\n"); return 1; }
struct wl_registry *registry = wl_display_get_registry(wl_display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_dispatch(wl_display);
wl_display_roundtrip(wl_display);
wl_surface = wl_compositor_create_surface(wl_compositor);
shell_surface = wl_shell_get_shell_surface(wl_shell, wl_surface);
wl_shell_surface_set_toplevel(shell_surface);
egl_display = eglGetDisplay((EGLNativeDisplayType)wl_display);
eglInitialize(egl_display, NULL, NULL);
eglBindAPI(EGL_OPENGL_ES_API);
EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, 16, EGL_NONE
};
EGLConfig egl_config; EGLint num_configs;
eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &num_configs);
EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, ctx_attribs);
egl_window = wl_egl_window_create(wl_surface, WIDTH, HEIGHT);
egl_surface = eglCreateWindowSurface(egl_display, egl_config,
(EGLNativeWindowType)egl_window, NULL);
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
printf("GPU: %s\n", glGetString(GL_RENDERER));
printf("OpenGL ES Version: %s\n", glGetString(GL_VERSION));
GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader_src);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_src);
GLuint program = glCreateProgram();
glAttachShader(program, vs); glAttachShader(program, fs);
glLinkProgram(program); glUseProgram(program);
GLint pos_loc = glGetAttribLocation(program, "a_position");
GLint color_loc = glGetAttribLocation(program, "a_color");
GLint angle_loc = glGetUniformLocation(program, "u_angle");
glViewport(0, 0, WIDTH, HEIGHT);
float angle = 0.0f;
int frames = 0;
printf("Rendering spinning triangle (Ctrl+C to exit)...\n");
while (1) {
wl_display_dispatch_pending(wl_display);
glClearColor(0.1f, 0.1f, 0.15f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUniform1f(angle_loc, angle);
glEnableVertexAttribArray(pos_loc);
glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), vertices);
glEnableVertexAttribArray(color_loc);
glVertexAttribPointer(color_loc, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), vertices + 2);
glDrawArrays(GL_TRIANGLES, 0, 3);
eglSwapBuffers(egl_display, egl_surface);
angle += 0.02f;
if (angle > 6.2832f) angle -= 6.2832f;
frames++;
if (frames % 300 == 0)
printf("Frame %d — angle: %.2f rad\n", frames, angle);
}
eglDestroyContext(egl_display, egl_context);
eglDestroySurface(egl_display, egl_surface);
eglTerminate(egl_display);
wl_display_disconnect(wl_display);
return 0;
}
9.4 Compile and Run
# Compile
gcc -o triangle_gles triangle_gles.c \
-lwayland-client -lwayland-egl \
-lEGL -lGLESv2 -lm
# Make sure Weston is running and set Wayland environment
export XDG_RUNTIME_DIR=/run/user/0
export WAYLAND_DISPLAY=wayland-0
# Run
./triangle_gles
Expected terminal output:
GPU: Vivante GC7000UL
OpenGL ES Version: OpenGL ES 3.1 V6.4.3.p4.398061
Rendering spinning triangle (Ctrl+C to exit)...
Frame 300 — angle: 6.00 rad
Frame 600 — angle: 5.68 rad
A RGB triangle spinning on a dark background will appear on screen, rendered by the GC7000UL GPU.
9.5 Yocto Recipe
SUMMARY = "OpenGL ES 2.0 spinning triangle example — LS1028A"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://triangle_gles.c"
DEPENDS = "virtual/libgles2 virtual/egl wayland"
S = "${WORKDIR}"
do_compile() {
${CC} ${CFLAGS} -o triangle_gles triangle_gles.c \
-lwayland-client -lwayland-egl -lEGL -lGLESv2 -lm ${LDFLAGS}
}
do_install() {
install -d ${D}${bindir}
install -m 0755 triangle_gles ${D}${bindir}/
}
9.6 Yocto Recipe for OpenCV with GPU
Add to local.conf:
IMAGE_INSTALL:append = " opencv python3-opencv"
PACKAGECONFIG:append:pn-opencv = " opencl"
bitbake ls-image-desktop
References:
1) https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/layerscape-processors:QORIQ-ARM
2) https://www.khronos.org/news/permalink/vivante-gc7000-gpus
3) Layerscape Software Development Kit User Guide for Yocto :
https://docs.nxp.com/bundle/GUID-E5527A77-2F97-4244-BF9C-D08F068EFD16/page/GUID-B386D634-B9B1-4980-AA56-9C705D7FBC52.html?gad_source=1&gad_campaignid=24106955438&gclid=EAIaIQobChMI3cKf4KqZlwMVbnJ_AB17dwoGEAAYASAAEgJURPD_BwE
Regards