// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include #include #include MATERIALX_NAMESPACE_BEGIN GLTextureHandler::GLTextureHandler(ImageLoaderPtr imageLoader) : ImageHandler(imageLoader) { if (!glActiveTexture) { gladLoadGL(); } int maxTextureUnits; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits); _boundTextureLocations.resize(maxTextureUnits, GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID); } bool GLTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties) { if (!image) { return false; } // Create renderer resources if needed. if (image->getResourceId() == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { if (!createRenderResources(image, true)) { return false; } } // Update bound location if not already bound int textureUnit = getBoundTextureLocation(image->getResourceId()); if (textureUnit < 0) { textureUnit = getNextAvailableTextureLocation(); } if (textureUnit < 0) { std::cerr << "Exceeded maximum number of bound textures in GLTextureHandler::bindImage" << std::endl; return false; } _boundTextureLocations[textureUnit] = image->getResourceId(); glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(GL_TEXTURE_2D, image->getResourceId()); // Set up texture properties GLint minFilterType = mapFilterTypeToGL(samplingProperties.filterType, samplingProperties.enableMipmaps); GLint magFilterType = GL_LINEAR; // Magnification filters are more restrictive than minification GLint uaddressMode = mapAddressModeToGL(samplingProperties.uaddressMode); GLint vaddressMode = mapAddressModeToGL(samplingProperties.vaddressMode); Color4 borderColor(samplingProperties.defaultColor); glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, uaddressMode); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vaddressMode); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilterType); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilterType); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, 16.0f); return true; } bool GLTextureHandler::unbindImage(ImagePtr image) { if (image->getResourceId() != GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { int textureUnit = getBoundTextureLocation(image->getResourceId()); if (textureUnit >= 0) { glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(GL_TEXTURE_2D, GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID); _boundTextureLocations[textureUnit] = GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID; return true; } } return false; } bool GLTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool) { if (image->getResourceId() == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { unsigned int resourceId; glGenTextures(1, &resourceId); if (resourceId == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { std::cerr << "Failed to generate render resource for texture" << std::endl; return false; } image->setResourceId(resourceId); } int textureUnit = getNextAvailableTextureLocation(); if (textureUnit < 0) { return false; } glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(GL_TEXTURE_2D, image->getResourceId()); int glType, glFormat, glInternalFormat; mapTextureFormatToGL(image->getBaseType(), image->getChannelCount(), false, glType, glFormat, glInternalFormat); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, image->getWidth(), image->getHeight(), 0, glFormat, glType, image->getResourceBuffer()); if (image->getChannelCount() == 1) { GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } if (generateMipMaps) { glGenerateMipmap(GL_TEXTURE_2D); } glBindTexture(GL_TEXTURE_2D, 0); return true; } void GLTextureHandler::releaseRenderResources(ImagePtr image) { if (!image) { for (auto iter : _imageCache) { if (iter.second) { releaseRenderResources(iter.second); } } return; } if (image->getResourceId() == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { return; } unbindImage(image); unsigned int resourceId = image->getResourceId(); glDeleteTextures(1, &resourceId); image->setResourceId(GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID); } int GLTextureHandler::getBoundTextureLocation(unsigned int resourceId) { for (size_t i = 0; i < _boundTextureLocations.size(); i++) { if (_boundTextureLocations[i] == resourceId) { return static_cast(i); } } return -1; } int GLTextureHandler::getNextAvailableTextureLocation() { for (size_t i = 0; i < _boundTextureLocations.size(); i++) { if (_boundTextureLocations[i] == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { return static_cast(i); } } return -1; } int GLTextureHandler::mapAddressModeToGL(ImageSamplingProperties::AddressMode addressModeEnum) { const std::array ADDRESS_MODES { // Constant color. Use clamp to border // with border color to achieve this GL_CLAMP_TO_BORDER, // Clamp GL_CLAMP_TO_EDGE, // Repeat GL_REPEAT, // Mirror GL_MIRRORED_REPEAT }; int addressMode = GL_REPEAT; if (addressModeEnum != ImageSamplingProperties::AddressMode::UNSPECIFIED) { addressMode = ADDRESS_MODES[static_cast(addressModeEnum)]; } return addressMode; } int GLTextureHandler::mapFilterTypeToGL(ImageSamplingProperties::FilterType filterTypeEnum, bool enableMipmaps) { if (filterTypeEnum == ImageSamplingProperties::FilterType::CLOSEST) { return enableMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; } return enableMipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; } void GLTextureHandler::mapTextureFormatToGL(Image::BaseType baseType, unsigned int channelCount, bool srgb, int& glType, int& glFormat, int& glInternalFormat) { switch (channelCount) { case 4: glFormat = GL_RGBA; break; case 3: glFormat = GL_RGB; break; case 2: glFormat = GL_RG; break; case 1: glFormat = GL_RED; break; default: throw Exception("Unsupported channel count in mapTextureFormatToGL"); } if (baseType == Image::BaseType::UINT8 || baseType == Image::BaseType::INT8) { glType = baseType == Image::BaseType::UINT8 ? GL_UNSIGNED_BYTE : GL_BYTE; switch (channelCount) { case 4: glInternalFormat = srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; break; case 3: glInternalFormat = srgb ? GL_SRGB8 : GL_RGB8; break; case 2: glInternalFormat = GL_RG8; break; case 1: glInternalFormat = GL_R8; break; default: throw Exception("Unsupported channel count in mapTextureFormatToGL"); } } else if (baseType == Image::BaseType::UINT16 || baseType == Image::BaseType::INT16) { glType = baseType == Image::BaseType::UINT16 ? GL_UNSIGNED_SHORT : GL_SHORT; switch (channelCount) { case 4: glInternalFormat = GL_RGBA16; break; case 3: glInternalFormat = GL_RGB16; break; case 2: glInternalFormat = GL_RG16; break; case 1: glInternalFormat = GL_R16; break; default: throw Exception("Unsupported channel count in mapTextureFormatToGL"); } } else if (baseType == Image::BaseType::HALF) { glType = GL_HALF_FLOAT; switch (channelCount) { case 4: glInternalFormat = GL_RGBA16F; break; case 3: glInternalFormat = GL_RGB16F; break; case 2: glInternalFormat = GL_RG16F; break; case 1: glInternalFormat = GL_R16F; break; default: throw Exception("Unsupported channel count in mapTextureFormatToGL"); } } else if (baseType == Image::BaseType::FLOAT) { glType = GL_FLOAT; switch (channelCount) { case 4: glInternalFormat = GL_RGBA32F; break; case 3: glInternalFormat = GL_RGB32F; break; case 2: glInternalFormat = GL_RG32F; break; case 1: glInternalFormat = GL_R32F; break; default: throw Exception("Unsupported channel count in mapTextureFormatToGL"); } } else { throw Exception("Unsupported base type in mapTextureFormatToGL"); } } MATERIALX_NAMESPACE_END