rhi: gl, metal, d3d: Reuse shader objects when source is the same

Now that Qt Quick's batch renderer misses one level of shader source
caching due to the nature of pipeline state objects, it can be useful
to keep and reuse shader objects when the hash of the source code
matches.

The goal here is to allow Qt Quick to be on par with what the direct
OpenGL path has when it comes to caching shader sources and compilation
results. The program binary disk cache is not in scope in this patch.

Also adds QRhi::releaseCachedResources(), similarly to what the scenegraph
has. This can be called to clear caches such as the shader object
cache we keep here.

Change-Id: Ie3d81d823f61fa65ec814439e882c498f7774d43
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
bb10
Laszlo Agocs 2019-09-03 11:59:24 +02:00
parent 89d0a03c06
commit 7af6649e88
13 changed files with 315 additions and 141 deletions

View File

@ -5019,6 +5019,23 @@ QRhiProfiler *QRhi::profiler()
return &d->profiler;
}
/*!
Attempts to release resources in the backend's caches. This can include both
CPU and GPU resources. Only memory and resources that can be recreated
automatically are in scope. As an example, if the backend's
QRhiGraphicsPipeline implementation maintains a cache of shader compilation
results, calling this function leads to emptying that cache, thus
potentially freeing up memory and graphics resources.
Calling this function makes sense in resource constrained environments,
where at a certain point there is a need to ensure minimal resource usage,
at the expense of performance.
*/
void QRhi::releaseCachedResources()
{
d->releaseCachedResources();
}
/*!
\return a new graphics pipeline resource.

View File

@ -1420,6 +1420,8 @@ public:
static const int MAX_LAYERS = 6; // cubemaps only
static const int MAX_LEVELS = 16; // a width and/or height of 65536 should be enough for everyone
void releaseCachedResources();
protected:
QRhi();

View File

@ -157,6 +157,7 @@ public:
virtual const QRhiNativeHandles *nativeHandles() = 0;
virtual void sendVMemStatsToProfiler() = 0;
virtual void makeThreadLocalNativeContextCurrent() = 0;
virtual void releaseCachedResources() = 0;
bool isCompressedFormat(QRhiTexture::Format format) const;
void compressedFormatInfo(QRhiTexture::Format format, const QSize &size,
@ -205,6 +206,8 @@ public:
QRhi *q;
static const int MAX_SHADER_CACHE_ENTRIES = 128;
protected:
bool debugMarkers = false;
int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11.

View File

@ -267,10 +267,20 @@ bool QRhiD3D11::create(QRhi::Flags flags)
return true;
}
void QRhiD3D11::clearShaderCache()
{
for (Shader &s : m_shaderCache)
s.s->Release();
m_shaderCache.clear();
}
void QRhiD3D11::destroy()
{
finishActiveReadbacks();
clearShaderCache();
if (annotations) {
annotations->Release();
annotations = nullptr;
@ -461,6 +471,11 @@ void QRhiD3D11::makeThreadLocalNativeContextCurrent()
// nothing to do here
}
void QRhiD3D11::releaseCachedResources()
{
clearShaderCache();
}
QRhiRenderBuffer *QRhiD3D11::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
{
@ -3408,30 +3423,57 @@ bool QD3D11GraphicsPipeline::build()
QByteArray vsByteCode;
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
QString error;
QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(), shaderStage.shaderVariant(), &error);
if (bytecode.isEmpty()) {
qWarning("HLSL shader compilation failed: %s", qPrintable(error));
return false;
}
switch (shaderStage.type()) {
case QRhiShaderStage::Vertex:
hr = rhiD->dev->CreateVertexShader(bytecode.constData(), bytecode.size(), nullptr, &vs);
if (FAILED(hr)) {
qWarning("Failed to create vertex shader: %s", qPrintable(comErrorMessage(hr)));
auto cacheIt = rhiD->m_shaderCache.constFind(shaderStage);
if (cacheIt != rhiD->m_shaderCache.constEnd()) {
switch (shaderStage.type()) {
case QRhiShaderStage::Vertex:
vs = static_cast<ID3D11VertexShader *>(cacheIt->s);
vs->AddRef();
vsByteCode = cacheIt->bytecode;
break;
case QRhiShaderStage::Fragment:
fs = static_cast<ID3D11PixelShader *>(cacheIt->s);
fs->AddRef();
break;
default:
break;
}
} else {
QString error;
const QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(), shaderStage.shaderVariant(), &error);
if (bytecode.isEmpty()) {
qWarning("HLSL shader compilation failed: %s", qPrintable(error));
return false;
}
vsByteCode = bytecode;
break;
case QRhiShaderStage::Fragment:
hr = rhiD->dev->CreatePixelShader(bytecode.constData(), bytecode.size(), nullptr, &fs);
if (FAILED(hr)) {
qWarning("Failed to create pixel shader: %s", qPrintable(comErrorMessage(hr)));
return false;
if (rhiD->m_shaderCache.count() >= QRhiD3D11::MAX_SHADER_CACHE_ENTRIES) {
// Use the simplest strategy: too many cached shaders -> drop them all.
rhiD->clearShaderCache();
}
switch (shaderStage.type()) {
case QRhiShaderStage::Vertex:
hr = rhiD->dev->CreateVertexShader(bytecode.constData(), bytecode.size(), nullptr, &vs);
if (FAILED(hr)) {
qWarning("Failed to create vertex shader: %s", qPrintable(comErrorMessage(hr)));
return false;
}
vsByteCode = bytecode;
rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(vs, bytecode));
vs->AddRef();
break;
case QRhiShaderStage::Fragment:
hr = rhiD->dev->CreatePixelShader(bytecode.constData(), bytecode.size(), nullptr, &fs);
if (FAILED(hr)) {
qWarning("Failed to create pixel shader: %s", qPrintable(comErrorMessage(hr)));
return false;
}
rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(fs, bytecode));
fs->AddRef();
break;
default:
break;
}
break;
default:
break;
}
}
@ -3501,18 +3543,30 @@ bool QD3D11ComputePipeline::build()
QRHI_RES_RHI(QRhiD3D11);
QString error;
QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(), m_shaderStage.shaderVariant(), &error);
if (bytecode.isEmpty()) {
qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
return false;
auto cacheIt = rhiD->m_shaderCache.constFind(m_shaderStage);
if (cacheIt != rhiD->m_shaderCache.constEnd()) {
cs = static_cast<ID3D11ComputeShader *>(cacheIt->s);
} else {
QString error;
const QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(), m_shaderStage.shaderVariant(), &error);
if (bytecode.isEmpty()) {
qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
return false;
}
HRESULT hr = rhiD->dev->CreateComputeShader(bytecode.constData(), bytecode.size(), nullptr, &cs);
if (FAILED(hr)) {
qWarning("Failed to create compute shader: %s", qPrintable(comErrorMessage(hr)));
return false;
}
if (rhiD->m_shaderCache.count() >= QRhiD3D11::MAX_SHADER_CACHE_ENTRIES)
rhiD->clearShaderCache();
rhiD->m_shaderCache.insert(m_shaderStage, QRhiD3D11::Shader(cs, bytecode));
}
HRESULT hr = rhiD->dev->CreateComputeShader(bytecode.constData(), bytecode.size(), nullptr, &cs);
if (FAILED(hr)) {
qWarning("Failed to create compute shader: %s", qPrintable(comErrorMessage(hr)));
return false;
}
cs->AddRef();
generation += 1;
rhiD->registerResource(this);

View File

@ -632,6 +632,7 @@ public:
const QRhiNativeHandles *nativeHandles() override;
void sendVMemStatsToProfiler() override;
void makeThreadLocalNativeContextCurrent() override;
void releaseCachedResources() override;
void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
@ -646,6 +647,7 @@ public:
DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const;
void finishActiveReadbacks();
void reportLiveObjects(ID3D11Device *device);
void clearShaderCache();
bool debugLayer = false;
bool importedDevice = false;
@ -684,6 +686,14 @@ public:
QRhiTexture::Format format;
};
QVector<ActiveReadback> activeReadbacks;
struct Shader {
Shader() = default;
Shader(IUnknown *s, const QByteArray &bytecode) : s(s), bytecode(bytecode) { }
IUnknown *s;
QByteArray bytecode;
};
QHash<QRhiShaderStage, Shader> m_shaderCache;
};
Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE);

View File

@ -502,6 +502,10 @@ void QRhiGles2::destroy()
ensureContext();
executeDeferredReleases();
for (uint shader : m_shaderCache)
f->glDeleteShader(shader);
m_shaderCache.clear();
if (!importedContext) {
delete ctx;
ctx = nullptr;
@ -757,6 +761,17 @@ void QRhiGles2::makeThreadLocalNativeContextCurrent()
ensureContext();
}
void QRhiGles2::releaseCachedResources()
{
if (!ensureContext())
return;
for (uint shader : m_shaderCache)
f->glDeleteShader(shader);
m_shaderCache.clear();
}
QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
{
@ -2673,7 +2688,6 @@ static inline GLenum toGlShaderType(QRhiShaderStage::Type type)
bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage,
QShaderDescription *desc, int *glslVersionUsed)
{
GLuint shader = f->glCreateShader(toGlShaderType(shaderStage.type()));
const QShader bakedShader = shaderStage.shader();
QVector<int> versionsToTry;
QByteArray source;
@ -2733,27 +2747,40 @@ bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage
return false;
}
const char *srcStr = source.constData();
const GLint srcLength = source.count();
f->glShaderSource(shader, 1, &srcStr, &srcLength);
f->glCompileShader(shader);
GLint compiled = 0;
f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLogLength = 0;
f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
QByteArray log;
if (infoLogLength > 1) {
GLsizei length = 0;
log.resize(infoLogLength);
f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data());
GLuint shader;
auto cacheIt = m_shaderCache.constFind(shaderStage);
if (cacheIt != m_shaderCache.constEnd()) {
shader = *cacheIt;
} else {
shader = f->glCreateShader(toGlShaderType(shaderStage.type()));
const char *srcStr = source.constData();
const GLint srcLength = source.count();
f->glShaderSource(shader, 1, &srcStr, &srcLength);
f->glCompileShader(shader);
GLint compiled = 0;
f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLogLength = 0;
f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
QByteArray log;
if (infoLogLength > 1) {
GLsizei length = 0;
log.resize(infoLogLength);
f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data());
}
qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData());
return false;
}
qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData());
return false;
if (m_shaderCache.count() >= MAX_SHADER_CACHE_ENTRIES) {
// Use the simplest strategy: too many cached shaders -> drop them all.
for (uint shader : m_shaderCache)
f->glDeleteShader(shader); // does not actually get released yet when attached to a not-yet-released program
m_shaderCache.clear();
}
m_shaderCache.insert(shaderStage, shader);
}
f->glAttachShader(program, shader);
f->glDeleteShader(shader);
*desc = bakedShader.description();
return true;

View File

@ -665,6 +665,7 @@ public:
const QRhiNativeHandles *nativeHandles() override;
void sendVMemStatsToProfiler() override;
void makeThreadLocalNativeContextCurrent() override;
void releaseCachedResources() override;
bool ensureContext(QSurface *surface = nullptr) const;
void executeDeferredReleases();
@ -804,6 +805,8 @@ public:
bool active = false;
QGles2CommandBuffer cbWrapper;
} ofr;
QHash<QRhiShaderStage, uint> m_shaderCache;
};
Q_DECLARE_TYPEINFO(QRhiGles2::DeferredReleaseEntry, Q_MOVABLE_TYPE);

View File

@ -138,6 +138,20 @@ QT_BEGIN_NAMESPACE
\l{QRhiCommandBuffer::endPass()}.
*/
struct QMetalShader
{
id<MTLLibrary> lib = nil;
id<MTLFunction> func = nil;
std::array<uint, 3> localSize;
void release() {
[lib release];
lib = nil;
[func release];
func = nil;
}
};
struct QRhiMetalData
{
QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
@ -206,6 +220,8 @@ struct QRhiMetalData
API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
static const int TEXBUF_ALIGN = 256; // probably not accurate
QHash<QRhiShaderStage, QMetalShader> shaderCache;
};
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
@ -289,17 +305,14 @@ struct QMetalGraphicsPipelineData
MTLPrimitiveType primitiveType;
MTLWinding winding;
MTLCullMode cullMode;
id<MTLLibrary> vsLib = nil;
id<MTLFunction> vsFunc = nil;
id<MTLLibrary> fsLib = nil;
id<MTLFunction> fsFunc = nil;
QMetalShader vs;
QMetalShader fs;
};
struct QMetalComputePipelineData
{
id<MTLComputePipelineState> ps = nil;
id<MTLLibrary> csLib = nil;
id<MTLFunction> csFunc = nil;
QMetalShader cs;
MTLSize localSize;
};
@ -404,6 +417,10 @@ void QRhiMetal::destroy()
executeDeferredReleases(true);
finishActiveReadbacks(true);
for (QMetalShader &s : d->shaderCache)
s.release();
d->shaderCache.clear();
if (@available(macOS 10.13, iOS 11.0, *)) {
[d->captureScope release];
d->captureScope = nil;
@ -570,6 +587,14 @@ void QRhiMetal::makeThreadLocalNativeContextCurrent()
// nothing to do here
}
void QRhiMetal::releaseCachedResources()
{
for (QMetalShader &s : d->shaderCache)
s.release();
d->shaderCache.clear();
}
QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
{
@ -2843,36 +2868,17 @@ void QMetalGraphicsPipeline::release()
{
QRHI_RES_RHI(QRhiMetal);
d->vs.release();
d->fs.release();
[d->ds release];
d->ds = nil;
if (!d->ps)
return;
if (d->ps) {
[d->ps release];
d->ps = nil;
}
if (d->ds) {
[d->ds release];
d->ds = nil;
}
if (d->vsFunc) {
[d->vsFunc release];
d->vsFunc = nil;
}
if (d->vsLib) {
[d->vsLib release];
d->vsLib = nil;
}
if (d->fsFunc) {
[d->fsFunc release];
d->fsFunc = nil;
}
if (d->fsLib) {
[d->fsLib release];
d->fsLib = nil;
}
[d->ps release];
d->ps = nil;
rhiD->unregisterResource(this);
}
@ -3159,34 +3165,66 @@ bool QMetalGraphicsPipeline::build()
// buffers not just the resource binding layout) so leave it at the default
for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
QString error;
QByteArray entryPoint;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint);
if (!lib) {
qWarning("MSL shader compilation failed: %s", qPrintable(error));
return false;
}
id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
if (!func) {
qWarning("MSL function for entry point %s not found", entryPoint.constData());
[lib release];
return false;
}
switch (shaderStage.type()) {
case QRhiShaderStage::Vertex:
rpDesc.vertexFunction = func;
d->vsLib = lib;
d->vsFunc = func;
break;
case QRhiShaderStage::Fragment:
rpDesc.fragmentFunction = func;
d->fsLib = lib;
d->fsFunc = func;
break;
default:
[func release];
[lib release];
break;
auto cacheIt = rhiD->d->shaderCache.constFind(shaderStage);
if (cacheIt != rhiD->d->shaderCache.constEnd()) {
switch (shaderStage.type()) {
case QRhiShaderStage::Vertex:
d->vs = *cacheIt;
[d->vs.lib retain];
[d->vs.func retain];
rpDesc.vertexFunction = d->vs.func;
break;
case QRhiShaderStage::Fragment:
d->fs = *cacheIt;
[d->fs.lib retain];
[d->fs.func retain];
rpDesc.fragmentFunction = d->fs.func;
break;
default:
break;
}
} else {
QString error;
QByteArray entryPoint;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint);
if (!lib) {
qWarning("MSL shader compilation failed: %s", qPrintable(error));
return false;
}
id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
if (!func) {
qWarning("MSL function for entry point %s not found", entryPoint.constData());
[lib release];
return false;
}
if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
// Use the simplest strategy: too many cached shaders -> drop them all.
for (QMetalShader &s : rhiD->d->shaderCache)
s.release();
rhiD->d->shaderCache.clear();
}
switch (shaderStage.type()) {
case QRhiShaderStage::Vertex:
d->vs.lib = lib;
d->vs.func = func;
rhiD->d->shaderCache.insert(shaderStage, d->vs);
[d->vs.lib retain];
[d->vs.func retain];
rpDesc.vertexFunction = func;
break;
case QRhiShaderStage::Fragment:
d->fs.lib = lib;
d->fs.func = func;
rhiD->d->shaderCache.insert(shaderStage, d->fs);
[d->fs.lib retain];
[d->fs.func retain];
rpDesc.fragmentFunction = func;
break;
default:
[func release];
[lib release];
break;
}
}
}
@ -3286,22 +3324,13 @@ void QMetalComputePipeline::release()
{
QRHI_RES_RHI(QRhiMetal);
if (d->csFunc) {
[d->csFunc release];
d->csFunc = nil;
}
if (d->csLib) {
[d->csLib release];
d->csLib = nil;
}
d->cs.release();
if (!d->ps)
return;
if (d->ps) {
[d->ps release];
d->ps = nil;
}
[d->ps release];
d->ps = nil;
rhiD->unregisterResource(this);
}
@ -3313,28 +3342,44 @@ bool QMetalComputePipeline::build()
QRHI_RES_RHI(QRhiMetal);
const QShader shader = m_shaderStage.shader();
QString error;
QByteArray entryPoint;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
&error, &entryPoint);
if (!lib) {
qWarning("MSL shader compilation failed: %s", qPrintable(error));
return false;
auto cacheIt = rhiD->d->shaderCache.constFind(m_shaderStage);
if (cacheIt != rhiD->d->shaderCache.constEnd()) {
d->cs = *cacheIt;
} else {
const QShader shader = m_shaderStage.shader();
QString error;
QByteArray entryPoint;
id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
&error, &entryPoint);
if (!lib) {
qWarning("MSL shader compilation failed: %s", qPrintable(error));
return false;
}
id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
if (!func) {
qWarning("MSL function for entry point %s not found", entryPoint.constData());
[lib release];
return false;
}
d->cs.lib = lib;
d->cs.func = func;
d->cs.localSize = shader.description().computeShaderLocalSize();
if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) {
for (QMetalShader &s : rhiD->d->shaderCache)
s.release();
rhiD->d->shaderCache.clear();
}
rhiD->d->shaderCache.insert(m_shaderStage, d->cs);
}
id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
if (!func) {
qWarning("MSL function for entry point %s not found", entryPoint.constData());
[lib release];
return false;
}
d->csLib = lib;
d->csFunc = func;
std::array<uint, 3> localSize = shader.description().computeShaderLocalSize();
d->localSize = MTLSizeMake(localSize[0], localSize[1], localSize[2]);
[d->cs.lib retain];
[d->cs.func retain];
d->localSize = MTLSizeMake(d->cs.localSize[0], d->cs.localSize[1], d->cs.localSize[2]);
NSError *err = nil;
d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->csFunc error: &err];
d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->cs.func error: &err];
if (!d->ps) {
const QString msg = QString::fromNSString(err.localizedDescription);
qWarning("Failed to create render pipeline state: %s", qPrintable(msg));

View File

@ -417,6 +417,7 @@ public:
const QRhiNativeHandles *nativeHandles() override;
void sendVMemStatsToProfiler() override;
void makeThreadLocalNativeContextCurrent() override;
void releaseCachedResources() override;
void executeDeferredReleases(bool forced = false);
void finishActiveReadbacks(bool forced = false);

View File

@ -174,6 +174,11 @@ void QRhiNull::makeThreadLocalNativeContextCurrent()
// nothing to do here
}
void QRhiNull::releaseCachedResources()
{
// nothing to do here
}
QRhiRenderBuffer *QRhiNull::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
{

View File

@ -283,6 +283,7 @@ public:
const QRhiNativeHandles *nativeHandles() override;
void sendVMemStatsToProfiler() override;
void makeThreadLocalNativeContextCurrent() override;
void releaseCachedResources() override;
QRhiNullNativeHandles nativeHandlesStruct;
QRhiSwapChain *currentSwapChain = nullptr;

View File

@ -3744,6 +3744,11 @@ void QRhiVulkan::makeThreadLocalNativeContextCurrent()
// nothing to do here
}
void QRhiVulkan::releaseCachedResources()
{
// nothing to do here
}
QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
{

View File

@ -712,6 +712,7 @@ public:
const QRhiNativeHandles *nativeHandles() override;
void sendVMemStatsToProfiler() override;
void makeThreadLocalNativeContextCurrent() override;
void releaseCachedResources() override;
VkResult createDescriptorPool(VkDescriptorPool *pool);
bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex);