niri-wm/niri · error

short texture mapping

Error message

short texture mapping

What it means

After copy_framebuffer renders into an intermediate texture and map_texture maps it into CPU memory, this check ensures the mapped byte slice is large enough to hold height rows of row_len bytes. A short mapping means the renderer returned fewer bytes than the declared buffer geometry expects, so copying to the shm pool would read out of bounds.

Solutions

  1. Check that the size passed to copy_framebuffer matches the output/framebuffer's actual dimensions at map time; recompute after any resize.
  2. Verify the fourcc format is supported by the renderer and that its bytes-per-pixel matches the mapping's actual layout (row_len vs mapping stride).
  3. Update the renderer/GPU driver; some backends return truncated mappings on texture copy failure.
  4. Guard the caller (render_for_screencopy_internal) to re-fetch output dimensions immediately before rendering to avoid size-change races.

Example fix

// before: trusting possibly stale size
let size = output.current_size();
let mapping = copy_framebuffer(renderer, &target, fourcc)?;

// after: validate mapped geometry against actual mapping
let mapping = copy_framebuffer(renderer, &target, fourcc)?;
assert_eq!(mapping.size, output.current_size(), "output resized mid-screencopy");
let bytes = renderer.map_texture(&mapping)?;
Defensive patterns

Strategy: validation

Validate before calling

// Validate mapping size before consuming it
let bytes = renderer.map_texture(&mapping)?;
let expected = row_len.checked_mul(height).expect("size overflow");
if bytes.len() < expected {
    return Err(format!(
        "mapped {} bytes, need {}",
        bytes.len(), expected
    ));
}

Type guard

fn is_full_mapping(bytes: &[u8], row_len: usize, height: usize) -> bool {
    bytes.len() >= row_len.saturating_mul(height)
}

Try / catch

match render_to_shm(...) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("short texture mapping") => {
        // re-fetch output size and retry once, or fall back to a different capture path
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: render_to_shm (via render_for_screencopy_internal) calls renderer.map_texture on the copy_framebuffer result and the returned slice length is < row_len * height — e.g. the renderer mapped only part of the texture, the framebuffer copy was clipped/smaller than the requested size, or the mapping's stride/format differs from what render_to_shm computed.

Common situations: Renderer backend (e.g. GL/gles vulkan) returning a mapping whose dimensions were clamped or failed partially; screencopy of an output whose logical size changed between size computation and mapping; format/fourcc mismatch causing row_len (width*bpp) to exceed the actual mapped buffer's stride*height; buggy or mismatched renderer versions.

Related errors


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

Appendix: source

Thrown at src/render_helpers/mod.rs:397

        let _res = damage_tracker
            .render_output_with_states(
                renderer,
                &mut target,
                0,
                elements,
                Color32F::TRANSPARENT,
                states,
            )
            .context("error rendering")?;

        let mapping =
            copy_framebuffer(renderer, &target, fourcc).context("error copying framebuffer")?;
        let bytes = renderer
            .map_texture(&mapping)
            .context("error mapping texture")?;

        ensure!(bytes.len() >= row_len * height, "short texture mapping");

        unsafe {
            let _span = tracy_client::span!("copy_nonoverlapping");
            let dst = pool.add(offset);
            if stride == row_len {
                ptr::copy_nonoverlapping(bytes.as_ptr(), dst, row_len * height);
            } else {
                for y in 0..height {
                    ptr::copy_nonoverlapping(
                        bytes.as_ptr().add(y * row_len),
                        dst.add(y * stride),
                        row_len,
                    );
                }
            }
        }

        Ok(())

View on GitHub (pinned to 62c230a662)