Occlusion query fixes from PR review.

MVKOcclusionQueryCommandEncoderState::beginOcclusionQuery() check for visibility
buffer exhaustion, and if needed, log an error and disable further visibility
tracking for the remainder of the current MTLCommandBuffer.
Create visibility buffer if needed during MVKCommandEncoder::beginMetalRenderPass.
Simplify MVKCommandEncodingContext to PODS.
This commit is contained in:
Bill Hollings 2021-06-25 08:46:52 -04:00
parent 53dde5718a
commit 45ffcc9564
3 changed files with 25 additions and 43 deletions

View File

@ -52,13 +52,7 @@ typedef uint64_t MVKMTLCommandBufferID;
/** Context for tracking information across multiple encodings. */
typedef struct MVKCommandEncodingContext {
NSUInteger mtlVisibilityResultOffset = 0;
void incrementMTLVisibilityResultOffset(MVKCommandEncoder* cmdEncoder);
const MVKMTLBufferAllocation* getVisibilityResultBuffer(MVKCommandEncoder* cmdEncoder);
private:
const MVKMTLBufferAllocation* _visibilityResultBuffer = nullptr;
const MVKMTLBufferAllocation* visibilityResultBuffer = nullptr;
} MVKCommandEncodingContext;

View File

@ -350,7 +350,10 @@ void MVKCommandEncoder::beginMetalRenderPass(bool loadOverride) {
_isRenderingEntireAttachment,
loadOverride);
if (_cmdBuffer->_needsVisibilityResultMTLBuffer) {
mtlRPDesc.visibilityResultBuffer = _pEncodingContext->getVisibilityResultBuffer(this)->_mtlBuffer;
if ( !_pEncodingContext->visibilityResultBuffer ) {
_pEncodingContext->visibilityResultBuffer = getTempMTLBuffer(_pDeviceMetalFeatures->maxQueryBufferSize, true, true);
}
mtlRPDesc.visibilityResultBuffer = _pEncodingContext->visibilityResultBuffer->_mtlBuffer;
}
VkExtent2D fbExtent = _framebufferExtent;
@ -803,32 +806,6 @@ MVKCommandEncoder::MVKCommandEncoder(MVKCommandBuffer* cmdBuffer) : MVKBaseDevic
}
#pragma mark -
#pragma mark MVKCommandEncodingContext
// Increment to the next query slot offset. If we reach the size of the visibility buffer,
// reset to retrieve and start filling another visibility buffer. This approach may still
// cause Metal validation errors if the platform does not permit offsets to be reused
// witin a MTLCommandBuffer, even when a different visibility buffer is used.
// We don't test against the size of the visibility buffer itself, because this call may
// arrive before the visibiltiy buffer in the case of a query that ends before the renderpass.
void MVKCommandEncodingContext::incrementMTLVisibilityResultOffset(MVKCommandEncoder* cmdEncoder) {
mtlVisibilityResultOffset += kMVKQuerySlotSizeInBytes;
if (mtlVisibilityResultOffset + kMVKQuerySlotSizeInBytes > cmdEncoder->_pDeviceMetalFeatures->maxQueryBufferSize) {
_visibilityResultBuffer = nullptr;
mtlVisibilityResultOffset = 0;
}
}
const MVKMTLBufferAllocation* MVKCommandEncodingContext::getVisibilityResultBuffer(MVKCommandEncoder* cmdEncoder) {
if ( !_visibilityResultBuffer ) {
_visibilityResultBuffer = cmdEncoder->getTempMTLBuffer(cmdEncoder->_pDeviceMetalFeatures->maxQueryBufferSize, true, true);
}
return _visibilityResultBuffer;
}
#pragma mark -
#pragma mark Support functions

View File

@ -1073,10 +1073,9 @@ void MVKComputeResourcesCommandEncoderState::encodeArgumentBufferResourceUsage(M
#pragma mark MVKOcclusionQueryCommandEncoderState
void MVKOcclusionQueryCommandEncoderState::endMetalRenderPass() {
const MVKMTLBufferAllocation* vizBuff = _cmdEncoder->_pEncodingContext->visibilityResultBuffer;
if ( !vizBuff || _mtlRenderPassQueries.empty() ) { return; } // Nothing to do.
if (_mtlRenderPassQueries.empty()) { return; } // Nothing to do.
const MVKMTLBufferAllocation* vizResultBuffer = _cmdEncoder->_pEncodingContext->getVisibilityResultBuffer(_cmdEncoder);
id<MTLComputePipelineState> mtlAccumState = _cmdEncoder->getCommandEncodingPool()->getAccumulateOcclusionQueryResultsMTLComputePipelineState();
id<MTLComputeCommandEncoder> mtlAccumEncoder = _cmdEncoder->getMTLComputeEncoder(kMVKCommandUseAccumOcclusionQuery);
[mtlAccumEncoder setComputePipelineState: mtlAccumState];
@ -1085,8 +1084,8 @@ void MVKOcclusionQueryCommandEncoderState::endMetalRenderPass() {
[mtlAccumEncoder setBuffer: qryLoc.queryPool->getVisibilityResultMTLBuffer()
offset: qryLoc.queryPool->getVisibilityResultOffset(qryLoc.query)
atIndex: 0];
[mtlAccumEncoder setBuffer: vizResultBuffer->_mtlBuffer
offset: vizResultBuffer->_offset + qryLoc.visibilityBufferOffset
[mtlAccumEncoder setBuffer: vizBuff->_mtlBuffer
offset: vizBuff->_offset + qryLoc.visibilityBufferOffset
atIndex: 1];
[mtlAccumEncoder dispatchThreadgroups: MTLSizeMake(1, 1, 1)
threadsPerThreadgroup: MTLSizeMake(1, 1, 1)];
@ -1095,16 +1094,28 @@ void MVKOcclusionQueryCommandEncoderState::endMetalRenderPass() {
_mtlRenderPassQueries.clear();
}
// The Metal visibility buffer has a finite size, and on some Metal platforms (looking at you M1),
// query offsets cannnot be reused with the same MTLCommandBuffer. If enough occlusion queries are
// begun within a single MTLCommandBuffer, it may exhaust the visibility buffer. If that occurs,
// report an error and disable further visibility tracking for the remainder of the MTLCommandBuffer.
// In most cases, a MTLCommandBuffer corresponds to a Vulkan command submit (VkSubmitInfo),
// and so the error text is framed in terms of the Vulkan submit.
void MVKOcclusionQueryCommandEncoderState::beginOcclusionQuery(MVKOcclusionQueryPool* pQueryPool, uint32_t query, VkQueryControlFlags flags) {
bool shouldCount = _cmdEncoder->_pDeviceFeatures->occlusionQueryPrecise && mvkAreAllFlagsEnabled(flags, VK_QUERY_CONTROL_PRECISE_BIT);
_mtlVisibilityResultMode = shouldCount ? MTLVisibilityResultModeCounting : MTLVisibilityResultModeBoolean;
_mtlRenderPassQueries.emplace_back(pQueryPool, query, _cmdEncoder->_pEncodingContext->mtlVisibilityResultOffset);
if (_cmdEncoder->_pEncodingContext->mtlVisibilityResultOffset + kMVKQuerySlotSizeInBytes <= _cmdEncoder->_pDeviceMetalFeatures->maxQueryBufferSize) {
bool shouldCount = _cmdEncoder->_pDeviceFeatures->occlusionQueryPrecise && mvkAreAllFlagsEnabled(flags, VK_QUERY_CONTROL_PRECISE_BIT);
_mtlVisibilityResultMode = shouldCount ? MTLVisibilityResultModeCounting : MTLVisibilityResultModeBoolean;
_mtlRenderPassQueries.emplace_back(pQueryPool, query, _cmdEncoder->_pEncodingContext->mtlVisibilityResultOffset);
} else {
reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCmdBeginQuery(): The maximum number of queries in a single Vulkan command submission is %llu.", _cmdEncoder->_pDeviceMetalFeatures->maxQueryBufferSize / kMVKQuerySlotSizeInBytes);
_mtlVisibilityResultMode = MTLVisibilityResultModeDisabled;
_cmdEncoder->_pEncodingContext->mtlVisibilityResultOffset -= kMVKQuerySlotSizeInBytes;
}
markDirty();
}
void MVKOcclusionQueryCommandEncoderState::endOcclusionQuery(MVKOcclusionQueryPool* pQueryPool, uint32_t query) {
_mtlVisibilityResultMode = MTLVisibilityResultModeDisabled;
_cmdEncoder->_pEncodingContext->incrementMTLVisibilityResultOffset(_cmdEncoder);
_cmdEncoder->_pEncodingContext->mtlVisibilityResultOffset += kMVKQuerySlotSizeInBytes;
markDirty();
}