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