Begin with a generated label
Generate a tiny checker or striped exhibit label in owned CPU memory. Validate dimensions, channel count, row bytes, and total byte count before upload. The texture owns GPU storage after the upload; its C++ wrapper owns deletion. The caller’s upload span only needs to remain valid for the synchronous data-copy contract of this ordinary client-memory call.
Use a sized internal format to describe GPU storage and a matching source format/type to describe the CPU bytes. A texture object is separate from a texture unit: activate a unit, bind the object for that unit, and set the sampler uniform to the unit index rather than the GLuint texture name.
Pixel unpack alignment describes row layout. A tightly packed RGB row can violate the default alignment even when the buffer length looks plausible. Either prepare correctly aligned rows or establish and restore a suitable unpack alignment as part of the upload boundary. These examples use client memory with no GL_PIXEL_UNPACK_BUFFER bound and the default zero row-length/skip settings; a bound pixel-unpack buffer changes the data argument into an offset into that buffer.
Make your own two-color label. Test a row layout that is not naturally four-byte aligned with a CPU byte-count calculation before submitting it to GL.
