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 // First allocation 54 ANGLE_TRY(Buffer::MakeBufferWithResOpt(contextMtl, MTLResourceStorageModePrivate, 55 kOcclusionQueryResultSize, nullptr, 56 &mRenderPassResultsPool)); 57 mRenderPassResultsPool->get().label = @"OcclusionQueryPool"; 58 } 59 else if (currentOffset + kOcclusionQueryResultSize > mRenderPassResultsPool->size()) 60 { 61 // Double the capacity 62 ANGLE_TRY(Buffer::MakeBufferWithResOpt(contextMtl, MTLResourceStorageModePrivate, 63 mRenderPassResultsPool->size() * 2, nullptr, 64 &mRenderPassResultsPool)); 65 mRenderPassResultsPool->get().label = @"OcclusionQueryPool"; 66 } 67 68 if (clearOldValue) 69 { 70 // If old value is not needed, deallocate any offset previously allocated for this query. 71 deallocateQueryOffset(contextMtl, query); 72 } 73 if (query->getAllocatedVisibilityOffsets().empty()) 74 { 75 mAllocatedQueries.push_back(query); 76 query->setFirstAllocatedVisibilityOffset(currentOffset); 77 } 78 else 79 { 80 // Additional offset allocated for a query is only allowed if it is a continuous region. 81 ASSERT(currentOffset == 82 query->getAllocatedVisibilityOffsets().back() + kOcclusionQueryResultSize); 83 // Just reserve an empty slot in the allocated query array 84 mAllocatedQueries.push_back(nullptr); 85 query->addAllocatedVisibilityOffset(); 86 } 87 88 if (currentOffset == 0) 89 { 90 mResetFirstQuery = clearOldValue; 91 if (!clearOldValue && !contextMtl->getDisplay()->getFeatures().allowBufferReadWrite.enabled) 92 { 93 // If old value of first query needs to be retained and device doesn't support buffer 94 // read-write, we need an additional offset to store the old value of the query. 95 return allocateQueryOffset(contextMtl, query, false); 96 } 97 } 98 99 return angle::Result::Continue; 100} 101 102void OcclusionQueryPool::deallocateQueryOffset(ContextMtl *contextMtl, QueryMtl *query) 103{ 104 if (query->getAllocatedVisibilityOffsets().empty()) 105 { 106 return; 107 } 108 109 mAllocatedQueries[query->getAllocatedVisibilityOffsets().front() / kOcclusionQueryResultSize] = 110 nullptr; 111 query->clearAllocatedVisibilityOffsets(); 112} 113 114void OcclusionQueryPool::resolveVisibilityResults(ContextMtl *contextMtl) 115{ 116 if (mAllocatedQueries.empty()) 117 { 118 return; 119 } 120 121 RenderUtils &utils = contextMtl->getDisplay()->getUtils(); 122 BlitCommandEncoder *blitEncoder = nullptr; 123 // Combine the values stored in the offsets allocated for first query 124 if (mAllocatedQueries[0]) 125 { 126 const BufferRef &dstBuf = mAllocatedQueries[0]->getVisibilityResultBuffer(); 127 const VisibilityBufferOffsetsMtl &allocatedOffsets = 128 mAllocatedQueries[0]->getAllocatedVisibilityOffsets(); 129 if (!mResetFirstQuery && 130 !contextMtl->getDisplay()->getFeatures().allowBufferReadWrite.enabled) 131 { 132 // If we cannot read and write to the same buffer in shader. We need to copy the old 133 // value of first query to first offset allocated for it. 134 blitEncoder = contextMtl->getBlitCommandEncoder(); 135 blitEncoder->copyBuffer(dstBuf, 0, mRenderPassResultsPool, allocatedOffsets.front(), 136 kOcclusionQueryResultSize); 137 utils.combineVisibilityResult(contextMtl, false, allocatedOffsets, 138 mRenderPassResultsPool, dstBuf); 139 } 140 else 141 { 142 utils.combineVisibilityResult(contextMtl, !mResetFirstQuery, allocatedOffsets, 143 mRenderPassResultsPool, dstBuf); 144 } 145 } 146 147 // Combine the values stored in the offsets allocated for each of the remaining queries 148 for (size_t i = 1; i < mAllocatedQueries.size(); ++i) 149 { 150 QueryMtl *query = mAllocatedQueries[i]; 151 if (!query) 152 { 153 continue; 154 } 155 156 const BufferRef &dstBuf = mAllocatedQueries[i]->getVisibilityResultBuffer(); 157 const VisibilityBufferOffsetsMtl &allocatedOffsets = 158 mAllocatedQueries[i]->getAllocatedVisibilityOffsets(); 159 utils.combineVisibilityResult(contextMtl, false, allocatedOffsets, mRenderPassResultsPool, 160 dstBuf); 161 } 162 163 // Request synchronization and cleanup 164 blitEncoder = contextMtl->getBlitCommandEncoder(); 165 for (size_t i = 0; i < mAllocatedQueries.size(); ++i) 166 { 167 QueryMtl *query = mAllocatedQueries[i]; 168 if (!query) 169 { 170 continue; 171 } 172 173 const BufferRef &dstBuf = mAllocatedQueries[i]->getVisibilityResultBuffer(); 174 175 dstBuf->syncContent(contextMtl, blitEncoder); 176 177 query->clearAllocatedVisibilityOffsets(); 178 } 179 180 mAllocatedQueries.clear(); 181} 182 183} 184} 185