Hash :
50704dc3
Author :
Date :
2020-08-04T12:08:00
Metal: Implement EXT_occlusion_query_boolean.
- Metal's occlusion(called visibility) query operates per render pass.
Implementation details are in
src/libANGLE/renderer/metal/doc/OcclusionQueries.md.
- New tests:
- OcclusionQueriesTest.ClearNotCounted.
- OcclusionQueriesTest.MultiQueries (failure on OpenGL/D3D11
back-end).
Bug: angleproject:2634
Change-Id: Idd1327b5472d0e8c2b69307a7f04a1da4a847a40
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2336121
Commit-Queue: Le Hoang Quyen <le.hoang.q@gmail.com>
Reviewed-by: back sept 10 - Jamie Madill <jmadill@chromium.org>
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
RenderCommandEncoder‘s method restart() will create an instance of Metal
framework’s native object MTLRenderCommandEncoder immediately to start encoding a render pass. RenderCommandEncoder‘s functions such as draw(), setBuffer(), setTexture(),
etc will invoke the equivalent MTLRenderCommandEncoder‘s methods. RenderCommandEncoder.endEncoding() is called. MTLRenderCommandEncoder creation will be deferred until all information about the render pass
have been recorded and known to the Metal backend. RenderCommandEncoder‘s methods such as draw(), setVisibilityResultMode(), setBuffer(),
etc will be recorded in a back-end owned buffer instead of encoding directly into an
MTLRenderCommandEncoder object. RenderCommandEncoder.endEncoding() will: MTLRenderCommandEncoder object. RenderCommandEncoder.endEncoding(): glClear between glBeginQuery - glEndQuery pair, its pixels should not be
counted by the occlusion test. To avoid this, current visibility test will end, then another
offset in the visibility buffer will be allocated for the query, this new offset will be used
to continue the test after the glClear operation ends. In the final step, the values stored
in both the old offset and the new offset will be accumulated together. glBeginQuery then glEndQuery then glBeginQuery again within a single pass,
then the query will be allocated 2 offsets since Metal doesn’t allow an offset to be re-used
in a render pass. Only the value stored in the 2nd offset will be copied back to the query at
the end of the render pass though.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
# Occlusion queries in the Metal back-end
- OpenGL allows occlusion query to start and end at any time.
- On the other hand, Metal only allows occlusion query (called visibility result test) to begin and
end within a render pass.
- Furthermore, a visibility result buffer must be set before encoding the render pass. This buffer
must be allocated beforehand and will be used to store the visibility results of all queries
within the render pass. Each query uses one offset within the buffer to store the result. Once the
render pass's encoding starts, this buffer must not be changed.
- The visibility result buffer will always be reset to zeros within the render pass.
### Previous implementation
- Metal back-end object `RenderCommandEncoder`'s method restart() will create an instance of Metal
framework's native object `MTLRenderCommandEncoder` immediately to start encoding a render pass.
- Afterwards, calling `RenderCommandEncoder`'s functions such as draw(), setBuffer(), setTexture(),
etc will invoke the equivalent `MTLRenderCommandEncoder`'s methods.
- The render pass's encoding ends when `RenderCommandEncoder.endEncoding()` is called.
### Current implementation
- `MTLRenderCommandEncoder` creation will be deferred until all information about the render pass
have been recorded and known to the Metal backend.
- Invoking `RenderCommandEncoder`'s methods such as draw(), setVisibilityResultMode(), setBuffer(),
etc will be recorded in a back-end owned buffer instead of encoding directly into an
`MTLRenderCommandEncoder` object.
- Whenever an occlusion query starts, an offset within a visibility result buffer will be allocated
for this request. This offset is valid only for the current render pass. The visibility buffer's
capacity will be extended if needed to have enough storage for all the queries within the render
pass. The offset will be used to activate the visibility test in the render pass.
- Calling `RenderCommandEncoder.endEncoding()` will:
- Bind the visibility result buffer allocated above.
- Create an `MTLRenderCommandEncoder` object.
- Encode using all render commands memorized in the back-end owned buffer.
- Immediately after `RenderCommandEncoder.endEncoding()`:
- An extra compute shader or copying pass is added to copy the results from visibility result
buffer to the respective assigned occlusion queries' buffers. Each query will simply copy the
value from its respective allocated offset in the visibility buffer.
- Note that if the query spans across multiple render passes, its old value will be accumulated
with the result stored in the visibility result buffer instead of being replaced.
- Special cases:
- If user calls `glClear` between `glBeginQuery` - `glEndQuery` pair, its pixels should not be
counted by the occlusion test. To avoid this, current visibility test will end, then another
offset in the visibility buffer will be allocated for the query, this new offset will be used
to continue the test after the `glClear` operation ends. In the final step, the values stored
in both the old offset and the new offset will be accumulated together.
- If user calls `glBeginQuery` then `glEndQuery` then `glBeginQuery` again within a single pass,
then the query will be allocated 2 offsets since Metal doesn't allow an offset to be re-used
in a render pass. Only the value stored in the 2nd offset will be copied back to the query at
the end of the render pass though.
### Future improvements
- One could simply allocates an offset within the visibility result buffer permanently for a query.
Then the extra copy step at the end of the render pass could be removed.
- However, doing so means the visibility result buffer would be very large in order to store every
query object created. Even if the query object might never be activated in a render pass.
- Furthermore, in order for the client to read back the result of a query, a host memory
synchronization for the visibility result buffer must be inserted. This could be slow if the
buffer is huge, and there are some offsets within the buffer are inactive within a render pass,
thus it is a wasteful synchronization.