niri-wm/niri · error

invalid buffer format or size

Error message

invalid buffer format or size

What it means

After mapping the format, render_to_shm verifies the buffer's actual metadata (format, width, height) matches the output mode size derived from damage_tracker.mode(). If the buffer's dimensions or format disagree with the logical output size, rendering is aborted with this error to avoid out-of-bounds writes or corrupted frames.

Solutions

  1. Reallocate the shm buffer to match damage_tracker.mode().try_into().unwrap() (size.w x size.h) and the same format before rendering
  2. Re-run buffer allocation on output mode/scale change events instead of caching buffers
  3. Check buffer_data.format equals the wl_shm::Format you pass in

Example fix

// before (buffer allocated before resize)
let buffer = create_shm_buffer(old_width, old_height, wl_shm::Format::Argb8888);
// after (reallocate on mode change)
let (size, _, _) = damage_tracker.mode().try_into()?;
let buffer = create_shm_buffer(size.w, size.h, wl_shm::Format::Argb8888);
Defensive patterns

Strategy: validation

Validate before calling

let (size, _, _) = damage_tracker.mode().try_into()?;
if buffer_data.width != size.w || buffer_data.height != size.h || buffer_data.format != format {
    // reallocate the buffer to (size.w, size.h, format) before rendering
}

Try / catch

match render_to_shm(...) {
    Err(e) if e.to_string().contains("invalid buffer format or size") => {
        // drop cached buffers, reallocate at current mode size, retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Passing a shm buffer sized differently from damage_tracker.mode()'s logical size (w,h), or a buffer whose buffer_data.format differs from the wl_shm::Format being rendered with — typically a stale or wrongly allocated buffer after an output resize/mode change.

Common situations: Output resolution or scale changed while capture buffers were allocated for the old size; client-allocated buffers with wrong stride/dimensions; mismatched format between the requested wl_shm::Format and the pool's actual format.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of niri-wm/niri@62c230a662 (2026-09-19). Data as JSON: /api/errors/fa3998f32103975a. Report an issue: GitHub.

Appendix: source

Thrown at src/render_helpers/mod.rs:348

    renderer: &mut GlesRenderer,
    damage_tracker: &mut OutputDamageTracker,
    buffer: &WlBuffer,
    format: wl_shm::Format,
    elements: &[impl RenderElement<GlesRenderer>],
    states: RenderElementStates,
) -> anyhow::Result<()> {
    let _span = tracy_client::span!();
    // The pointer and length are the client's entire pool, which may hold other
    // buffers besides this one... buffer_data is the one we want.
    shm::with_buffer_contents_mut(buffer, |pool, pool_len, buffer_data| {
        let (size, _scale, _transform) = damage_tracker.mode().try_into().unwrap();
        let fourcc = match format {
            wl_shm::Format::Xrgb8888 => Fourcc::Xrgb8888,
            wl_shm::Format::Argb8888 => Fourcc::Argb8888,
            _ => bail!("unsupported shm format: {format:?}"),
        };

        ensure!(
            // The buffer prefers pixels in little endian ...
            buffer_data.format == format
                && buffer_data.width == size.w
                && buffer_data.height == size.h,
            "invalid buffer format or size"
        );

        // The client chooses the stride and may pad rows, so only the first
        // row_len bytes can be used here.
        let row_len = size.w as usize * 4;
        let height = size.h as usize;
        let offset = usize::try_from(buffer_data.offset).context("negative buffer offset")?;
        let stride = usize::try_from(buffer_data.stride).context("negative buffer stride")?;

        // This should have already been validated by wl_shm, and a pool can
        // only grow, but check again just in case.
        let end = stride
            .checked_mul(height.saturating_sub(1))

View on GitHub (pinned to 62c230a662)