|
@@ -113,6 +113,8 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
|
|
|
for (const auto &id : imagedata)
|
|
|
data.push_back(id);
|
|
|
|
|
|
+ format = data[0]->getFormat();
|
|
|
+
|
|
|
preload();
|
|
|
loadVolatile();
|
|
|
|
|
@@ -148,6 +150,8 @@ Image::Image(const std::vector<love::image::CompressedImageData *> &compressedda
|
|
|
for (image::CompressedImageData *cd : compresseddata)
|
|
|
cdata.push_back(cd);
|
|
|
|
|
|
+ format = cdata[0]->getFormat();
|
|
|
+
|
|
|
preload();
|
|
|
loadVolatile();
|
|
|
|
|
@@ -230,7 +234,7 @@ void Image::loadDefaultTexture()
|
|
|
|
|
|
void Image::loadFromCompressedData()
|
|
|
{
|
|
|
- GLenum iformat = getCompressedFormat(cdata[0]->getFormat(), sRGB);
|
|
|
+ OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
|
|
|
|
|
|
if (isGammaCorrect() && !sRGB)
|
|
|
flags.linear = true;
|
|
@@ -249,17 +253,15 @@ void Image::loadFromCompressedData()
|
|
|
auto cd = cdata.size() > 1 ? cdata[i].get() : cdata[0].get();
|
|
|
int datamip = cdata.size() > 1 ? 0 : i;
|
|
|
|
|
|
- glCompressedTexImage2D(GL_TEXTURE_2D, i, iformat, cd->getWidth(datamip),
|
|
|
- cd->getHeight(datamip), 0,
|
|
|
+ glCompressedTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat,
|
|
|
+ cd->getWidth(datamip), cd->getHeight(datamip), 0,
|
|
|
(GLsizei) cd->getSize(datamip), cd->getData(datamip));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void Image::loadFromImageData()
|
|
|
{
|
|
|
- GLenum glformat = GL_RGBA;
|
|
|
- GLenum gltype = GL_UNSIGNED_BYTE;
|
|
|
- GLenum iformat = getFormat(data[0]->getFormat(), glformat, gltype, sRGB);
|
|
|
+ OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
|
|
|
|
|
|
if (isGammaCorrect() && !sRGB)
|
|
|
flags.linear = true;
|
|
@@ -271,8 +273,8 @@ void Image::loadFromImageData()
|
|
|
love::image::ImageData *id = data[i].get();
|
|
|
love::thread::Lock lock(id->getMutex());
|
|
|
|
|
|
- glTexImage2D(GL_TEXTURE_2D, i, iformat, id->getWidth(), id->getHeight(),
|
|
|
- 0, glformat, gltype, id->getData());
|
|
|
+ glTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat, id->getWidth(), id->getHeight(),
|
|
|
+ 0, fmt.externalformat, fmt.type, id->getData());
|
|
|
}
|
|
|
|
|
|
if (data.size() <= 1)
|
|
@@ -286,13 +288,13 @@ bool Image::loadVolatile()
|
|
|
|
|
|
OpenGL::TempDebugGroup debuggroup("Image load");
|
|
|
|
|
|
- if (isCompressed() && !hasCompressedTextureSupport(cdata[0]->getFormat(), sRGB))
|
|
|
+ if (!OpenGL::isPixelFormatSupported(format, false, sRGB))
|
|
|
{
|
|
|
const char *str;
|
|
|
- if (image::CompressedImageData::getConstant(cdata[0]->getFormat(), str))
|
|
|
+ if (love::getConstant(format, str))
|
|
|
{
|
|
|
throw love::Exception("Cannot create image: "
|
|
|
- "%s%s compressed images are not supported on this system.", sRGB ? "sRGB " : "", str);
|
|
|
+ "%s%s images are not supported on this system.", sRGB ? "sRGB " : "", str);
|
|
|
}
|
|
|
else
|
|
|
throw love::Exception("cannot create image: format is not supported on this system.");
|
|
@@ -417,9 +419,8 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- GLenum glformat = GL_RGBA;
|
|
|
- GLenum gltype = GL_UNSIGNED_BYTE;
|
|
|
- getFormat(data[0]->getFormat(), glformat, gltype, sRGB);
|
|
|
+ bool isSRGB = sRGB;
|
|
|
+ OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB);
|
|
|
|
|
|
int mipcount = flags.mipmaps ? (int) data.size() : 1;
|
|
|
|
|
@@ -430,8 +431,8 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
|
|
|
pdata += yoffset * data[i]->getWidth() + xoffset;
|
|
|
|
|
|
thread::Lock lock(data[i]->getMutex());
|
|
|
- glTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, w, h, glformat,
|
|
|
- gltype, pdata);
|
|
|
+ glTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, w, h,
|
|
|
+ fmt.externalformat, fmt.type, pdata);
|
|
|
|
|
|
xoffset /= 2;
|
|
|
yoffset /= 2;
|
|
@@ -501,7 +502,7 @@ void Image::setFilter(const Texture::Filter &f)
|
|
|
|
|
|
filter = f;
|
|
|
|
|
|
- if (!data.empty() && !hasTextureFilteringSupport(data[0]->getFormat()))
|
|
|
+ if (!data.empty() && !OpenGL::hasTextureFilteringSupport(data[0]->getFormat()))
|
|
|
{
|
|
|
filter.mag = filter.min = FILTER_NEAREST;
|
|
|
|
|
@@ -599,245 +600,9 @@ bool Image::isCompressed() const
|
|
|
return compressed;
|
|
|
}
|
|
|
|
|
|
-GLenum Image::getFormat(image::ImageData::Format format, GLenum &glformat, GLenum &gltype, bool &isSRGB) const
|
|
|
-{
|
|
|
- using image::ImageData;
|
|
|
-
|
|
|
- GLenum internalformat = GL_RGBA8;
|
|
|
- glformat = GL_RGBA;
|
|
|
- gltype = GL_UNSIGNED_BYTE;
|
|
|
-
|
|
|
- switch (format)
|
|
|
- {
|
|
|
- case ImageData::FORMAT_RGBA8:
|
|
|
- if (isSRGB)
|
|
|
- {
|
|
|
- internalformat = GL_SRGB8_ALPHA8;
|
|
|
- if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
|
|
|
- glformat = GL_SRGB_ALPHA;
|
|
|
- }
|
|
|
- break;
|
|
|
- case ImageData::FORMAT_RGBA16:
|
|
|
- internalformat = GL_RGBA16;
|
|
|
- gltype = GL_UNSIGNED_SHORT;
|
|
|
- isSRGB = false;
|
|
|
- break;
|
|
|
- case ImageData::FORMAT_RGBA16F:
|
|
|
- internalformat = GL_RGBA16F;
|
|
|
- // HALF_FLOAT_OES has a different value than HALF_FLOAT... of course
|
|
|
- if (GLAD_OES_texture_half_float && !GLAD_ES_VERSION_3_0)
|
|
|
- gltype = GL_HALF_FLOAT_OES;
|
|
|
- else
|
|
|
- gltype = GL_HALF_FLOAT;
|
|
|
- isSRGB = false;
|
|
|
- break;
|
|
|
- case ImageData::FORMAT_RGBA32F:
|
|
|
- internalformat = GL_RGBA32F;
|
|
|
- gltype = GL_FLOAT;
|
|
|
- isSRGB = false;
|
|
|
- break;
|
|
|
- default:
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
|
|
|
- internalformat = glformat;
|
|
|
-
|
|
|
- return internalformat;
|
|
|
-}
|
|
|
-
|
|
|
-GLenum Image::getCompressedFormat(image::CompressedImageData::Format cformat, bool &isSRGB) const
|
|
|
-{
|
|
|
- using image::CompressedImageData;
|
|
|
-
|
|
|
- switch (cformat)
|
|
|
- {
|
|
|
- case CompressedImageData::FORMAT_DXT1:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
|
|
- case CompressedImageData::FORMAT_DXT3:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
|
|
- case CompressedImageData::FORMAT_DXT5:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
|
- case CompressedImageData::FORMAT_BC4:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_RED_RGTC1;
|
|
|
- case CompressedImageData::FORMAT_BC4s:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_SIGNED_RED_RGTC1;
|
|
|
- case CompressedImageData::FORMAT_BC5:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_RG_RGTC2;
|
|
|
- case CompressedImageData::FORMAT_BC5s:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_SIGNED_RG_RGTC2;
|
|
|
- case CompressedImageData::FORMAT_BC6H:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
|
|
|
- case CompressedImageData::FORMAT_BC6Hs:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
|
|
|
- case CompressedImageData::FORMAT_BC7:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM : GL_COMPRESSED_RGBA_BPTC_UNORM;
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGB2:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT : GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGB4:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGBA2:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT : GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGBA4:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT : GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
|
|
|
- case CompressedImageData::FORMAT_ETC1:
|
|
|
- // The ETC2 format can load ETC1 textures.
|
|
|
- if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ETC2 : GL_COMPRESSED_RGB8_ETC2;
|
|
|
- else
|
|
|
- {
|
|
|
- isSRGB = false;
|
|
|
- return GL_ETC1_RGB8_OES;
|
|
|
- }
|
|
|
- case CompressedImageData::FORMAT_ETC2_RGB:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ETC2 : GL_COMPRESSED_RGB8_ETC2;
|
|
|
- case CompressedImageData::FORMAT_ETC2_RGBA:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : GL_COMPRESSED_RGBA8_ETC2_EAC;
|
|
|
- case CompressedImageData::FORMAT_ETC2_RGBA1:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 : GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
|
|
- case CompressedImageData::FORMAT_EAC_R:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_R11_EAC;
|
|
|
- case CompressedImageData::FORMAT_EAC_Rs:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_SIGNED_R11_EAC;
|
|
|
- case CompressedImageData::FORMAT_EAC_RG:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_RG11_EAC;
|
|
|
- case CompressedImageData::FORMAT_EAC_RGs:
|
|
|
- isSRGB = false;
|
|
|
- return GL_COMPRESSED_SIGNED_RG11_EAC;
|
|
|
- case CompressedImageData::FORMAT_ASTC_4x4:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_5x4:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : GL_COMPRESSED_RGBA_ASTC_5x4_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_5x5:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : GL_COMPRESSED_RGBA_ASTC_5x5_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_6x5:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : GL_COMPRESSED_RGBA_ASTC_6x5_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_6x6:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : GL_COMPRESSED_RGBA_ASTC_6x6_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_8x5:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_8x6:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : GL_COMPRESSED_RGBA_ASTC_8x6_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_8x8:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : GL_COMPRESSED_RGBA_ASTC_8x8_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x5:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : GL_COMPRESSED_RGBA_ASTC_10x5_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x6:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : GL_COMPRESSED_RGBA_ASTC_10x6_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x8:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : GL_COMPRESSED_RGBA_ASTC_10x8_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x10:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : GL_COMPRESSED_RGBA_ASTC_10x10_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_12x10:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : GL_COMPRESSED_RGBA_ASTC_12x10_KHR;
|
|
|
- case CompressedImageData::FORMAT_ASTC_12x12:
|
|
|
- return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : GL_COMPRESSED_RGBA_ASTC_12x12_KHR;
|
|
|
- default:
|
|
|
- return isSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-bool Image::hasAnisotropicFilteringSupport()
|
|
|
-{
|
|
|
- return GLAD_EXT_texture_filter_anisotropic != GL_FALSE;
|
|
|
-}
|
|
|
-
|
|
|
-bool Image::hasTextureSupport(image::ImageData::Format format)
|
|
|
-{
|
|
|
- using image::ImageData;
|
|
|
-
|
|
|
- switch (format)
|
|
|
- {
|
|
|
- case ImageData::FORMAT_RGBA8:
|
|
|
- return true;
|
|
|
- case ImageData::FORMAT_RGBA16:
|
|
|
- return GLAD_VERSION_1_1 || GLAD_EXT_texture_norm16;
|
|
|
- case ImageData::FORMAT_RGBA16F:
|
|
|
- return GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_half_float_pixel) || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float;
|
|
|
- case ImageData::FORMAT_RGBA32F:
|
|
|
- return GLAD_VERSION_3_0 || GLAD_ARB_texture_float || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_float;
|
|
|
- default:
|
|
|
- return false;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-bool Image::hasTextureFilteringSupport(image::ImageData::Format format)
|
|
|
+bool Image::isFormatSupported(PixelFormat pixelformat)
|
|
|
{
|
|
|
- switch (format)
|
|
|
- {
|
|
|
- case image::ImageData::FORMAT_RGBA16F:
|
|
|
- return GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear;
|
|
|
- case image::ImageData::FORMAT_RGBA32F:
|
|
|
- return GLAD_VERSION_1_1;
|
|
|
- default:
|
|
|
- return true;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-bool Image::hasCompressedTextureSupport(image::CompressedImageData::Format format, bool sRGB)
|
|
|
-{
|
|
|
- using image::CompressedImageData;
|
|
|
-
|
|
|
- switch (format)
|
|
|
- {
|
|
|
- case CompressedImageData::FORMAT_DXT1:
|
|
|
- return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
|
|
|
- case CompressedImageData::FORMAT_DXT3:
|
|
|
- return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3;
|
|
|
- case CompressedImageData::FORMAT_DXT5:
|
|
|
- return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5;
|
|
|
- case CompressedImageData::FORMAT_BC4:
|
|
|
- case CompressedImageData::FORMAT_BC4s:
|
|
|
- case CompressedImageData::FORMAT_BC5:
|
|
|
- case CompressedImageData::FORMAT_BC5s:
|
|
|
- return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc);
|
|
|
- case CompressedImageData::FORMAT_BC6H:
|
|
|
- case CompressedImageData::FORMAT_BC6Hs:
|
|
|
- case CompressedImageData::FORMAT_BC7:
|
|
|
- return GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc;
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGB2:
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGB4:
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGBA2:
|
|
|
- case CompressedImageData::FORMAT_PVR1_RGBA4:
|
|
|
- return sRGB ? GLAD_EXT_pvrtc_sRGB : GLAD_IMG_texture_compression_pvrtc;
|
|
|
- case CompressedImageData::FORMAT_ETC1:
|
|
|
- // ETC2 support guarantees ETC1 support as well.
|
|
|
- return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture;
|
|
|
- case CompressedImageData::FORMAT_ETC2_RGB:
|
|
|
- case CompressedImageData::FORMAT_ETC2_RGBA:
|
|
|
- case CompressedImageData::FORMAT_ETC2_RGBA1:
|
|
|
- case CompressedImageData::FORMAT_EAC_R:
|
|
|
- case CompressedImageData::FORMAT_EAC_Rs:
|
|
|
- case CompressedImageData::FORMAT_EAC_RG:
|
|
|
- case CompressedImageData::FORMAT_EAC_RGs:
|
|
|
- return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility;
|
|
|
- case CompressedImageData::FORMAT_ASTC_4x4:
|
|
|
- case CompressedImageData::FORMAT_ASTC_5x4:
|
|
|
- case CompressedImageData::FORMAT_ASTC_5x5:
|
|
|
- case CompressedImageData::FORMAT_ASTC_6x5:
|
|
|
- case CompressedImageData::FORMAT_ASTC_6x6:
|
|
|
- case CompressedImageData::FORMAT_ASTC_8x5:
|
|
|
- case CompressedImageData::FORMAT_ASTC_8x6:
|
|
|
- case CompressedImageData::FORMAT_ASTC_8x8:
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x5:
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x6:
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x8:
|
|
|
- case CompressedImageData::FORMAT_ASTC_10x10:
|
|
|
- case CompressedImageData::FORMAT_ASTC_12x10:
|
|
|
- case CompressedImageData::FORMAT_ASTC_12x12:
|
|
|
- return GLAD_ES_VERSION_3_2 || GLAD_KHR_texture_compression_astc_ldr;
|
|
|
- default:
|
|
|
- return false;
|
|
|
- }
|
|
|
+ return OpenGL::isPixelFormatSupported(pixelformat, false, false);
|
|
|
}
|
|
|
|
|
|
bool Image::hasSRGBSupport()
|