rhi: d3d11: Rework timestamp query logic

For offscreen frames this is a significant improvement since
now we will have timings available after endOffscreenFrame()
returns, meaning the behavior matches the Vulkan, D3D12,
and Metal backends in this regard.

It also enables getting timings in frames that have
QRhi::finish() or QRhiCommandBuffer::begin/endExternal()
calls.

Change-Id: I1cc45a47e7215f342df0b1c600cc481088c8135b
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2023-08-10 16:21:37 +02:00
parent 373c08d03d
commit 02f30f700d
2 changed files with 162 additions and 104 deletions

View File

@ -346,11 +346,6 @@ bool QRhiD3D11::create(QRhi::Flags flags)
if (FAILED(context->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), reinterpret_cast<void **>(&annotations))))
annotations = nullptr;
if (flags.testFlag(QRhi::EnableTimestamps)) {
ofr.timestamps.prepare(2, this);
// timestamp queries are optional so we can go on even if they failed
}
deviceLost = false;
nativeHandlesStruct.dev = dev;
@ -376,7 +371,16 @@ void QRhiD3D11::destroy()
clearShaderCache();
ofr.timestamps.destroy();
if (ofr.tsDisjointQuery) {
ofr.tsDisjointQuery->Release();
ofr.tsDisjointQuery = nullptr;
}
for (int i = 0; i < 2; ++i) {
if (ofr.tsQueries[i]) {
ofr.tsQueries[i]->Release();
ofr.tsQueries[i] = nullptr;
}
}
if (annotations) {
annotations->Release();
@ -1258,7 +1262,6 @@ const QRhiNativeHandles *QRhiD3D11::nativeHandles(QRhiCommandBuffer *cb)
void QRhiD3D11::beginExternal(QRhiCommandBuffer *cb)
{
QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
// no timestampSwapChain, in order to avoid timestamp mess
executeCommandBuffer(cbD);
cbD->resetCommands();
}
@ -1281,6 +1284,19 @@ double QRhiD3D11::lastCompletedGpuTime(QRhiCommandBuffer *cb)
return cbD->lastGpuTime;
}
static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
{
switch (rt->resourceType()) {
case QRhiResource::SwapChainRenderTarget:
return &QRHI_RES(QD3D11SwapChainRenderTarget, rt)->d;
case QRhiResource::TextureRenderTarget:
return &QRHI_RES(QD3D11TextureRenderTarget, rt)->d;
default:
Q_UNREACHABLE();
return nullptr;
}
}
QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
{
Q_UNUSED(flags);
@ -1297,12 +1313,22 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
finishActiveReadbacks();
if (swapChainD->timestamps.active[currentFrameSlot]) {
if (swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]) {
double elapsedSec = 0;
if (swapChainD->timestamps.tryQueryTimestamps(currentFrameSlot, context, &elapsedSec))
if (swapChainD->timestamps.tryQueryTimestamps(swapChainD->currentTimestampPairIndex, context, &elapsedSec))
swapChainD->cb.lastGpuTime = elapsedSec;
}
ID3D11Query *tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[swapChainD->currentTimestampPairIndex];
const bool recordTimestamps = tsStart && tsDisjoint && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
QD3D11CommandBuffer::Command &cmd(swapChainD->cb.commands.get());
cmd.cmd = QD3D11CommandBuffer::Command::BeginFrame;
cmd.args.beginFrame.tsQuery = recordTimestamps ? tsStart : nullptr;
cmd.args.beginFrame.tsDisjointQuery = recordTimestamps ? tsDisjoint : nullptr;
cmd.args.beginFrame.swapchainData = rtData(&swapChainD->rt);
return QRhi::FrameOpSuccess;
}
@ -1312,17 +1338,13 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
Q_ASSERT(contextState.currentSwapChain = swapChainD);
const int currentFrameSlot = swapChainD->currentFrameSlot;
ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[currentFrameSlot];
const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
ID3D11Query *tsStart = swapChainD->timestamps.query[tsIdx];
ID3D11Query *tsEnd = swapChainD->timestamps.query[tsIdx + 1];
const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestamps.active[currentFrameSlot];
QD3D11CommandBuffer::Command &cmd(swapChainD->cb.commands.get());
cmd.cmd = QD3D11CommandBuffer::Command::EndFrame;
cmd.args.endFrame.tsQuery = nullptr; // done later manually, see below
cmd.args.endFrame.tsDisjointQuery = nullptr;
// send all commands to the context
if (recordTimestamps)
executeCommandBuffer(&swapChainD->cb, swapChainD);
else
executeCommandBuffer(&swapChainD->cb);
executeCommandBuffer(&swapChainD->cb);
if (swapChainD->sampleDesc.Count > 1) {
context->ResolveSubresource(swapChainD->backBufferTex, 0,
@ -1330,11 +1352,15 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
swapChainD->colorFormat);
}
// this is here because we want to include the time spent on the resolve as well
// this is here because we want to include the time spent on the ResolveSubresource as well
ID3D11Query *tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[swapChainD->currentTimestampPairIndex];
const bool recordTimestamps = tsEnd && tsDisjoint && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
if (recordTimestamps) {
context->End(tsEnd);
context->End(tsDisjoint);
swapChainD->timestamps.active[currentFrameSlot] = true;
swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex] = true;
swapChainD->currentTimestampPairIndex = (swapChainD->currentTimestampPairIndex + 1) % QD3D11SwapChainTimestamps::TIMESTAMP_PAIRS;
}
if (!flags.testFlag(QRhi::SkipPresent)) {
@ -1375,12 +1401,36 @@ QRhi::FrameOpResult QRhiD3D11::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi:
ofr.cbWrapper.resetState();
*cb = &ofr.cbWrapper;
if (ofr.timestamps.active[ofr.timestampIdx]) {
double elapsedSec = 0;
if (ofr.timestamps.tryQueryTimestamps(ofr.timestampIdx, context, &elapsedSec))
ofr.cbWrapper.lastGpuTime = elapsedSec;
if (rhiFlags.testFlag(QRhi::EnableTimestamps)) {
D3D11_QUERY_DESC queryDesc = {};
if (!ofr.tsDisjointQuery) {
queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
HRESULT hr = dev->CreateQuery(&queryDesc, &ofr.tsDisjointQuery);
if (FAILED(hr)) {
qWarning("Failed to create timestamp disjoint query: %s",
qPrintable(QSystemError::windowsComString(hr)));
return QRhi::FrameOpError;
}
}
queryDesc.Query = D3D11_QUERY_TIMESTAMP;
for (int i = 0; i < 2; ++i) {
if (!ofr.tsQueries[i]) {
HRESULT hr = dev->CreateQuery(&queryDesc, &ofr.tsQueries[i]);
if (FAILED(hr)) {
qWarning("Failed to create timestamp query: %s",
qPrintable(QSystemError::windowsComString(hr)));
return QRhi::FrameOpError;
}
}
}
}
QD3D11CommandBuffer::Command &cmd(ofr.cbWrapper.commands.get());
cmd.cmd = QD3D11CommandBuffer::Command::BeginFrame;
cmd.args.beginFrame.tsQuery = ofr.tsQueries[0] ? ofr.tsQueries[0] : nullptr;
cmd.args.beginFrame.tsDisjointQuery = ofr.tsDisjointQuery ? ofr.tsDisjointQuery : nullptr;
cmd.args.beginFrame.swapchainData = nullptr;
return QRhi::FrameOpSuccess;
}
@ -1389,25 +1439,39 @@ QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame(QRhi::EndFrameFlags flags)
Q_UNUSED(flags);
ofr.active = false;
ID3D11Query *tsDisjoint = ofr.timestamps.disjointQuery[ofr.timestampIdx];
ID3D11Query *tsStart = ofr.timestamps.query[ofr.timestampIdx * 2];
ID3D11Query *tsEnd = ofr.timestamps.query[ofr.timestampIdx * 2 + 1];
const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !ofr.timestamps.active[ofr.timestampIdx];
if (recordTimestamps) {
context->Begin(tsDisjoint);
context->End(tsStart); // record timestamp; no Begin() for D3D11_QUERY_TIMESTAMP
}
QD3D11CommandBuffer::Command &cmd(ofr.cbWrapper.commands.get());
cmd.cmd = QD3D11CommandBuffer::Command::EndFrame;
cmd.args.endFrame.tsQuery = ofr.tsQueries[1] ? ofr.tsQueries[1] : nullptr;
cmd.args.endFrame.tsDisjointQuery = ofr.tsDisjointQuery ? ofr.tsDisjointQuery : nullptr;
executeCommandBuffer(&ofr.cbWrapper);
context->Flush();
finishActiveReadbacks();
if (recordTimestamps) {
context->End(tsEnd);
context->End(tsDisjoint);
ofr.timestamps.active[ofr.timestampIdx] = true;
ofr.timestampIdx = (ofr.timestampIdx + 1) % 2;
if (ofr.tsQueries[0]) {
quint64 timestamps[2];
D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
HRESULT hr;
bool ok = true;
do {
hr = context->GetData(ofr.tsDisjointQuery, &dj, sizeof(dj), 0);
} while (hr == S_FALSE);
ok &= hr == S_OK;
do {
hr = context->GetData(ofr.tsQueries[1], &timestamps[1], sizeof(quint64), 0);
} while (hr == S_FALSE);
ok &= hr == S_OK;
do {
hr = context->GetData(ofr.tsQueries[0], &timestamps[0], sizeof(quint64), 0);
} while (hr == S_FALSE);
ok &= hr == S_OK;
if (ok) {
if (!dj.Disjoint && dj.Frequency) {
const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f;
ofr.cbWrapper.lastGpuTime = elapsedMs / 1000.0;
}
}
}
return QRhi::FrameOpSuccess;
@ -1550,7 +1614,7 @@ QRhi::FrameOpResult QRhiD3D11::finish()
} else {
Q_ASSERT(contextState.currentSwapChain);
Q_ASSERT(contextState.currentSwapChain->cb.recordingPass == QD3D11CommandBuffer::NoPass);
executeCommandBuffer(&contextState.currentSwapChain->cb); // no timestampSwapChain, in order to avoid timestamp mess
executeCommandBuffer(&contextState.currentSwapChain->cb);
contextState.currentSwapChain->cb.resetCommands();
}
}
@ -1925,19 +1989,6 @@ void QRhiD3D11::finishActiveReadbacks()
f();
}
static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
{
switch (rt->resourceType()) {
case QRhiResource::SwapChainRenderTarget:
return &QRHI_RES(QD3D11SwapChainRenderTarget, rt)->d;
case QRhiResource::TextureRenderTarget:
return &QRHI_RES(QD3D11TextureRenderTarget, rt)->d;
default:
Q_UNREACHABLE();
return nullptr;
}
}
void QRhiD3D11::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
{
Q_ASSERT(QRHI_RES(QD3D11CommandBuffer, cb)->recordingPass == QD3D11CommandBuffer::NoPass);
@ -2643,7 +2694,7 @@ void QRhiD3D11::resetShaderResources()
currentShaderMask &= ~StageU##MaskBit; \
}
void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain)
void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD)
{
quint32 stencilRef = 0;
float blendConstants[] = { 1, 1, 1, 1 };
@ -2656,26 +2707,30 @@ void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *
};
int currentShaderMask = 0xFF;
if (timestampSwapChain) {
const int currentFrameSlot = timestampSwapChain->currentFrameSlot;
ID3D11Query *tsDisjoint = timestampSwapChain->timestamps.disjointQuery[currentFrameSlot];
const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
ID3D11Query *tsStart = timestampSwapChain->timestamps.query[tsIdx];
if (tsDisjoint && tsStart && !timestampSwapChain->timestamps.active[currentFrameSlot]) {
// The timestamps seem to include vsync time with Present(1), except
// when running on a non-primary gpu. This is not ideal. So try working
// it around by issuing a semi-fake OMSetRenderTargets early and
// writing the first timestamp only afterwards.
context->Begin(tsDisjoint);
QD3D11RenderTargetData *rtD = rtData(&timestampSwapChain->rt);
context->OMSetRenderTargets(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
context->End(tsStart); // just record a timestamp, no Begin needed
}
}
for (auto it = cbD->commands.cbegin(), end = cbD->commands.cend(); it != end; ++it) {
const QD3D11CommandBuffer::Command &cmd(*it);
switch (cmd.cmd) {
case QD3D11CommandBuffer::Command::BeginFrame:
if (cmd.args.beginFrame.tsDisjointQuery)
context->Begin(cmd.args.beginFrame.tsDisjointQuery);
if (cmd.args.beginFrame.tsQuery) {
if (cmd.args.beginFrame.swapchainData) {
// The timestamps seem to include vsync time with Present(1), except
// when running on a non-primary gpu. This is not ideal. So try working
// it around by issuing a semi-fake OMSetRenderTargets early and
// writing the first timestamp only afterwards.
QD3D11RenderTargetData *rtD = cmd.args.beginFrame.swapchainData;
context->OMSetRenderTargets(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
}
context->End(cmd.args.beginFrame.tsQuery); // no Begin() for D3D11_QUERY_TIMESTAMP
}
break;
case QD3D11CommandBuffer::Command::EndFrame:
if (cmd.args.endFrame.tsQuery)
context->End(cmd.args.endFrame.tsQuery);
if (cmd.args.endFrame.tsDisjointQuery)
context->End(cmd.args.endFrame.tsDisjointQuery);
break;
case QD3D11CommandBuffer::Command::ResetShaderResources:
resetShaderResources();
break;
@ -4694,14 +4749,13 @@ void QD3D11CommandBuffer::destroy()
// nothing to do here
}
bool QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD)
bool QD3D11SwapChainTimestamps::prepare(QRhiD3D11 *rhiD)
{
// Creates the query objects if not yet done, but otherwise calling this
// function is expected to be a no-op.
Q_ASSERT(pairCount <= MAX_TIMESTAMP_PAIRS);
D3D11_QUERY_DESC queryDesc = {};
for (int i = 0; i < pairCount; ++i) {
for (int i = 0; i < TIMESTAMP_PAIRS; ++i) {
if (!disjointQuery[i]) {
queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &disjointQuery[i]);
@ -4713,7 +4767,7 @@ bool QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD)
}
queryDesc.Query = D3D11_QUERY_TIMESTAMP;
for (int j = 0; j < 2; ++j) {
const int idx = pairCount * i + j;
const int idx = 2 * i + j;
if (!query[idx]) {
HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &query[idx]);
if (FAILED(hr)) {
@ -4724,20 +4778,19 @@ bool QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD)
}
}
}
this->pairCount = pairCount;
return true;
}
void QD3D11Timestamps::destroy()
void QD3D11SwapChainTimestamps::destroy()
{
for (int i = 0; i < MAX_TIMESTAMP_PAIRS; ++i) {
for (int i = 0; i < TIMESTAMP_PAIRS; ++i) {
active[i] = false;
if (disjointQuery[i]) {
disjointQuery[i]->Release();
disjointQuery[i] = nullptr;
}
for (int j = 0; j < 2; ++j) {
const int idx = MAX_TIMESTAMP_PAIRS * i + j;
const int idx = TIMESTAMP_PAIRS * i + j;
if (query[idx]) {
query[idx]->Release();
query[idx] = nullptr;
@ -4746,26 +4799,21 @@ void QD3D11Timestamps::destroy()
}
}
bool QD3D11Timestamps::tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec)
bool QD3D11SwapChainTimestamps::tryQueryTimestamps(int pairIndex, ID3D11DeviceContext *context, double *elapsedSec)
{
bool result = false;
if (!active[idx])
if (!active[pairIndex])
return result;
ID3D11Query *tsDisjoint = disjointQuery[idx];
const int tsIdx = pairCount * idx;
ID3D11Query *tsStart = query[tsIdx];
ID3D11Query *tsEnd = query[tsIdx + 1];
ID3D11Query *tsDisjoint = disjointQuery[pairIndex];
ID3D11Query *tsStart = query[pairIndex * 2];
ID3D11Query *tsEnd = query[pairIndex * 2 + 1];
quint64 timestamps[2];
D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
bool ok = true;
ok &= context->GetData(tsDisjoint, &dj, sizeof(dj), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
ok &= context->GetData(tsEnd, &timestamps[1], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
// this above is often not ready, not even in frame_where_recorded+2,
// not clear why. so make the whole thing async and do not touch the
// queries until they are finally all available in frame this+2 or
// this+4 or ...
ok &= context->GetData(tsStart, &timestamps[0], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
if (ok) {
@ -4774,8 +4822,8 @@ bool QD3D11Timestamps::tryQueryTimestamps(int idx, ID3D11DeviceContext *context,
*elapsedSec = elapsedMs / 1000.0;
result = true;
}
active[idx] = false;
} // else leave active set, will retry in a subsequent beginFrame or similar
active[pairIndex] = false;
} // else leave active set, will retry in a subsequent beginFrame
return result;
}
@ -5252,7 +5300,7 @@ bool QD3D11SwapChain::createOrResize()
}
if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps)) {
timestamps.prepare(BUFFER_COUNT, rhiD);
timestamps.prepare(rhiD);
// timestamp queries are optional so we can go on even if they failed
}

View File

@ -356,6 +356,8 @@ struct QD3D11CommandBuffer : public QRhiCommandBuffer
struct Command {
enum Cmd {
BeginFrame,
EndFrame,
ResetShaderResources,
SetRenderTarget,
Clear,
@ -385,6 +387,15 @@ struct QD3D11CommandBuffer : public QRhiCommandBuffer
// QRhi*/QD3D11* references should be kept at minimum (so no
// QRhiTexture/Buffer/etc. pointers).
union Args {
struct {
ID3D11Query *tsQuery;
ID3D11Query *tsDisjointQuery;
QD3D11RenderTargetData *swapchainData;
} beginFrame;
struct {
ID3D11Query *tsQuery;
ID3D11Query *tsDisjointQuery;
} endFrame;
struct {
QRhiRenderTarget *rt;
} setRenderTarget;
@ -556,17 +567,15 @@ struct QD3D11CommandBuffer : public QRhiCommandBuffer
}
};
static const int QD3D11_SWAPCHAIN_BUFFER_COUNT = 2;
struct QD3D11Timestamps
struct QD3D11SwapChainTimestamps
{
static const int MAX_TIMESTAMP_PAIRS = QD3D11_SWAPCHAIN_BUFFER_COUNT;
bool active[MAX_TIMESTAMP_PAIRS] = {};
ID3D11Query *disjointQuery[MAX_TIMESTAMP_PAIRS] = {};
ID3D11Query *query[MAX_TIMESTAMP_PAIRS * 2] = {};
int pairCount = 0;
static const int TIMESTAMP_PAIRS = 2;
bool prepare(int pairCount, QRhiD3D11 *rhiD);
bool active[TIMESTAMP_PAIRS] = {};
ID3D11Query *disjointQuery[TIMESTAMP_PAIRS] = {};
ID3D11Query *query[TIMESTAMP_PAIRS * 2] = {};
bool prepare(QRhiD3D11 *rhiD);
void destroy();
bool tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec);
};
@ -604,7 +613,7 @@ struct QD3D11SwapChain : public QRhiSwapChain
ID3D11Texture2D *backBufferTex;
ID3D11RenderTargetView *backBufferRtv;
ID3D11RenderTargetView *backBufferRtvRight = nullptr;
static const int BUFFER_COUNT = QD3D11_SWAPCHAIN_BUFFER_COUNT;
static const int BUFFER_COUNT = 2;
ID3D11Texture2D *msaaTex[BUFFER_COUNT];
ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT];
DXGI_SAMPLE_DESC sampleDesc;
@ -614,7 +623,8 @@ struct QD3D11SwapChain : public QRhiSwapChain
UINT swapInterval = 1;
IDCompositionTarget *dcompTarget = nullptr;
IDCompositionVisual *dcompVisual = nullptr;
QD3D11Timestamps timestamps;
QD3D11SwapChainTimestamps timestamps;
int currentTimestampPairIndex = 0;
};
class QRhiD3D11 : public QRhiImplementation
@ -739,7 +749,7 @@ public:
const uint *dynOfsPairs, int dynOfsPairCount,
bool offsetOnlyChange);
void resetShaderResources();
void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr);
void executeCommandBuffer(QD3D11CommandBuffer *cbD);
DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const;
void finishActiveReadbacks();
void reportLiveObjects(ID3D11Device *device);
@ -781,8 +791,8 @@ public:
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
bool active = false;
QD3D11CommandBuffer cbWrapper;
QD3D11Timestamps timestamps;
int timestampIdx = 0;
ID3D11Query *tsQueries[2] = {};
ID3D11Query *tsDisjointQuery = nullptr;
} ofr;
struct TextureReadback {