Repository navigation
[Bug] Linux guest: virgl reports 32 texture image units, but shaders using more than 16 samplers fail on the host #41
Description
Description
In a Linux guest with GPU acceleration on, WebGL2 on the guest's GL driver (virgl) reports MAX_TEXTURE_IMAGE_UNITS = 32, but any fragment shader that uses more than 16 samplers fails on the host. The guest gets no GL error and no context loss: the draw silently produces nothing, and the kernel logs virtio_gpu errors. Programs that size their shaders from the reported limit therefore render nothing, and one that redraws every frame can leave its whole window blank.
Steps to Reproduce
- Create an Ubuntu 26.04 ARM64 VM with GPU acceleration enabled.
- In any Chromium-based browser or app in the guest, open DevTools and run the snippet below, changing the final
(17)to(16)to compare. - Watch
sudo dmesg -win the guest while it runs.
(N => {
const gl = document.createElement('canvas').getContext('webgl2');
const sh = (t, s) => { const x = gl.createShader(t); gl.shaderSource(x, s); gl.compileShader(x); return x; };
let chain = ''; for (let i = 1; i < N; i++) chain += ` else if (page == ${i}) o = texture(tex[${i}], vec2(0.5));`;
const p = gl.createProgram();
gl.attachShader(p, sh(gl.VERTEX_SHADER, `#version 300 es
layout(location=0) in vec2 a; void main(){ gl_Position = vec4(a, 0.0, 1.0); }`));
gl.attachShader(p, sh(gl.FRAGMENT_SHADER, `#version 300 es
precision lowp float; uniform int page; uniform sampler2D tex[${N}]; out vec4 o;
void main(){ if (page == 0) o = texture(tex[0], vec2(0.5));${chain} }`));
gl.linkProgram(p); gl.useProgram(p);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
for (let i = 0; i < N; i++) {
gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
}
gl.uniform1iv(gl.getUniformLocation(p, 'tex'), Int32Array.from({length: N}, (_, i) => i));
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
const px = new Uint8Array(4); gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, px);
return {maxUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), linked: gl.getProgramParameter(p, gl.LINK_STATUS),
glError: gl.getError(), lost: gl.isContextLost(), pixel: [...px]};
})(17)Current Behavior
| Samplers in shader | Pixel read back | Guest GL error / context lost | virtio_gpu kernel errors |
|---|---|---|---|
| 8 | [255, 0, 0, 255] |
none / no | 0 |
| 16 | [255, 0, 0, 255] |
none / no | 0 |
| 17 | [0, 0, 0, 0] |
none / no | 5–6 |
| 32 | [0, 0, 0, 0] |
none / no | 4–6 |
MAX_TEXTURE_IMAGE_UNITS is reported as 32 in every case, and the program links successfully in the guest. Each failing draw logs:
[drm:virtio_gpu_dequeue_ctrl_func [virtio_gpu]] *ERROR* response 0x1200 (command 0x207)
That is VIRTIO_GPU_RESP_ERR_UNSPEC in answer to VIRTIO_GPU_CMD_SUBMIT_3D.
Draws, instanced draws, texture uploads and mipmap generation with 16 or fewer samplers all work without errors.
Expected Behavior
Either the guest should report the limit the host can actually honour (16), or shaders using up to the reported 32 samplers should work. Failures should surface to the guest as a GL error or link failure rather than silently dropping the draw.
Environment
- App version: VMPal 0.67 (67)
- Mac: Apple M5 Pro
- macOS version: macOS 27.0.1 (26A434)
- VM's OS: Ubuntu 26.04.1 LTS (ARM64), kernel 7.0.0-38-generic, Mesa 26.0.8, GNOME on Wayland
- GL renderer in guest:
ANGLE (Mesa, virgl (Apple M5 Pro), OpenGL ES 3.0) - Kernel:
virtio_gpufeatures+virgl +edid -resource_blob -host_visible -context_init
Log
No crash; the VM keeps running. Kernel message as above.
Activity
huyphams commentedon Oct 20, 2026
Hi @janah01, thanks for the precise report and the snippet. You were right: virglrenderer told the guest it had 32 texture units, while macOS’s OpenGL has 16 per shader stage. A shader with more linked in the guest, failed on the Mac, and put the whole GL context of that process in error, which is why everything it drew stayed blank.
Fixed in VMPal 0.90: the guest now gets the Mac’s real limit, so
MAX_TEXTURE_IMAGE_UNITSreads 16 and apps that size their shaders from it (like xterm.js’s WebGL renderer in VS Code’s terminal) stay within it. A Linux VM picks it up at its next start.If your snippet still misbehaves on 0.90, please reopen with the VM’s log.