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

  1. Verify the total data buffer size is at least the sum of all per-mip/per-slice BufferStride values before calling the copy API
  2. Recheck the Image Description (MipLevels, ArraySize, Depth, Format) against the actual data; regenerate or fix the source asset
  3. Ensure the file/asset was not truncated during download or packaging (compare file size against header metadata)
  4. 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

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


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)