VIRTUAL ENGINE UAPI(9)

πŸ“¦ version 7.3.1, in nixpkgs unstable

✨ NAME ✨

Virtual Engine uAPI β€” the part of the kernel API manual (Kernel API Manual, section 9) that lets your userspace pick a whole squad of physical engines and then just vibe, letting the driver decide which one actually runs your batch buffer πŸ’…


🌸 DESCRIPTION 🌸

okay so bestie, listen β€” a virtual engine is this concept where userspace gets to configure a set of physical engines, submit a batch buffer, and then just let the driver execute it on any engine from the set as it sees fit. no cap, it picks. you don't. and honestly? that's the whole charm 🫢

this is primarily useful on parts which have multiple instances of a same class engine β€” like, for example, GT3+ Skylake parts with their two VCS engines. those twins are literally giving main character energy, and the virtual engine is how you exploit them both ✨

so for instance, userspace can enumerate all engines of a certain class using the previously described Engine Discovery uAPI. after that, userspace can create a GEM context with a placeholder slot for the virtual engine β€” using I915_ENGINE_CLASS_INVALID and I915_ENGINE_CLASS_INVALID_NONE for class and instance respectively, iconic placeholder behavior πŸŽ€ β€” and finally, using the I915_CONTEXT_ENGINES_EXT_LOAD_BALANCE extension, place a virtual engine in that same reserved slot. slotted in. periodt.

πŸ’œ example: creating a virtual engine and submitting a batch buffer to it

this is the good part, grab a snack πŸ“

I915_DEFINE_CONTEXT_ENGINES_LOAD_BALANCE(virtual, 2) = {
        .base.name = I915_CONTEXT_ENGINES_EXT_LOAD_BALANCE,
        .engine_index = 0, // Place this virtual engine into engine map slot 0
        .num_siblings = 2,
        .engines = { { I915_ENGINE_CLASS_VIDEO, 0 },
                     { I915_ENGINE_CLASS_VIDEO, 1 }, },
};
I915_DEFINE_CONTEXT_PARAM_ENGINES(engines, 1) = {
        .engines = { { I915_ENGINE_CLASS_INVALID,
                       I915_ENGINE_CLASS_INVALID_NONE } },
        .extensions = to_user_pointer(virtual), // Chains after load_balance extension
};
struct drm_i915_gem_context_create_ext_setparam p_engines = {
        .base = {
                .name = I915_CONTEXT_CREATE_EXT_SETPARAM,
        },
        .param = {
                .param = I915_CONTEXT_PARAM_ENGINES,
                .value = to_user_pointer(engines),
                .size = sizeof(engines),
        },
};
struct drm_i915_gem_context_create_ext create = {
        .flags = I915_CONTEXT_CREATE_FLAGS_USE_EXTENSIONS,
        .extensions = to_user_pointer(p_engines);
};

ctx_id = gem_context_create_ext(drm_fd, create);

// Now we have created a GEM context with its engine map containing a
// single virtual engine. Submissions to this slot can go either to
// vcs0 or vcs1, depending on the load balancing algorithm used inside
// the driver. The load balancing is dynamic from one batch buffer to
// another and transparent to userspace.

...
execbuf.rsvd1 = ctx_id;
execbuf.flags = 0; // Submits to index 0 which is the virtual engine
gem_execbuf(drm_fd, execbuf);

so what just happened? you created a GEM context whose engine map holds one (1) single virtual engine. submissions to that slot can go either to vcs0 or vcs1, depending on the load balancing algorithm used inside the driver. and the load balancing is dynamic from one batch buffer to another and transparent to userspace β€” meaning you literally don't have to think about it, the driver handles the shuffle and you just keep submitting πŸ˜­πŸ‘‘


πŸ‘‘ SEE ALSO πŸ‘‘

the whole friend group, no one left out πŸ’–:

  • Kernel file ./tools/perf/trace/beauty/include/uapi/drm/i915_drm.h
  • uevents generated by i915 on its device node(9)
  • struct i915_user_extension(9)
  • enum drm_i915_gem_engine_class(9)
  • struct i915_engine_class_instance(9)
  • perf_events exposed by i915 through /sys/bus/event_sources/drivers/i915(9)
  • struct drm_i915_getparam(9)
  • drm_i915_getparam_t(9)
  • struct drm_i915_gem_mmap_offset(9)
  • struct drm_i915_gem_set_domain(9)
  • struct drm_i915_gem_exec_fence(9)
  • struct drm_i915_gem_execbuffer_ext_timeline_fences(9)
  • struct drm_i915_gem_execbuffer2(9)
  • struct drm_i915_gem_caching(9)
  • struct drm_i915_gem_context_create_ext(9)
  • struct drm_i915_gem_context_param(9)
  • struct i915_context_engines_parallel_submit(9)
  • Context Engine Map uAPI(9)
  • struct drm_i915_gem_context_create_ext_setparam(9)
  • struct drm_i915_gem_vm_control(9)
  • struct drm_i915_gem_userptr(9)
  • struct drm_i915_perf_oa_config(9)
  • struct drm_i915_query_item(9)
  • struct drm_i915_query(9)
  • struct drm_i915_query_topology_info(9)
  • Engine Discovery uAPI(9)
  • struct drm_i915_engine_info(9)
  • struct drm_i915_query_engine_info(9)
  • struct drm_i915_query_perf_config(9)
  • enum drm_i915_gem_memory_class(9)
  • struct drm_i915_gem_memory_class_instance(9)
  • struct drm_i915_memory_region_info(9)
  • struct drm_i915_query_memory_regions(9)
  • struct drm_i915_query_guc_submission_version(9)
  • GuC HWCONFIG blob uAPI(9)
  • struct drm_i915_gem_create_ext(9)
  • struct drm_i915_gem_create_ext_memory_regions(9)
  • struct drm_i915_gem_create_ext_protected_content(9)
  • struct drm_i915_gem_create_ext_set_pat(9)

that's the whole page bestie, go forth and load-balance πŸŒˆπŸ’…

sign-off time πŸŽ€ β€” this page came out of ./tools/perf/trace/beauty/incl... and it's dated October 2026, and the manual it lives in is Virtual Engine uAPI(9). mwah πŸ’‹

πŸ“¦ where this page comes from