{"record":{"id":"b7a9b31458683ba6","repo":"niri-wm/niri","slug":"short-texture-mapping","errorCode":null,"errorMessage":"short texture mapping","messagePattern":"short texture mapping","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/render_helpers/mod.rs","lineNumber":363,"sourceCode":"\n        let _res = damage_tracker\n            .render_output_with_states(\n                renderer,\n                &mut target,\n                0,\n                elements,\n                Color32F::TRANSPARENT,\n                states,\n            )\n            .context(\"error rendering\")?;\n\n        let mapping =\n            copy_framebuffer(renderer, &target, fourcc).context(\"error copying framebuffer\")?;\n        let bytes = renderer\n            .map_texture(&mapping)\n            .context(\"error mapping texture\")?;\n\n        ensure!(bytes.len() >= row_len * height, \"short texture mapping\");\n\n        unsafe {\n            let _span = tracy_client::span!(\"copy_nonoverlapping\");\n            let dst = pool.add(offset);\n            if stride == row_len {\n                ptr::copy_nonoverlapping(bytes.as_ptr(), dst, row_len * height);\n            } else {\n                for y in 0..height {\n                    ptr::copy_nonoverlapping(\n                        bytes.as_ptr().add(y * row_len),\n                        dst.add(y * stride),\n                        row_len,\n                    );\n                }\n            }\n        }\n\n        Ok(())","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/niri-wm/niri/blob/9e72e4917ca31baf4010496bf7f4aaf78d34d236/src/render_helpers/mod.rs#L345-L381","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check that the size passed to copy_framebuffer matches the output/framebuffer's actual dimensions at map time; recompute after any resize.","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).","Update the renderer/GPU driver; some backends return truncated mappings on texture copy failure.","Guard the caller (render_for_screencopy_internal) to re-fetch output dimensions immediately before rendering to avoid size-change races."],"exampleFix":"// before: trusting possibly stale size\nlet size = output.current_size();\nlet mapping = copy_framebuffer(renderer, &target, fourcc)?;\n\n// after: validate mapped geometry against actual mapping\nlet mapping = copy_framebuffer(renderer, &target, fourcc)?;\nassert_eq!(mapping.size, output.current_size(), \"output resized mid-screencopy\");\nlet bytes = renderer.map_texture(&mapping)?;","handlingStrategy":"validation","validationCode":"// Validate mapping size before consuming it\nlet bytes = renderer.map_texture(&mapping)?;\nlet expected = row_len.checked_mul(height).expect(\"size overflow\");\nif bytes.len() < expected {\n    return Err(format!(\n        \"mapped {} bytes, need {}\",\n        bytes.len(), expected\n    ));\n}","typeGuard":"fn is_full_mapping(bytes: &[u8], row_len: usize, height: usize) -> bool {\n    bytes.len() >= row_len.saturating_mul(height)\n}","tryCatchPattern":"match render_to_shm(...) {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"short texture mapping\") => {\n        // re-fetch output size and retry once, or fall back to a different capture path\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Re-read output/framebuffer dimensions immediately before rendering to avoid resize races.","Confirm the renderer backend supports the requested fourcc and that its bpp matches row_len calculations.","Keep the renderer and smithay/render_helpers versions in sync; stale backends can return truncated mappings.","Log mapping dimensions on failure to distinguish driver truncation from geometry mismatches."],"tags":["texture","rendering","shm","screencopy","gpu"],"backgroundTag":"unexpected-response-shape","analyzedSha":"9e72e4917ca31baf4010496bf7f4aaf78d34d236","analyzedAt":"2026-09-12T15:03:56.013Z","contentChangedAt":"2026-09-12T15:03:56.013Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}