Merge remote-tracking branch 'origin/5.15' into dev
Change-Id: I14f539ccfa4ab6e21188b98c314bdb030f3b9f70
This commit is contained in:
commit
50d41f14ee
@ -141,7 +141,8 @@ bool
|
||||
BuildsMetaMakefileGenerator::write()
|
||||
{
|
||||
Build *glue = nullptr;
|
||||
if(!makefiles.isEmpty() && !makefiles.first()->build.isNull()) {
|
||||
if(!makefiles.isEmpty() && !makefiles.first()->build.isNull()
|
||||
&& Option::qmake_mode != Option::QMAKE_GENERATE_PRL) {
|
||||
glue = new Build;
|
||||
glue->name = name;
|
||||
glue->makefile = createMakefileGenerator(project, true);
|
||||
|
@ -118,16 +118,16 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool runIterations(Iterator sequenceBeginIterator, int begin, int end, ReducedResultType *) override
|
||||
bool runIterations(Iterator sequenceBeginIterator, int beginIndex, int endIndex, ReducedResultType *) override
|
||||
{
|
||||
IntermediateResults<typename MapFunctor::result_type> results;
|
||||
results.begin = begin;
|
||||
results.end = end;
|
||||
results.vector.reserve(end - begin);
|
||||
results.begin = beginIndex;
|
||||
results.end = endIndex;
|
||||
results.vector.reserve(endIndex - beginIndex);
|
||||
|
||||
Iterator it = sequenceBeginIterator;
|
||||
std::advance(it, begin);
|
||||
for (int i = begin; i < end; ++i) {
|
||||
std::advance(it, beginIndex);
|
||||
for (int i = beginIndex; i < endIndex; ++i) {
|
||||
results.vector.append(map(*(it)));
|
||||
std::advance(it, 1);
|
||||
}
|
||||
@ -176,13 +176,13 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool runIterations(Iterator sequenceBeginIterator, int begin, int end, T *results) override
|
||||
bool runIterations(Iterator sequenceBeginIterator, int beginIndex, int endIndex, T *results) override
|
||||
{
|
||||
|
||||
Iterator it = sequenceBeginIterator;
|
||||
std::advance(it, begin);
|
||||
for (int i = begin; i < end; ++i) {
|
||||
runIteration(it, i, results + (i - begin));
|
||||
std::advance(it, beginIndex);
|
||||
for (int i = beginIndex; i < endIndex; ++i) {
|
||||
runIteration(it, i, results + (i - beginIndex));
|
||||
std::advance(it, 1);
|
||||
}
|
||||
|
||||
|
@ -727,6 +727,11 @@ QLibraryInfo::rawLocation(LibraryLocation loc, PathGroup group)
|
||||
|
||||
#ifndef QT_BUILD_QMAKE_BOOTSTRAP
|
||||
if (!fromConf) {
|
||||
// "volatile" here is a hack to prevent compilers from doing a
|
||||
// compile-time strlen() on "path". The issue is that Qt installers
|
||||
// will binary-patch the Qt installation paths -- in such scenarios, Qt
|
||||
// will be built with a dummy path, thus the compile-time result of
|
||||
// strlen is meaningless.
|
||||
const char * volatile path = 0;
|
||||
if (loc == PrefixPath) {
|
||||
path = getPrefix(
|
||||
|
@ -566,7 +566,19 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
|
||||
|
||||
\value TriangleFanTopology Indicates that QRhiGraphicsPipeline::setTopology()
|
||||
supports QRhiGraphicsPipeline::TriangleFan.
|
||||
*/
|
||||
|
||||
\value ReadBackNonUniformBuffer Indicates that
|
||||
\l{QRhiResourceUpdateBatch::readBackBuffer()}{reading buffer contents} is
|
||||
supported for QRhiBuffer instances with a usage different than
|
||||
UniformBuffer. While this is supported in the majority of cases, it will be
|
||||
unsupported with OpenGL ES older than 3.0.
|
||||
|
||||
\value ReadBackNonBaseMipLevel Indicates that specifying a mip level other
|
||||
than 0 is supported when reading back texture contents. When not supported,
|
||||
specifying a non-zero level in QRhiReadbackDescription leads to returning
|
||||
an all-zero image. In practice this feature will be unsupported with OpenGL
|
||||
ES 2.0, while it will likely be supported everywhere else.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QRhi::BeginFrameFlag
|
||||
@ -3072,9 +3084,13 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
|
||||
\inmodule QtGui
|
||||
\brief Graphics pipeline state resource.
|
||||
|
||||
\note Setting the shader stages is mandatory. There must be at least one
|
||||
stage, and there must be a vertex stage.
|
||||
|
||||
\note Setting the shader resource bindings is mandatory. The referenced
|
||||
QRhiShaderResourceBindings must already be built by the time build() is
|
||||
called.
|
||||
called. Associating with a QRhiShaderResourceBindings that has no bindings
|
||||
is also valid, as long as no shader in any stage expects any resources.
|
||||
|
||||
\note Setting the render pass descriptor is mandatory. To obtain a
|
||||
QRhiRenderPassDescriptor that can be passed to setRenderPassDescriptor(),
|
||||
@ -3083,8 +3099,6 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
|
||||
|
||||
\note Setting the vertex input layout is mandatory.
|
||||
|
||||
\note Setting the shader stages is mandatory.
|
||||
|
||||
\note sampleCount() defaults to 1 and must match the sample count of the
|
||||
render target's color and depth stencil attachments.
|
||||
|
||||
@ -3900,6 +3914,46 @@ quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format,
|
||||
return approxSize;
|
||||
}
|
||||
|
||||
bool QRhiImplementation::sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps)
|
||||
{
|
||||
if (ps->cbeginShaderStages() == ps->cendShaderStages()) {
|
||||
qWarning("Cannot build a graphics pipeline without any stages");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasVertexStage = false;
|
||||
for (auto it = ps->cbeginShaderStages(), itEnd = ps->cendShaderStages(); it != itEnd; ++it) {
|
||||
if (!it->shader().isValid()) {
|
||||
qWarning("Empty shader passed to graphics pipeline");
|
||||
return false;
|
||||
}
|
||||
if (it->type() == QRhiShaderStage::Vertex) {
|
||||
hasVertexStage = true;
|
||||
const QRhiVertexInputLayout inputLayout = ps->vertexInputLayout();
|
||||
if (inputLayout.cbeginAttributes() == inputLayout.cendAttributes()) {
|
||||
qWarning("Vertex stage present without any vertex inputs");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasVertexStage) {
|
||||
qWarning("Cannot build a graphics pipeline without a vertex stage");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ps->renderPassDescriptor()) {
|
||||
qWarning("Cannot build a graphics pipeline without a QRhiRenderPassDescriptor");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ps->shaderResourceBindings()) {
|
||||
qWarning("Cannot build a graphics pipeline without QRhiShaderResourceBindings");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
@ -4175,7 +4229,7 @@ void QRhiResourceUpdateBatch::merge(QRhiResourceUpdateBatch *other)
|
||||
void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data)
|
||||
{
|
||||
if (size > 0)
|
||||
d->dynamicBufferUpdates.append({ buf, offset, size, data });
|
||||
d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(buf, offset, size, data));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4189,7 +4243,7 @@ void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, i
|
||||
void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data)
|
||||
{
|
||||
if (size > 0)
|
||||
d->staticBufferUploads.append({ buf, offset, size, data });
|
||||
d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::staticUpload(buf, offset, size, data));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4199,7 +4253,28 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, in
|
||||
void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *data)
|
||||
{
|
||||
if (buf->size() > 0)
|
||||
d->staticBufferUploads.append({ buf, 0, 0, data });
|
||||
d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::staticUpload(buf, 0, 0, data));
|
||||
}
|
||||
|
||||
/*!
|
||||
Enqueues reading back a region of the QRhiBuffer \a buf. The size of the
|
||||
region is specified by \a size in bytes, \a offset is the offset in bytes
|
||||
to start reading from.
|
||||
|
||||
A readback is asynchronous. \a result contains a callback that is invoked
|
||||
when the operation has completed. The data is provided in
|
||||
QRhiBufferReadbackResult::data. Upon successful completion that QByteArray
|
||||
will have a size equal to \a size. On failure the QByteArray will be empty.
|
||||
|
||||
\note Reading buffers with a usage different than QRhiBuffer::UniformBuffer
|
||||
is supported only when the QRhi::ReadBackNonUniformBuffer feature is
|
||||
reported as supported.
|
||||
|
||||
\a readBackTexture(), QRhi::isFeatureSupported()
|
||||
*/
|
||||
void QRhiResourceUpdateBatch::readBackBuffer(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result)
|
||||
{
|
||||
d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::read(buf, offset, size, result));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4212,7 +4287,7 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *da
|
||||
void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
|
||||
{
|
||||
if (desc.cbeginEntries() != desc.cendEntries())
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureUpload(tex, desc));
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::upload(tex, desc));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4237,7 +4312,7 @@ void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QImage &imag
|
||||
*/
|
||||
void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
|
||||
{
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureCopy(dst, src, desc));
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::copy(dst, src, desc));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4289,7 +4364,7 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co
|
||||
*/
|
||||
void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
|
||||
{
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureRead(rb, result));
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::read(rb, result));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4301,7 +4376,7 @@ void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb,
|
||||
*/
|
||||
void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex, int layer)
|
||||
{
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureMipGen(tex, layer));
|
||||
d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::genMips(tex, layer));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -4350,8 +4425,7 @@ void QRhiResourceUpdateBatchPrivate::free()
|
||||
{
|
||||
Q_ASSERT(poolIndex >= 0 && rhi->resUpdPool[poolIndex] == q);
|
||||
|
||||
dynamicBufferUpdates.clear();
|
||||
staticBufferUploads.clear();
|
||||
bufferOps.clear();
|
||||
textureOps.clear();
|
||||
|
||||
rhi->resUpdPoolMap.clearBit(poolIndex);
|
||||
@ -4360,9 +4434,13 @@ void QRhiResourceUpdateBatchPrivate::free()
|
||||
|
||||
void QRhiResourceUpdateBatchPrivate::merge(QRhiResourceUpdateBatchPrivate *other)
|
||||
{
|
||||
dynamicBufferUpdates += other->dynamicBufferUpdates;
|
||||
staticBufferUploads += other->staticBufferUploads;
|
||||
textureOps += other->textureOps;
|
||||
bufferOps.reserve(bufferOps.size() + other->bufferOps.size());
|
||||
for (const BufferOp &op : qAsConst(other->bufferOps))
|
||||
bufferOps.append(op);
|
||||
|
||||
textureOps.reserve(textureOps.size() + other->textureOps.size());
|
||||
for (const TextureOp &op : qAsConst(other->textureOps))
|
||||
textureOps.append(op);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -72,7 +72,6 @@ class QRhiCommandBuffer;
|
||||
class QRhiResourceUpdateBatch;
|
||||
class QRhiResourceUpdateBatchPrivate;
|
||||
class QRhiProfiler;
|
||||
class QRhiShaderResourceBindingPrivate;
|
||||
|
||||
class Q_GUI_EXPORT QRhiDepthStencilClearValue
|
||||
{
|
||||
@ -1355,6 +1354,12 @@ struct Q_GUI_EXPORT QRhiReadbackResult
|
||||
QByteArray data;
|
||||
}; // non-movable due to the std::function
|
||||
|
||||
struct Q_GUI_EXPORT QRhiBufferReadbackResult
|
||||
{
|
||||
std::function<void()> completed = nullptr;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
class Q_GUI_EXPORT QRhiResourceUpdateBatch
|
||||
{
|
||||
public:
|
||||
@ -1367,6 +1372,7 @@ public:
|
||||
void updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data);
|
||||
void uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data);
|
||||
void uploadStaticBuffer(QRhiBuffer *buf, const void *data);
|
||||
void readBackBuffer(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result);
|
||||
void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc);
|
||||
void uploadTexture(QRhiTexture *tex, const QImage &image);
|
||||
void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription());
|
||||
@ -1428,7 +1434,9 @@ public:
|
||||
VertexShaderPointSize,
|
||||
BaseVertex,
|
||||
BaseInstance,
|
||||
TriangleFanTopology
|
||||
TriangleFanTopology,
|
||||
ReadBackNonUniformBuffer,
|
||||
ReadBackNonBaseMipLevel
|
||||
};
|
||||
|
||||
enum BeginFrameFlag {
|
||||
|
@ -205,6 +205,8 @@ public:
|
||||
cleanupCallbacks.append(callback);
|
||||
}
|
||||
|
||||
bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps);
|
||||
|
||||
QRhi *q;
|
||||
|
||||
static const int MAX_SHADER_CACHE_ENTRIES = 128;
|
||||
@ -218,7 +220,7 @@ private:
|
||||
QRhi::Implementation implType;
|
||||
QThread *implThread;
|
||||
QRhiProfiler profiler;
|
||||
QVector<QRhiResourceUpdateBatch *> resUpdPool;
|
||||
QVarLengthArray<QRhiResourceUpdateBatch *, 4> resUpdPool;
|
||||
QBitArray resUpdPoolMap;
|
||||
QSet<QRhiResource *> resources;
|
||||
QSet<QRhiResource *> pendingReleaseAndDestroyResources;
|
||||
@ -271,26 +273,49 @@ bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array<T,
|
||||
class QRhiResourceUpdateBatchPrivate
|
||||
{
|
||||
public:
|
||||
struct DynamicBufferUpdate {
|
||||
DynamicBufferUpdate() { }
|
||||
DynamicBufferUpdate(QRhiBuffer *buf_, int offset_, int size_, const void *data_)
|
||||
: buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_)
|
||||
{ }
|
||||
|
||||
QRhiBuffer *buf = nullptr;
|
||||
int offset = 0;
|
||||
struct BufferOp {
|
||||
enum Type {
|
||||
DynamicUpdate,
|
||||
StaticUpload,
|
||||
Read
|
||||
};
|
||||
Type type;
|
||||
QRhiBuffer *buf;
|
||||
int offset;
|
||||
QByteArray data;
|
||||
};
|
||||
int readSize;
|
||||
QRhiBufferReadbackResult *result;
|
||||
|
||||
struct StaticBufferUpload {
|
||||
StaticBufferUpload() { }
|
||||
StaticBufferUpload(QRhiBuffer *buf_, int offset_, int size_, const void *data_)
|
||||
: buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_ ? size_ : buf_->size())
|
||||
{ }
|
||||
static BufferOp dynamicUpdate(QRhiBuffer *buf, int offset, int size, const void *data)
|
||||
{
|
||||
BufferOp op;
|
||||
op.type = DynamicUpdate;
|
||||
op.buf = buf;
|
||||
op.offset = offset;
|
||||
op.data = QByteArray(reinterpret_cast<const char *>(data), size ? size : buf->size());
|
||||
return op;
|
||||
}
|
||||
|
||||
QRhiBuffer *buf = nullptr;
|
||||
int offset = 0;
|
||||
QByteArray data;
|
||||
static BufferOp staticUpload(QRhiBuffer *buf, int offset, int size, const void *data)
|
||||
{
|
||||
BufferOp op;
|
||||
op.type = StaticUpload;
|
||||
op.buf = buf;
|
||||
op.offset = offset;
|
||||
op.data = QByteArray(reinterpret_cast<const char *>(data), size ? size : buf->size());
|
||||
return op;
|
||||
}
|
||||
|
||||
static BufferOp read(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result)
|
||||
{
|
||||
BufferOp op;
|
||||
op.type = Read;
|
||||
op.buf = buf;
|
||||
op.offset = offset;
|
||||
op.readSize = size;
|
||||
op.result = result;
|
||||
return op;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureOp {
|
||||
@ -298,73 +323,62 @@ public:
|
||||
Upload,
|
||||
Copy,
|
||||
Read,
|
||||
MipGen
|
||||
GenMips
|
||||
};
|
||||
Type type;
|
||||
struct SUpload {
|
||||
QRhiTexture *tex = nullptr;
|
||||
// Specifying multiple uploads for a subresource must be supported.
|
||||
// In the backend this can then end up, where applicable, as a
|
||||
// single, batched copy operation with only one set of barriers.
|
||||
// This helps when doing for example glyph cache fills.
|
||||
QVector<QRhiTextureSubresourceUploadDescription> subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS];
|
||||
} upload;
|
||||
struct SCopy {
|
||||
QRhiTexture *dst = nullptr;
|
||||
QRhiTexture *src = nullptr;
|
||||
QRhiTextureCopyDescription desc;
|
||||
} copy;
|
||||
struct SRead {
|
||||
QRhiReadbackDescription rb;
|
||||
QRhiReadbackResult *result;
|
||||
} read;
|
||||
struct SMipGen {
|
||||
QRhiTexture *tex = nullptr;
|
||||
int layer = 0;
|
||||
} mipgen;
|
||||
QRhiTexture *dst;
|
||||
// Specifying multiple uploads for a subresource must be supported.
|
||||
// In the backend this can then end up, where applicable, as a
|
||||
// single, batched copy operation with only one set of barriers.
|
||||
// This helps when doing for example glyph cache fills.
|
||||
QVector<QRhiTextureSubresourceUploadDescription> subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS];
|
||||
QRhiTexture *src;
|
||||
QRhiTextureCopyDescription desc;
|
||||
QRhiReadbackDescription rb;
|
||||
QRhiReadbackResult *result;
|
||||
int layer;
|
||||
|
||||
static TextureOp textureUpload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
|
||||
static TextureOp upload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
|
||||
{
|
||||
TextureOp op;
|
||||
op.type = Upload;
|
||||
op.upload.tex = tex;
|
||||
op.dst = tex;
|
||||
for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it)
|
||||
op.upload.subresDesc[it->layer()][it->level()].append(it->description());
|
||||
op.subresDesc[it->layer()][it->level()].append(it->description());
|
||||
return op;
|
||||
}
|
||||
|
||||
static TextureOp textureCopy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
|
||||
static TextureOp copy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
|
||||
{
|
||||
TextureOp op;
|
||||
op.type = Copy;
|
||||
op.copy.dst = dst;
|
||||
op.copy.src = src;
|
||||
op.copy.desc = desc;
|
||||
op.dst = dst;
|
||||
op.src = src;
|
||||
op.desc = desc;
|
||||
return op;
|
||||
}
|
||||
|
||||
static TextureOp textureRead(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
|
||||
static TextureOp read(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
|
||||
{
|
||||
TextureOp op;
|
||||
op.type = Read;
|
||||
op.read.rb = rb;
|
||||
op.read.result = result;
|
||||
op.rb = rb;
|
||||
op.result = result;
|
||||
return op;
|
||||
}
|
||||
|
||||
static TextureOp textureMipGen(QRhiTexture *tex, int layer)
|
||||
static TextureOp genMips(QRhiTexture *tex, int layer)
|
||||
{
|
||||
TextureOp op;
|
||||
op.type = MipGen;
|
||||
op.mipgen.tex = tex;
|
||||
op.mipgen.layer = layer;
|
||||
op.type = GenMips;
|
||||
op.dst = tex;
|
||||
op.layer = layer;
|
||||
return op;
|
||||
}
|
||||
};
|
||||
|
||||
QVector<DynamicBufferUpdate> dynamicBufferUpdates;
|
||||
QVector<StaticBufferUpload> staticBufferUploads;
|
||||
QVector<TextureOp> textureOps;
|
||||
QVarLengthArray<BufferOp, 1024> bufferOps;
|
||||
QVarLengthArray<TextureOp, 256> textureOps;
|
||||
|
||||
QRhiResourceUpdateBatch *q = nullptr;
|
||||
QRhiImplementation *rhi = nullptr;
|
||||
@ -376,8 +390,7 @@ public:
|
||||
static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; }
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::StaticBufferUpload, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::BufferOp, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::TextureOp, Q_MOVABLE_TYPE);
|
||||
|
||||
template<typename T>
|
||||
|
@ -471,6 +471,10 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
|
||||
return true;
|
||||
case QRhi::TriangleFanTopology:
|
||||
return false;
|
||||
case QRhi::ReadBackNonUniformBuffer:
|
||||
return true;
|
||||
case QRhi::ReadBackNonBaseMipLevel:
|
||||
return true;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
return false;
|
||||
@ -1323,61 +1327,105 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
|
||||
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
|
||||
QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
bufD->hasPendingDynamicUpdates = true;
|
||||
}
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
|
||||
QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
bufD->hasPendingDynamicUpdates = true;
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
|
||||
QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
QD3D11CommandBuffer::Command cmd;
|
||||
cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
|
||||
cmd.args.updateSubRes.dst = bufD->buffer;
|
||||
cmd.args.updateSubRes.dstSubRes = 0;
|
||||
cmd.args.updateSubRes.src = cbD->retainData(u.data);
|
||||
cmd.args.updateSubRes.srcRowPitch = 0;
|
||||
// Specify the region (even when offset is 0 and all data is provided)
|
||||
// since the ID3D11Buffer's size is rounded up to be a multiple of 256
|
||||
// while the data we have has the original size.
|
||||
D3D11_BOX box;
|
||||
box.left = UINT(u.offset);
|
||||
box.top = box.front = 0;
|
||||
box.back = box.bottom = 1;
|
||||
box.right = UINT(u.offset + u.data.size()); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc
|
||||
cmd.args.updateSubRes.hasDstBox = true;
|
||||
cmd.args.updateSubRes.dstBox = box;
|
||||
cbD->commands.append(cmd);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
|
||||
QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
|
||||
if (bufD->m_type == QRhiBuffer::Dynamic) {
|
||||
u.result->data.resize(u.readSize);
|
||||
memcpy(u.result->data.data(), bufD->dynBuf.constData() + u.offset, size_t(u.readSize));
|
||||
} else {
|
||||
BufferReadback readback;
|
||||
readback.result = u.result;
|
||||
readback.byteSize = u.readSize;
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
|
||||
QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
QD3D11CommandBuffer::Command cmd;
|
||||
cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
|
||||
cmd.args.updateSubRes.dst = bufD->buffer;
|
||||
cmd.args.updateSubRes.dstSubRes = 0;
|
||||
cmd.args.updateSubRes.src = cbD->retainData(u.data);
|
||||
cmd.args.updateSubRes.srcRowPitch = 0;
|
||||
// Specify the region (even when offset is 0 and all data is provided)
|
||||
// since the ID3D11Buffer's size is rounded up to be a multiple of 256
|
||||
// while the data we have has the original size.
|
||||
D3D11_BOX box;
|
||||
box.left = UINT(u.offset);
|
||||
box.top = box.front = 0;
|
||||
box.back = box.bottom = 1;
|
||||
box.right = UINT(u.offset + u.data.size()); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc
|
||||
cmd.args.updateSubRes.hasDstBox = true;
|
||||
cmd.args.updateSubRes.dstBox = box;
|
||||
cbD->commands.append(cmd);
|
||||
D3D11_BUFFER_DESC desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.ByteWidth = readback.byteSize;
|
||||
desc.Usage = D3D11_USAGE_STAGING;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
HRESULT hr = dev->CreateBuffer(&desc, nullptr, &readback.stagingBuf);
|
||||
if (FAILED(hr)) {
|
||||
qWarning("Failed to create buffer: %s", qPrintable(comErrorMessage(hr)));
|
||||
continue;
|
||||
}
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.stagingBuf)), bufD, readback.byteSize));
|
||||
|
||||
QD3D11CommandBuffer::Command cmd;
|
||||
cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
|
||||
cmd.args.copySubRes.dst = readback.stagingBuf;
|
||||
cmd.args.copySubRes.dstSubRes = 0;
|
||||
cmd.args.copySubRes.dstX = 0;
|
||||
cmd.args.copySubRes.dstY = 0;
|
||||
cmd.args.copySubRes.src = bufD->buffer;
|
||||
cmd.args.copySubRes.srcSubRes = 0;
|
||||
cmd.args.copySubRes.hasSrcBox = true;
|
||||
D3D11_BOX box;
|
||||
box.left = UINT(u.offset);
|
||||
box.top = box.front = 0;
|
||||
box.back = box.bottom = 1;
|
||||
box.right = UINT(u.offset + u.readSize);
|
||||
cmd.args.copySubRes.srcBox = box;
|
||||
cbD->commands.append(cmd);
|
||||
|
||||
activeBufferReadbacks.append(readback);
|
||||
}
|
||||
if (u.result->completed)
|
||||
u.result->completed();
|
||||
}
|
||||
}
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
|
||||
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.upload.tex);
|
||||
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.dst);
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
|
||||
enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
|
||||
}
|
||||
}
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
|
||||
Q_ASSERT(u.copy.src && u.copy.dst);
|
||||
QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.copy.src);
|
||||
QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.copy.dst);
|
||||
UINT srcSubRes = D3D11CalcSubresource(UINT(u.copy.desc.sourceLevel()), UINT(u.copy.desc.sourceLayer()), srcD->mipLevelCount);
|
||||
UINT dstSubRes = D3D11CalcSubresource(UINT(u.copy.desc.destinationLevel()), UINT(u.copy.desc.destinationLayer()), dstD->mipLevelCount);
|
||||
const QPoint dp = u.copy.desc.destinationTopLeft();
|
||||
const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
|
||||
const QPoint sp = u.copy.desc.sourceTopLeft();
|
||||
Q_ASSERT(u.src && u.dst);
|
||||
QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.src);
|
||||
QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.dst);
|
||||
UINT srcSubRes = D3D11CalcSubresource(UINT(u.desc.sourceLevel()), UINT(u.desc.sourceLayer()), srcD->mipLevelCount);
|
||||
UINT dstSubRes = D3D11CalcSubresource(UINT(u.desc.destinationLevel()), UINT(u.desc.destinationLayer()), dstD->mipLevelCount);
|
||||
const QPoint dp = u.desc.destinationTopLeft();
|
||||
const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
|
||||
const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
|
||||
const QPoint sp = u.desc.sourceTopLeft();
|
||||
D3D11_BOX srcBox;
|
||||
srcBox.left = UINT(sp.x());
|
||||
srcBox.top = UINT(sp.y());
|
||||
srcBox.front = 0;
|
||||
// back, right, bottom are exclusive
|
||||
srcBox.right = srcBox.left + UINT(size.width());
|
||||
srcBox.bottom = srcBox.top + UINT(size.height());
|
||||
srcBox.right = srcBox.left + UINT(copySize.width());
|
||||
srcBox.bottom = srcBox.top + UINT(copySize.height());
|
||||
srcBox.back = 1;
|
||||
QD3D11CommandBuffer::Command cmd;
|
||||
cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
|
||||
@ -1391,16 +1439,16 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
cmd.args.copySubRes.srcBox = srcBox;
|
||||
cbD->commands.append(cmd);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
||||
ActiveReadback aRb;
|
||||
aRb.desc = u.read.rb;
|
||||
aRb.result = u.read.result;
|
||||
TextureReadback readback;
|
||||
readback.desc = u.rb;
|
||||
readback.result = u.result;
|
||||
|
||||
ID3D11Resource *src;
|
||||
DXGI_FORMAT dxgiFormat;
|
||||
QSize pixelSize;
|
||||
QRhiTexture::Format format;
|
||||
UINT subres = 0;
|
||||
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.read.rb.texture());
|
||||
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.rb.texture());
|
||||
QD3D11SwapChain *swapChainD = nullptr;
|
||||
|
||||
if (texD) {
|
||||
@ -1410,9 +1458,9 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
}
|
||||
src = texD->tex;
|
||||
dxgiFormat = texD->dxgiFormat;
|
||||
pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) : texD->m_pixelSize;
|
||||
pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
format = texD->m_format;
|
||||
subres = D3D11CalcSubresource(UINT(u.read.rb.level()), UINT(u.read.rb.layer()), texD->mipLevelCount);
|
||||
subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(u.rb.layer()), texD->mipLevelCount);
|
||||
} else {
|
||||
Q_ASSERT(contextState.currentSwapChain);
|
||||
swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain);
|
||||
@ -1435,9 +1483,9 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
if (format == QRhiTexture::UnknownFormat)
|
||||
continue;
|
||||
}
|
||||
quint32 bufSize = 0;
|
||||
quint32 byteSize = 0;
|
||||
quint32 bpl = 0;
|
||||
textureFormatInfo(format, pixelSize, &bpl, &bufSize);
|
||||
textureFormatInfo(format, pixelSize, &bpl, &byteSize);
|
||||
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
@ -1457,7 +1505,7 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
}
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(stagingTex)),
|
||||
texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
|
||||
bufSize));
|
||||
byteSize));
|
||||
|
||||
QD3D11CommandBuffer::Command cmd;
|
||||
cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
|
||||
@ -1470,18 +1518,18 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
cmd.args.copySubRes.hasSrcBox = false;
|
||||
cbD->commands.append(cmd);
|
||||
|
||||
aRb.stagingTex = stagingTex;
|
||||
aRb.bufSize = bufSize;
|
||||
aRb.bpl = bpl;
|
||||
aRb.pixelSize = pixelSize;
|
||||
aRb.format = format;
|
||||
readback.stagingTex = stagingTex;
|
||||
readback.byteSize = byteSize;
|
||||
readback.bpl = bpl;
|
||||
readback.pixelSize = pixelSize;
|
||||
readback.format = format;
|
||||
|
||||
activeReadbacks.append(aRb);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
|
||||
Q_ASSERT(u.mipgen.tex->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
|
||||
activeTextureReadbacks.append(readback);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
Q_ASSERT(u.dst->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
|
||||
QD3D11CommandBuffer::Command cmd;
|
||||
cmd.cmd = QD3D11CommandBuffer::Command::GenMip;
|
||||
cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.mipgen.tex)->srv;
|
||||
cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.dst)->srv;
|
||||
cbD->commands.append(cmd);
|
||||
}
|
||||
}
|
||||
@ -1494,37 +1542,58 @@ void QRhiD3D11::finishActiveReadbacks()
|
||||
QVarLengthArray<std::function<void()>, 4> completedCallbacks;
|
||||
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
||||
|
||||
for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiD3D11::ActiveReadback &aRb(activeReadbacks[i]);
|
||||
aRb.result->format = aRb.format;
|
||||
aRb.result->pixelSize = aRb.pixelSize;
|
||||
aRb.result->data.resize(int(aRb.bufSize));
|
||||
for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiD3D11::TextureReadback &readback(activeTextureReadbacks[i]);
|
||||
readback.result->format = readback.format;
|
||||
readback.result->pixelSize = readback.pixelSize;
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mp;
|
||||
HRESULT hr = context->Map(aRb.stagingTex, 0, D3D11_MAP_READ, 0, &mp);
|
||||
if (FAILED(hr)) {
|
||||
HRESULT hr = context->Map(readback.stagingTex, 0, D3D11_MAP_READ, 0, &mp);
|
||||
if (SUCCEEDED(hr)) {
|
||||
readback.result->data.resize(int(readback.byteSize));
|
||||
// nothing says the rows are tightly packed in the texture, must take
|
||||
// the stride into account
|
||||
char *dst = readback.result->data.data();
|
||||
char *src = static_cast<char *>(mp.pData);
|
||||
for (int y = 0, h = readback.pixelSize.height(); y != h; ++y) {
|
||||
memcpy(dst, src, readback.bpl);
|
||||
dst += readback.bpl;
|
||||
src += mp.RowPitch;
|
||||
}
|
||||
context->Unmap(readback.stagingTex, 0);
|
||||
} else {
|
||||
qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr)));
|
||||
aRb.stagingTex->Release();
|
||||
continue;
|
||||
}
|
||||
// nothing says the rows are tightly packed in the texture, must take
|
||||
// the stride into account
|
||||
char *dst = aRb.result->data.data();
|
||||
char *src = static_cast<char *>(mp.pData);
|
||||
for (int y = 0, h = aRb.pixelSize.height(); y != h; ++y) {
|
||||
memcpy(dst, src, aRb.bpl);
|
||||
dst += aRb.bpl;
|
||||
src += mp.RowPitch;
|
||||
|
||||
readback.stagingTex->Release();
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.stagingTex))));
|
||||
|
||||
if (readback.result->completed)
|
||||
completedCallbacks.append(readback.result->completed);
|
||||
|
||||
activeTextureReadbacks.removeAt(i);
|
||||
}
|
||||
|
||||
for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiD3D11::BufferReadback &readback(activeBufferReadbacks[i]);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mp;
|
||||
HRESULT hr = context->Map(readback.stagingBuf, 0, D3D11_MAP_READ, 0, &mp);
|
||||
if (SUCCEEDED(hr)) {
|
||||
readback.result->data.resize(int(readback.byteSize));
|
||||
memcpy(readback.result->data.data(), mp.pData, readback.byteSize);
|
||||
context->Unmap(readback.stagingBuf, 0);
|
||||
} else {
|
||||
qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr)));
|
||||
}
|
||||
context->Unmap(aRb.stagingTex, 0);
|
||||
|
||||
aRb.stagingTex->Release();
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(aRb.stagingTex))));
|
||||
readback.stagingBuf->Release();
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.stagingBuf))));
|
||||
|
||||
if (aRb.result->completed)
|
||||
completedCallbacks.append(aRb.result->completed);
|
||||
if (readback.result->completed)
|
||||
completedCallbacks.append(readback.result->completed);
|
||||
|
||||
activeReadbacks.removeAt(i);
|
||||
activeBufferReadbacks.removeAt(i);
|
||||
}
|
||||
|
||||
for (auto f : completedCallbacks)
|
||||
@ -3423,6 +3492,8 @@ bool QD3D11GraphicsPipeline::build()
|
||||
release();
|
||||
|
||||
QRHI_RES_RHI(QRhiD3D11);
|
||||
if (!rhiD->sanityCheckGraphicsPipeline(this))
|
||||
return false;
|
||||
|
||||
D3D11_RASTERIZER_DESC rastDesc;
|
||||
memset(&rastDesc, 0, sizeof(rastDesc));
|
||||
|
@ -678,16 +678,22 @@ public:
|
||||
QD3D11CommandBuffer cbWrapper;
|
||||
} ofr;
|
||||
|
||||
struct ActiveReadback {
|
||||
struct TextureReadback {
|
||||
QRhiReadbackDescription desc;
|
||||
QRhiReadbackResult *result;
|
||||
ID3D11Texture2D *stagingTex;
|
||||
quint32 bufSize;
|
||||
quint32 byteSize;
|
||||
quint32 bpl;
|
||||
QSize pixelSize;
|
||||
QRhiTexture::Format format;
|
||||
};
|
||||
QVector<ActiveReadback> activeReadbacks;
|
||||
QVector<TextureReadback> activeTextureReadbacks;
|
||||
struct BufferReadback {
|
||||
QRhiBufferReadbackResult *result;
|
||||
quint32 byteSize;
|
||||
ID3D11Buffer *stagingBuf;
|
||||
};
|
||||
QVector<BufferReadback> activeBufferReadbacks;
|
||||
|
||||
struct Shader {
|
||||
Shader() = default;
|
||||
@ -711,7 +717,8 @@ public:
|
||||
} deviceCurse;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_MOVABLE_TYPE);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
@ -276,6 +276,10 @@ QT_BEGIN_NAMESPACE
|
||||
#define GL_POINT_SPRITE 0x8861
|
||||
#endif
|
||||
|
||||
#ifndef GL_MAP_READ_BIT
|
||||
#define GL_MAP_READ_BIT 0x0001
|
||||
#endif
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcOpenGLProgramDiskCache)
|
||||
|
||||
/*!
|
||||
@ -492,6 +496,15 @@ bool QRhiGles2::create(QRhi::Flags flags)
|
||||
else
|
||||
caps.textureCompareMode = true;
|
||||
|
||||
// proper as in ES 3.0 (glMapBufferRange), not the old glMapBuffer
|
||||
// extension(s) (which is not in ES 3.0...messy)
|
||||
caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange);
|
||||
|
||||
if (caps.gles)
|
||||
caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0
|
||||
else
|
||||
caps.nonBaseLevelFramebufferTexture = true;
|
||||
|
||||
if (!caps.gles) {
|
||||
f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
||||
f->glEnable(GL_POINT_SPRITE);
|
||||
@ -734,6 +747,10 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
|
||||
return false; // not in ES 3.2, so won't bother
|
||||
case QRhi::TriangleFanTopology:
|
||||
return true;
|
||||
case QRhi::ReadBackNonUniformBuffer:
|
||||
return !caps.gles || caps.properMapBuffer;
|
||||
case QRhi::ReadBackNonBaseMipLevel:
|
||||
return caps.nonBaseLevelFramebufferTexture;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
return false;
|
||||
@ -1417,65 +1434,83 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
|
||||
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
|
||||
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
||||
memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
} else {
|
||||
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
|
||||
cmd.args.bufferSubData.target = bufD->targetForDataOps;
|
||||
cmd.args.bufferSubData.buffer = bufD->buffer;
|
||||
cmd.args.bufferSubData.offset = u.offset;
|
||||
cmd.args.bufferSubData.size = u.data.size();
|
||||
cmd.args.bufferSubData.data = cbD->retainData(u.data);
|
||||
cbD->commands.append(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
|
||||
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
||||
memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
} else {
|
||||
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
|
||||
cmd.args.bufferSubData.target = bufD->targetForDataOps;
|
||||
cmd.args.bufferSubData.buffer = bufD->buffer;
|
||||
cmd.args.bufferSubData.offset = u.offset;
|
||||
cmd.args.bufferSubData.size = u.data.size();
|
||||
cmd.args.bufferSubData.data = cbD->retainData(u.data);
|
||||
cbD->commands.append(cmd);
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
|
||||
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
||||
memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
} else {
|
||||
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
|
||||
cmd.args.bufferSubData.target = bufD->targetForDataOps;
|
||||
cmd.args.bufferSubData.buffer = bufD->buffer;
|
||||
cmd.args.bufferSubData.offset = u.offset;
|
||||
cmd.args.bufferSubData.size = u.data.size();
|
||||
cmd.args.bufferSubData.data = cbD->retainData(u.data);
|
||||
cbD->commands.append(cmd);
|
||||
}
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
|
||||
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
||||
memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
} else {
|
||||
trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate);
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
|
||||
cmd.args.bufferSubData.target = bufD->targetForDataOps;
|
||||
cmd.args.bufferSubData.buffer = bufD->buffer;
|
||||
cmd.args.bufferSubData.offset = u.offset;
|
||||
cmd.args.bufferSubData.size = u.data.size();
|
||||
cmd.args.bufferSubData.data = cbD->retainData(u.data);
|
||||
cbD->commands.append(cmd);
|
||||
}
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
|
||||
QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
|
||||
if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
|
||||
u.result->data.resize(u.readSize);
|
||||
memcpy(u.result->data.data(), bufD->ubuf.constData() + u.offset, size_t(u.readSize));
|
||||
if (u.result->completed)
|
||||
u.result->completed();
|
||||
} else {
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::GetBufferSubData;
|
||||
cmd.args.getBufferSubData.result = u.result;
|
||||
cmd.args.getBufferSubData.target = bufD->targetForDataOps;
|
||||
cmd.args.getBufferSubData.buffer = bufD->buffer;
|
||||
cmd.args.getBufferSubData.offset = u.offset;
|
||||
cmd.args.getBufferSubData.size = u.readSize;
|
||||
cbD->commands.append(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.upload.tex);
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst);
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
|
||||
enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
|
||||
}
|
||||
}
|
||||
texD->specified = true;
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
|
||||
Q_ASSERT(u.copy.src && u.copy.dst);
|
||||
QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.copy.src);
|
||||
QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.copy.dst);
|
||||
Q_ASSERT(u.src && u.dst);
|
||||
QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src);
|
||||
QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst);
|
||||
|
||||
trackedImageBarrier(cbD, srcD, QGles2Texture::AccessRead);
|
||||
trackedImageBarrier(cbD, dstD, QGles2Texture::AccessUpdate);
|
||||
|
||||
const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
|
||||
const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
|
||||
const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
|
||||
// do not translate coordinates, even if sp is bottom-left from gl's pov
|
||||
const QPoint sp = u.copy.desc.sourceTopLeft();
|
||||
const QPoint dp = u.copy.desc.destinationTopLeft();
|
||||
const QPoint sp = u.desc.sourceTopLeft();
|
||||
const QPoint dp = u.desc.destinationTopLeft();
|
||||
|
||||
const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap)
|
||||
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target;
|
||||
@ -1485,43 +1520,44 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::CopyTex;
|
||||
|
||||
cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.copy.desc.sourceLayer());
|
||||
cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.desc.sourceLayer());
|
||||
cmd.args.copyTex.srcTexture = srcD->texture;
|
||||
cmd.args.copyTex.srcLevel = u.copy.desc.sourceLevel();
|
||||
cmd.args.copyTex.srcLevel = u.desc.sourceLevel();
|
||||
cmd.args.copyTex.srcX = sp.x();
|
||||
cmd.args.copyTex.srcY = sp.y();
|
||||
|
||||
cmd.args.copyTex.dstTarget = dstD->target;
|
||||
cmd.args.copyTex.dstTexture = dstD->texture;
|
||||
cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.copy.desc.destinationLayer());
|
||||
cmd.args.copyTex.dstLevel = u.copy.desc.destinationLevel();
|
||||
cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.desc.destinationLayer());
|
||||
cmd.args.copyTex.dstLevel = u.desc.destinationLevel();
|
||||
cmd.args.copyTex.dstX = dp.x();
|
||||
cmd.args.copyTex.dstY = dp.y();
|
||||
|
||||
cmd.args.copyTex.w = size.width();
|
||||
cmd.args.copyTex.h = size.height();
|
||||
cmd.args.copyTex.w = copySize.width();
|
||||
cmd.args.copyTex.h = copySize.height();
|
||||
|
||||
cbD->commands.append(cmd);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::ReadPixels;
|
||||
cmd.args.readPixels.result = u.read.result;
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.read.rb.texture());
|
||||
cmd.args.readPixels.result = u.result;
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.rb.texture());
|
||||
if (texD)
|
||||
trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead);
|
||||
cmd.args.readPixels.texture = texD ? texD->texture : 0;
|
||||
if (texD) {
|
||||
cmd.args.readPixels.w = texD->m_pixelSize.width();
|
||||
cmd.args.readPixels.h = texD->m_pixelSize.height();
|
||||
const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
cmd.args.readPixels.w = readImageSize.width();
|
||||
cmd.args.readPixels.h = readImageSize.height();
|
||||
cmd.args.readPixels.format = texD->m_format;
|
||||
const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
|
||||
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
|
||||
cmd.args.readPixels.readTarget = faceTargetBase + uint(u.read.rb.layer());
|
||||
cmd.args.readPixels.level = u.read.rb.level();
|
||||
cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer());
|
||||
cmd.args.readPixels.level = u.rb.level();
|
||||
}
|
||||
cbD->commands.append(cmd);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.mipgen.tex);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst);
|
||||
trackedImageBarrier(cbD, texD, QGles2Texture::AccessFramebuffer);
|
||||
QGles2CommandBuffer::Command cmd;
|
||||
cmd.cmd = QGles2CommandBuffer::Command::GenMip;
|
||||
@ -2080,6 +2116,33 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
||||
f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size,
|
||||
cmd.args.bufferSubData.data);
|
||||
break;
|
||||
case QGles2CommandBuffer::Command::GetBufferSubData:
|
||||
{
|
||||
QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result;
|
||||
f->glBindBuffer(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer);
|
||||
if (caps.gles) {
|
||||
if (caps.properMapBuffer) {
|
||||
void *p = f->glMapBufferRange(cmd.args.getBufferSubData.target,
|
||||
cmd.args.getBufferSubData.offset,
|
||||
cmd.args.getBufferSubData.size,
|
||||
GL_MAP_READ_BIT);
|
||||
if (p) {
|
||||
result->data.resize(cmd.args.getBufferSubData.size);
|
||||
memcpy(result->data.data(), p, size_t(cmd.args.getBufferSubData.size));
|
||||
f->glUnmapBuffer(cmd.args.getBufferSubData.target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result->data.resize(cmd.args.getBufferSubData.size);
|
||||
f->glGetBufferSubData(cmd.args.getBufferSubData.target,
|
||||
cmd.args.getBufferSubData.offset,
|
||||
cmd.args.getBufferSubData.size,
|
||||
result->data.data());
|
||||
}
|
||||
if (result->completed)
|
||||
result->completed();
|
||||
}
|
||||
break;
|
||||
case QGles2CommandBuffer::Command::CopyTex:
|
||||
{
|
||||
GLuint fbo;
|
||||
@ -2101,23 +2164,31 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
||||
QRhiReadbackResult *result = cmd.args.readPixels.result;
|
||||
GLuint tex = cmd.args.readPixels.texture;
|
||||
GLuint fbo = 0;
|
||||
int mipLevel = 0;
|
||||
if (tex) {
|
||||
result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
|
||||
result->format = cmd.args.readPixels.format;
|
||||
f->glGenFramebuffers(1, &fbo);
|
||||
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, cmd.args.readPixels.level);
|
||||
mipLevel = cmd.args.readPixels.level;
|
||||
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
|
||||
f->glGenFramebuffers(1, &fbo);
|
||||
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, mipLevel);
|
||||
}
|
||||
} else {
|
||||
result->pixelSize = currentSwapChain->pixelSize;
|
||||
result->format = QRhiTexture::RGBA8;
|
||||
// readPixels handles multisample resolving implicitly
|
||||
}
|
||||
result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4);
|
||||
// With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it.
|
||||
f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(),
|
||||
GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
result->data.data());
|
||||
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
|
||||
// With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it.
|
||||
f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(),
|
||||
GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
result->data.data());
|
||||
} else {
|
||||
result->data.fill('\0');
|
||||
}
|
||||
if (fbo) {
|
||||
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
|
||||
f->glDeleteFramebuffers(1, &fbo);
|
||||
@ -3620,6 +3691,9 @@ bool QGles2GraphicsPipeline::build()
|
||||
if (!rhiD->ensureContext())
|
||||
return false;
|
||||
|
||||
if (!rhiD->sanityCheckGraphicsPipeline(this))
|
||||
return false;
|
||||
|
||||
drawMode = toGlTopology(m_topology);
|
||||
|
||||
program = rhiD->f->glCreateProgram();
|
||||
|
@ -312,8 +312,8 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
|
||||
BindShaderResources,
|
||||
BindFramebuffer,
|
||||
Clear,
|
||||
BufferData,
|
||||
BufferSubData,
|
||||
GetBufferSubData,
|
||||
CopyTex,
|
||||
ReadPixels,
|
||||
SubImage,
|
||||
@ -401,6 +401,13 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
|
||||
int size;
|
||||
const void *data; // must come from retainData()
|
||||
} bufferSubData;
|
||||
struct {
|
||||
QRhiBufferReadbackResult *result;
|
||||
GLenum target;
|
||||
GLuint buffer;
|
||||
int offset;
|
||||
int size;
|
||||
} getBufferSubData;
|
||||
struct {
|
||||
GLenum srcFaceTarget;
|
||||
GLuint srcTexture;
|
||||
@ -744,7 +751,10 @@ public:
|
||||
rgba8Format(false),
|
||||
instancing(false),
|
||||
baseVertex(false),
|
||||
compute(false)
|
||||
compute(false),
|
||||
textureCompareMode(false),
|
||||
properMapBuffer(false),
|
||||
nonBaseLevelFramebufferTexture(false)
|
||||
{ }
|
||||
int ctxMajor;
|
||||
int ctxMinor;
|
||||
@ -775,6 +785,8 @@ public:
|
||||
uint baseVertex : 1;
|
||||
uint compute : 1;
|
||||
uint textureCompareMode : 1;
|
||||
uint properMapBuffer : 1;
|
||||
uint nonBaseLevelFramebufferTexture : 1;
|
||||
} caps;
|
||||
QGles2SwapChain *currentSwapChain = nullptr;
|
||||
QVector<GLint> supportedCompressedFormats;
|
||||
|
@ -205,7 +205,7 @@ struct QRhiMetalData
|
||||
QMetalCommandBuffer cbWrapper;
|
||||
} ofr;
|
||||
|
||||
struct ActiveReadback {
|
||||
struct TextureReadback {
|
||||
int activeFrameSlot = -1;
|
||||
QRhiReadbackDescription desc;
|
||||
QRhiReadbackResult *result;
|
||||
@ -214,7 +214,7 @@ struct QRhiMetalData
|
||||
QSize pixelSize;
|
||||
QRhiTexture::Format format;
|
||||
};
|
||||
QVector<ActiveReadback> activeReadbacks;
|
||||
QVector<TextureReadback> activeTextureReadbacks;
|
||||
|
||||
API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
|
||||
API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
|
||||
@ -225,14 +225,14 @@ struct QRhiMetalData
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiMetalData::TextureReadback, Q_MOVABLE_TYPE);
|
||||
|
||||
struct QMetalBufferData
|
||||
{
|
||||
bool managed;
|
||||
bool slotted;
|
||||
id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
|
||||
QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
|
||||
QVarLengthArray<QRhiResourceUpdateBatchPrivate::BufferOp, 16> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
|
||||
};
|
||||
|
||||
struct QMetalRenderBufferData
|
||||
@ -367,6 +367,11 @@ bool QRhiMetal::create(QRhi::Flags flags)
|
||||
else
|
||||
d->dev = MTLCreateSystemDefaultDevice();
|
||||
|
||||
if (!d->dev) {
|
||||
qWarning("No MTLDevice");
|
||||
return false;
|
||||
}
|
||||
|
||||
qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));
|
||||
|
||||
if (importedCmdQueue)
|
||||
@ -552,6 +557,10 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
|
||||
return true;
|
||||
case QRhi::TriangleFanTopology:
|
||||
return false;
|
||||
case QRhi::ReadBackNonUniformBuffer:
|
||||
return true;
|
||||
case QRhi::ReadBackNonBaseMipLevel:
|
||||
return true;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
return false;
|
||||
@ -1541,21 +1550,33 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
|
||||
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
|
||||
QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
|
||||
bufD->d->pendingUpdates[i].append(u);
|
||||
}
|
||||
|
||||
// Due to the Metal API the handling of static and dynamic buffers is
|
||||
// basically the same. So go through the same pendingUpdates machinery.
|
||||
for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
|
||||
QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
|
||||
bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() });
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
|
||||
QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
|
||||
bufD->d->pendingUpdates[i].append(u);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
|
||||
// Due to the Metal API the handling of static and dynamic buffers is
|
||||
// basically the same. So go through the same pendingUpdates machinery.
|
||||
QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
|
||||
bufD->d->pendingUpdates[i].append(
|
||||
QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(u.buf, u.offset, u.data.size(), u.data.constData()));
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
|
||||
QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
|
||||
executeBufferHostWritesForCurrentFrame(bufD);
|
||||
const int idx = bufD->d->slotted ? currentFrameSlot : 0;
|
||||
char *p = reinterpret_cast<char *>([bufD->d->buf[idx] contents]);
|
||||
if (p) {
|
||||
u.result->data.resize(u.readSize);
|
||||
memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize));
|
||||
}
|
||||
if (u.result->completed)
|
||||
u.result->completed();
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEnc = nil;
|
||||
@ -1569,11 +1590,11 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
|
||||
QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex);
|
||||
QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
|
||||
qsizetype stagingSize = 0;
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
|
||||
stagingSize += subresUploadByteSize(subresDesc);
|
||||
}
|
||||
}
|
||||
@ -1588,7 +1609,7 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
qsizetype curOfs = 0;
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
|
||||
enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
|
||||
}
|
||||
}
|
||||
@ -1603,32 +1624,33 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
d->releaseQueue.append(e);
|
||||
QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
|
||||
Q_ASSERT(u.copy.src && u.copy.dst);
|
||||
QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src);
|
||||
QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst);
|
||||
const QPoint dp = u.copy.desc.destinationTopLeft();
|
||||
const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
|
||||
const QPoint sp = u.copy.desc.sourceTopLeft();
|
||||
Q_ASSERT(u.src && u.dst);
|
||||
QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src);
|
||||
QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst);
|
||||
const QPoint dp = u.desc.destinationTopLeft();
|
||||
const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
|
||||
const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
|
||||
const QPoint sp = u.desc.sourceTopLeft();
|
||||
|
||||
ensureBlit();
|
||||
[blitEnc copyFromTexture: srcD->d->tex
|
||||
sourceSlice: NSUInteger(u.copy.desc.sourceLayer())
|
||||
sourceLevel: NSUInteger(u.copy.desc.sourceLevel())
|
||||
sourceSlice: NSUInteger(u.desc.sourceLayer())
|
||||
sourceLevel: NSUInteger(u.desc.sourceLevel())
|
||||
sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), 0)
|
||||
sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1)
|
||||
sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1)
|
||||
toTexture: dstD->d->tex
|
||||
destinationSlice: NSUInteger(u.copy.desc.destinationLayer())
|
||||
destinationLevel: NSUInteger(u.copy.desc.destinationLevel())
|
||||
destinationSlice: NSUInteger(u.desc.destinationLayer())
|
||||
destinationLevel: NSUInteger(u.desc.destinationLevel())
|
||||
destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), 0)];
|
||||
|
||||
srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
||||
QRhiMetalData::ActiveReadback aRb;
|
||||
aRb.activeFrameSlot = currentFrameSlot;
|
||||
aRb.desc = u.read.rb;
|
||||
aRb.result = u.read.result;
|
||||
QRhiMetalData::TextureReadback readback;
|
||||
readback.activeFrameSlot = currentFrameSlot;
|
||||
readback.desc = u.rb;
|
||||
readback.result = u.result;
|
||||
|
||||
QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture());
|
||||
QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
|
||||
QMetalSwapChain *swapChainD = nullptr;
|
||||
id<MTLTexture> src;
|
||||
QSize srcSize;
|
||||
@ -1637,17 +1659,16 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
qWarning("Multisample texture cannot be read back");
|
||||
continue;
|
||||
}
|
||||
aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
|
||||
: texD->m_pixelSize;
|
||||
aRb.format = texD->m_format;
|
||||
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
readback.format = texD->m_format;
|
||||
src = texD->d->tex;
|
||||
srcSize = texD->m_pixelSize;
|
||||
srcSize = readback.pixelSize;
|
||||
texD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else {
|
||||
Q_ASSERT(currentSwapChain);
|
||||
swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
|
||||
aRb.pixelSize = swapChainD->pixelSize;
|
||||
aRb.format = swapChainD->d->rhiColorFormat;
|
||||
readback.pixelSize = swapChainD->pixelSize;
|
||||
readback.format = swapChainD->d->rhiColorFormat;
|
||||
// Multisample swapchains need nothing special since resolving
|
||||
// happens when ending a renderpass.
|
||||
const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
|
||||
@ -1656,28 +1677,28 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
}
|
||||
|
||||
quint32 bpl = 0;
|
||||
textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
|
||||
aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];
|
||||
textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize);
|
||||
readback.buf = [d->dev newBufferWithLength: readback.bufSize options: MTLResourceStorageModeShared];
|
||||
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(aRb.buf)),
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.buf)),
|
||||
texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
|
||||
aRb.bufSize));
|
||||
readback.bufSize));
|
||||
|
||||
ensureBlit();
|
||||
[blitEnc copyFromTexture: src
|
||||
sourceSlice: NSUInteger(u.read.rb.layer())
|
||||
sourceLevel: NSUInteger(u.read.rb.level())
|
||||
sourceSlice: NSUInteger(u.rb.layer())
|
||||
sourceLevel: NSUInteger(u.rb.level())
|
||||
sourceOrigin: MTLOriginMake(0, 0, 0)
|
||||
sourceSize: MTLSizeMake(NSUInteger(srcSize.width()), NSUInteger(srcSize.height()), 1)
|
||||
toBuffer: aRb.buf
|
||||
toBuffer: readback.buf
|
||||
destinationOffset: 0
|
||||
destinationBytesPerRow: bpl
|
||||
destinationBytesPerImage: 0
|
||||
options: MTLBlitOptionNone];
|
||||
|
||||
d->activeReadbacks.append(aRb);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
|
||||
QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex);
|
||||
d->activeTextureReadbacks.append(readback);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst);
|
||||
ensureBlit();
|
||||
[blitEnc generateMipmapsForTexture: utexD->d->tex];
|
||||
utexD->lastActiveFrameSlot = currentFrameSlot;
|
||||
@ -1697,14 +1718,13 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
|
||||
{
|
||||
const int idx = bufD->d->slotted ? currentFrameSlot : 0;
|
||||
QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]);
|
||||
if (updates.isEmpty())
|
||||
if (bufD->d->pendingUpdates[idx].isEmpty())
|
||||
return;
|
||||
|
||||
void *p = [bufD->d->buf[idx] contents];
|
||||
int changeBegin = -1;
|
||||
int changeEnd = -1;
|
||||
for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->d->pendingUpdates[idx])) {
|
||||
Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
|
||||
memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
if (changeBegin == -1 || u.offset < changeBegin)
|
||||
@ -1715,7 +1735,7 @@ void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
|
||||
if (changeBegin >= 0 && bufD->d->managed)
|
||||
[bufD->d->buf[idx] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))];
|
||||
|
||||
updates.clear();
|
||||
bufD->d->pendingUpdates[idx].clear();
|
||||
}
|
||||
|
||||
void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
|
||||
@ -1951,22 +1971,22 @@ void QRhiMetal::finishActiveReadbacks(bool forced)
|
||||
QVarLengthArray<std::function<void()>, 4> completedCallbacks;
|
||||
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
||||
|
||||
for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]);
|
||||
if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
|
||||
aRb.result->format = aRb.format;
|
||||
aRb.result->pixelSize = aRb.pixelSize;
|
||||
aRb.result->data.resize(int(aRb.bufSize));
|
||||
void *p = [aRb.buf contents];
|
||||
memcpy(aRb.result->data.data(), p, aRb.bufSize);
|
||||
[aRb.buf release];
|
||||
for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]);
|
||||
if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
|
||||
readback.result->format = readback.format;
|
||||
readback.result->pixelSize = readback.pixelSize;
|
||||
readback.result->data.resize(int(readback.bufSize));
|
||||
void *p = [readback.buf contents];
|
||||
memcpy(readback.result->data.data(), p, readback.bufSize);
|
||||
[readback.buf release];
|
||||
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(aRb.buf))));
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.buf))));
|
||||
|
||||
if (aRb.result->completed)
|
||||
completedCallbacks.append(aRb.result->completed);
|
||||
if (readback.result->completed)
|
||||
completedCallbacks.append(readback.result->completed);
|
||||
|
||||
d->activeReadbacks.removeAt(i);
|
||||
d->activeTextureReadbacks.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2042,7 +2062,6 @@ bool QMetalBuffer::build()
|
||||
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
|
||||
if (i == 0 || d->slotted) {
|
||||
d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
|
||||
d->pendingUpdates[i].reserve(16);
|
||||
if (!m_objectName.isEmpty()) {
|
||||
if (!d->slotted) {
|
||||
d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
|
||||
@ -3120,6 +3139,8 @@ bool QMetalGraphicsPipeline::build()
|
||||
release();
|
||||
|
||||
QRHI_RES_RHI(QRhiMetal);
|
||||
if (!rhiD->sanityCheckGraphicsPipeline(this))
|
||||
return false;
|
||||
|
||||
// same binding space for vertex and constant buffers - work it around
|
||||
const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1;
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
#include "qrhinull_p_p.h"
|
||||
#include <qmath.h>
|
||||
#include <QPainter>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -385,27 +386,125 @@ QRhi::FrameOpResult QRhiNull::finish()
|
||||
return QRhi::FrameOpSuccess;
|
||||
}
|
||||
|
||||
void QRhiNull::simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u)
|
||||
{
|
||||
QNullTexture *texD = QRHI_RES(QNullTexture, u.dst);
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) {
|
||||
if (!subresDesc.image().isNull()) {
|
||||
const QImage src = subresDesc.image();
|
||||
QPainter painter(&texD->image[layer][level]);
|
||||
const QSize srcSize = subresDesc.sourceSize().isEmpty()
|
||||
? src.size() : subresDesc.sourceSize();
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawImage(subresDesc.destinationTopLeft(), src,
|
||||
QRect(subresDesc.sourceTopLeft(), srcSize));
|
||||
} else if (!subresDesc.data().isEmpty()) {
|
||||
const QSize subresSize = q->sizeForMipLevel(level, texD->pixelSize());
|
||||
int w = subresSize.width();
|
||||
int h = subresSize.height();
|
||||
if (!subresDesc.sourceSize().isEmpty()) {
|
||||
w = subresDesc.sourceSize().width();
|
||||
h = subresDesc.sourceSize().height();
|
||||
}
|
||||
// sourceTopLeft is not supported on this path as per QRhi docs
|
||||
const char *src = subresDesc.data().constData();
|
||||
const int srcBpl = w * 4;
|
||||
const QPoint dstOffset = subresDesc.destinationTopLeft();
|
||||
uchar *dst = texD->image[layer][level].bits();
|
||||
const int dstBpl = texD->image[layer][level].bytesPerLine();
|
||||
for (int y = 0; y < h; ++y) {
|
||||
memcpy(dst + dstOffset.x() * 4 + (y + dstOffset.y()) * dstBpl,
|
||||
src + y * srcBpl,
|
||||
size_t(srcBpl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QRhiNull::simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u)
|
||||
{
|
||||
QNullTexture *srcD = QRHI_RES(QNullTexture, u.src);
|
||||
QNullTexture *dstD = QRHI_RES(QNullTexture, u.dst);
|
||||
const QImage &srcImage(srcD->image[u.desc.sourceLayer()][u.desc.sourceLevel()]);
|
||||
QImage &dstImage(dstD->image[u.desc.destinationLayer()][u.desc.destinationLevel()]);
|
||||
const QPoint dstPos = u.desc.destinationTopLeft();
|
||||
const QSize size = u.desc.pixelSize().isEmpty() ? srcD->pixelSize() : u.desc.pixelSize();
|
||||
const QPoint srcPos = u.desc.sourceTopLeft();
|
||||
|
||||
QPainter painter(&dstImage);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawImage(QRect(dstPos, size), srcImage, QRect(srcPos, size));
|
||||
}
|
||||
|
||||
void QRhiNull::simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u)
|
||||
{
|
||||
QNullTexture *texD = QRHI_RES(QNullTexture, u.dst);
|
||||
const QSize baseSize = texD->pixelSize();
|
||||
const int levelCount = q->mipLevelsForSize(baseSize);
|
||||
for (int level = 1; level < levelCount; ++level)
|
||||
texD->image[0][level] = texD->image[0][0].scaled(q->sizeForMipLevel(level, baseSize));
|
||||
}
|
||||
|
||||
void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
|
||||
{
|
||||
Q_UNUSED(cb);
|
||||
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate
|
||||
|| u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload)
|
||||
{
|
||||
QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf);
|
||||
memcpy(bufD->data.data() + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
|
||||
QRhiBufferReadbackResult *result = u.result;
|
||||
result->data.resize(u.readSize);
|
||||
QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf);
|
||||
memcpy(result->data.data(), bufD->data.constData() + u.offset, size_t(u.readSize));
|
||||
if (result->completed)
|
||||
result->completed();
|
||||
}
|
||||
}
|
||||
for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
||||
QRhiReadbackResult *result = u.read.result;
|
||||
QRhiTexture *tex = u.read.rb.texture();
|
||||
if (tex) {
|
||||
result->format = tex->format();
|
||||
result->pixelSize = q->sizeForMipLevel(u.read.rb.level(), tex->pixelSize());
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
|
||||
if (u.dst->format() == QRhiTexture::RGBA8)
|
||||
simulateTextureUpload(u);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
|
||||
if (u.src->format() == QRhiTexture::RGBA8 && u.dst->format() == QRhiTexture::RGBA8)
|
||||
simulateTextureCopy(u);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
||||
QRhiReadbackResult *result = u.result;
|
||||
QNullTexture *texD = QRHI_RES(QNullTexture, u.rb.texture());
|
||||
if (texD) {
|
||||
result->format = texD->format();
|
||||
result->pixelSize = q->sizeForMipLevel(u.rb.level(), texD->pixelSize());
|
||||
} else {
|
||||
Q_ASSERT(currentSwapChain);
|
||||
result->format = QRhiTexture::RGBA8;
|
||||
result->pixelSize = currentSwapChain->currentPixelSize();
|
||||
}
|
||||
quint32 bytesPerLine = 0;
|
||||
quint32 byteSize = 0;
|
||||
textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize);
|
||||
result->data.fill(0, byteSize);
|
||||
textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize);
|
||||
if (texD && texD->format() == QRhiTexture::RGBA8) {
|
||||
result->data.resize(int(byteSize));
|
||||
const QImage &src(texD->image[u.rb.layer()][u.rb.level()]);
|
||||
char *dst = result->data.data();
|
||||
for (int y = 0, h = src.height(); y < h; ++y) {
|
||||
memcpy(dst, src.constScanLine(y), bytesPerLine);
|
||||
dst += bytesPerLine;
|
||||
}
|
||||
} else {
|
||||
result->data.fill(0, int(byteSize));
|
||||
}
|
||||
if (result->completed)
|
||||
result->completed();
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
if (u.dst->format() == QRhiTexture::RGBA8)
|
||||
simulateTextureGenMips(u);
|
||||
}
|
||||
}
|
||||
ud->free();
|
||||
@ -454,14 +553,18 @@ QNullBuffer::~QNullBuffer()
|
||||
|
||||
void QNullBuffer::release()
|
||||
{
|
||||
data.clear();
|
||||
|
||||
QRHI_PROF;
|
||||
QRHI_PROF_F(releaseBuffer(this));
|
||||
}
|
||||
|
||||
bool QNullBuffer::build()
|
||||
{
|
||||
data.fill('\0', m_size);
|
||||
|
||||
QRHI_PROF;
|
||||
QRHI_PROF_F(newBuffer(this, m_size, 1, 0));
|
||||
QRHI_PROF_F(newBuffer(this, uint(m_size), 1, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -513,22 +616,36 @@ void QNullTexture::release()
|
||||
|
||||
bool QNullTexture::build()
|
||||
{
|
||||
QRHI_RES_RHI(QRhiNull);
|
||||
const bool isCube = m_flags.testFlag(CubeMap);
|
||||
const bool hasMipMaps = m_flags.testFlag(MipMapped);
|
||||
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
|
||||
const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
|
||||
const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
|
||||
const int layerCount = isCube ? 6 : 1;
|
||||
|
||||
if (m_format == RGBA8) {
|
||||
for (int layer = 0; layer < layerCount; ++layer) {
|
||||
for (int level = 0; level < mipLevelCount; ++level) {
|
||||
image[layer][level] = QImage(rhiD->q->sizeForMipLevel(level, size),
|
||||
QImage::Format_RGBA8888_Premultiplied);
|
||||
image[layer][level].fill(Qt::yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QRHI_PROF;
|
||||
QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
|
||||
QRHI_PROF_F(newTexture(this, true, mipLevelCount, layerCount, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QNullTexture::buildFrom(const QRhiNativeHandles *src)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
QRHI_RES_RHI(QRhiNull);
|
||||
const bool isCube = m_flags.testFlag(CubeMap);
|
||||
const bool hasMipMaps = m_flags.testFlag(MipMapped);
|
||||
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
|
||||
const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
|
||||
const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
|
||||
QRHI_PROF;
|
||||
QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1));
|
||||
return true;
|
||||
@ -690,6 +807,10 @@ void QNullGraphicsPipeline::release()
|
||||
|
||||
bool QNullGraphicsPipeline::build()
|
||||
{
|
||||
QRHI_RES_RHI(QRhiNull);
|
||||
if (!rhiD->sanityCheckGraphicsPipeline(this))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,8 @@ struct QNullBuffer : public QRhiBuffer
|
||||
~QNullBuffer();
|
||||
void release() override;
|
||||
bool build() override;
|
||||
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
struct QNullRenderBuffer : public QRhiRenderBuffer
|
||||
@ -82,6 +84,7 @@ struct QNullTexture : public QRhiTexture
|
||||
const QRhiNativeHandles *nativeHandles() override;
|
||||
|
||||
QRhiNullTextureNativeHandles nativeHandlesStruct;
|
||||
QImage image[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS];
|
||||
};
|
||||
|
||||
struct QNullSampler : public QRhiSampler
|
||||
@ -286,6 +289,10 @@ public:
|
||||
void releaseCachedResources() override;
|
||||
bool isDeviceLost() const override;
|
||||
|
||||
void simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u);
|
||||
void simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u);
|
||||
void simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u);
|
||||
|
||||
QRhiNullNativeHandles nativeHandlesStruct;
|
||||
QRhiSwapChain *currentSwapChain = nullptr;
|
||||
QNullCommandBuffer offscreenCommandBuffer;
|
||||
|
@ -363,6 +363,11 @@ bool QRhiVulkan::create(QRhi::Flags flags)
|
||||
Q_UNUSED(flags);
|
||||
Q_ASSERT(inst);
|
||||
|
||||
if (!inst->isValid()) {
|
||||
qWarning("Vulkan instance is not valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application
|
||||
|
||||
f = inst->functions();
|
||||
@ -2649,100 +2654,164 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
|
||||
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
|
||||
QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
|
||||
bufD->pendingDynamicUpdates[i].append(u);
|
||||
}
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
|
||||
QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
|
||||
bufD->pendingDynamicUpdates[i].append(u);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
|
||||
QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
|
||||
QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
|
||||
Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
|
||||
Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
|
||||
if (!bufD->stagingBuffers[currentFrameSlot]) {
|
||||
VkBufferCreateInfo bufferInfo;
|
||||
memset(&bufferInfo, 0, sizeof(bufferInfo));
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
// must cover the entire buffer - this way multiple, partial updates per frame
|
||||
// are supported even when the staging buffer is reused (Static)
|
||||
bufferInfo.size = VkDeviceSize(bufD->m_size);
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
if (!bufD->stagingBuffers[currentFrameSlot]) {
|
||||
VkBufferCreateInfo bufferInfo;
|
||||
memset(&bufferInfo, 0, sizeof(bufferInfo));
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
// must cover the entire buffer - this way multiple, partial updates per frame
|
||||
// are supported even when the staging buffer is reused (Static)
|
||||
bufferInfo.size = VkDeviceSize(bufD->m_size);
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
VmaAllocationCreateInfo allocInfo;
|
||||
memset(&allocInfo, 0, sizeof(allocInfo));
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo;
|
||||
memset(&allocInfo, 0, sizeof(allocInfo));
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
|
||||
VmaAllocation allocation;
|
||||
VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
|
||||
&bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
|
||||
if (err == VK_SUCCESS) {
|
||||
bufD->stagingAllocations[currentFrameSlot] = allocation;
|
||||
QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size)));
|
||||
} else {
|
||||
qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
VmaAllocation allocation;
|
||||
VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
|
||||
&bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
|
||||
if (err == VK_SUCCESS) {
|
||||
bufD->stagingAllocations[currentFrameSlot] = allocation;
|
||||
QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size)));
|
||||
} else {
|
||||
qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err);
|
||||
void *p = nullptr;
|
||||
VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]);
|
||||
VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
|
||||
if (err != VK_SUCCESS) {
|
||||
qWarning("Failed to map buffer: %d", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
vmaUnmapMemory(toVmaAllocator(allocator), a);
|
||||
vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size()));
|
||||
|
||||
void *p = nullptr;
|
||||
VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]);
|
||||
VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
|
||||
if (err != VK_SUCCESS) {
|
||||
qWarning("Failed to map buffer: %d", err);
|
||||
continue;
|
||||
}
|
||||
memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
vmaUnmapMemory(toVmaAllocator(allocator), a);
|
||||
vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size()));
|
||||
trackedBufferBarrier(cbD, bufD, 0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
trackedBufferBarrier(cbD, bufD, 0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
VkBufferCopy copyInfo;
|
||||
memset(©Info, 0, sizeof(copyInfo));
|
||||
copyInfo.srcOffset = VkDeviceSize(u.offset);
|
||||
copyInfo.dstOffset = VkDeviceSize(u.offset);
|
||||
copyInfo.size = VkDeviceSize(u.data.size());
|
||||
|
||||
VkBufferCopy copyInfo;
|
||||
memset(©Info, 0, sizeof(copyInfo));
|
||||
copyInfo.srcOffset = VkDeviceSize(u.offset);
|
||||
copyInfo.dstOffset = VkDeviceSize(u.offset);
|
||||
copyInfo.size = VkDeviceSize(u.data.size());
|
||||
QVkCommandBuffer::Command cmd;
|
||||
cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
|
||||
cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot];
|
||||
cmd.args.copyBuffer.dst = bufD->buffers[0];
|
||||
cmd.args.copyBuffer.desc = copyInfo;
|
||||
cbD->commands.append(cmd);
|
||||
|
||||
QVkCommandBuffer::Command cmd;
|
||||
cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
|
||||
cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot];
|
||||
cmd.args.copyBuffer.dst = bufD->buffers[0];
|
||||
cmd.args.copyBuffer.desc = copyInfo;
|
||||
cbD->commands.append(cmd);
|
||||
// Where's the barrier for read-after-write? (assuming the common case
|
||||
// of binding this buffer as vertex/index, or, less likely, as uniform
|
||||
// buffer, in a renderpass later on) That is handled by the pass
|
||||
// resource tracking: the appropriate pipeline barrier will be
|
||||
// generated and recorded right before the renderpass, that binds this
|
||||
// buffer in one of its commands, gets its BeginRenderPass recorded.
|
||||
|
||||
// Where's the barrier for read-after-write? (assuming the common case
|
||||
// of binding this buffer as vertex/index, or, less likely, as uniform
|
||||
// buffer, in a renderpass later on) That is handled by the pass
|
||||
// resource tracking: the appropriate pipeline barrier will be
|
||||
// generated and recorded right before the renderpass, that binds this
|
||||
// buffer in one of its commands, gets its BeginRenderPass recorded.
|
||||
bufD->lastActiveFrameSlot = currentFrameSlot;
|
||||
|
||||
bufD->lastActiveFrameSlot = currentFrameSlot;
|
||||
if (bufD->m_type == QRhiBuffer::Immutable) {
|
||||
QRhiVulkan::DeferredReleaseEntry e;
|
||||
e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
|
||||
e.lastActiveFrameSlot = currentFrameSlot;
|
||||
e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot];
|
||||
e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot];
|
||||
bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
|
||||
bufD->stagingAllocations[currentFrameSlot] = nullptr;
|
||||
releaseQueue.append(e);
|
||||
QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot));
|
||||
}
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
|
||||
QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
|
||||
if (bufD->m_type == QRhiBuffer::Dynamic) {
|
||||
executeBufferHostWritesForCurrentFrame(bufD);
|
||||
void *p = nullptr;
|
||||
VmaAllocation a = toVmaAllocation(bufD->allocations[currentFrameSlot]);
|
||||
VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
|
||||
if (err == VK_SUCCESS) {
|
||||
u.result->data.resize(u.readSize);
|
||||
memcpy(u.result->data.data(), reinterpret_cast<char *>(p) + u.offset, size_t(u.readSize));
|
||||
vmaUnmapMemory(toVmaAllocator(allocator), a);
|
||||
}
|
||||
if (u.result->completed)
|
||||
u.result->completed();
|
||||
} else {
|
||||
// Non-Dynamic buffers may not be host visible, so have to
|
||||
// create a readback buffer, enqueue a copy from
|
||||
// bufD->buffers[0] to this buffer, and then once the command
|
||||
// buffer completes, copy the data out of the host visible
|
||||
// readback buffer. Quite similar to what we do for texture
|
||||
// readbacks.
|
||||
BufferReadback readback;
|
||||
readback.activeFrameSlot = currentFrameSlot;
|
||||
readback.result = u.result;
|
||||
readback.byteSize = u.readSize;
|
||||
|
||||
if (bufD->m_type == QRhiBuffer::Immutable) {
|
||||
QRhiVulkan::DeferredReleaseEntry e;
|
||||
e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
|
||||
e.lastActiveFrameSlot = currentFrameSlot;
|
||||
e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot];
|
||||
e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot];
|
||||
bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
|
||||
bufD->stagingAllocations[currentFrameSlot] = nullptr;
|
||||
releaseQueue.append(e);
|
||||
QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot));
|
||||
VkBufferCreateInfo bufferInfo;
|
||||
memset(&bufferInfo, 0, sizeof(bufferInfo));
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = VkDeviceSize(readback.byteSize);
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo;
|
||||
memset(&allocInfo, 0, sizeof(allocInfo));
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
|
||||
|
||||
VmaAllocation allocation;
|
||||
VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr);
|
||||
if (err == VK_SUCCESS) {
|
||||
readback.stagingAlloc = allocation;
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), bufD, uint(readback.byteSize)));
|
||||
} else {
|
||||
qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
VkBufferCopy copyInfo;
|
||||
memset(©Info, 0, sizeof(copyInfo));
|
||||
copyInfo.srcOffset = VkDeviceSize(u.offset);
|
||||
copyInfo.size = VkDeviceSize(u.readSize);
|
||||
|
||||
QVkCommandBuffer::Command cmd;
|
||||
cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
|
||||
cmd.args.copyBuffer.src = bufD->buffers[0];
|
||||
cmd.args.copyBuffer.dst = readback.stagingBuf;
|
||||
cmd.args.copyBuffer.desc = copyInfo;
|
||||
cbD->commands.append(cmd);
|
||||
|
||||
bufD->lastActiveFrameSlot = currentFrameSlot;
|
||||
|
||||
activeBufferReadbacks.append(readback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
|
||||
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
|
||||
QVkTexture *utexD = QRHI_RES(QVkTexture, u.upload.tex);
|
||||
QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
|
||||
// batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos
|
||||
VkDeviceSize stagingSize = 0;
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level]))
|
||||
stagingSize += subresUploadByteSize(subresDesc);
|
||||
}
|
||||
}
|
||||
@ -2780,7 +2849,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
|
||||
for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
|
||||
for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
|
||||
const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.upload.subresDesc[layer][level]);
|
||||
const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]);
|
||||
if (srd.isEmpty())
|
||||
continue;
|
||||
for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(srd)) {
|
||||
@ -2821,36 +2890,37 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
|
||||
utexD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
|
||||
Q_ASSERT(u.copy.src && u.copy.dst);
|
||||
if (u.copy.src == u.copy.dst) {
|
||||
Q_ASSERT(u.src && u.dst);
|
||||
if (u.src == u.dst) {
|
||||
qWarning("Texture copy with matching source and destination is not supported");
|
||||
continue;
|
||||
}
|
||||
QVkTexture *srcD = QRHI_RES(QVkTexture, u.copy.src);
|
||||
QVkTexture *dstD = QRHI_RES(QVkTexture, u.copy.dst);
|
||||
QVkTexture *srcD = QRHI_RES(QVkTexture, u.src);
|
||||
QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst);
|
||||
|
||||
VkImageCopy region;
|
||||
memset(®ion, 0, sizeof(region));
|
||||
|
||||
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.srcSubresource.mipLevel = uint32_t(u.copy.desc.sourceLevel());
|
||||
region.srcSubresource.baseArrayLayer = uint32_t(u.copy.desc.sourceLayer());
|
||||
region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel());
|
||||
region.srcSubresource.baseArrayLayer = uint32_t(u.desc.sourceLayer());
|
||||
region.srcSubresource.layerCount = 1;
|
||||
|
||||
region.srcOffset.x = u.copy.desc.sourceTopLeft().x();
|
||||
region.srcOffset.y = u.copy.desc.sourceTopLeft().y();
|
||||
region.srcOffset.x = u.desc.sourceTopLeft().x();
|
||||
region.srcOffset.y = u.desc.sourceTopLeft().y();
|
||||
|
||||
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.dstSubresource.mipLevel = uint32_t(u.copy.desc.destinationLevel());
|
||||
region.dstSubresource.baseArrayLayer = uint32_t(u.copy.desc.destinationLayer());
|
||||
region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel());
|
||||
region.dstSubresource.baseArrayLayer = uint32_t(u.desc.destinationLayer());
|
||||
region.dstSubresource.layerCount = 1;
|
||||
|
||||
region.dstOffset.x = u.copy.desc.destinationTopLeft().x();
|
||||
region.dstOffset.y = u.copy.desc.destinationTopLeft().y();
|
||||
region.dstOffset.x = u.desc.destinationTopLeft().x();
|
||||
region.dstOffset.y = u.desc.destinationTopLeft().y();
|
||||
|
||||
const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
|
||||
region.extent.width = uint32_t(size.width());
|
||||
region.extent.height = uint32_t(size.height());
|
||||
const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
|
||||
const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
|
||||
region.extent.width = uint32_t(copySize.width());
|
||||
region.extent.height = uint32_t(copySize.height());
|
||||
region.extent.depth = 1;
|
||||
|
||||
trackedImageBarrier(cbD, srcD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
@ -2869,21 +2939,20 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
|
||||
srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
|
||||
ActiveReadback aRb;
|
||||
aRb.activeFrameSlot = currentFrameSlot;
|
||||
aRb.desc = u.read.rb;
|
||||
aRb.result = u.read.result;
|
||||
TextureReadback readback;
|
||||
readback.activeFrameSlot = currentFrameSlot;
|
||||
readback.desc = u.rb;
|
||||
readback.result = u.result;
|
||||
|
||||
QVkTexture *texD = QRHI_RES(QVkTexture, u.read.rb.texture());
|
||||
QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture());
|
||||
QVkSwapChain *swapChainD = nullptr;
|
||||
if (texD) {
|
||||
if (texD->samples > VK_SAMPLE_COUNT_1_BIT) {
|
||||
qWarning("Multisample texture cannot be read back");
|
||||
continue;
|
||||
}
|
||||
aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
|
||||
: texD->m_pixelSize;
|
||||
aRb.format = texD->m_format;
|
||||
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
readback.format = texD->m_format;
|
||||
texD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else {
|
||||
Q_ASSERT(currentSwapChain);
|
||||
@ -2892,21 +2961,21 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
qWarning("Swapchain does not support readback");
|
||||
continue;
|
||||
}
|
||||
aRb.pixelSize = swapChainD->pixelSize;
|
||||
aRb.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr);
|
||||
if (aRb.format == QRhiTexture::UnknownFormat)
|
||||
readback.pixelSize = swapChainD->pixelSize;
|
||||
readback.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr);
|
||||
if (readback.format == QRhiTexture::UnknownFormat)
|
||||
continue;
|
||||
|
||||
// Multisample swapchains need nothing special since resolving
|
||||
// happens when ending a renderpass.
|
||||
}
|
||||
textureFormatInfo(aRb.format, aRb.pixelSize, nullptr, &aRb.bufSize);
|
||||
textureFormatInfo(readback.format, readback.pixelSize, nullptr, &readback.byteSize);
|
||||
|
||||
// Create a host visible buffer.
|
||||
// Create a host visible readback buffer.
|
||||
VkBufferCreateInfo bufferInfo;
|
||||
memset(&bufferInfo, 0, sizeof(bufferInfo));
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = aRb.bufSize;
|
||||
bufferInfo.size = readback.byteSize;
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo;
|
||||
@ -2914,14 +2983,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
|
||||
|
||||
VmaAllocation allocation;
|
||||
VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &aRb.buf, &allocation, nullptr);
|
||||
VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr);
|
||||
if (err == VK_SUCCESS) {
|
||||
aRb.bufAlloc = allocation;
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(aRb.buf),
|
||||
readback.stagingAlloc = allocation;
|
||||
QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf),
|
||||
texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
|
||||
aRb.bufSize));
|
||||
readback.byteSize));
|
||||
} else {
|
||||
qWarning("Failed to create readback buffer of size %u: %d", aRb.bufSize, err);
|
||||
qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -2930,11 +2999,11 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
memset(©Desc, 0, sizeof(copyDesc));
|
||||
copyDesc.bufferOffset = 0;
|
||||
copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copyDesc.imageSubresource.mipLevel = uint32_t(u.read.rb.level());
|
||||
copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.read.rb.layer());
|
||||
copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level());
|
||||
copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer());
|
||||
copyDesc.imageSubresource.layerCount = 1;
|
||||
copyDesc.imageExtent.width = uint32_t(aRb.pixelSize.width());
|
||||
copyDesc.imageExtent.height = uint32_t(aRb.pixelSize.height());
|
||||
copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width());
|
||||
copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height());
|
||||
copyDesc.imageExtent.depth = 1;
|
||||
|
||||
if (texD) {
|
||||
@ -2944,7 +3013,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
|
||||
cmd.args.copyImageToBuffer.src = texD->image;
|
||||
cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout;
|
||||
cmd.args.copyImageToBuffer.dst = aRb.buf;
|
||||
cmd.args.copyImageToBuffer.dst = readback.stagingBuf;
|
||||
cmd.args.copyImageToBuffer.desc = copyDesc;
|
||||
cbD->commands.append(cmd);
|
||||
} else {
|
||||
@ -2969,14 +3038,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
|
||||
cmd.args.copyImageToBuffer.src = image;
|
||||
cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
cmd.args.copyImageToBuffer.dst = aRb.buf;
|
||||
cmd.args.copyImageToBuffer.dst = readback.stagingBuf;
|
||||
cmd.args.copyImageToBuffer.desc = copyDesc;
|
||||
cbD->commands.append(cmd);
|
||||
}
|
||||
|
||||
activeReadbacks.append(aRb);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
|
||||
QVkTexture *utexD = QRHI_RES(QVkTexture, u.mipgen.tex);
|
||||
activeTextureReadbacks.append(readback);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
|
||||
Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips));
|
||||
int w = utexD->m_pixelSize.width();
|
||||
int h = utexD->m_pixelSize.height();
|
||||
@ -2993,14 +3062,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
origLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
origAccess, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
u.mipgen.layer, 1,
|
||||
u.layer, 1,
|
||||
level - 1, 1);
|
||||
} else {
|
||||
subresourceBarrier(cbD, utexD->image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
u.mipgen.layer, 1,
|
||||
u.layer, 1,
|
||||
level - 1, 1);
|
||||
}
|
||||
|
||||
@ -3008,7 +3077,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
origLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
origAccess, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
u.mipgen.layer, 1,
|
||||
u.layer, 1,
|
||||
level, 1);
|
||||
|
||||
VkImageBlit region;
|
||||
@ -3016,7 +3085,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
|
||||
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.srcSubresource.mipLevel = uint32_t(level) - 1;
|
||||
region.srcSubresource.baseArrayLayer = uint32_t(u.mipgen.layer);
|
||||
region.srcSubresource.baseArrayLayer = uint32_t(u.layer);
|
||||
region.srcSubresource.layerCount = 1;
|
||||
|
||||
region.srcOffsets[1].x = qMax(1, w);
|
||||
@ -3025,7 +3094,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
|
||||
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.dstSubresource.mipLevel = uint32_t(level);
|
||||
region.dstSubresource.baseArrayLayer = uint32_t(u.mipgen.layer);
|
||||
region.dstSubresource.baseArrayLayer = uint32_t(u.layer);
|
||||
region.dstSubresource.layerCount = 1;
|
||||
|
||||
region.dstOffsets[1].x = qMax(1, w >> 1);
|
||||
@ -3051,13 +3120,13 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origLayout,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, origAccess,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
|
||||
u.mipgen.layer, 1,
|
||||
u.layer, 1,
|
||||
0, int(utexD->mipLevelCount) - 1);
|
||||
subresourceBarrier(cbD, utexD->image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origLayout,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, origAccess,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
|
||||
u.mipgen.layer, 1,
|
||||
u.layer, 1,
|
||||
int(utexD->mipLevelCount) - 1, 1);
|
||||
}
|
||||
|
||||
@ -3070,8 +3139,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
|
||||
void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD)
|
||||
{
|
||||
QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->pendingDynamicUpdates[currentFrameSlot]);
|
||||
if (updates.isEmpty())
|
||||
if (bufD->pendingDynamicUpdates[currentFrameSlot].isEmpty())
|
||||
return;
|
||||
|
||||
Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
|
||||
@ -3087,7 +3155,7 @@ void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD)
|
||||
}
|
||||
int changeBegin = -1;
|
||||
int changeEnd = -1;
|
||||
for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
|
||||
for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->pendingDynamicUpdates[currentFrameSlot])) {
|
||||
Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf));
|
||||
memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size()));
|
||||
if (changeBegin == -1 || u.offset < changeBegin)
|
||||
@ -3099,7 +3167,7 @@ void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD)
|
||||
if (changeBegin >= 0)
|
||||
vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(changeBegin), VkDeviceSize(changeEnd - changeBegin));
|
||||
|
||||
updates.clear();
|
||||
bufD->pendingDynamicUpdates[currentFrameSlot].clear();
|
||||
}
|
||||
|
||||
static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator)
|
||||
@ -3193,29 +3261,53 @@ void QRhiVulkan::finishActiveReadbacks(bool forced)
|
||||
QVarLengthArray<std::function<void()>, 4> completedCallbacks;
|
||||
QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
|
||||
|
||||
for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiVulkan::ActiveReadback &aRb(activeReadbacks[i]);
|
||||
if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
|
||||
aRb.result->format = aRb.format;
|
||||
aRb.result->pixelSize = aRb.pixelSize;
|
||||
aRb.result->data.resize(int(aRb.bufSize));
|
||||
for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]);
|
||||
if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
|
||||
readback.result->format = readback.format;
|
||||
readback.result->pixelSize = readback.pixelSize;
|
||||
VmaAllocation a = toVmaAllocation(readback.stagingAlloc);
|
||||
void *p = nullptr;
|
||||
VmaAllocation a = toVmaAllocation(aRb.bufAlloc);
|
||||
VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
|
||||
if (err != VK_SUCCESS) {
|
||||
qWarning("Failed to map readback buffer: %d", err);
|
||||
continue;
|
||||
if (err == VK_SUCCESS && p) {
|
||||
readback.result->data.resize(int(readback.byteSize));
|
||||
memcpy(readback.result->data.data(), p, readback.byteSize);
|
||||
vmaUnmapMemory(toVmaAllocator(allocator), a);
|
||||
} else {
|
||||
qWarning("Failed to map texture readback buffer of size %u: %d", readback.byteSize, err);
|
||||
}
|
||||
memcpy(aRb.result->data.data(), p, aRb.bufSize);
|
||||
vmaUnmapMemory(toVmaAllocator(allocator), a);
|
||||
|
||||
vmaDestroyBuffer(toVmaAllocator(allocator), aRb.buf, a);
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(aRb.buf)));
|
||||
vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a);
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf)));
|
||||
|
||||
if (aRb.result->completed)
|
||||
completedCallbacks.append(aRb.result->completed);
|
||||
if (readback.result->completed)
|
||||
completedCallbacks.append(readback.result->completed);
|
||||
|
||||
activeReadbacks.removeAt(i);
|
||||
activeTextureReadbacks.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) {
|
||||
const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]);
|
||||
if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
|
||||
VmaAllocation a = toVmaAllocation(readback.stagingAlloc);
|
||||
void *p = nullptr;
|
||||
VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
|
||||
if (err == VK_SUCCESS && p) {
|
||||
readback.result->data.resize(readback.byteSize);
|
||||
memcpy(readback.result->data.data(), p, size_t(readback.byteSize));
|
||||
vmaUnmapMemory(toVmaAllocator(allocator), a);
|
||||
} else {
|
||||
qWarning("Failed to map buffer readback buffer of size %d: %d", readback.byteSize, err);
|
||||
}
|
||||
|
||||
vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a);
|
||||
QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf)));
|
||||
|
||||
if (readback.result->completed)
|
||||
completedCallbacks.append(readback.result->completed);
|
||||
|
||||
activeBufferReadbacks.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3733,6 +3825,10 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
|
||||
return true;
|
||||
case QRhi::TriangleFanTopology:
|
||||
return true;
|
||||
case QRhi::ReadBackNonUniformBuffer:
|
||||
return true;
|
||||
case QRhi::ReadBackNonBaseMipLevel:
|
||||
return true;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
return false;
|
||||
@ -4902,7 +4998,7 @@ bool QVkBuffer::build()
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
} else {
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
QRHI_RES_RHI(QRhiVulkan);
|
||||
@ -4916,11 +5012,7 @@ bool QVkBuffer::build()
|
||||
err = vmaCreateBuffer(toVmaAllocator(rhiD->allocator), &bufferInfo, &allocInfo, &buffers[i], &allocation, nullptr);
|
||||
if (err != VK_SUCCESS)
|
||||
break;
|
||||
|
||||
allocations[i] = allocation;
|
||||
if (m_type == Dynamic)
|
||||
pendingDynamicUpdates[i].reserve(16);
|
||||
|
||||
rhiD->setObjectName(uint64_t(buffers[i]), VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, m_objectName,
|
||||
m_type == Dynamic ? i : -1);
|
||||
}
|
||||
@ -5798,6 +5890,9 @@ bool QVkGraphicsPipeline::build()
|
||||
release();
|
||||
|
||||
QRHI_RES_RHI(QRhiVulkan);
|
||||
if (!rhiD->sanityCheckGraphicsPipeline(this))
|
||||
return false;
|
||||
|
||||
if (!rhiD->ensurePipelineCache())
|
||||
return false;
|
||||
|
||||
|
@ -79,7 +79,7 @@ struct QVkBuffer : public QRhiBuffer
|
||||
|
||||
VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
|
||||
QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];
|
||||
QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT];
|
||||
QVarLengthArray<QRhiResourceUpdateBatchPrivate::BufferOp, 16> pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT];
|
||||
VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
|
||||
QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
|
||||
struct UsageState {
|
||||
@ -853,17 +853,25 @@ public:
|
||||
VkFence cmdFence = VK_NULL_HANDLE;
|
||||
} ofr;
|
||||
|
||||
struct ActiveReadback {
|
||||
struct TextureReadback {
|
||||
int activeFrameSlot = -1;
|
||||
QRhiReadbackDescription desc;
|
||||
QRhiReadbackResult *result;
|
||||
VkBuffer buf;
|
||||
QVkAlloc bufAlloc;
|
||||
quint32 bufSize;
|
||||
VkBuffer stagingBuf;
|
||||
QVkAlloc stagingAlloc;
|
||||
quint32 byteSize;
|
||||
QSize pixelSize;
|
||||
QRhiTexture::Format format;
|
||||
};
|
||||
QVector<ActiveReadback> activeReadbacks;
|
||||
QVector<TextureReadback> activeTextureReadbacks;
|
||||
struct BufferReadback {
|
||||
int activeFrameSlot = -1;
|
||||
QRhiBufferReadbackResult *result;
|
||||
int byteSize;
|
||||
VkBuffer stagingBuf;
|
||||
QVkAlloc stagingAlloc;
|
||||
};
|
||||
QVector<BufferReadback> activeBufferReadbacks;
|
||||
|
||||
struct DeferredReleaseEntry {
|
||||
enum Type {
|
||||
@ -933,7 +941,8 @@ public:
|
||||
|
||||
Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiVulkan::ActiveReadback, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiVulkan::TextureReadback, Q_MOVABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiVulkan::BufferReadback, Q_MOVABLE_TYPE);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
@ -102,13 +102,23 @@
|
||||
"gssapi": {
|
||||
"label": "KRB5 GSSAPI Support",
|
||||
"test": {
|
||||
"head": [
|
||||
"#if defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__))",
|
||||
"# include <TargetConditionals.h>",
|
||||
"# if defined(TARGET_OS_MAC) && TARGET_OS_MAC",
|
||||
"# include <GSS/GSS.h>",
|
||||
"# endif",
|
||||
"#else",
|
||||
"# include <gssapi/gssapi.h>",
|
||||
"#endif"
|
||||
],
|
||||
"main": [
|
||||
"gss_ctx_id_t ctx;",
|
||||
"gss_context_time(nullptr, ctx, nullptr);"
|
||||
]
|
||||
},
|
||||
"headers": [ "gssapi/gssapi.h" ],
|
||||
"sources": [
|
||||
{ "libs": "-framework GSS", "condition": "config.darwin" },
|
||||
{ "type": "pkgConfig", "args": "krb5-gssapi" },
|
||||
"-lgssapi_krb5"
|
||||
]
|
||||
|
@ -59,8 +59,12 @@
|
||||
#define SECURITY_WIN32 1
|
||||
#include <security.h>
|
||||
#elif QT_CONFIG(gssapi) // GSSAPI
|
||||
#if defined(Q_OS_DARWIN)
|
||||
#include <GSS/GSS.h>
|
||||
#else
|
||||
#include <gssapi/gssapi.h>
|
||||
#endif
|
||||
#endif // Q_OS_DARWIN
|
||||
#endif // Q_CONFIG(sspi)
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
@ -69,6 +69,7 @@ extern "C" {
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_LOGGING_CATEGORY(qLcEvdevTouch, "qt.qpa.input")
|
||||
Q_LOGGING_CATEGORY(qLcEvents, "qt.qpa.input.events")
|
||||
|
||||
/* android (and perhaps some other linux-derived stuff) don't define everything
|
||||
* in linux/input.h, so we'll need to do that ourselves.
|
||||
@ -539,6 +540,9 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data)
|
||||
if (m_typeB)
|
||||
m_contacts[m_currentSlot].maj = m_currentData.maj;
|
||||
} else if (data->code == ABS_PRESSURE || data->code == ABS_MT_PRESSURE) {
|
||||
if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
|
||||
qCDebug(qLcEvents, "EV_ABS code 0x%x: pressure %d; bounding to [%d,%d]",
|
||||
data->code, data->value, hw_pressure_min, hw_pressure_max);
|
||||
m_currentData.pressure = qBound(hw_pressure_min, data->value, hw_pressure_max);
|
||||
if (m_typeB || m_singleTouch)
|
||||
m_contacts[m_currentSlot].pressure = m_currentData.pressure;
|
||||
@ -577,6 +581,7 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data)
|
||||
m_lastTouchPoints = m_touchPoints;
|
||||
m_touchPoints.clear();
|
||||
Qt::TouchPointStates combinedStates;
|
||||
bool hasPressure = false;
|
||||
|
||||
for (auto i = m_contacts.begin(), end = m_contacts.end(); i != end; /*erasing*/) {
|
||||
auto it = i++;
|
||||
@ -607,6 +612,9 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (contact.pressure)
|
||||
hasPressure = true;
|
||||
|
||||
addTouchPoint(contact, &combinedStates);
|
||||
}
|
||||
|
||||
@ -651,7 +659,7 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data)
|
||||
m_contacts.clear();
|
||||
|
||||
|
||||
if (!m_touchPoints.isEmpty() && combinedStates != Qt::TouchPointStationary)
|
||||
if (!m_touchPoints.isEmpty() && (hasPressure || combinedStates != Qt::TouchPointStationary))
|
||||
reportPoints();
|
||||
}
|
||||
|
||||
@ -777,6 +785,9 @@ void QEvdevTouchScreenData::reportPoints()
|
||||
tp.pressure = tp.state == Qt::TouchPointReleased ? 0 : 1;
|
||||
else
|
||||
tp.pressure = (tp.pressure - hw_pressure_min) / qreal(hw_pressure_max - hw_pressure_min);
|
||||
|
||||
if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
|
||||
qCDebug(qLcEvents) << "reporting" << tp;
|
||||
}
|
||||
|
||||
// Let qguiapp pick the target window.
|
||||
|
@ -9213,9 +9213,11 @@ void QWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
The default implementation calls mousePressEvent().
|
||||
|
||||
\note The widget will also receive mouse press and mouse release
|
||||
events in addition to the double click event. It is up to the
|
||||
developer to ensure that the application interprets these events
|
||||
correctly.
|
||||
events in addition to the double click event. And if another widget
|
||||
that overlaps this widget disappears in response to press or
|
||||
release events, then this widget will only receive the double click
|
||||
event. It is up to the developer to ensure that the application
|
||||
interprets these events correctly.
|
||||
|
||||
\sa mousePressEvent(), mouseReleaseEvent(), mouseMoveEvent(),
|
||||
event(), QMouseEvent
|
||||
|
@ -4593,6 +4593,8 @@ void tst_QString::fromLatin1()
|
||||
}
|
||||
|
||||
#if QT_DEPRECATED_SINCE(5, 0)
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
void tst_QString::fromAscii()
|
||||
{
|
||||
QString a;
|
||||
@ -4613,6 +4615,7 @@ void tst_QString::fromAscii()
|
||||
a = QString::fromAscii("\0abcd", 5);
|
||||
QVERIFY(a.size() == 5);
|
||||
}
|
||||
QT_WARNING_POP
|
||||
#endif
|
||||
|
||||
void tst_QString::fromUcs4()
|
||||
|
48
tests/auto/gui/rhi/qrhi/data/compile.bat
Normal file
48
tests/auto/gui/rhi/qrhi/data/compile.bat
Normal file
@ -0,0 +1,48 @@
|
||||
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Copyright (C) 2019 The Qt Company Ltd.
|
||||
:: Contact: https://www.qt.io/licensing/
|
||||
::
|
||||
:: This file is part of the QtQuick module of the Qt Toolkit.
|
||||
::
|
||||
:: $QT_BEGIN_LICENSE:LGPL$
|
||||
:: Commercial License Usage
|
||||
:: Licensees holding valid commercial Qt licenses may use this file in
|
||||
:: accordance with the commercial license agreement provided with the
|
||||
:: Software or, alternatively, in accordance with the terms contained in
|
||||
:: a written agreement between you and The Qt Company. For licensing terms
|
||||
:: and conditions see https://www.qt.io/terms-conditions. For further
|
||||
:: information use the contact form at https://www.qt.io/contact-us.
|
||||
::
|
||||
:: GNU Lesser General Public License Usage
|
||||
:: Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
:: General Public License version 3 as published by the Free Software
|
||||
:: Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
:: packaging of this file. Please review the following information to
|
||||
:: ensure the GNU Lesser General Public License version 3 requirements
|
||||
:: will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
::
|
||||
:: GNU General Public License Usage
|
||||
:: Alternatively, this file may be used under the terms of the GNU
|
||||
:: General Public License version 2.0 or (at your option) the GNU General
|
||||
:: Public license version 3 or any later version approved by the KDE Free
|
||||
:: Qt Foundation. The licenses are as published by the Free Software
|
||||
:: Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
:: included in the packaging of this file. Please review the following
|
||||
:: information to ensure the GNU General Public License requirements will
|
||||
:: be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
:: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
::
|
||||
:: $QT_END_LICENSE$
|
||||
::
|
||||
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
:: Note the -c argument: we do not want runtime HLSL compilation since that is
|
||||
:: not an option on UWP (WinRT). This means that running qsb must happen on Windows.
|
||||
|
||||
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert
|
||||
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag
|
||||
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert
|
||||
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag
|
||||
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert
|
||||
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag
|
BIN
tests/auto/gui/rhi/qrhi/data/qt256.png
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/qt256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
8
tests/auto/gui/rhi/qrhi/data/simple.frag
Normal file
8
tests/auto/gui/rhi/qrhi/data/simple.frag
Normal file
@ -0,0 +1,8 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
BIN
tests/auto/gui/rhi/qrhi/data/simple.frag.qsb
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/simple.frag.qsb
Normal file
Binary file not shown.
10
tests/auto/gui/rhi/qrhi/data/simple.vert
Normal file
10
tests/auto/gui/rhi/qrhi/data/simple.vert
Normal file
@ -0,0 +1,10 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec4 position;
|
||||
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = position;
|
||||
}
|
BIN
tests/auto/gui/rhi/qrhi/data/simple.vert.qsb
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/simple.vert.qsb
Normal file
Binary file not shown.
13
tests/auto/gui/rhi/qrhi/data/simpletextured.frag
Normal file
13
tests/auto/gui/rhi/qrhi/data/simpletextured.frag
Normal file
@ -0,0 +1,13 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c = texture(tex, uv);
|
||||
c.rgb *= c.a;
|
||||
fragColor = c;
|
||||
}
|
BIN
tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb
Normal file
Binary file not shown.
14
tests/auto/gui/rhi/qrhi/data/simpletextured.vert
Normal file
14
tests/auto/gui/rhi/qrhi/data/simpletextured.vert
Normal file
@ -0,0 +1,14 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec4 position;
|
||||
layout(location = 1) in vec2 texcoord;
|
||||
|
||||
layout(location = 0) out vec2 uv;
|
||||
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
|
||||
void main()
|
||||
{
|
||||
uv = texcoord;
|
||||
gl_Position = position;
|
||||
}
|
BIN
tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb
Normal file
Binary file not shown.
@ -1,12 +0,0 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 v_texcoord;
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(binding = 1) uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = texture(tex, v_texcoord);
|
||||
}
|
19
tests/auto/gui/rhi/qrhi/data/textured.frag
Normal file
19
tests/auto/gui/rhi/qrhi/data/textured.frag
Normal file
@ -0,0 +1,19 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 matrix;
|
||||
float opacity;
|
||||
} ubuf;
|
||||
|
||||
layout(binding = 1) uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c = texture(tex, uv);
|
||||
c.a *= ubuf.opacity;
|
||||
c.rgb *= c.a;
|
||||
fragColor = c;
|
||||
}
|
BIN
tests/auto/gui/rhi/qrhi/data/textured.frag.qsb
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/textured.frag.qsb
Normal file
Binary file not shown.
@ -3,16 +3,17 @@
|
||||
layout(location = 0) in vec4 position;
|
||||
layout(location = 1) in vec2 texcoord;
|
||||
|
||||
layout(location = 0) out vec2 v_texcoord;
|
||||
layout(location = 0) out vec2 uv;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 mvp;
|
||||
mat4 matrix;
|
||||
float opacity;
|
||||
} ubuf;
|
||||
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
|
||||
void main()
|
||||
{
|
||||
v_texcoord = texcoord;
|
||||
gl_Position = ubuf.mvp * position;
|
||||
uv = texcoord;
|
||||
gl_Position = ubuf.matrix * position;
|
||||
}
|
BIN
tests/auto/gui/rhi/qrhi/data/textured.vert.qsb
Normal file
BIN
tests/auto/gui/rhi/qrhi/data/textured.vert.qsb
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@ SUBDIRS=\
|
||||
macplist \
|
||||
networkselftest \
|
||||
qaccessibility \
|
||||
# qaccessibilitylinux \ # QTBUG-44434
|
||||
# qaccessibilitylinux # QTBUG-44434 \
|
||||
qaccessibilitymac \
|
||||
qcomplextext \
|
||||
qfocusevent \
|
||||
|
@ -29,7 +29,7 @@
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) {
|
||||
QXmlStreamReader reader(QByteArray(Data, Size));
|
||||
QXmlStreamReader reader(QByteArray::fromRawData(Data, Size));
|
||||
while (!reader.atEnd())
|
||||
reader.readNext();
|
||||
return 0;
|
||||
|
@ -32,6 +32,6 @@
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
|
||||
static int c = 0;
|
||||
static QGuiApplication a(c, nullptr);
|
||||
QColorSpace cs = QColorSpace::fromIccProfile(QByteArray(data, size));
|
||||
QColorSpace cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(data, size));
|
||||
return 0;
|
||||
}
|
||||
|
@ -32,6 +32,6 @@
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) {
|
||||
static int c = 0;
|
||||
static QApplication a(c, nullptr);
|
||||
QTextDocument().setHtml(QByteArray(Data, Size));
|
||||
QTextDocument().setHtml(QByteArray::fromRawData(Data, Size));
|
||||
return 0;
|
||||
}
|
||||
|
@ -29,6 +29,6 @@
|
||||
#include <QTextDocument>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) {
|
||||
QTextDocument().setMarkdown(QByteArray(Data, Size));
|
||||
QTextDocument().setMarkdown(QByteArray::fromRawData(Data, Size));
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user