// // Copyright 2018 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // /// \file glf/udimTexture.cpp #include "pxr/imaging/glf/glew.h" #include "pxr/imaging/glf/udimTexture.h" #include "pxr/imaging/glf/contextCaps.h" #include "pxr/imaging/glf/diagnostic.h" #include "pxr/imaging/glf/glContext.h" #include "pxr/imaging/glf/image.h" #include "pxr/base/tf/stringUtils.h" #include "pxr/base/trace/trace.h" #include "pxr/base/work/loops.h" PXR_NAMESPACE_OPEN_SCOPE namespace { struct _TextureSize { _TextureSize(unsigned int w, unsigned int h) : width(w), height(h) { } unsigned int width; unsigned int height; }; struct _MipDesc { _MipDesc(const _TextureSize& s, GlfImageSharedPtr&& i) : size(s), image(std::move(i)) { } _TextureSize size; GlfImageSharedPtr image; }; using _MipDescArray = std::vector<_MipDesc>; _MipDescArray _GetMipLevels(const TfToken& filePath) { constexpr int maxMipReads = 32; _MipDescArray ret {}; ret.reserve(maxMipReads); unsigned int prevWidth = std::numeric_limits::max(); unsigned int prevHeight = std::numeric_limits::max(); for (unsigned int mip = 0; mip < maxMipReads; ++mip) { GlfImageSharedPtr image = GlfImage::OpenForReading(filePath, 0, mip); if (image == nullptr) { break; } const unsigned int currHeight = std::max(1, image->GetWidth()); const unsigned int currWidth = std::max(1, image->GetHeight()); if (currWidth < prevWidth && currHeight < prevHeight) { prevWidth = currWidth; prevHeight = currHeight; ret.push_back({{currWidth, currHeight}, std::move(image)}); } } return ret; }; } bool GlfIsSupportedUdimTexture(std::string const& imageFilePath) { return TfStringContains(imageFilePath, ""); } TF_REGISTRY_FUNCTION(TfType) { TfType::Define>(); } GlfUdimTexture::GlfUdimTexture( TfToken const& imageFilePath, GlfImage::ImageOriginLocation originLocation, std::vector>&& tiles) : GlfTexture(originLocation), _tiles(std::move(tiles)) { } GlfUdimTexture::~GlfUdimTexture() { _FreeTextureObject(); } GlfUdimTextureRefPtr GlfUdimTexture::New( TfToken const& imageFilePath, GlfImage::ImageOriginLocation originLocation, std::vector>&& tiles) { return TfCreateRefPtr(new GlfUdimTexture( imageFilePath, originLocation, std::move(tiles))); } GlfTexture::BindingVector GlfUdimTexture::GetBindings( TfToken const& identifier, GLuint samplerId) { _ReadImage(); BindingVector ret; ret.push_back(Binding( TfToken(identifier.GetString() + "_Images"), GlfTextureTokens->texels, GL_TEXTURE_2D_ARRAY, _imageArray, samplerId)); ret.push_back(Binding( TfToken(identifier.GetString() + "_Layout"), GlfTextureTokens->layout, GL_TEXTURE_1D, _layout, 0)); return ret; } GLuint GlfUdimTexture::GetGlTextureName() { _ReadImage(); return _imageArray; } VtDictionary GlfUdimTexture::GetTextureInfo(bool forceLoad) { VtDictionary ret; if (forceLoad) { _ReadImage(); } if (_loaded) { ret["memoryUsed"] = GetMemoryUsed(); ret["width"] = _width; ret["height"] = _height; ret["depth"] = _depth; ret["format"] = _format; if (!_tiles.empty()) { ret["imageFilePath"] = std::get<1>(_tiles.front()); } } else { ret["memoryUsed"] = size_t{0}; ret["width"] = 0; ret["height"] = 0; ret["depth"] = 1; ret["format"] = _format; } ret["referenceCount"] = GetCurrentCount(); return ret; } void GlfUdimTexture::_FreeTextureObject() { GlfSharedGLContextScopeHolder sharedGLContextScopeHolder; if (glIsTexture(_imageArray)) { glDeleteTextures(1, &_imageArray); _imageArray = 0; } if (glIsTexture(_layout)) { glDeleteTextures(1, &_layout); _layout = 0; } } void GlfUdimTexture::_ReadImage() { TRACE_FUNCTION(); if (_loaded) { return; } _loaded = true; _FreeTextureObject(); if (_tiles.empty()) { return; } const _MipDescArray firstImageMips = _GetMipLevels(std::get<1>(_tiles[0])); if (firstImageMips.empty()) { return; } _format = firstImageMips[0].image->GetFormat(); const GLenum type = firstImageMips[0].image->GetType(); unsigned int numChannels; if (_format == GL_RED || _format == GL_LUMINANCE) { numChannels = 1; } else if (_format == GL_RG) { numChannels = 2; } else if (_format == GL_RGB) { numChannels = 3; } else if (_format == GL_RGBA) { numChannels = 4; } else { return; } GLenum internalFormat = GL_RGBA8; unsigned int sizePerElem = 1; if (type == GL_FLOAT) { constexpr GLenum internalFormats[] = { GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F }; internalFormat = internalFormats[numChannels - 1]; sizePerElem = 4; } else if (type == GL_UNSIGNED_SHORT) { constexpr GLenum internalFormats[] = { GL_R16, GL_RG16, GL_RGB16, GL_RGBA16 }; internalFormat = internalFormats[numChannels - 1]; sizePerElem = 2; } else if (type == GL_HALF_FLOAT_ARB) { constexpr GLenum internalFormats[] = { GL_R16F, GL_RG16F, GL_RGB16F, GL_RGBA16F }; internalFormat = internalFormats[numChannels - 1]; sizePerElem = 2; } else if (type == GL_UNSIGNED_BYTE) { constexpr GLenum internalFormats[] = { GL_R8, GL_RG8, GL_RGB8, GL_RGBA8 }; internalFormat = internalFormats[numChannels - 1]; sizePerElem = 1; } const unsigned int maxTileCount = std::get<0>(_tiles.back()) + 1; _depth = static_cast(_tiles.size()); const unsigned int numBytesPerPixel = sizePerElem * numChannels; const unsigned int numBytesPerPixelLayer = numBytesPerPixel * _depth; unsigned int targetPixelCount = static_cast(GetMemoryRequested()); const bool loadAllTiles = targetPixelCount == 0; targetPixelCount /= _depth * numBytesPerPixel; std::vector<_TextureSize> mips {}; mips.reserve(firstImageMips.size()); if (firstImageMips.size() == 1) { unsigned int width = firstImageMips[0].size.width; unsigned int height = firstImageMips[0].size.height; while (true) { mips.emplace_back(width, height); if (width == 1 && height == 1) { break; } width = std::max(1u, width / 2u); height = std::max(1u, height / 2u); } if (!loadAllTiles) { std::reverse(mips.begin(), mips.end()); } } else { if (loadAllTiles) { for (_MipDesc const& mip: firstImageMips) { mips.emplace_back(mip.size); } } else { for (auto it = firstImageMips.crbegin(); it != firstImageMips.crend(); ++it) { mips.emplace_back(it->size); } } } unsigned int mipCount = mips.size(); if (!loadAllTiles) { mipCount = 0; for (auto const& mip: mips) { const unsigned int currentPixelCount = mip.width * mip.height; if (targetPixelCount <= currentPixelCount) { break; } ++mipCount; targetPixelCount -= currentPixelCount; } if (mipCount == 0) { mips.clear(); mips.emplace_back(1, 1); mipCount = 1; } else { mips.resize(mipCount, {0, 0}); std::reverse(mips.begin(), mips.end()); } } std::vector> mipData; mipData.resize(mipCount); _width = mips[0].width; _height = mips[0].height; // Texture array queries will use a float as the array specifier. std::vector layoutData; layoutData.resize(maxTileCount, 0); glGenTextures(1, &_imageArray); glBindTexture(GL_TEXTURE_2D_ARRAY, _imageArray); glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipCount, internalFormat, _width, _height, _depth); size_t totalTextureMemory = 0; for (unsigned int mip = 0; mip < mipCount; ++mip) { _TextureSize const& mipSize = mips[mip]; const unsigned int currentMipMemory = mipSize.width * mipSize.height * numBytesPerPixelLayer; mipData[mip].resize(currentMipMemory, 0); totalTextureMemory += currentMipMemory; } WorkParallelForN(_tiles.size(), [&](size_t begin, size_t end) { for (size_t tileId = begin; tileId < end; ++tileId) { std::tuple const& tile = _tiles[tileId]; layoutData[std::get<0>(tile)] = tileId + 1; _MipDescArray images = _GetMipLevels(std::get<1>(tile)); if (images.empty()) { continue; } for (unsigned int mip = 0; mip < mipCount; ++mip) { _TextureSize const& mipSize = mips[mip]; const unsigned int numBytesPerLayer = mipSize.width * mipSize.height * numBytesPerPixel; GlfImage::StorageSpec spec; spec.width = mipSize.width; spec.height = mipSize.height; spec.format = _format; spec.type = type; spec.flipped = true; spec.data = mipData[mip].data() + (tileId * numBytesPerLayer); const auto it = std::find_if(images.rbegin(), images.rend(), [&mipSize](_MipDesc const& i) { return mipSize.width <= i.size.width && mipSize.height <= i.size.height;}); (it == images.rend() ? images.front() : *it).image->Read(spec); } } }, 1); for (unsigned int mip = 0; mip < mipCount; ++mip) { _TextureSize const& mipSize = mips[mip]; glTexSubImage3D(GL_TEXTURE_2D_ARRAY, mip, 0, 0, 0, mipSize.width, mipSize.height, _depth, _format, type, mipData[mip].data()); } glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D_ARRAY, 0); glGenTextures(1, &_layout); glBindTexture(GL_TEXTURE_1D, _layout); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexImage1D(GL_TEXTURE_1D, 0, GL_R32F, layoutData.size(), 0, GL_RED, GL_FLOAT, layoutData.data()); glBindTexture(GL_TEXTURE_1D, 0); GLF_POST_PENDING_GL_ERRORS(); _SetMemoryUsed(totalTextureMemory + _tiles.size() * sizeof(float)); } void GlfUdimTexture::_OnMemoryRequestedDirty() { _loaded = false; } PXR_NAMESPACE_CLOSE_SCOPE