stride3d/stride · error · InvalidOperationException
Unexpected end of buffer
Error message
Unexpected end of buffer
What it means
Thrown by DDS/texture image copying helpers (e.g. Copy) when the sum of source image BufferStride values exceeds the size of the underlying data buffer. It means the caller provided image descriptions whose computed total size is larger than the actual data supplied, so the copy loop would read past the end of the buffer. The library throws eagerly to avoid a memory corruption or access violation during a raw memcpy of pixel data.
Solutions
- Verify the total data buffer size is at least the sum of all per-mip/per-slice BufferStride values before calling the copy API
- Recheck the Image Description (MipLevels, ArraySize, Depth, Format) against the actual data; regenerate or fix the source asset
- Ensure the file/asset was not truncated during download or packaging (compare file size against header metadata)
- If computing strides yourself, use the library's PixelBuffer helpers for the format instead of manual block-size math
Example fix
// before: trust header-derived mip count
var image = Image.New(loadDescription, dataPointer, totalSize);
// after: verify buffer covers full mip chain
long required = 0;
foreach (var m in ComputeMipSizes(loadDescription)) required += m;
if (dataSize < required) throw new InvalidDataException($"Truncated texture: need {required}, have {dataSize}");
var image = Image.New(loadDescription, dataPointer, totalSize); Defensive patterns
Strategy: validation
Validate before calling
long required = 0;
for (int i = 0; i < imageCount; i++)
required += images[i].BufferStride;
if (totalBufferSize < required)
throw new InvalidDataException($"Texture data truncated: need {required} bytes, have {totalBufferSize}"); Type guard
bool HasFullMipChain(Image image) =>
image.Description.Dimension == TextureDimension.Texture3D
? image.Description.Depth > 0
: image.Description.MipLevels > 0 && image.TotalSizeInBytes > 0; Try / catch
try
{
image.Copy(destImages);
}
catch (InvalidOperationException ex) when (ex.Message == "Unexpected end of buffer")
{
log.Error("Truncated texture data: description does not match buffer size", ex);
return LoadFallbackTexture();
} Prevention
- Always compute total size from the Description via library helpers instead of trusting file headers
- Validate asset file sizes against header metadata at build/import time
- Treat truncated downloads as build failures; checksum assets in CI
When it happens
Trigger: Calling Image.Copy / DDS load helpers where the cumulative images[i].BufferStride exceeds checkSize (the total buffer size), e.g. constructing an Image with Description.MipLevels/ArraySize/Depth larger than the data buffer actually contains.
Common situations: Loading a truncated DDS/KTX file whose header claims more mipmaps or array slices than the file contains; hand-crafting Image from raw bytes with a wrong data size; format or block-size mismatch causing BufferStride to be computed larger than expected; off-by-one in mip chain calculation after a format change.
Understand the failure class
Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.
Related errors
- Pointer to DDS header cannot be null
- Unexpected ArraySize == 0 from DDS HeaderDX10
- Invalid Format from DDS HeaderDX10
- Unexpected Height != 1 from DDS HeaderDX10
- Texture3D missing HeaderFlags.Volume from DDS HeaderDX10
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/ccd57c925f77970e.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Foundation/Graphics/DDSHelper.cs:1145
tflags |= ScanlineFlags.Legacy;
int index = 0;
int checkSize = size;
for (int arrayIndex = 0; arrayIndex < metadata.ArraySize; arrayIndex++)
{
int d = metadata.Depth;
// Else we need to go through each mips/depth slice to convert all scanlines.
for (int level = 0; level < metadata.MipLevels; ++level)
{
for (int slice = 0; slice < d; ++slice, ++index)
{
IntPtr pSrc = images[index].DataPointer;
IntPtr pDest = imagesDst[index].DataPointer;
checkSize -= images[index].BufferStride;
if (checkSize < 0)
throw new InvalidOperationException("Unexpected end of buffer");
if (metadata.Format.IsCompressed)
{
MemoryUtilities.CopyWithAlignmentFallback((void*)pDest, (void*)pSrc, (uint)Math.Min(images[index].BufferStride, imagesDst[index].BufferStride));
}
else
{
int spitch = images[index].RowStride;
int dpitch = imagesDst[index].RowStride;
for (int h = 0; h < images[index].Height; ++h)
{
if ((convFlags & ConversionFlags.Expand) != 0)
{
#if DIRECTX11_1
if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551 | ConversionFlags.Format4444)) != 0)
#else
if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551)) != 0)View on GitHub (pinned to 96fad776d2)