mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 19:59:28 +02:00
Adding support for variable allocated textures to the GL 4.1 backend
This commit is contained in:
parent
c545558b8a
commit
b2aa3271f7
10 changed files with 791 additions and 559 deletions
|
@ -744,6 +744,10 @@ void GLBackend::recycle() const {
|
||||||
glDeleteQueries((GLsizei)ids.size(), ids.data());
|
glDeleteQueries((GLsizei)ids.size(), ids.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLVariableAllocationSupport::manageMemory();
|
||||||
|
GLVariableAllocationSupport::_frameTexturesCreated = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLBackend::setCameraCorrection(const Mat4& correction) {
|
void GLBackend::setCameraCorrection(const Mat4& correction) {
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
#include "GLTexture.h"
|
#include "GLTexture.h"
|
||||||
|
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
#include <NumericalConstants.h>
|
||||||
|
|
||||||
#include "GLBackend.h"
|
#include "GLBackend.h"
|
||||||
|
|
||||||
using namespace gpu;
|
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<GLBackend>& backend, const Texture& texture, GLuint id)
|
GLExternalTexture::GLExternalTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id)
|
||||||
: Parent(backend, texture, id) { }
|
: Parent(backend, texture, id) { }
|
||||||
|
@ -127,3 +144,414 @@ GLExternalTexture::~GLExternalTexture() {
|
||||||
const_cast<GLuint&>(_id) = 0;
|
const_cast<GLuint&>(_id) = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Variable sized textures
|
||||||
|
using MemoryPressureState = GLVariableAllocationSupport::MemoryPressureState;
|
||||||
|
using WorkQueue = GLVariableAllocationSupport::WorkQueue;
|
||||||
|
|
||||||
|
std::list<TextureWeakPointer> GLVariableAllocationSupport::_memoryManagedTextures;
|
||||||
|
MemoryPressureState GLVariableAllocationSupport::_memoryPressureState { MemoryPressureState::Idle };
|
||||||
|
std::atomic<bool> 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<std::thread> TransferJob::_bufferThread { nullptr };
|
||||||
|
std::atomic<bool> TransferJob::_shutdownBufferingThread { false };
|
||||||
|
Mutex TransferJob::_mutex;
|
||||||
|
TransferJob::VoidLambdaQueue TransferJob::_bufferLambdaQueue;
|
||||||
|
|
||||||
|
void TransferJob::startTransferLoop() {
|
||||||
|
if (_bufferThread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shutdownBufferingThread = false;
|
||||||
|
_bufferThread = std::make_shared<std::thread>([] {
|
||||||
|
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<uint8_t> emptyVector;
|
||||||
|
_buffer.swap(emptyVector);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TransferJob::TransferJob(const GLTexture& parent, std::function<void()> 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<GLTexture>(*texturePointer);
|
||||||
|
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(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<TexturePointer> 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<GLTexture>(*texture);
|
||||||
|
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(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<GLTexture>(*texture);
|
||||||
|
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
#include "GLShared.h"
|
#include "GLShared.h"
|
||||||
#include "GLBackend.h"
|
#include "GLBackend.h"
|
||||||
#include "GLTexelFormat.h"
|
#include "GLTexelFormat.h"
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#define THREADED_TEXTURE_BUFFERING 1
|
||||||
|
|
||||||
namespace gpu { namespace gl {
|
namespace gpu { namespace gl {
|
||||||
|
|
||||||
|
@ -19,9 +22,124 @@ struct GLFilterMode {
|
||||||
GLint magFilter;
|
GLint magFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class GLVariableAllocationSupport {
|
||||||
|
friend class GLBackend;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GLVariableAllocationSupport();
|
||||||
|
virtual ~GLVariableAllocationSupport();
|
||||||
|
|
||||||
|
enum class MemoryPressureState {
|
||||||
|
Idle,
|
||||||
|
Transfer,
|
||||||
|
Oversubscribed,
|
||||||
|
Undersubscribed,
|
||||||
|
};
|
||||||
|
|
||||||
|
using QueuePair = std::pair<TextureWeakPointer, float>;
|
||||||
|
struct QueuePairLess {
|
||||||
|
bool operator()(const QueuePair& a, const QueuePair& b) {
|
||||||
|
return a.second < b.second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using WorkQueue = std::priority_queue<QueuePair, std::vector<QueuePair>, QueuePairLess>;
|
||||||
|
|
||||||
|
class TransferJob {
|
||||||
|
using VoidLambda = std::function<void()>;
|
||||||
|
using VoidLambdaQueue = std::queue<VoidLambda>;
|
||||||
|
using ThreadPointer = std::shared_ptr<std::thread>;
|
||||||
|
const GLTexture& _parent;
|
||||||
|
// Holds the contents to transfer to the GPU in CPU memory
|
||||||
|
std::vector<uint8_t> _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<bool> _shutdownBufferingThread;
|
||||||
|
static void bufferLoop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
TransferJob(const TransferJob& other) = delete;
|
||||||
|
TransferJob(const GLTexture& parent, std::function<void()> 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<std::unique_ptr<TransferJob>>;
|
||||||
|
static MemoryPressureState _memoryPressureState;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void addMemoryManagedTexture(const TexturePointer& texturePointer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static size_t _frameTexturesCreated;
|
||||||
|
static std::atomic<bool> _memoryPressureStateStale;
|
||||||
|
static std::list<TextureWeakPointer> _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<Texture> {
|
class GLTexture : public GLObject<Texture> {
|
||||||
using Parent = GLObject<Texture>;
|
using Parent = GLObject<Texture>;
|
||||||
friend class GLBackend;
|
friend class GLBackend;
|
||||||
|
friend class GLVariableAllocationSupport;
|
||||||
public:
|
public:
|
||||||
static const uint16_t INVALID_MIP { (uint16_t)-1 };
|
static const uint16_t INVALID_MIP { (uint16_t)-1 };
|
||||||
static const uint8_t INVALID_FACE { (uint8_t)-1 };
|
static const uint8_t INVALID_FACE { (uint8_t)-1 };
|
||||||
|
@ -45,6 +163,8 @@ public:
|
||||||
protected:
|
protected:
|
||||||
virtual Size size() const = 0;
|
virtual Size size() const = 0;
|
||||||
virtual void generateMips() 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<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
||||||
};
|
};
|
||||||
|
@ -57,6 +177,8 @@ public:
|
||||||
protected:
|
protected:
|
||||||
GLExternalTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
GLExternalTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
||||||
void generateMips() const override {}
|
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; }
|
Size size() const override { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
||||||
void generateMips() const override;
|
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 override;
|
||||||
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const;
|
|
||||||
virtual void syncSampler() const;
|
virtual void syncSampler() const;
|
||||||
|
|
||||||
void withPreservedTexture(std::function<void()> f) const;
|
void withPreservedTexture(std::function<void()> f) const;
|
||||||
|
@ -86,8 +85,29 @@ public:
|
||||||
GL41StrictResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
GL41StrictResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
||||||
};
|
};
|
||||||
|
|
||||||
class GL41ResourceTexture : public GL41FixedAllocationTexture {
|
class GL41VariableAllocationTexture : public GL41Texture, public GLVariableAllocationSupport {
|
||||||
using Parent = GL41FixedAllocationTexture;
|
using Parent = GL41Texture;
|
||||||
|
friend class GL41Backend;
|
||||||
|
using PromoteLambda = std::function<void()>;
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& 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;
|
friend class GL41Backend;
|
||||||
protected:
|
protected:
|
||||||
GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
||||||
|
|
|
@ -46,11 +46,11 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||||
object = new GL41StrictResourceTexture(shared_from_this(), texture);
|
object = new GL41StrictResourceTexture(shared_from_this(), texture);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextureUsageType::RESOURCE: {
|
case TextureUsageType::RESOURCE:
|
||||||
qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str();
|
qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str();
|
||||||
object = new GL41ResourceTexture(shared_from_this(), texture);
|
object = new GL41ResourceTexture(shared_from_this(), texture);
|
||||||
|
GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Q_UNREACHABLE();
|
Q_UNREACHABLE();
|
||||||
|
@ -69,7 +69,9 @@ GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
|
||||||
|
|
||||||
GLuint GL41Texture::allocate(const Texture& texture) {
|
GLuint GL41Texture::allocate(const Texture& texture) {
|
||||||
GLuint result;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,20 +107,6 @@ void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
|
||||||
(void)CHECK_GL_ERROR();
|
(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 {
|
void GL41Texture::syncSampler() const {
|
||||||
const Sampler& sampler = _gpuObject.getSampler();
|
const Sampler& sampler = _gpuObject.getSampler();
|
||||||
|
|
||||||
|
@ -216,29 +204,217 @@ GL41StrictResourceTexture::GL41StrictResourceTexture(const std::weak_ptr<GLBacke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using GL41VariableAllocationTexture = GL41Backend::GL41VariableAllocationTexture;
|
||||||
|
|
||||||
|
GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& 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<uint16_t>(_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<GLuint&>(_id) = allocate(_gpuObject);
|
||||||
|
uint16_t oldAllocatedMip = _allocatedMip;
|
||||||
|
|
||||||
|
// allocate storage for new level
|
||||||
|
allocateStorage(_allocatedMip - std::min<uint16_t>(_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<GLuint&>(_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<uint32_t>(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
|
// resource textures
|
||||||
using GL41ResourceTexture = GL41Backend::GL41ResourceTexture;
|
using GL41ResourceTexture = GL41Backend::GL41ResourceTexture;
|
||||||
|
|
||||||
GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41FixedAllocationTexture(backend, texture) {
|
GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41VariableAllocationTexture(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (texture.isAutogenerateMips()) {
|
if (texture.isAutogenerateMips()) {
|
||||||
generateMips();
|
generateMips();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GL41ResourceTexture::~GL41ResourceTexture() {
|
GL41ResourceTexture::~GL41ResourceTexture() {
|
||||||
Backend::updateTextureGPUMemoryUsage(size(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@ using namespace gpu::gl45;
|
||||||
|
|
||||||
void GL45Backend::recycle() const {
|
void GL45Backend::recycle() const {
|
||||||
Parent::recycle();
|
Parent::recycle();
|
||||||
GL45VariableAllocationTexture::manageMemory();
|
|
||||||
GL45VariableAllocationTexture::_frameTexturesCreated = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
||||||
|
|
|
@ -40,8 +40,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
||||||
void generateMips() const override;
|
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 override;
|
||||||
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const;
|
|
||||||
virtual void syncSampler() const;
|
virtual void syncSampler() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,116 +82,17 @@ public:
|
||||||
// Textures that can be managed at runtime to increase or decrease their memory load
|
// 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;
|
using Parent = GL45Texture;
|
||||||
friend class GL45Backend;
|
friend class GL45Backend;
|
||||||
using PromoteLambda = std::function<void()>;
|
using PromoteLambda = std::function<void()>;
|
||||||
|
|
||||||
public:
|
|
||||||
enum class MemoryPressureState {
|
|
||||||
Idle,
|
|
||||||
Transfer,
|
|
||||||
Oversubscribed,
|
|
||||||
Undersubscribed,
|
|
||||||
};
|
|
||||||
|
|
||||||
using QueuePair = std::pair<TextureWeakPointer, float>;
|
|
||||||
struct QueuePairLess {
|
|
||||||
bool operator()(const QueuePair& a, const QueuePair& b) {
|
|
||||||
return a.second < b.second;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
using WorkQueue = std::priority_queue<QueuePair, std::vector<QueuePair>, QueuePairLess>;
|
|
||||||
|
|
||||||
class TransferJob {
|
|
||||||
using VoidLambda = std::function<void()>;
|
|
||||||
using VoidLambdaQueue = std::queue<VoidLambda>;
|
|
||||||
using ThreadPointer = std::shared_ptr<std::thread>;
|
|
||||||
const GL45VariableAllocationTexture& _parent;
|
|
||||||
// Holds the contents to transfer to the GPU in CPU memory
|
|
||||||
std::vector<uint8_t> _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<bool> _shutdownBufferingThread;
|
|
||||||
static void bufferLoop();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
|
||||||
TransferJob(const TransferJob& other) = delete;
|
|
||||||
TransferJob(const GL45VariableAllocationTexture& parent, std::function<void()> 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<std::unique_ptr<TransferJob>>;
|
|
||||||
static MemoryPressureState _memoryPressureState;
|
|
||||||
protected:
|
|
||||||
static size_t _frameTexturesCreated;
|
|
||||||
static std::atomic<bool> _memoryPressureStateStale;
|
|
||||||
static std::list<TextureWeakPointer> _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:
|
protected:
|
||||||
GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
|
||||||
~GL45VariableAllocationTexture();
|
~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; }
|
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 };
|
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 {
|
class GL45ResourceTexture : public GL45VariableAllocationTexture {
|
||||||
|
|
|
@ -67,7 +67,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||||
#else
|
#else
|
||||||
object = new GL45ResourceTexture(shared_from_this(), texture);
|
object = new GL45ResourceTexture(shared_from_this(), texture);
|
||||||
#endif
|
#endif
|
||||||
GL45VariableAllocationTexture::addMemoryManagedTexture(texturePointer);
|
GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer);
|
||||||
} else {
|
} else {
|
||||||
auto fallback = texturePointer->getFallbackTexture();
|
auto fallback = texturePointer->getFallbackTexture();
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
|
@ -135,20 +135,6 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
|
||||||
(void)CHECK_GL_ERROR();
|
(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 {
|
void GL45Texture::syncSampler() const {
|
||||||
const Sampler& sampler = _gpuObject.getSampler();
|
const Sampler& sampler = _gpuObject.getSampler();
|
||||||
|
|
||||||
|
|
|
@ -27,416 +27,16 @@ using namespace gpu;
|
||||||
using namespace gpu::gl;
|
using namespace gpu::gl;
|
||||||
using namespace gpu::gl45;
|
using namespace gpu::gl45;
|
||||||
|
|
||||||
// Variable sized textures
|
|
||||||
using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture;
|
using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture;
|
||||||
using MemoryPressureState = GL45VariableAllocationTexture::MemoryPressureState;
|
|
||||||
using WorkQueue = GL45VariableAllocationTexture::WorkQueue;
|
|
||||||
|
|
||||||
std::list<TextureWeakPointer> GL45VariableAllocationTexture::_memoryManagedTextures;
|
|
||||||
MemoryPressureState GL45VariableAllocationTexture::_memoryPressureState = MemoryPressureState::Idle;
|
|
||||||
std::atomic<bool> 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<std::thread> TransferJob::_bufferThread { nullptr };
|
|
||||||
std::atomic<bool> TransferJob::_shutdownBufferingThread { false };
|
|
||||||
Mutex TransferJob::_mutex;
|
|
||||||
TransferJob::VoidLambdaQueue TransferJob::_bufferLambdaQueue;
|
|
||||||
|
|
||||||
void TransferJob::startTransferLoop() {
|
|
||||||
if (_bufferThread) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_shutdownBufferingThread = false;
|
|
||||||
_bufferThread = std::make_shared<std::thread>([] {
|
|
||||||
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<uint8_t> emptyVector;
|
|
||||||
_buffer.swap(emptyVector);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, std::function<void()> 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<GL45VariableAllocationTexture>(*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<TexturePointer> 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<GL45VariableAllocationTexture>(*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<GL45VariableAllocationTexture>(*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<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture) {
|
GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture) {
|
||||||
++_frameTexturesCreated;
|
++_frameTexturesCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {
|
GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {
|
||||||
_memoryPressureStateStale = true;
|
|
||||||
Backend::updateTextureGPUMemoryUsage(_size, 0);
|
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
|
// Managed size resource textures
|
||||||
using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
|
using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
|
||||||
|
|
||||||
|
@ -453,7 +53,6 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
|
||||||
|
|
||||||
uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
|
uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
|
||||||
allocateStorage(allocatedMip);
|
allocateStorage(allocatedMip);
|
||||||
_memoryPressureStateStale = true;
|
|
||||||
copyMipsFromTexture();
|
copyMipsFromTexture();
|
||||||
syncSampler();
|
syncSampler();
|
||||||
|
|
||||||
|
@ -521,7 +120,6 @@ void GL45ResourceTexture::promote() {
|
||||||
glDeleteTextures(1, &oldId);
|
glDeleteTextures(1, &oldId);
|
||||||
// update the memory usage
|
// update the memory usage
|
||||||
Backend::updateTextureGPUMemoryUsage(oldSize, 0);
|
Backend::updateTextureGPUMemoryUsage(oldSize, 0);
|
||||||
_memoryPressureStateStale = true;
|
|
||||||
syncSampler();
|
syncSampler();
|
||||||
populateTransferQueue();
|
populateTransferQueue();
|
||||||
}
|
}
|
||||||
|
@ -554,7 +152,6 @@ void GL45ResourceTexture::demote() {
|
||||||
glDeleteTextures(1, &oldId);
|
glDeleteTextures(1, &oldId);
|
||||||
// update the memory usage
|
// update the memory usage
|
||||||
Backend::updateTextureGPUMemoryUsage(oldSize, 0);
|
Backend::updateTextureGPUMemoryUsage(oldSize, 0);
|
||||||
_memoryPressureStateStale = true;
|
|
||||||
syncSampler();
|
syncSampler();
|
||||||
populateTransferQueue();
|
populateTransferQueue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ namespace gpu {
|
||||||
class Sampler;
|
class Sampler;
|
||||||
class Texture;
|
class Texture;
|
||||||
using TexturePointer = std::shared_ptr<Texture>;
|
using TexturePointer = std::shared_ptr<Texture>;
|
||||||
|
using TextureWeakPointer = std::weak_ptr<Texture>;
|
||||||
using Textures = std::vector<TexturePointer>;
|
using Textures = std::vector<TexturePointer>;
|
||||||
class TextureView;
|
class TextureView;
|
||||||
using TextureViews = std::vector<TextureView>;
|
using TextureViews = std::vector<TextureView>;
|
||||||
|
|
Loading…
Reference in a new issue