1// 2// Copyright (c) 2020 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// mtl_occlusion_query_pool: A visibility pool for allocating visibility query within 7// one render pass. 8// 9 10#include "libANGLE/renderer/metal/mtl_occlusion_query_pool.h" 11 12#include "libANGLE/renderer/metal/ContextMtl.h" 13#include "libANGLE/renderer/metal/DisplayMtl.h" 14#include "libANGLE/renderer/metal/QueryMtl.h" 15 16namespace rx 17{ 18namespace mtl 19{ 20 21// OcclusionQueryPool implementation 22OcclusionQueryPool::OcclusionQueryPool() {} 23OcclusionQueryPool::~OcclusionQueryPool() {} 24 25void OcclusionQueryPool::destroy(ContextMtl *contextMtl) 26{ 27 mRenderPassResultsPool = nullptr; 28 for (QueryMtl *allocatedQuery : mAllocatedQueries) 29 { 30 if (!allocatedQuery) 31 { 32 continue; 33 } 34 allocatedQuery->clearAllocatedVisibilityOffsets(); 35 } 36 mAllocatedQueries.clear(); 37} 38 39angle::Result OcclusionQueryPool::allocateQueryOffset(ContextMtl *contextMtl, 40 QueryMtl *query, 41 bool clearOldValue) 42{ 43 // Only query that already has allocated offset or first query of the render pass is allowed to 44 // keep old value. Other queries must be reset to zero before counting the samples visibility in 45 // draw calls. 46 ASSERT(clearOldValue || mAllocatedQueries.empty() || 47 !query->getAllocatedVisibilityOffsets().empty()); 48 49 uint32_t currentOffset = 50 static_cast<uint32_t>(mAllocatedQueries.size()) * kOcclusionQueryResultSize; 51 if (!mRenderPassResultsPool) 52 { 53 ASSERT(!mUsed); 54 // First allocation 55 ANGLE_TRY(Buffer::MakeBufferWithStorageMode(contextMtl, MTLStorageModePrivate, 56 kOcclusionQueryResultSize, nullptr, 57 &mRenderPassResultsPool)); 58 mRenderPassResultsPool->get().label = @"OcclusionQueryPool"; 59 } 60 else if (currentOffset + kOcclusionQueryResultSize > mRenderPassResultsPool->size()) 61 { 62 // Double the capacity 63 ANGLE_TRY(Buffer::MakeBufferWithStorageMode(contextMtl, MTLStorageModePrivate, 64 mRenderPassResultsPool->size() * 2, nullptr, 65 &mRenderPassResultsPool)); 66 mUsed = false; 67 mRenderPassResultsPool->get().label = @"OcclusionQueryPool"; 68 } 69 70 if (clearOldValue) 71 { 72 // If old value is not needed, deallocate any offset previously allocated for this query. 73 deallocateQueryOffset(contextMtl, query); 74 } 75 if (query->getAllocatedVisibilityOffsets().empty()) 76 { 77 mAllocatedQueries.push_back(query); 78 query->setFirstAllocatedVisibilityOffset(currentOffset); 79 } 80 else 81 { 82 // Additional offset allocated for a query is only allowed if it is a continuous region. 83 ASSERT(currentOffset == 84 query->getAllocatedVisibilityOffsets().back() + kOcclusionQueryResultSize); 85 // Just reserve an empty slot in the allocated query array 86 mAllocatedQueries.push_back(nullptr); 87 query->addAllocatedVisibilityOffset(); 88 } 89 90 if (currentOffset == 0) 91 { 92 mResetFirstQuery = clearOldValue; 93 if (!clearOldValue && !contextMtl->getDisplay()->getFeatures().allowBufferReadWrite.enabled) 94 { 95 // If old value of first query needs to be retained and device doesn't support buffer 96 // read-write, we need an additional offset to store the old value of the query. 97 return allocateQueryOffset(contextMtl, query, false); 98 } 99 } 100 101 return angle::Result::Continue; 102} 103 104void OcclusionQueryPool::deallocateQueryOffset(ContextMtl *contextMtl, QueryMtl *query) 105{ 106 if (query->getAllocatedVisibilityOffsets().empty()) 107 { 108 return; 109 } 110 111 mAllocatedQueries[query->getAllocatedVisibilityOffsets().front() / kOcclusionQueryResultSize] = 112 nullptr; 113 query->clearAllocatedVisibilityOffsets(); 114} 115 116void OcclusionQueryPool::resolveVisibilityResults(ContextMtl *contextMtl) 117{ 118 if (mAllocatedQueries.empty()) 119 { 120 return; 121 } 122 123 RenderUtils &utils = contextMtl->getDisplay()->getUtils(); 124 BlitCommandEncoder *blitEncoder = nullptr; 125 // Combine the values stored in the offsets allocated for first query 126 if (mAllocatedQueries[0]) 127 { 128 const BufferRef &dstBuf = mAllocatedQueries[0]->getVisibilityResultBuffer(); 129 const VisibilityBufferOffsetsMtl &allocatedOffsets = 130 mAllocatedQueries[0]->getAllocatedVisibilityOffsets(); 131 if (!mResetFirstQuery && 132 !contextMtl->getDisplay()->getFeatures().allowBufferReadWrite.enabled) 133 { 134 // If we cannot read and write to the same buffer in shader. We need to copy the old 135 // value of first query to first offset allocated for it. 136 blitEncoder = contextMtl->getBlitCommandEncoder(); 137 blitEncoder->copyBuffer(dstBuf, 0, mRenderPassResultsPool, allocatedOffsets.front(), 138 kOcclusionQueryResultSize); 139 utils.combineVisibilityResult(contextMtl, false, allocatedOffsets, 140 mRenderPassResultsPool, dstBuf); 141 } 142 else 143 { 144 utils.combineVisibilityResult(contextMtl, !mResetFirstQuery, allocatedOffsets, 145 mRenderPassResultsPool, dstBuf); 146 } 147 } 148 149 // Combine the values stored in the offsets allocated for each of the remaining queries 150 for (size_t i = 1; i < mAllocatedQueries.size(); ++i) 151 { 152 QueryMtl *query = mAllocatedQueries[i]; 153 if (!query) 154 { 155 continue; 156 } 157 158 const BufferRef &dstBuf = mAllocatedQueries[i]->getVisibilityResultBuffer(); 159 const VisibilityBufferOffsetsMtl &allocatedOffsets = 160 mAllocatedQueries[i]->getAllocatedVisibilityOffsets(); 161 utils.combineVisibilityResult(contextMtl, false, allocatedOffsets, mRenderPassResultsPool, 162 dstBuf); 163 } 164 165 // Request synchronization and cleanup 166 blitEncoder = contextMtl->getBlitCommandEncoder(); 167 for (size_t i = 0; i < mAllocatedQueries.size(); ++i) 168 { 169 QueryMtl *query = mAllocatedQueries[i]; 170 if (!query) 171 { 172 continue; 173 } 174 175 const BufferRef &dstBuf = mAllocatedQueries[i]->getVisibilityResultBuffer(); 176 177 dstBuf->syncContent(contextMtl, blitEncoder); 178 179 query->clearAllocatedVisibilityOffsets(); 180 } 181 182 mAllocatedQueries.clear(); 183} 184 185void OcclusionQueryPool::prepareRenderPassVisibilityPoolBuffer(ContextMtl *contextMtl) 186{ 187 if (mAllocatedQueries.empty()) 188 { 189 return; 190 } 191 192 // If the current visibility pool buffer was not used before, 193 // ensure that it will be cleared next time. 194 if (!mUsed) 195 { 196 mUsed = true; 197 return; 198 } 199 200 mUsed = false; 201 202 // If the current visibility pool buffer was used before, 203 // ensure that it does not contain previous results. 204 auto blitEncoder = contextMtl->getBlitCommandEncoderWithoutEndingRenderEncoder(); 205 blitEncoder->fillBuffer(mRenderPassResultsPool, 206 NSMakeRange(0, mAllocatedQueries.size() * kOcclusionQueryResultSize), 207 0); 208 blitEncoder->endEncoding(); 209} 210 211} // namespace mtl 212} // namespace rx 213