717 lines · 26.1 KB · C++
RawDownload
////  GL45BackendTexture.cpp//  libraries/gpu/src/gpu////  Created by Sam Gateau on 1/19/2015.//  Copyright 2014 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "GL45Backend.h"#include <mutex>#include <algorithm>#include <condition_variable>#include <unordered_set>#include <unordered_map>#include <glm/gtx/component_wise.hpp> #include <QtCore/QDebug>#include <QtCore/QThread> #include <NumericalConstants.h>#include <gpu/gl/GLTexelFormat.h> using namespace gpu;using namespace gpu::gl;using namespace gpu::gl45; using GL45Texture = GL45Backend::GL45Texture;using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture; GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture) {} GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {} #if GPU_BINDLESS_TEXTURESconst GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const {    // The parent call may re-initialize the _bindless member, so we need to call it first    const auto& result = Parent::getBindless();    // Make sure the referenced structure has the correct minimum available mip value    _bindless.minMip = _populatedMip - _allocatedMip;    // Now return the result    return result;}#endif Size GL45VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {    Size amountCopied = 0;    amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);    return amountCopied;}  void copyTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {    for (uint16_t mip = populatedMips; mip < numMips; ++mip) {        auto mipDimensions = texture.evalMipDimensions(mip);        uint16_t targetMip = mip - destMipOffset;        uint16_t sourceMip = mip - srcMipOffset;        auto faces = GLTexture::getFaceCount(texTarget);        for (uint8_t face = 0; face < faces; ++face) {            glCopyImageSubData(                srcId, texTarget, sourceMip, 0, 0, face,                destId, texTarget, targetMip, 0, 0, face,                mipDimensions.x, mipDimensions.y, 1                );            (void)CHECK_GL_ERROR();        }    }} void GL45VariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {    uint16_t numMips = _gpuObject.getNumMips();    copyTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips);} // Managed size resource texturesusing GL45ResourceTexture = GL45Backend::GL45ResourceTexture; GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {    auto mipLevels = texture.getNumMips();    _allocatedMip = mipLevels;    _maxAllocatedMip = _populatedMip = mipLevels;    _minAllocatedMip = texture.minAvailableMipLevel();     for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) {        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {            _maxAllocatedMip = _populatedMip = mip;            break;        }    }     auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);    uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);     allocateStorage(allocatedMip);    copyMipsFromTexture();    syncSampler(texture.getSampler());} void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) {    _allocatedMip = allocatedMip;    const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat());    const auto dimensions = _gpuObject.evalMipDimensions(_allocatedMip);    const auto totalMips = _gpuObject.getNumMips();    const auto mips = totalMips - _allocatedMip;    glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y);    auto mipLevels = _gpuObject.getNumMips();    _size = 0;    for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) {        _size += _gpuObject.evalMipSize(mip);    }    Backend::textureResourceGPUMemSize.update(0, _size);} Size GL45ResourceTexture::copyMipsFromTexture() {    auto mipLevels = _gpuObject.getNumMips();    size_t maxFace = GLTexture::getFaceCount(_target);    Size amount = 0;    for (uint16_t sourceMip = _populatedMip; sourceMip < mipLevels; ++sourceMip) {        uint16_t targetMip = sourceMip - _allocatedMip;        for (uint8_t face = 0; face < maxFace; ++face) {            amount += copyMipFaceFromTexture(sourceMip, targetMip, face);        }    }    incrementPopulatedSize(amount);    return amount;} void GL45ResourceTexture::syncSampler(const Sampler& sampler) const {    Parent::syncSampler(sampler);#if GPU_BINDLESS_TEXTURES    if (!isBindless()) {        glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);    }#else    glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);#endif} size_t GL45ResourceTexture::promote() {    PROFILE_RANGE(render_gpu_gl, __FUNCTION__);    Q_ASSERT(_allocatedMip > 0);     uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);    targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip); #if GPU_BINDLESS_TEXTURES    bool bindless = isBindless();    if (bindless) {        releaseBindless();    }#endif     GLuint oldId = _id;    auto oldSize = _size;    uint16_t oldAllocatedMip = _allocatedMip;     // create new texture    const_cast<GLuint&>(_id) = allocate(_gpuObject);     // allocate storage for new level    allocateStorage(targetAllocatedMip);     // copy pre-existing mips    copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); #if GPU_BINDLESS_TEXTURES    if (bindless) {        getBindless();    }#endif     // destroy the old texture    glDeleteTextures(1, &oldId);     // Update sampler    _cachedSampler = getInvalidSampler();    syncSampler(_gpuObject.getSampler());     // update the memory usage    Backend::textureResourceGPUMemSize.update(oldSize, 0);    // no change to Backend::textureResourcePopulatedGPUMemSize    return (_size - oldSize);} size_t GL45ResourceTexture::demote() {    PROFILE_RANGE(render_gpu_gl, __FUNCTION__);    Q_ASSERT(_allocatedMip < _maxAllocatedMip);    auto oldId = _id;    auto oldSize = _size;    auto oldPopulatedMip = _populatedMip; #if GPU_BINDLESS_TEXTURES    bool bindless = isBindless();    if (bindless) {        releaseBindless();    }#endif     // allocate new texture    const_cast<GLuint&>(_id) = allocate(_gpuObject);    uint16_t oldAllocatedMip = _allocatedMip;    allocateStorage(_allocatedMip + 1);    _populatedMip = std::max(_populatedMip, _allocatedMip);     // copy pre-existing mips    copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); #if GPU_BINDLESS_TEXTURES    if (bindless) {        getBindless();    }#endif     // destroy the old texture    glDeleteTextures(1, &oldId);     // Update sampler    _cachedSampler = getInvalidSampler();    syncSampler(_gpuObject.getSampler());     // update the memory usage    Backend::textureResourceGPUMemSize.update(oldSize, 0);    // Demoting unpopulate the memory delta    if (oldPopulatedMip != _populatedMip) {        auto numPopulatedDemoted = _populatedMip - oldPopulatedMip;        Size amountUnpopulated = 0;        for (int i = 0; i < numPopulatedDemoted; i++) {            amountUnpopulated += _gpuObject.evalMipSize(oldPopulatedMip + i);        }        decrementPopulatedSize(amountUnpopulated);    }    return (oldSize - _size);} void GL45ResourceTexture::populateTransferQueue(TransferQueue& pendingTransfers) {    PROFILE_RANGE(render_gpu_gl, __FUNCTION__);    sanityCheck();     if (_populatedMip <= _allocatedMip) {        return;    }     const uint8_t maxFace = GLTexture::getFaceCount(_target);    uint16_t sourceMip = _populatedMip;    do {        --sourceMip;        auto targetMip = sourceMip - _allocatedMip;        auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);        for (uint8_t face = 0; face < maxFace; ++face) {            if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {                continue;            }             // If the mip is less than the max transfer size, then just do it in one transfer            if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {                // Can the mip be transferred in one go                pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face));                continue;            }             // break down the transfers into chunks so that no single transfer is            // consuming more than X bandwidth            // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block            auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face);            const auto lines = mipDimensions.y;            const uint32_t BLOCK_NUM_LINES{ 4 };            const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES;            auto bytesPerBlock = mipSize / numBlocks;            Q_ASSERT(0 == (mipSize % lines));            uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock);            uint32_t lineOffset = 0;            while (lineOffset < lines) {                uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);                pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face, linesToCopy, lineOffset));                lineOffset += linesToCopy;            }        }         // queue up the sampler and populated mip change for after the transfer has completed        pendingTransfers.emplace(new TransferJob(sourceMip, [=, this] {            _populatedMip = sourceMip;            incrementPopulatedSize(_gpuObject.evalMipSize(sourceMip));            sanityCheck();            syncSampler(_gpuObject.getSampler());        }));    } while (sourceMip != _allocatedMip);} // Sparsely allocated, managed size resource textures#if 0#define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f using GL45SparseResourceTexture = GL45Backend::GL45SparseResourceTexture; GL45Texture::PageDimensionsMap GL45Texture::pageDimensionsByFormat;Mutex GL45Texture::pageDimensionsMutex; GL45Texture::PageDimensions GL45Texture::getPageDimensionsForFormat(const TextureTypeFormat& typeFormat) {    {        Lock lock(pageDimensionsMutex);        if (pageDimensionsByFormat.count(typeFormat)) {            return pageDimensionsByFormat[typeFormat];        }    }     GLint count = 0;    glGetInternalformativ(typeFormat.first, typeFormat.second, GL_NUM_VIRTUAL_PAGE_SIZES_ARB, 1, &count);     std::vector<uvec3> result;    if (count > 0) {        std::vector<GLint> x, y, z;        x.resize(count);        glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_X_ARB, 1, &x[0]);        y.resize(count);        glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Y_ARB, 1, &y[0]);        z.resize(count);        glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Z_ARB, 1, &z[0]);         result.resize(count);        for (GLint i = 0; i < count; ++i) {            result[i] = uvec3(x[i], y[i], z[i]);        }    }     {        Lock lock(pageDimensionsMutex);        if (0 == pageDimensionsByFormat.count(typeFormat)) {            pageDimensionsByFormat[typeFormat] = result;        }    }     return result;} GL45Texture::PageDimensions GL45Texture::getPageDimensionsForFormat(GLenum target, GLenum format) {    return getPageDimensionsForFormat({ target, format });}bool GL45Texture::isSparseEligible(const Texture& texture) {    Q_ASSERT(TextureUsageType::RESOURCE == texture.getUsageType());     // Disabling sparse for the momemnt    return false;     const auto allowedPageDimensions = getPageDimensionsForFormat(getGLTextureType(texture),         gl::GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat()));    const auto textureDimensions = texture.getDimensions();    for (const auto& pageDimensions : allowedPageDimensions) {        if (uvec3(0) == (textureDimensions % pageDimensions)) {            return true;        }    }     return false;}  GL45SparseResourceTexture::GL45SparseResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {    const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat());    const uvec3 dimensions = _gpuObject.getDimensions();    auto allowedPageDimensions = getPageDimensionsForFormat(_target, texelFormat.internalFormat);    uint32_t pageDimensionsIndex = 0;    // In order to enable sparse the texture size must be an integer multiple of the page size    for (size_t i = 0; i < allowedPageDimensions.size(); ++i) {        pageDimensionsIndex = (uint32_t)i;        _pageDimensions = allowedPageDimensions[i];        // Is this texture an integer multiple of page dimensions?        if (uvec3(0) == (dimensions % _pageDimensions)) {            qCDebug(gpugl45logging) << "Enabling sparse for texture " << _gpuObject.source().c_str();            break;        }    }    glTextureParameteri(_id, GL_TEXTURE_SPARSE_ARB, GL_TRUE);    glTextureParameteri(_id, GL_VIRTUAL_PAGE_SIZE_INDEX_ARB, pageDimensionsIndex);    glGetTextureParameterIuiv(_id, GL_NUM_SPARSE_LEVELS_ARB, &_maxSparseLevel);     _pageBytes = _gpuObject.getTexelFormat().getSize();    _pageBytes *= _pageDimensions.x * _pageDimensions.y * _pageDimensions.z;    // Testing with a simple texture allocating app shows an estimated 20% GPU memory overhead for     // sparse textures as compared to non-sparse, so we acount for that here.    _pageBytes = (uint32_t)(_pageBytes * SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE);     //allocateStorage();    syncSampler(texture.getSampler());} GL45SparseResourceTexture::~GL45SparseResourceTexture() {    Backend::updateTextureGPUVirtualMemoryUsage(size(), 0);} uvec3 GL45SparseResourceTexture::getPageCounts(const uvec3& dimensions) const {    auto result = (dimensions / _pageDimensions) +        glm::clamp(dimensions % _pageDimensions, glm::uvec3(0), glm::uvec3(1));    return result;} uint32_t GL45SparseResourceTexture::getPageCount(const uvec3& dimensions) const {    auto pageCounts = getPageCounts(dimensions);    return pageCounts.x * pageCounts.y * pageCounts.z;} void GL45SparseResourceTexture::promote() {} void GL45SparseResourceTexture::demote() {} SparseInfo::SparseInfo(GL45Texture& texture)    : texture(texture) {} void SparseInfo::maybeMakeSparse() {    // Don't enable sparse for objects with explicitly managed mip levels    if (!texture._gpuObject.isAutogenerateMips()) {        return;    }     const uvec3 dimensions = texture._gpuObject.getDimensions();    auto allowedPageDimensions = getPageDimensionsForFormat(texture._target, texture._internalFormat);    // In order to enable sparse the texture size must be an integer multiple of the page size    for (size_t i = 0; i < allowedPageDimensions.size(); ++i) {        pageDimensionsIndex = (uint32_t)i;        pageDimensions = allowedPageDimensions[i];        // Is this texture an integer multiple of page dimensions?        if (uvec3(0) == (dimensions % pageDimensions)) {            qCDebug(gpugl45logging) << "Enabling sparse for texture " << texture._source.c_str();            sparse = true;            break;        }    }     if (sparse) {        glTextureParameteri(texture._id, GL_TEXTURE_SPARSE_ARB, GL_TRUE);        glTextureParameteri(texture._id, GL_VIRTUAL_PAGE_SIZE_INDEX_ARB, pageDimensionsIndex);    } else {        qCDebug(gpugl45logging) << "Size " << dimensions.x << " x " << dimensions.y <<            " is not supported by any sparse page size for texture" << texture._source.c_str();    }}  // This can only be called after we've established our storage sizevoid SparseInfo::update() {    if (!sparse) {        return;    }    glGetTextureParameterIuiv(texture._id, GL_NUM_SPARSE_LEVELS_ARB, &maxSparseLevel);     for (uint16_t mipLevel = 0; mipLevel <= maxSparseLevel; ++mipLevel) {        auto mipDimensions = texture._gpuObject.evalMipDimensions(mipLevel);        auto mipPageCount = getPageCount(mipDimensions);        maxPages += mipPageCount;    }    if (texture._target == GL_TEXTURE_CUBE_MAP) {        maxPages *= GLTexture::CUBE_NUM_FACES;    }}  void SparseInfo::allocateToMip(uint16_t targetMip) {    // Not sparse, do nothing    if (!sparse) {        return;    }     if (allocatedMip == INVALID_MIP) {        allocatedMip = maxSparseLevel + 1;    }     // Don't try to allocate below the maximum sparse level    if (targetMip > maxSparseLevel) {        targetMip = maxSparseLevel;    }     // Already allocated this level    if (allocatedMip <= targetMip) {        return;    }     uint32_t maxFace = (uint32_t)(GL_TEXTURE_CUBE_MAP == texture._target ? CUBE_NUM_FACES : 1);    for (uint16_t mip = targetMip; mip < allocatedMip; ++mip) {        auto size = texture._gpuObject.evalMipDimensions(mip);        glTexturePageCommitmentEXT(texture._id, mip, 0, 0, 0, size.x, size.y, maxFace, GL_TRUE);        allocatedPages += getPageCount(size);    }    allocatedMip = targetMip;} uint32_t SparseInfo::getSize() const {    return allocatedPages * pageBytes;}using SparseInfo = GL45Backend::GL45Texture::SparseInfo; void GL45Texture::updateSize() const {    if (_gpuObject.getTexelFormat().isCompressed()) {        qFatal("Compressed textures not yet supported");    }

Lines 1–500 of 717.