From d02fd06305b33242fbbfd918abfcd3dea23033a6 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 30 Mar 2017 07:45:18 -0400 Subject: [PATCH 1/6] mark joint translations dirty --- libraries/entities/src/ModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 911ff224b2..37591f5ce6 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -429,7 +429,7 @@ void ModelEntityItem::setJointTranslations(const QVector& translation for (int index = 0; index < translations.size(); index++) { if (_localJointTranslationsSet[index]) { _localJointTranslations[index] = translations[index]; - _localJointTranslationsSet[index] = true; + _localJointTranslationsDirty[index] = true; } } }); From b2aa3271f75ff33644db7e2ea54d69114ad05d32 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 11 Apr 2017 15:40:59 -0700 Subject: [PATCH 2/6] Adding support for variable allocated textures to the GL 4.1 backend --- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 4 + libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 428 ++++++++++++++++++ libraries/gpu-gl/src/gpu/gl/GLTexture.h | 122 +++++ libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 28 +- .../src/gpu/gl41/GL41BackendTexture.cpp | 242 ++++++++-- libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp | 2 - libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 104 +---- .../src/gpu/gl45/GL45BackendTexture.cpp | 16 +- .../gpu/gl45/GL45BackendVariableTexture.cpp | 403 ----------------- libraries/gpu/src/gpu/Forward.h | 1 + 10 files changed, 791 insertions(+), 559 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 0800c27839..d0db376a9d 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -744,6 +744,10 @@ void GLBackend::recycle() const { glDeleteQueries((GLsizei)ids.size(), ids.data()); } } + + GLVariableAllocationSupport::manageMemory(); + GLVariableAllocationSupport::_frameTexturesCreated = 0; + } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 65c5788227..199513fc0a 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -8,6 +8,9 @@ #include "GLTexture.h" +#include +#include + #include "GLBackend.h" using namespace gpu; @@ -111,6 +114,20 @@ GLTexture::~GLTexture() { } } +void GLTexture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { + if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { + return; + } + auto size = _gpuObject.evalMipDimensions(sourceMip); + auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); + if (mipData) { + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat()); + copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.format, texelFormat.type, mipData->readData()); + } else { + qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str(); + } +} + GLExternalTexture::GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) : Parent(backend, texture, id) { } @@ -127,3 +144,414 @@ GLExternalTexture::~GLExternalTexture() { const_cast(_id) = 0; } } + + +// Variable sized textures +using MemoryPressureState = GLVariableAllocationSupport::MemoryPressureState; +using WorkQueue = GLVariableAllocationSupport::WorkQueue; + +std::list GLVariableAllocationSupport::_memoryManagedTextures; +MemoryPressureState GLVariableAllocationSupport::_memoryPressureState { MemoryPressureState::Idle }; +std::atomic GLVariableAllocationSupport::_memoryPressureStateStale { false }; +const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; +WorkQueue GLVariableAllocationSupport::_transferQueue; +WorkQueue GLVariableAllocationSupport::_promoteQueue; +WorkQueue GLVariableAllocationSupport::_demoteQueue; +TexturePointer GLVariableAllocationSupport::_currentTransferTexture; +size_t GLVariableAllocationSupport::_frameTexturesCreated { 0 }; + +#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f +#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f +#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024) + +static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); + +using TransferJob = GLVariableAllocationSupport::TransferJob; + +const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; +const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.x * GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.y * 4; + +#if THREADED_TEXTURE_BUFFERING +std::shared_ptr TransferJob::_bufferThread { nullptr }; +std::atomic TransferJob::_shutdownBufferingThread { false }; +Mutex TransferJob::_mutex; +TransferJob::VoidLambdaQueue TransferJob::_bufferLambdaQueue; + +void TransferJob::startTransferLoop() { + if (_bufferThread) { + return; + } + _shutdownBufferingThread = false; + _bufferThread = std::make_shared([] { + TransferJob::bufferLoop(); + }); +} + +void TransferJob::stopTransferLoop() { + if (!_bufferThread) { + return; + } + _shutdownBufferingThread = true; + _bufferThread->join(); + _bufferThread.reset(); + _shutdownBufferingThread = false; +} +#endif + +TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) + : _parent(parent) { + + auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip); + GLenum format; + GLenum type; + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), _parent._gpuObject.getStoredMipFormat()); + format = texelFormat.format; + type = texelFormat.type; + auto mipSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face); + + + if (0 == lines) { + _transferSize = mipSize; + _bufferingLambda = [=] { + auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); + _buffer.resize(_transferSize); + memcpy(&_buffer[0], mipData->readData(), _transferSize); + _bufferingCompleted = true; + }; + + } else { + transferDimensions.y = lines; + auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); + auto bytesPerLine = (uint32_t)mipSize / dimensions.y; + auto sourceOffset = bytesPerLine * lineOffset; + _transferSize = bytesPerLine * lines; + _bufferingLambda = [=] { + auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); + _buffer.resize(_transferSize); + memcpy(&_buffer[0], mipData->readData() + sourceOffset, _transferSize); + _bufferingCompleted = true; + }; + } + + Backend::updateTextureTransferPendingSize(0, _transferSize); + + _transferLambda = [=] { + _parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, format, type, _buffer.data()); + std::vector emptyVector; + _buffer.swap(emptyVector); + }; +} + +TransferJob::TransferJob(const GLTexture& parent, std::function transferLambda) + : _parent(parent), _bufferingCompleted(true), _transferLambda(transferLambda) { +} + +TransferJob::~TransferJob() { + Backend::updateTextureTransferPendingSize(_transferSize, 0); +} + + +bool TransferJob::tryTransfer() { + // Disable threaded texture transfer for now +#if THREADED_TEXTURE_BUFFERING + // Are we ready to transfer + if (_bufferingCompleted) { + _transferLambda(); + return true; + } + + startBuffering(); + return false; +#else + if (!_bufferingCompleted) { + _bufferingLambda(); + _bufferingCompleted = true; + } + _transferLambda(); + return true; +#endif +} + +#if THREADED_TEXTURE_BUFFERING + +void TransferJob::startBuffering() { + if (_bufferingStarted) { + return; + } + _bufferingStarted = true; + { + Lock lock(_mutex); + _bufferLambdaQueue.push(_bufferingLambda); + } +} + +void TransferJob::bufferLoop() { + while (!_shutdownBufferingThread) { + VoidLambdaQueue workingQueue; + { + Lock lock(_mutex); + _bufferLambdaQueue.swap(workingQueue); + } + + if (workingQueue.empty()) { + QThread::msleep(5); + continue; + } + + while (!workingQueue.empty()) { + workingQueue.front()(); + workingQueue.pop(); + } + } +} +#endif + +GLVariableAllocationSupport::GLVariableAllocationSupport() { + _memoryPressureStateStale = true; +} + +GLVariableAllocationSupport::~GLVariableAllocationSupport() { + _memoryPressureStateStale = true; +} + +void GLVariableAllocationSupport::addMemoryManagedTexture(const TexturePointer& texturePointer) { + _memoryManagedTextures.push_back(texturePointer); + addToWorkQueue(texturePointer); +} + +void GLVariableAllocationSupport::addToWorkQueue(const TexturePointer& texturePointer) { + GLTexture* gltexture = Backend::getGPUObject(*texturePointer); + GLVariableAllocationSupport* vargltexture = dynamic_cast(gltexture); + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + if (vargltexture->canDemote()) { + // Demote largest first + _demoteQueue.push({ texturePointer, (float)gltexture->size() }); + } + break; + + case MemoryPressureState::Undersubscribed: + if (vargltexture->canPromote()) { + // Promote smallest first + _promoteQueue.push({ texturePointer, 1.0f / (float)gltexture->size() }); + } + break; + + case MemoryPressureState::Transfer: + if (vargltexture->hasPendingTransfers()) { + // Transfer priority given to smaller mips first + _transferQueue.push({ texturePointer, 1.0f / (float)gltexture->_gpuObject.evalMipSize(vargltexture->_populatedMip) }); + } + break; + + case MemoryPressureState::Idle: + break; + + default: + Q_UNREACHABLE(); + } +} + +WorkQueue& GLVariableAllocationSupport::getActiveWorkQueue() { + static WorkQueue empty; + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + return _demoteQueue; + + case MemoryPressureState::Undersubscribed: + return _promoteQueue; + + case MemoryPressureState::Transfer: + return _transferQueue; + + default: + break; + } + Q_UNREACHABLE(); + return empty; +} + +// FIXME hack for stats display +QString getTextureMemoryPressureModeString() { + switch (GLVariableAllocationSupport::_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + return "Oversubscribed"; + + case MemoryPressureState::Undersubscribed: + return "Undersubscribed"; + + case MemoryPressureState::Transfer: + return "Transfer"; + + case MemoryPressureState::Idle: + return "Idle"; + } + Q_UNREACHABLE(); + return "Unknown"; +} + +void GLVariableAllocationSupport::updateMemoryPressure() { + static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + + size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + if (0 == allowedMemoryAllocation) { + allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; + } + + // If the user explicitly changed the allowed memory usage, we need to mark ourselves stale + // so that we react + if (allowedMemoryAllocation != lastAllowedMemoryAllocation) { + _memoryPressureStateStale = true; + lastAllowedMemoryAllocation = allowedMemoryAllocation; + } + + if (!_memoryPressureStateStale.exchange(false)) { + return; + } + + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + + // Clear any defunct textures (weak pointers that no longer have a valid texture) + _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { + return weakPointer.expired(); + }); + + // Convert weak pointers to strong. This new list may still contain nulls if a texture was + // deleted on another thread between the previous line and this one + std::vector strongTextures; { + strongTextures.reserve(_memoryManagedTextures.size()); + std::transform( + _memoryManagedTextures.begin(), _memoryManagedTextures.end(), + std::back_inserter(strongTextures), + [](const TextureWeakPointer& p) { return p.lock(); }); + } + + size_t totalVariableMemoryAllocation = 0; + size_t idealMemoryAllocation = 0; + bool canDemote = false; + bool canPromote = false; + bool hasTransfers = false; + for (const auto& texture : strongTextures) { + // Race conditions can still leave nulls in the list, so we need to check + if (!texture) { + continue; + } + GLTexture* gltexture = Backend::getGPUObject(*texture); + GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); + // Track how much the texture thinks it should be using + idealMemoryAllocation += texture->evalTotalSize(); + // Track how much we're actually using + totalVariableMemoryAllocation += gltexture->size(); + canDemote |= vartexture->canDemote(); + canPromote |= vartexture->canPromote(); + hasTransfers |= vartexture->hasPendingTransfers(); + } + + size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; + float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation; + + auto newState = MemoryPressureState::Idle; + if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) { + newState = MemoryPressureState::Oversubscribed; + } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) { + newState = MemoryPressureState::Undersubscribed; + } else if (hasTransfers) { + newState = MemoryPressureState::Transfer; + } + + if (newState != _memoryPressureState) { +#if THREADED_TEXTURE_BUFFERING + if (MemoryPressureState::Transfer == _memoryPressureState) { + TransferJob::stopTransferLoop(); + } + _memoryPressureState = newState; + if (MemoryPressureState::Transfer == _memoryPressureState) { + TransferJob::startTransferLoop(); + } +#else + _memoryPressureState = newState; +#endif + // Clear the existing queue + _transferQueue = WorkQueue(); + _promoteQueue = WorkQueue(); + _demoteQueue = WorkQueue(); + + // Populate the existing textures into the queue + for (const auto& texture : strongTextures) { + addToWorkQueue(texture); + } + } +} + +void GLVariableAllocationSupport::processWorkQueues() { + if (MemoryPressureState::Idle == _memoryPressureState) { + return; + } + + auto& workQueue = getActiveWorkQueue(); + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + while (!workQueue.empty()) { + auto workTarget = workQueue.top(); + workQueue.pop(); + auto texture = workTarget.first.lock(); + if (!texture) { + continue; + } + + // Grab the first item off the demote queue + GLTexture* gltexture = Backend::getGPUObject(*texture); + GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); + if (MemoryPressureState::Oversubscribed == _memoryPressureState) { + if (!vartexture->canDemote()) { + continue; + } + vartexture->demote(); + } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) { + if (!vartexture->canPromote()) { + continue; + } + vartexture->promote(); + } else if (MemoryPressureState::Transfer == _memoryPressureState) { + if (!vartexture->hasPendingTransfers()) { + continue; + } + vartexture->executeNextTransfer(texture); + } else { + Q_UNREACHABLE(); + } + + // Reinject into the queue if more work to be done + addToWorkQueue(texture); + break; + } + + if (workQueue.empty()) { + _memoryPressureStateStale = true; + } +} + +void GLVariableAllocationSupport::manageMemory() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + updateMemoryPressure(); + processWorkQueues(); +} + + +void GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& currentTexture) { + if (_populatedMip <= _allocatedMip) { + return; + } + + if (_pendingTransfers.empty()) { + populateTransferQueue(); + } + + if (!_pendingTransfers.empty()) { + // Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture + _currentTransferTexture = currentTexture; + if (_pendingTransfers.front()->tryTransfer()) { + _pendingTransfers.pop(); + _currentTransferTexture.reset(); + } + } +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h index b47aa3e8dd..960575827f 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -11,6 +11,9 @@ #include "GLShared.h" #include "GLBackend.h" #include "GLTexelFormat.h" +#include + +#define THREADED_TEXTURE_BUFFERING 1 namespace gpu { namespace gl { @@ -19,9 +22,124 @@ struct GLFilterMode { GLint magFilter; }; +class GLVariableAllocationSupport { + friend class GLBackend; + +public: + GLVariableAllocationSupport(); + virtual ~GLVariableAllocationSupport(); + + enum class MemoryPressureState { + Idle, + Transfer, + Oversubscribed, + Undersubscribed, + }; + + using QueuePair = std::pair; + struct QueuePairLess { + bool operator()(const QueuePair& a, const QueuePair& b) { + return a.second < b.second; + } + }; + using WorkQueue = std::priority_queue, QueuePairLess>; + + class TransferJob { + using VoidLambda = std::function; + using VoidLambdaQueue = std::queue; + using ThreadPointer = std::shared_ptr; + const GLTexture& _parent; + // Holds the contents to transfer to the GPU in CPU memory + std::vector _buffer; + // Indicates if a transfer from backing storage to interal storage has started + bool _bufferingStarted { false }; + bool _bufferingCompleted { false }; + VoidLambda _transferLambda; + VoidLambda _bufferingLambda; + +#if THREADED_TEXTURE_BUFFERING + static Mutex _mutex; + static VoidLambdaQueue _bufferLambdaQueue; + static ThreadPointer _bufferThread; + static std::atomic _shutdownBufferingThread; + static void bufferLoop(); +#endif + + public: + TransferJob(const TransferJob& other) = delete; + TransferJob(const GLTexture& parent, std::function transferLambda); + TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0); + ~TransferJob(); + bool tryTransfer(); + +#if THREADED_TEXTURE_BUFFERING + static void startTransferLoop(); + static void stopTransferLoop(); +#endif + + private: + size_t _transferSize { 0 }; +#if THREADED_TEXTURE_BUFFERING + void startBuffering(); +#endif + void transfer(); + }; + + using TransferQueue = std::queue>; + static MemoryPressureState _memoryPressureState; + +public: + static void addMemoryManagedTexture(const TexturePointer& texturePointer); + +protected: + static size_t _frameTexturesCreated; + static std::atomic _memoryPressureStateStale; + static std::list _memoryManagedTextures; + static WorkQueue _transferQueue; + static WorkQueue _promoteQueue; + static WorkQueue _demoteQueue; + static TexturePointer _currentTransferTexture; + static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; + static const uvec3 MAX_TRANSFER_DIMENSIONS; + static const size_t MAX_TRANSFER_SIZE; + + + static void updateMemoryPressure(); + static void processWorkQueues(); + static void addToWorkQueue(const TexturePointer& texture); + static WorkQueue& getActiveWorkQueue(); + + static void manageMemory(); + + //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; } + bool canPromote() const { return _allocatedMip > 0; } + bool canDemote() const { return _allocatedMip < _maxAllocatedMip; } + bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; } + void executeNextTransfer(const TexturePointer& currentTexture); + virtual void populateTransferQueue() = 0; + virtual void promote() = 0; + virtual void demote() = 0; + + // The allocated mip level, relative to the number of mips in the gpu::Texture object + // The relationship between a given glMip to the original gpu::Texture mip is always + // glMip + _allocatedMip + uint16 _allocatedMip { 0 }; + // The populated mip level, relative to the number of mips in the gpu::Texture object + // This must always be >= the allocated mip + uint16 _populatedMip { 0 }; + // The highest (lowest resolution) mip that we will support, relative to the number + // of mips in the gpu::Texture object + uint16 _maxAllocatedMip { 0 }; + // Contains a series of lambdas that when executed will transfer data to the GPU, modify + // the _populatedMip and update the sampler in order to fully populate the allocated texture + // until _populatedMip == _allocatedMip + TransferQueue _pendingTransfers; +}; + class GLTexture : public GLObject { using Parent = GLObject; friend class GLBackend; + friend class GLVariableAllocationSupport; public: static const uint16_t INVALID_MIP { (uint16_t)-1 }; static const uint8_t INVALID_FACE { (uint8_t)-1 }; @@ -45,6 +163,8 @@ public: protected: virtual Size size() const = 0; virtual void generateMips() const = 0; + virtual void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const = 0; + virtual void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const final; GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); }; @@ -57,6 +177,8 @@ public: protected: GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); void generateMips() const override {} + void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override {} + Size size() const override { return 0; } }; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 93d65b74dd..0c3c70dbb8 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -45,8 +45,7 @@ public: protected: GL41Texture(const std::weak_ptr& backend, const Texture& texture); void generateMips() const override; - void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const; - void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const; + void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override; virtual void syncSampler() const; void withPreservedTexture(std::function f) const; @@ -86,8 +85,29 @@ public: GL41StrictResourceTexture(const std::weak_ptr& backend, const Texture& texture); }; - class GL41ResourceTexture : public GL41FixedAllocationTexture { - using Parent = GL41FixedAllocationTexture; + class GL41VariableAllocationTexture : public GL41Texture, public GLVariableAllocationSupport { + using Parent = GL41Texture; + friend class GL41Backend; + using PromoteLambda = std::function; + + + protected: + GL41VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture); + ~GL41VariableAllocationTexture(); + + void allocateStorage(uint16 allocatedMip); + void syncSampler() const override; + void promote() override; + void demote() override; + void populateTransferQueue() override; + void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override; + + Size size() const override { return _size; } + Size _size { 0 }; + }; + + class GL41ResourceTexture : public GL41VariableAllocationTexture { + using Parent = GL41VariableAllocationTexture; friend class GL41Backend; protected: GL41ResourceTexture(const std::weak_ptr& backend, const Texture& texture); diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 2056085091..1cb5a42ab9 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -46,11 +46,11 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { object = new GL41StrictResourceTexture(shared_from_this(), texture); break; - case TextureUsageType::RESOURCE: { + case TextureUsageType::RESOURCE: qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str(); object = new GL41ResourceTexture(shared_from_this(), texture); + GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer); break; - } default: Q_UNREACHABLE(); @@ -69,7 +69,9 @@ GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& GLuint GL41Texture::allocate(const Texture& texture) { GLuint result; - glGenTextures(1, &result); + // FIXME technically GL 4.2, but OSX includes the ARB_texture_storage extension + glCreateTextures(getGLTextureType(texture), 1, &result); + //glGenTextures(1, &result); return result; } @@ -105,20 +107,6 @@ void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const (void)CHECK_GL_ERROR(); } -void GL41Texture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { - if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { - return; - } - auto size = _gpuObject.evalMipDimensions(sourceMip); - auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); - if (mipData) { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat()); - copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.format, texelFormat.type, mipData->readData()); - } else { - qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str(); - } -} - void GL41Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); @@ -216,29 +204,217 @@ GL41StrictResourceTexture::GL41StrictResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GL41Texture(backend, texture) { + auto mipLevels = texture.getNumMips(); + _allocatedMip = mipLevels; + uvec3 mipDimensions; + for (uint16_t mip = 0; mip < mipLevels; ++mip) { + if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) { + _maxAllocatedMip = _populatedMip = mip; + break; + } + } + + uint16_t allocatedMip = _populatedMip - std::min(_populatedMip, 2); + allocateStorage(allocatedMip); + _memoryPressureStateStale = true; + size_t maxFace = GLTexture::getFaceCount(_target); + for (uint16_t sourceMip = _populatedMip; sourceMip < mipLevels; ++sourceMip) { + uint16_t targetMip = sourceMip - _allocatedMip; + for (uint8_t face = 0; face < maxFace; ++face) { + copyMipFaceFromTexture(sourceMip, targetMip, face); + } + } + syncSampler(); +} + +GL41VariableAllocationTexture::~GL41VariableAllocationTexture() { + Backend::updateTextureGPUMemoryUsage(0, _size); +} + +void GL41VariableAllocationTexture::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; + withPreservedTexture([&] { + // FIXME technically GL 4.2, but OSX includes the ARB_texture_storage extension + glTexStorage2D(_target, 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::updateTextureGPUMemoryUsage(0, _size); +} + + +void GL41VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const { + withPreservedTexture([&] { + Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, format, type, sourcePointer); + }); +} + +void GL41VariableAllocationTexture::syncSampler() const { + withPreservedTexture([&] { + Parent::syncSampler(); + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); + }); +} + +void GL41VariableAllocationTexture::promote() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + Q_ASSERT(_allocatedMip > 0); + GLuint oldId = _id; + auto oldSize = _size; + // create new texture + const_cast(_id) = allocate(_gpuObject); + uint16_t oldAllocatedMip = _allocatedMip; + + // allocate storage for new level + allocateStorage(_allocatedMip - std::min(_allocatedMip, 2)); + + withPreservedTexture([&] { + GLuint fbo { 0 }; + glCreateFramebuffers(1, &fbo); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + + uint16_t mips = _gpuObject.getNumMips(); + // copy pre-existing mips + for (uint16_t mip = _populatedMip; mip < mips; ++mip) { + auto mipDimensions = _gpuObject.evalMipDimensions(mip); + uint16_t targetMip = mip - _allocatedMip; + uint16_t sourceMip = mip - oldAllocatedMip; + for (GLenum target : getFaceTargets(_target)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); + (void)CHECK_GL_ERROR(); + } + } + + // destroy the transfer framebuffer + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + + syncSampler(); + }); + + // destroy the old texture + glDeleteTextures(1, &oldId); + // update the memory usage + Backend::updateTextureGPUMemoryUsage(oldSize, 0); + populateTransferQueue(); +} + +void GL41VariableAllocationTexture::demote() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + Q_ASSERT(_allocatedMip < _maxAllocatedMip); + auto oldId = _id; + auto oldSize = _size; + const_cast(_id) = allocate(_gpuObject); + uint16_t oldAllocatedMip = _allocatedMip; + allocateStorage(_allocatedMip + 1); + _populatedMip = std::max(_populatedMip, _allocatedMip); + + withPreservedTexture([&] { + GLuint fbo { 0 }; + glCreateFramebuffers(1, &fbo); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + + uint16_t mips = _gpuObject.getNumMips(); + // copy pre-existing mips + for (uint16_t mip = _populatedMip; mip < mips; ++mip) { + auto mipDimensions = _gpuObject.evalMipDimensions(mip); + uint16_t targetMip = mip - _allocatedMip; + uint16_t sourceMip = mip - oldAllocatedMip; + for (GLenum target : getFaceTargets(_target)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); + (void)CHECK_GL_ERROR(); + } + } + + // destroy the transfer framebuffer + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + + syncSampler(); + }); + + + // destroy the old texture + glDeleteTextures(1, &oldId); + // update the memory usage + Backend::updateTextureGPUMemoryUsage(oldSize, 0); + populateTransferQueue(); +} + + +void GL41VariableAllocationTexture::populateTransferQueue() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + if (_populatedMip <= _allocatedMip) { + return; + } + _pendingTransfers = TransferQueue(); + + 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(*this, sourceMip, targetMip, face)); + continue; + } + + // break down the transfers into chunks so that no single transfer is + // consuming more than X bandwidth + auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); + const auto lines = mipDimensions.y; + auto bytesPerLine = mipSize / lines; + Q_ASSERT(0 == (mipSize % lines)); + uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); + uint32_t lineOffset = 0; + while (lineOffset < lines) { + uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); + _pendingTransfers.emplace(new TransferJob(*this, 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(*this, [=] { + _populatedMip = sourceMip; + syncSampler(); + })); + } while (sourceMip != _allocatedMip); +} + // resource textures using GL41ResourceTexture = GL41Backend::GL41ResourceTexture; -GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GL41FixedAllocationTexture(backend, texture) { - Backend::updateTextureGPUMemoryUsage(0, size()); - - withPreservedTexture([&] { - - auto mipLevels = _gpuObject.getNumMips(); - for (uint16_t sourceMip = 0; sourceMip < mipLevels; sourceMip++) { - uint16_t targetMip = sourceMip; - size_t maxFace = GLTexture::getFaceCount(_target); - for (uint8_t face = 0; face < maxFace; face++) { - copyMipFaceFromTexture(sourceMip, targetMip, face); - } - } - }); - +GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GL41VariableAllocationTexture(backend, texture) { if (texture.isAutogenerateMips()) { generateMips(); } } GL41ResourceTexture::~GL41ResourceTexture() { - Backend::updateTextureGPUMemoryUsage(size(), 0); } + + diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp index 12c4b818f7..c9d1fb1b2f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -20,8 +20,6 @@ using namespace gpu::gl45; void GL45Backend::recycle() const { Parent::recycle(); - GL45VariableAllocationTexture::manageMemory(); - GL45VariableAllocationTexture::_frameTexturesCreated = 0; } void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index ca8028aff6..152d4cfec5 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -40,8 +40,7 @@ public: protected: GL45Texture(const std::weak_ptr& backend, const Texture& texture); void generateMips() const override; - void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const; - void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const; + void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override; virtual void syncSampler() const; }; @@ -83,116 +82,17 @@ public: // Textures that can be managed at runtime to increase or decrease their memory load // - class GL45VariableAllocationTexture : public GL45Texture { + class GL45VariableAllocationTexture : public GL45Texture, public GLVariableAllocationSupport { using Parent = GL45Texture; friend class GL45Backend; using PromoteLambda = std::function; - public: - enum class MemoryPressureState { - Idle, - Transfer, - Oversubscribed, - Undersubscribed, - }; - - using QueuePair = std::pair; - struct QueuePairLess { - bool operator()(const QueuePair& a, const QueuePair& b) { - return a.second < b.second; - } - }; - using WorkQueue = std::priority_queue, QueuePairLess>; - - class TransferJob { - using VoidLambda = std::function; - using VoidLambdaQueue = std::queue; - using ThreadPointer = std::shared_ptr; - const GL45VariableAllocationTexture& _parent; - // Holds the contents to transfer to the GPU in CPU memory - std::vector _buffer; - // Indicates if a transfer from backing storage to interal storage has started - bool _bufferingStarted { false }; - bool _bufferingCompleted { false }; - VoidLambda _transferLambda; - VoidLambda _bufferingLambda; -#if THREADED_TEXTURE_BUFFERING - static Mutex _mutex; - static VoidLambdaQueue _bufferLambdaQueue; - static ThreadPointer _bufferThread; - static std::atomic _shutdownBufferingThread; - static void bufferLoop(); -#endif - - public: - TransferJob(const TransferJob& other) = delete; - TransferJob(const GL45VariableAllocationTexture& parent, std::function transferLambda); - TransferJob(const GL45VariableAllocationTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0); - ~TransferJob(); - bool tryTransfer(); - -#if THREADED_TEXTURE_BUFFERING - static void startTransferLoop(); - static void stopTransferLoop(); -#endif - - private: - size_t _transferSize { 0 }; -#if THREADED_TEXTURE_BUFFERING - void startBuffering(); -#endif - void transfer(); - }; - - using TransferQueue = std::queue>; - static MemoryPressureState _memoryPressureState; - protected: - static size_t _frameTexturesCreated; - static std::atomic _memoryPressureStateStale; - static std::list _memoryManagedTextures; - static WorkQueue _transferQueue; - static WorkQueue _promoteQueue; - static WorkQueue _demoteQueue; - static TexturePointer _currentTransferTexture; - static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; - - - static void updateMemoryPressure(); - static void processWorkQueues(); - static void addMemoryManagedTexture(const TexturePointer& texturePointer); - static void addToWorkQueue(const TexturePointer& texture); - static WorkQueue& getActiveWorkQueue(); - - static void manageMemory(); protected: GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture); ~GL45VariableAllocationTexture(); - //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; } - bool canPromote() const { return _allocatedMip > 0; } - bool canDemote() const { return _allocatedMip < _maxAllocatedMip; } - bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; } - void executeNextTransfer(const TexturePointer& currentTexture); Size size() const override { return _size; } - virtual void populateTransferQueue() = 0; - virtual void promote() = 0; - virtual void demote() = 0; - - // The allocated mip level, relative to the number of mips in the gpu::Texture object - // The relationship between a given glMip to the original gpu::Texture mip is always - // glMip + _allocatedMip - uint16 _allocatedMip { 0 }; - // The populated mip level, relative to the number of mips in the gpu::Texture object - // This must always be >= the allocated mip - uint16 _populatedMip { 0 }; - // The highest (lowest resolution) mip that we will support, relative to the number - // of mips in the gpu::Texture object - uint16 _maxAllocatedMip { 0 }; Size _size { 0 }; - // Contains a series of lambdas that when executed will transfer data to the GPU, modify - // the _populatedMip and update the sampler in order to fully populate the allocated texture - // until _populatedMip == _allocatedMip - TransferQueue _pendingTransfers; }; class GL45ResourceTexture : public GL45VariableAllocationTexture { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index ab4153c04c..73974addff 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -67,7 +67,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { #else object = new GL45ResourceTexture(shared_from_this(), texture); #endif - GL45VariableAllocationTexture::addMemoryManagedTexture(texturePointer); + GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer); } else { auto fallback = texturePointer->getFallbackTexture(); if (fallback) { @@ -135,20 +135,6 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const (void)CHECK_GL_ERROR(); } -void GL45Texture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { - if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { - return; - } - auto size = _gpuObject.evalMipDimensions(sourceMip); - auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); - if (mipData) { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat()); - copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.format, texelFormat.type, mipData->readData()); - } else { - qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str(); - } -} - void GL45Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index a614d62221..a453d4207d 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -27,416 +27,16 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -// Variable sized textures using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture; -using MemoryPressureState = GL45VariableAllocationTexture::MemoryPressureState; -using WorkQueue = GL45VariableAllocationTexture::WorkQueue; - -std::list GL45VariableAllocationTexture::_memoryManagedTextures; -MemoryPressureState GL45VariableAllocationTexture::_memoryPressureState = MemoryPressureState::Idle; -std::atomic GL45VariableAllocationTexture::_memoryPressureStateStale { false }; -const uvec3 GL45VariableAllocationTexture::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; -WorkQueue GL45VariableAllocationTexture::_transferQueue; -WorkQueue GL45VariableAllocationTexture::_promoteQueue; -WorkQueue GL45VariableAllocationTexture::_demoteQueue; -TexturePointer GL45VariableAllocationTexture::_currentTransferTexture; - -#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f -#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f -#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024) - -static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); - -using TransferJob = GL45VariableAllocationTexture::TransferJob; - -static const uvec3 MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; -static const size_t MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSIONS.x * MAX_TRANSFER_DIMENSIONS.y * 4; - -#if THREADED_TEXTURE_BUFFERING -std::shared_ptr TransferJob::_bufferThread { nullptr }; -std::atomic TransferJob::_shutdownBufferingThread { false }; -Mutex TransferJob::_mutex; -TransferJob::VoidLambdaQueue TransferJob::_bufferLambdaQueue; - -void TransferJob::startTransferLoop() { - if (_bufferThread) { - return; - } - _shutdownBufferingThread = false; - _bufferThread = std::make_shared([] { - TransferJob::bufferLoop(); - }); -} - -void TransferJob::stopTransferLoop() { - if (!_bufferThread) { - return; - } - _shutdownBufferingThread = true; - _bufferThread->join(); - _bufferThread.reset(); - _shutdownBufferingThread = false; -} -#endif - -TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) - : _parent(parent) { - - auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip); - GLenum format; - GLenum type; - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), _parent._gpuObject.getStoredMipFormat()); - format = texelFormat.format; - type = texelFormat.type; - auto mipSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face); - - - if (0 == lines) { - _transferSize = mipSize; - _bufferingLambda = [=] { - auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); - _buffer.resize(_transferSize); - memcpy(&_buffer[0], mipData->readData(), _transferSize); - _bufferingCompleted = true; - }; - - } else { - transferDimensions.y = lines; - auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); - auto bytesPerLine = (uint32_t)mipSize / dimensions.y; - auto sourceOffset = bytesPerLine * lineOffset; - _transferSize = bytesPerLine * lines; - _bufferingLambda = [=] { - auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); - _buffer.resize(_transferSize); - memcpy(&_buffer[0], mipData->readData() + sourceOffset, _transferSize); - _bufferingCompleted = true; - }; - } - - Backend::updateTextureTransferPendingSize(0, _transferSize); - - _transferLambda = [=] { - _parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, format, type, _buffer.data()); - std::vector emptyVector; - _buffer.swap(emptyVector); - }; -} - -TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, std::function transferLambda) - : _parent(parent), _bufferingCompleted(true), _transferLambda(transferLambda) { -} - -TransferJob::~TransferJob() { - Backend::updateTextureTransferPendingSize(_transferSize, 0); -} - - -bool TransferJob::tryTransfer() { - // Disable threaded texture transfer for now -#if THREADED_TEXTURE_BUFFERING - // Are we ready to transfer - if (_bufferingCompleted) { - _transferLambda(); - return true; - } - - startBuffering(); - return false; -#else - if (!_bufferingCompleted) { - _bufferingLambda(); - _bufferingCompleted = true; - } - _transferLambda(); - return true; -#endif -} - -#if THREADED_TEXTURE_BUFFERING - -void TransferJob::startBuffering() { - if (_bufferingStarted) { - return; - } - _bufferingStarted = true; - { - Lock lock(_mutex); - _bufferLambdaQueue.push(_bufferingLambda); - } -} - -void TransferJob::bufferLoop() { - while (!_shutdownBufferingThread) { - VoidLambdaQueue workingQueue; - { - Lock lock(_mutex); - _bufferLambdaQueue.swap(workingQueue); - } - - if (workingQueue.empty()) { - QThread::msleep(5); - continue; - } - - while (!workingQueue.empty()) { - workingQueue.front()(); - workingQueue.pop(); - } - } -} -#endif - - -void GL45VariableAllocationTexture::addMemoryManagedTexture(const TexturePointer& texturePointer) { - _memoryManagedTextures.push_back(texturePointer); - addToWorkQueue(texturePointer); -} - -void GL45VariableAllocationTexture::addToWorkQueue(const TexturePointer& texturePointer) { - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texturePointer); - switch (_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - if (object->canDemote()) { - // Demote largest first - _demoteQueue.push({ texturePointer, (float)object->size() }); - } - break; - - case MemoryPressureState::Undersubscribed: - if (object->canPromote()) { - // Promote smallest first - _promoteQueue.push({ texturePointer, 1.0f / (float)object->size() }); - } - break; - - case MemoryPressureState::Transfer: - if (object->hasPendingTransfers()) { - // Transfer priority given to smaller mips first - _transferQueue.push({ texturePointer, 1.0f / (float)object->_gpuObject.evalMipSize(object->_populatedMip) }); - } - break; - - case MemoryPressureState::Idle: - break; - - default: - Q_UNREACHABLE(); - } -} - -WorkQueue& GL45VariableAllocationTexture::getActiveWorkQueue() { - static WorkQueue empty; - switch (_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - return _demoteQueue; - - case MemoryPressureState::Undersubscribed: - return _promoteQueue; - - case MemoryPressureState::Transfer: - return _transferQueue; - - default: - break; - } - Q_UNREACHABLE(); - return empty; -} - -// FIXME hack for stats display -QString getTextureMemoryPressureModeString() { - switch (GL45VariableAllocationTexture::_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - return "Oversubscribed"; - - case MemoryPressureState::Undersubscribed: - return "Undersubscribed"; - - case MemoryPressureState::Transfer: - return "Transfer"; - - case MemoryPressureState::Idle: - return "Idle"; - } - Q_UNREACHABLE(); - return "Unknown"; -} - -void GL45VariableAllocationTexture::updateMemoryPressure() { - static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); - - size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); - if (0 == allowedMemoryAllocation) { - allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; - } - - // If the user explicitly changed the allowed memory usage, we need to mark ourselves stale - // so that we react - if (allowedMemoryAllocation != lastAllowedMemoryAllocation) { - _memoryPressureStateStale = true; - lastAllowedMemoryAllocation = allowedMemoryAllocation; - } - - if (!_memoryPressureStateStale.exchange(false)) { - return; - } - - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - - // Clear any defunct textures (weak pointers that no longer have a valid texture) - _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { - return weakPointer.expired(); - }); - - // Convert weak pointers to strong. This new list may still contain nulls if a texture was - // deleted on another thread between the previous line and this one - std::vector strongTextures; { - strongTextures.reserve(_memoryManagedTextures.size()); - std::transform( - _memoryManagedTextures.begin(), _memoryManagedTextures.end(), - std::back_inserter(strongTextures), - [](const TextureWeakPointer& p) { return p.lock(); }); - } - - size_t totalVariableMemoryAllocation = 0; - size_t idealMemoryAllocation = 0; - bool canDemote = false; - bool canPromote = false; - bool hasTransfers = false; - for (const auto& texture : strongTextures) { - // Race conditions can still leave nulls in the list, so we need to check - if (!texture) { - continue; - } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - // Track how much the texture thinks it should be using - idealMemoryAllocation += texture->evalTotalSize(); - // Track how much we're actually using - totalVariableMemoryAllocation += object->size(); - canDemote |= object->canDemote(); - canPromote |= object->canPromote(); - hasTransfers |= object->hasPendingTransfers(); - } - - size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; - float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation; - - auto newState = MemoryPressureState::Idle; - if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) { - newState = MemoryPressureState::Oversubscribed; - } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) { - newState = MemoryPressureState::Undersubscribed; - } else if (hasTransfers) { - newState = MemoryPressureState::Transfer; - } - - if (newState != _memoryPressureState) { -#if THREADED_TEXTURE_BUFFERING - if (MemoryPressureState::Transfer == _memoryPressureState) { - TransferJob::stopTransferLoop(); - } - _memoryPressureState = newState; - if (MemoryPressureState::Transfer == _memoryPressureState) { - TransferJob::startTransferLoop(); - } -#else - _memoryPressureState = newState; -#endif - // Clear the existing queue - _transferQueue = WorkQueue(); - _promoteQueue = WorkQueue(); - _demoteQueue = WorkQueue(); - - // Populate the existing textures into the queue - for (const auto& texture : strongTextures) { - addToWorkQueue(texture); - } - } -} - -void GL45VariableAllocationTexture::processWorkQueues() { - if (MemoryPressureState::Idle == _memoryPressureState) { - return; - } - - auto& workQueue = getActiveWorkQueue(); - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - while (!workQueue.empty()) { - auto workTarget = workQueue.top(); - workQueue.pop(); - auto texture = workTarget.first.lock(); - if (!texture) { - continue; - } - - // Grab the first item off the demote queue - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (MemoryPressureState::Oversubscribed == _memoryPressureState) { - if (!object->canDemote()) { - continue; - } - object->demote(); - } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) { - if (!object->canPromote()) { - continue; - } - object->promote(); - } else if (MemoryPressureState::Transfer == _memoryPressureState) { - if (!object->hasPendingTransfers()) { - continue; - } - object->executeNextTransfer(texture); - } else { - Q_UNREACHABLE(); - } - - // Reinject into the queue if more work to be done - addToWorkQueue(texture); - break; - } - - if (workQueue.empty()) { - _memoryPressureStateStale = true; - } -} - -void GL45VariableAllocationTexture::manageMemory() { - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - updateMemoryPressure(); - processWorkQueues(); -} - -size_t GL45VariableAllocationTexture::_frameTexturesCreated { 0 }; GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture) { ++_frameTexturesCreated; } GL45VariableAllocationTexture::~GL45VariableAllocationTexture() { - _memoryPressureStateStale = true; Backend::updateTextureGPUMemoryUsage(_size, 0); } -void GL45VariableAllocationTexture::executeNextTransfer(const TexturePointer& currentTexture) { - if (_populatedMip <= _allocatedMip) { - return; - } - - if (_pendingTransfers.empty()) { - populateTransferQueue(); - } - - if (!_pendingTransfers.empty()) { - // Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture - _currentTransferTexture = currentTexture; - if (_pendingTransfers.front()->tryTransfer()) { - _pendingTransfers.pop(); - _currentTransferTexture.reset(); - } - } -} - // Managed size resource textures using GL45ResourceTexture = GL45Backend::GL45ResourceTexture; @@ -453,7 +53,6 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr& backend uint16_t allocatedMip = _populatedMip - std::min(_populatedMip, 2); allocateStorage(allocatedMip); - _memoryPressureStateStale = true; copyMipsFromTexture(); syncSampler(); @@ -521,7 +120,6 @@ void GL45ResourceTexture::promote() { glDeleteTextures(1, &oldId); // update the memory usage Backend::updateTextureGPUMemoryUsage(oldSize, 0); - _memoryPressureStateStale = true; syncSampler(); populateTransferQueue(); } @@ -554,7 +152,6 @@ void GL45ResourceTexture::demote() { glDeleteTextures(1, &oldId); // update the memory usage Backend::updateTextureGPUMemoryUsage(oldSize, 0); - _memoryPressureStateStale = true; syncSampler(); populateTransferQueue(); } diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index f28c18eb11..ce95e8657e 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -87,6 +87,7 @@ namespace gpu { class Sampler; class Texture; using TexturePointer = std::shared_ptr; + using TextureWeakPointer = std::weak_ptr; using Textures = std::vector; class TextureView; using TextureViews = std::vector; From ce8dba21a0f1a273817cd5c1198035843021fe8b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 18 Apr 2017 22:49:43 +0100 Subject: [PATCH 3/6] fix tablet proxy choosing wrong root --- .../src/TabletScriptingInterface.cpp | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index e744e83ebb..60e40c76f5 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -410,40 +410,36 @@ void TabletProxy::loadQMLSource(const QVariant& path) { } void TabletProxy::pushOntoStack(const QVariant& path) { - if (_qmlTabletRoot) { - auto stack = _qmlTabletRoot->findChild("stack"); + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow->asQuickItem(); + } + + if (root) { + auto stack = root->findChild("stack"); if (stack) { QMetaObject::invokeMethod(stack, "pushSource", Q_ARG(const QVariant&, path)); } else { loadQMLSource(path); } - } else if (_desktopWindow) { - auto stack = _desktopWindow->asQuickItem()->findChild("stack"); - if (stack) { - QMetaObject::invokeMethod(stack, "pushSource", Q_ARG(const QVariant&, path)); - } else { - qCDebug(scriptengine) << "tablet cannot push QML because _desktopWindow doesn't have child stack"; - } } else { qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null"; } } void TabletProxy::popFromStack() { - if (_qmlTabletRoot) { - auto stack = _qmlTabletRoot->findChild("stack"); - if (stack) { - QMetaObject::invokeMethod(stack, "popSource"); - } else { - qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot doesn't have child stack"; - } - } else if (_desktopWindow) { - auto stack = _desktopWindow->asQuickItem()->findChild("stack"); - if (stack) { - QMetaObject::invokeMethod(stack, "popSource"); - } else { - qCDebug(scriptengine) << "tablet cannot pop QML because _desktopWindow doesn't have child stack"; - } + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow->asQuickItem(); + } + + if (root) { + auto stack = root->findChild("stack"); + QMetaObject::invokeMethod(stack, "popSource"); } else { qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null"; } From 2a2102c7fe3c1239acf651121163966fa7e504d1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 18 Apr 2017 15:12:34 -0700 Subject: [PATCH 4/6] Recompute memory pressure state after each promote / demote call --- libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 199513fc0a..22743f106b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -506,11 +506,13 @@ void GLVariableAllocationSupport::processWorkQueues() { continue; } vartexture->demote(); + _memoryPressureStateStale = true; } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) { if (!vartexture->canPromote()) { continue; } vartexture->promote(); + _memoryPressureStateStale = true; } else if (MemoryPressureState::Transfer == _memoryPressureState) { if (!vartexture->hasPendingTransfers()) { continue; From bccc06d2fe6f228e44b09934992279f4189c3122 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 19 Apr 2017 10:35:19 -0700 Subject: [PATCH 5/6] Fix resource texture memory usage tracking --- libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 1cb5a42ab9..1e02b3a385 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -231,7 +231,7 @@ GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr } GL41VariableAllocationTexture::~GL41VariableAllocationTexture() { - Backend::updateTextureGPUMemoryUsage(0, _size); + Backend::updateTextureGPUMemoryUsage(_size, 0); } void GL41VariableAllocationTexture::allocateStorage(uint16 allocatedMip) { From 472835d65c627285331f7639a2b1e172382afd27 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 19 Apr 2017 11:22:20 -0700 Subject: [PATCH 6/6] Port fix from master --- libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 22743f106b..19891d3370 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -478,6 +478,10 @@ void GLVariableAllocationSupport::updateMemoryPressure() { // Populate the existing textures into the queue for (const auto& texture : strongTextures) { + // Race conditions can still leave nulls in the list, so we need to check + if (!texture) { + continue; + } addToWorkQueue(texture); } }