# Source code for moderngl_window.loaders.texture.cube

    from collections import namedtuple
    from typing import Any, Optional

    import moderngl

    from moderngl_window.exceptions import ImproperlyConfigured
    from moderngl_window.loaders.texture.pillow import PillowLoader, image_data
    from moderngl_window.meta.base import ResourceDescription
    from moderngl_window.meta.texture import TextureDescription

    FaceInfo = namedtuple("FaceInfo", ["width", "height", "data", "components"])

    class Loader(PillowLoader):
        kind = "cube"
        meta: TextureDescription

        def __init__(self, meta: ResourceDescription):
            super().__init__(meta)

        def load(self) -> moderngl.TextureCube:
            """Load a texture cube as described by the supplied ``TextureDescription```

            Returns:
                moderngl.TextureCube: The TextureArray instance
            """
            pos_x = self._load_face(self.meta.pos_x, face_name="pos_x")
            pos_y = self._load_face(self.meta.pos_y, face_name="pos_y")
            pos_z = self._load_face(self.meta.pos_z, face_name="pos_z")
            neg_x = self._load_face(self.meta.neg_x, face_name="neg_x")
            neg_y = self._load_face(self.meta.neg_y, face_name="neg_y")
            neg_z = self._load_face(self.meta.neg_z, face_name="neg_z")

            self._validate([pos_x, pos_y, pos_z, neg_x, neg_y, neg_z])

            texture = self.ctx.texture_cube(
                (pos_x.width, pos_x.height),
                pos_x.components,
                pos_x.data + neg_x.data + pos_y.data + neg_y.data + pos_z.data + neg_z.data,
            )
            texture.extra = {"meta": self.meta}

            if self.meta.mipmap_levels is not None:
                self.meta.mipmap = True

            if self.meta.mipmap:
                if isinstance(self.meta.mipmap_levels, tuple):
                    texture.build_mipmaps(*self.meta.mipmap_levels)
                else:
                    texture.build_mipmaps()

                if self.meta.anisotropy:
                    texture.anisotropy = self.meta.anisotropy

            return texture

        def _load_face(self, path: Optional[str], face_name: Optional[str] = None) -> FaceInfo:
            """Obtain raw byte data for a face

            Returns:
                tuple[int, bytes]: number of components, byte data
            """
            if not path:
                raise ImproperlyConfigured(f"{face_name} texture face not supplied")

            image = self._load_texture(path)
            components, data = image_data(image)
            return FaceInfo(width=image.size[0], height=image.size[1], data=data, components=components)

        def _validate(self, faces: list[FaceInfo]) -> Any:
            """Validates each face ensuring components and size it the same"""
            components = faces[0].components
            data_size = len(faces[0].data)
            for face in faces:
                if face.components != components:
                    raise ImproperlyConfigured(
                        "Cubemap face textures have different number of components"
                    )
                if len(face.data) != data_size:
                    raise ImproperlyConfigured("Cubemap face textures must all have the same size")

            return components