Commit 50704dc3 by Le Hoang Quyen Committed by Commit Bot

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: 's avatarback sept 10 - Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com>
parent f005191d
...@@ -23,6 +23,8 @@ _metal_backend_sources = [ ...@@ -23,6 +23,8 @@ _metal_backend_sources = [
"FrameBufferMtl.mm", "FrameBufferMtl.mm",
"ProgramMtl.h", "ProgramMtl.h",
"ProgramMtl.mm", "ProgramMtl.mm",
"QueryMtl.h",
"QueryMtl.mm",
"RenderBufferMtl.h", "RenderBufferMtl.h",
"RenderBufferMtl.mm", "RenderBufferMtl.mm",
"RenderTargetMtl.h", "RenderTargetMtl.h",
...@@ -46,6 +48,8 @@ _metal_backend_sources = [ ...@@ -46,6 +48,8 @@ _metal_backend_sources = [
"mtl_format_utils.mm", "mtl_format_utils.mm",
"mtl_glslang_utils.h", "mtl_glslang_utils.h",
"mtl_glslang_utils.mm", "mtl_glslang_utils.mm",
"mtl_occlusion_query_pool.h",
"mtl_occlusion_query_pool.mm",
"mtl_render_utils.h", "mtl_render_utils.h",
"mtl_render_utils.mm", "mtl_render_utils.mm",
"mtl_resources.h", "mtl_resources.h",
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "libANGLE/renderer/ContextImpl.h" #include "libANGLE/renderer/ContextImpl.h"
#include "libANGLE/renderer/metal/mtl_buffer_pool.h" #include "libANGLE/renderer/metal/mtl_buffer_pool.h"
#include "libANGLE/renderer/metal/mtl_command_buffer.h" #include "libANGLE/renderer/metal/mtl_command_buffer.h"
#include "libANGLE/renderer/metal/mtl_occlusion_query_pool.h"
#include "libANGLE/renderer/metal/mtl_resources.h" #include "libANGLE/renderer/metal/mtl_resources.h"
#include "libANGLE/renderer/metal/mtl_state_cache.h" #include "libANGLE/renderer/metal/mtl_state_cache.h"
#include "libANGLE/renderer/metal/mtl_utils.h" #include "libANGLE/renderer/metal/mtl_utils.h"
...@@ -272,6 +273,21 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -272,6 +273,21 @@ class ContextMtl : public ContextImpl, public mtl::Context
bool renderPassChanged); bool renderPassChanged);
void onBackbufferResized(const gl::Context *context, WindowSurfaceMtl *backbuffer); void onBackbufferResized(const gl::Context *context, WindowSurfaceMtl *backbuffer);
// Invoke by QueryMtl
angle::Result onOcclusionQueryBegin(const gl::Context *context, QueryMtl *query);
void onOcclusionQueryEnd(const gl::Context *context, QueryMtl *query);
void onOcclusionQueryDestroy(const gl::Context *context, QueryMtl *query);
// Useful for temporarily pause then restart occlusion query during clear/blit with draw.
bool hasActiveOcclusionQuery() const { return mOcclusionQuery; }
// Disable the occlusion query in the current render pass.
// The render pass must already started.
void disableActiveOcclusionQueryInRenderPass();
// Re-enable the occlusion query in the current render pass.
// The render pass must already started.
// NOTE: the old query's result will be retained and combined with the new result.
angle::Result restartActiveOcclusionQueryInRenderPass();
const MTLClearColor &getClearColorValue() const; const MTLClearColor &getClearColorValue() const;
MTLColorWriteMask getColorMask() const; MTLColorWriteMask getColorMask() const;
float getClearDepthValue() const; float getClearDepthValue() const;
...@@ -292,7 +308,7 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -292,7 +308,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
// Recommended to call these methods to end encoding instead of invoking the encoder's // Recommended to call these methods to end encoding instead of invoking the encoder's
// endEncoding() directly. // endEncoding() directly.
void endEncoding(mtl::RenderCommandEncoder *encoder); void endRenderEncoding(mtl::RenderCommandEncoder *encoder);
// Ends any active command encoder // Ends any active command encoder
void endEncoding(bool forceSaveRenderPassContent); void endEncoding(bool forceSaveRenderPassContent);
...@@ -406,6 +422,8 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -406,6 +422,8 @@ class ContextMtl : public ContextImpl, public mtl::Context
gl::PrimitiveMode primitiveMode, gl::PrimitiveMode primitiveMode,
Optional<mtl::RenderPipelineDesc> *changedPipelineDesc); Optional<mtl::RenderPipelineDesc> *changedPipelineDesc);
angle::Result startOcclusionQueryInRenderPass(QueryMtl *query, bool clearOldValue);
// Dirty bits. // Dirty bits.
enum DirtyBitType : size_t enum DirtyBitType : size_t
{ {
...@@ -470,6 +488,8 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -470,6 +488,8 @@ class ContextMtl : public ContextImpl, public mtl::Context
float values[4]; float values[4];
}; };
mtl::OcclusionQueryPool mOcclusionQueryPool;
mtl::CommandBuffer mCmdBuffer; mtl::CommandBuffer mCmdBuffer;
mtl::RenderCommandEncoder mRenderEncoder; mtl::RenderCommandEncoder mRenderEncoder;
mtl::BlitCommandEncoder mBlitEncoder; mtl::BlitCommandEncoder mBlitEncoder;
...@@ -479,6 +499,7 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -479,6 +499,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
FramebufferMtl *mDrawFramebuffer = nullptr; FramebufferMtl *mDrawFramebuffer = nullptr;
VertexArrayMtl *mVertexArray = nullptr; VertexArrayMtl *mVertexArray = nullptr;
ProgramMtl *mProgram = nullptr; ProgramMtl *mProgram = nullptr;
QueryMtl *mOcclusionQuery = nullptr;
using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>; using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h"
#include "libANGLE/renderer/metal/FrameBufferMtl.h" #include "libANGLE/renderer/metal/FrameBufferMtl.h"
#include "libANGLE/renderer/metal/ProgramMtl.h" #include "libANGLE/renderer/metal/ProgramMtl.h"
#include "libANGLE/renderer/metal/QueryMtl.h"
#include "libANGLE/renderer/metal/RenderBufferMtl.h" #include "libANGLE/renderer/metal/RenderBufferMtl.h"
#include "libANGLE/renderer/metal/RenderTargetMtl.h" #include "libANGLE/renderer/metal/RenderTargetMtl.h"
#include "libANGLE/renderer/metal/ShaderMtl.h" #include "libANGLE/renderer/metal/ShaderMtl.h"
...@@ -97,7 +98,7 @@ ContextMtl::ContextMtl(const gl::State &state, gl::ErrorSet *errorSet, DisplayMt ...@@ -97,7 +98,7 @@ ContextMtl::ContextMtl(const gl::State &state, gl::ErrorSet *errorSet, DisplayMt
: ContextImpl(state, errorSet), : ContextImpl(state, errorSet),
mtl::Context(display), mtl::Context(display),
mCmdBuffer(&display->cmdQueue()), mCmdBuffer(&display->cmdQueue()),
mRenderEncoder(&mCmdBuffer), mRenderEncoder(&mCmdBuffer, mOcclusionQueryPool),
mBlitEncoder(&mCmdBuffer), mBlitEncoder(&mCmdBuffer),
mComputeEncoder(&mCmdBuffer) mComputeEncoder(&mCmdBuffer)
{} {}
...@@ -122,6 +123,7 @@ void ContextMtl::onDestroy(const gl::Context *context) ...@@ -122,6 +123,7 @@ void ContextMtl::onDestroy(const gl::Context *context)
{ {
mTriFanIndexBuffer.destroy(this); mTriFanIndexBuffer.destroy(this);
mLineLoopIndexBuffer.destroy(this); mLineLoopIndexBuffer.destroy(this);
mOcclusionQueryPool.destroy(this);
mIncompleteTextures.onDestroy(context); mIncompleteTextures.onDestroy(context);
mIncompleteTexturesInitialized = false; mIncompleteTexturesInitialized = false;
...@@ -927,9 +929,7 @@ VertexArrayImpl *ContextMtl::createVertexArray(const gl::VertexArrayState &state ...@@ -927,9 +929,7 @@ VertexArrayImpl *ContextMtl::createVertexArray(const gl::VertexArrayState &state
// Query and Fence creation // Query and Fence creation
QueryImpl *ContextMtl::createQuery(gl::QueryType type) QueryImpl *ContextMtl::createQuery(gl::QueryType type)
{ {
// NOTE(hqle): ES 3.0 return new QueryMtl(type);
UNIMPLEMENTED();
return nullptr;
} }
FenceNVImpl *ContextMtl::createFenceNV() FenceNVImpl *ContextMtl::createFenceNV()
{ {
...@@ -1131,9 +1131,18 @@ angle::Result ContextMtl::getIncompleteTexture(const gl::Context *context, ...@@ -1131,9 +1131,18 @@ angle::Result ContextMtl::getIncompleteTexture(const gl::Context *context,
return mIncompleteTextures.getIncompleteTexture(context, type, nullptr, textureOut); return mIncompleteTextures.getIncompleteTexture(context, type, nullptr, textureOut);
} }
void ContextMtl::endEncoding(mtl::RenderCommandEncoder *encoder) void ContextMtl::endRenderEncoding(mtl::RenderCommandEncoder *encoder)
{ {
// End any pending visibility query in the render pass
if (mOcclusionQuery)
{
disableActiveOcclusionQueryInRenderPass();
}
encoder->endEncoding(); encoder->endEncoding();
// Resolve visibility results
mOcclusionQueryPool.resolveVisibilityResults(this);
} }
void ContextMtl::endEncoding(bool forceSaveRenderPassContent) void ContextMtl::endEncoding(bool forceSaveRenderPassContent)
...@@ -1146,7 +1155,7 @@ void ContextMtl::endEncoding(bool forceSaveRenderPassContent) ...@@ -1146,7 +1155,7 @@ void ContextMtl::endEncoding(bool forceSaveRenderPassContent)
mRenderEncoder.setStoreAction(MTLStoreActionStore); mRenderEncoder.setStoreAction(MTLStoreActionStore);
} }
mRenderEncoder.endEncoding(); endRenderEncoding(&mRenderEncoder);
} }
if (mBlitEncoder.valid()) if (mBlitEncoder.valid())
...@@ -1461,6 +1470,87 @@ void ContextMtl::onBackbufferResized(const gl::Context *context, WindowSurfaceMt ...@@ -1461,6 +1470,87 @@ void ContextMtl::onBackbufferResized(const gl::Context *context, WindowSurfaceMt
onDrawFrameBufferChangedState(context, framebuffer, true); onDrawFrameBufferChangedState(context, framebuffer, true);
} }
angle::Result ContextMtl::onOcclusionQueryBegin(const gl::Context *context, QueryMtl *query)
{
ASSERT(mOcclusionQuery == nullptr);
mOcclusionQuery = query;
if (mRenderEncoder.valid())
{
// if render pass has started, start the query in the encoder
return startOcclusionQueryInRenderPass(query, true);
}
else
{
query->resetVisibilityResult(this);
}
return angle::Result::Continue;
}
void ContextMtl::onOcclusionQueryEnd(const gl::Context *context, QueryMtl *query)
{
ASSERT(mOcclusionQuery == query);
if (mRenderEncoder.valid())
{
// if render pass has started, end the query in the encoder
disableActiveOcclusionQueryInRenderPass();
}
mOcclusionQuery = nullptr;
}
void ContextMtl::onOcclusionQueryDestroy(const gl::Context *context, QueryMtl *query)
{
if (query->getAllocatedVisibilityOffsets().empty())
{
return;
}
if (mOcclusionQuery == query)
{
onOcclusionQueryEnd(context, query);
}
mOcclusionQueryPool.deallocateQueryOffset(this, query);
}
void ContextMtl::disableActiveOcclusionQueryInRenderPass()
{
if (!mOcclusionQuery || mOcclusionQuery->getAllocatedVisibilityOffsets().empty())
{
return;
}
ASSERT(mRenderEncoder.valid());
mRenderEncoder.setVisibilityResultMode(MTLVisibilityResultModeDisabled,
mOcclusionQuery->getAllocatedVisibilityOffsets().back());
}
angle::Result ContextMtl::restartActiveOcclusionQueryInRenderPass()
{
if (!mOcclusionQuery || mOcclusionQuery->getAllocatedVisibilityOffsets().empty())
{
return angle::Result::Continue;
}
return startOcclusionQueryInRenderPass(mOcclusionQuery, false);
}
angle::Result ContextMtl::startOcclusionQueryInRenderPass(QueryMtl *query, bool clearOldValue)
{
ASSERT(mRenderEncoder.valid());
ANGLE_TRY(mOcclusionQueryPool.allocateQueryOffset(this, query, clearOldValue));
mRenderEncoder.setVisibilityResultMode(MTLVisibilityResultModeBoolean,
query->getAllocatedVisibilityOffsets().back());
// We need to mark the query's buffer as being written in this command buffer now. Since the
// actual writing is deferred until the render pass ends and user could try to read the query
// result before the render pass ends.
mCmdBuffer.setWriteDependency(query->getVisibilityResultBuffer());
return angle::Result::Continue;
}
void ContextMtl::updateProgramExecutable(const gl::Context *context) void ContextMtl::updateProgramExecutable(const gl::Context *context)
{ {
// Need to rebind textures // Need to rebind textures
...@@ -1547,6 +1637,13 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context, ...@@ -1547,6 +1637,13 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
invalidateState(context); invalidateState(context);
} }
if (mOcclusionQuery && mOcclusionQueryPool.getNumRenderPassAllocatedQueries() == 0)
{
// The occlusion query is still active, and a new render pass has started.
// We need to continue the querying process in the new render encoder.
ANGLE_TRY(startOcclusionQueryInRenderPass(mOcclusionQuery, false));
}
Optional<mtl::RenderPipelineDesc> changedPipelineDesc; Optional<mtl::RenderPipelineDesc> changedPipelineDesc;
ANGLE_TRY(checkIfPipelineChanged(context, mode, &changedPipelineDesc)); ANGLE_TRY(checkIfPipelineChanged(context, mode, &changedPipelineDesc));
......
// //
// Copyright (c) 2019 The ANGLE Project Authors. All rights reserved. // Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
...@@ -627,8 +627,7 @@ void DisplayMtl::initializeExtensions() const ...@@ -627,8 +627,7 @@ void DisplayMtl::initializeExtensions() const
mNativeExtensions.eglSyncOES = false; mNativeExtensions.eglSyncOES = false;
// NOTE(hqle): support occlusion query mNativeExtensions.occlusionQueryBoolean = true;
mNativeExtensions.occlusionQueryBoolean = false;
mNativeExtensions.disjointTimerQuery = false; mNativeExtensions.disjointTimerQuery = false;
mNativeExtensions.queryCounterBitsTimeElapsed = false; mNativeExtensions.queryCounterBitsTimeElapsed = false;
......
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// QueryMtl.h:
// Defines the class interface for QueryMtl, implementing QueryImpl.
//
#ifndef LIBANGLE_RENDERER_METAL_QUERYMTL_H_
#define LIBANGLE_RENDERER_METAL_QUERYMTL_H_
#include "libANGLE/renderer/QueryImpl.h"
#include "libANGLE/renderer/metal/mtl_common.h"
#include "libANGLE/renderer/metal/mtl_resources.h"
namespace rx
{
class ContextMtl;
// The class represents offset(s) allocated in the visiblity buffer for an occlusion query.
// See doc/OcclusionQuery.md.
// An occlusion query might have more than one offsets allocated, but all of them must be adjacent
// to each other. Multiple offsets typically allocated when the query is paused and resumed during
// viewport clear emulation with draw operations. In such case, Metal doesn't allow an offset to
// be reused in a render pass, hence multiple offsets will be allocated, and their values will
// be accumulated.
class VisibilityBufferOffsetsMtl
{
public:
void clear() { mStartOffset = mNumOffsets = 0; }
bool empty() const { return mNumOffsets == 0; }
uint32_t size() const { return mNumOffsets; }
// Return last offset
uint32_t back() const
{
ASSERT(!empty());
return mStartOffset + (mNumOffsets - 1) * mtl::kOcclusionQueryResultSize;
}
uint32_t front() const
{
ASSERT(!empty());
return mStartOffset;
}
void setStartOffset(uint32_t offset)
{
ASSERT(empty());
mStartOffset = offset;
mNumOffsets = 1;
}
void addOffset()
{
ASSERT(!empty());
mNumOffsets++;
}
private:
uint32_t mStartOffset = 0;
uint32_t mNumOffsets = 0;
};
class QueryMtl : public QueryImpl
{
public:
QueryMtl(gl::QueryType type);
~QueryMtl() override;
void onDestroy(const gl::Context *context) override;
angle::Result begin(const gl::Context *context) override;
angle::Result end(const gl::Context *context) override;
angle::Result queryCounter(const gl::Context *context) override;
angle::Result getResult(const gl::Context *context, GLint *params) override;
angle::Result getResult(const gl::Context *context, GLuint *params) override;
angle::Result getResult(const gl::Context *context, GLint64 *params) override;
angle::Result getResult(const gl::Context *context, GLuint64 *params) override;
angle::Result isResultAvailable(const gl::Context *context, bool *available) override;
// Get allocated offsets in the render pass's occlusion query pool.
const VisibilityBufferOffsetsMtl &getAllocatedVisibilityOffsets() const
{
return mVisibilityBufferOffsets;
}
// Set first allocated offset in the render pass's occlusion query pool.
void setFirstAllocatedVisibilityOffset(uint32_t off)
{
mVisibilityBufferOffsets.setStartOffset(off);
}
// Add more offset allocated for the occlusion query
void addAllocatedVisibilityOffset() { mVisibilityBufferOffsets.addOffset(); }
void clearAllocatedVisibilityOffsets() { mVisibilityBufferOffsets.clear(); }
// Returns the buffer containing the final occlusion query result.
const mtl::BufferRef &getVisibilityResultBuffer() const { return mVisibilityResultBuffer; }
// Reset the occlusion query result stored in buffer to zero
void resetVisibilityResult(ContextMtl *contextMtl);
private:
template <typename T>
angle::Result waitAndGetResult(const gl::Context *context, T *params);
// List of offsets in the render pass's occlusion query pool buffer allocated for this query
VisibilityBufferOffsetsMtl mVisibilityBufferOffsets;
mtl::BufferRef mVisibilityResultBuffer;
};
} // namespace rx
#endif /* LIBANGLE_RENDERER_METAL_QUERYMTL_H_ */
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// QueryMtl.mm:
// Defines the class interface for QueryMtl, implementing QueryImpl.
//
#include "libANGLE/renderer/metal/QueryMtl.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
namespace rx
{
QueryMtl::QueryMtl(gl::QueryType type) : QueryImpl(type) {}
QueryMtl::~QueryMtl() {}
void QueryMtl::onDestroy(const gl::Context *context)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
if (!getAllocatedVisibilityOffsets().empty())
{
contextMtl->onOcclusionQueryDestroy(context, this);
}
mVisibilityResultBuffer = nullptr;
}
angle::Result QueryMtl::begin(const gl::Context *context)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
switch (getType())
{
case gl::QueryType::AnySamples:
case gl::QueryType::AnySamplesConservative:
if (!mVisibilityResultBuffer)
{
// Allocate buffer
ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, mtl::kOcclusionQueryResultSize,
nullptr, &mVisibilityResultBuffer));
ANGLE_MTL_OBJC_SCOPE
{
mVisibilityResultBuffer->get().label =
[NSString stringWithFormat:@"QueryMtl=%p", this];
}
}
ANGLE_TRY(contextMtl->onOcclusionQueryBegin(context, this));
break;
default:
UNIMPLEMENTED();
break;
}
return angle::Result::Continue;
}
angle::Result QueryMtl::end(const gl::Context *context)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
switch (getType())
{
case gl::QueryType::AnySamples:
case gl::QueryType::AnySamplesConservative:
contextMtl->onOcclusionQueryEnd(context, this);
break;
default:
UNIMPLEMENTED();
break;
}
return angle::Result::Continue;
}
angle::Result QueryMtl::queryCounter(const gl::Context *context)
{
UNIMPLEMENTED();
return angle::Result::Continue;
}
template <typename T>
angle::Result QueryMtl::waitAndGetResult(const gl::Context *context, T *params)
{
ASSERT(params);
ContextMtl *contextMtl = mtl::GetImpl(context);
switch (getType())
{
case gl::QueryType::AnySamples:
case gl::QueryType::AnySamplesConservative:
{
ASSERT(mVisibilityResultBuffer);
if (mVisibilityResultBuffer->hasPendingWorks(contextMtl))
{
contextMtl->flushCommandBufer();
}
// map() will wait for the pending GPU works to finish
const uint8_t *visibilityResultBytes = mVisibilityResultBuffer->mapReadOnly(contextMtl);
uint64_t queryResult;
memcpy(&queryResult, visibilityResultBytes, sizeof(queryResult));
mVisibilityResultBuffer->unmap(contextMtl);
*params = queryResult ? GL_TRUE : GL_FALSE;
}
break;
default:
UNIMPLEMENTED();
break;
}
return angle::Result::Continue;
}
angle::Result QueryMtl::isResultAvailable(const gl::Context *context, bool *available)
{
ASSERT(available);
ContextMtl *contextMtl = mtl::GetImpl(context);
// glGetQueryObjectuiv implicitly flush any pending works related to the query
switch (getType())
{
case gl::QueryType::AnySamples:
case gl::QueryType::AnySamplesConservative:
ASSERT(mVisibilityResultBuffer);
if (mVisibilityResultBuffer->hasPendingWorks(contextMtl))
{
contextMtl->flushCommandBufer();
}
*available = !mVisibilityResultBuffer->isBeingUsedByGPU(contextMtl);
break;
default:
UNIMPLEMENTED();
break;
}
return angle::Result::Continue;
}
angle::Result QueryMtl::getResult(const gl::Context *context, GLint *params)
{
return waitAndGetResult(context, params);
}
angle::Result QueryMtl::getResult(const gl::Context *context, GLuint *params)
{
return waitAndGetResult(context, params);
}
angle::Result QueryMtl::getResult(const gl::Context *context, GLint64 *params)
{
return waitAndGetResult(context, params);
}
angle::Result QueryMtl::getResult(const gl::Context *context, GLuint64 *params)
{
return waitAndGetResult(context, params);
}
void QueryMtl::resetVisibilityResult(ContextMtl *contextMtl)
{
// Occlusion query buffer must be allocated in QueryMtl::begin
ASSERT(mVisibilityResultBuffer);
// Fill the query's buffer with zeros
auto blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->fillBuffer(mVisibilityResultBuffer, NSMakeRange(0, mtl::kOcclusionQueryResultSize),
0);
mVisibilityResultBuffer->syncContent(contextMtl, blitEncoder);
}
}
# 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.
\ No newline at end of file
...@@ -35,6 +35,7 @@ namespace mtl ...@@ -35,6 +35,7 @@ namespace mtl
class CommandBuffer; class CommandBuffer;
class CommandEncoder; class CommandEncoder;
class RenderCommandEncoder; class RenderCommandEncoder;
class OcclusionQueryPool;
class CommandQueue final : public WrappedObject<id<MTLCommandQueue>>, angle::NonCopyable class CommandQueue final : public WrappedObject<id<MTLCommandQueue>>, angle::NonCopyable
{ {
...@@ -110,6 +111,7 @@ class CommandBuffer final : public WrappedObject<id<MTLCommandBuffer>>, angle::N ...@@ -110,6 +111,7 @@ class CommandBuffer final : public WrappedObject<id<MTLCommandBuffer>>, angle::N
void setWriteDependency(const ResourceRef &resource); void setWriteDependency(const ResourceRef &resource);
void setReadDependency(const ResourceRef &resource); void setReadDependency(const ResourceRef &resource);
void setReadDependency(Resource *resourcePtr);
CommandQueue &cmdQueue() { return mCmdQueue; } CommandQueue &cmdQueue() { return mCmdQueue; }
...@@ -274,13 +276,16 @@ struct RenderCommandEncoderStates ...@@ -274,13 +276,16 @@ struct RenderCommandEncoderStates
std::array<float, 4> blendColor; std::array<float, 4> blendColor;
gl::ShaderMap<RenderCommandEncoderShaderStates> perShaderStates; gl::ShaderMap<RenderCommandEncoderShaderStates> perShaderStates;
MTLVisibilityResultMode visibilityResultMode;
size_t visibilityResultBufferOffset;
}; };
// Encoder for encoding render commands // Encoder for encoding render commands
class RenderCommandEncoder final : public CommandEncoder class RenderCommandEncoder final : public CommandEncoder
{ {
public: public:
RenderCommandEncoder(CommandBuffer *cmdBuffer); RenderCommandEncoder(CommandBuffer *cmdBuffer, const OcclusionQueryPool &queryPool);
~RenderCommandEncoder() override; ~RenderCommandEncoder() override;
// override CommandEncoder // override CommandEncoder
...@@ -411,6 +416,8 @@ class RenderCommandEncoder final : public CommandEncoder ...@@ -411,6 +416,8 @@ class RenderCommandEncoder final : public CommandEncoder
uint32_t instances, uint32_t instances,
uint32_t baseVertex); uint32_t baseVertex);
RenderCommandEncoder &setVisibilityResultMode(MTLVisibilityResultMode mode, size_t offset);
RenderCommandEncoder &setColorStoreAction(MTLStoreAction action, uint32_t colorAttachmentIndex); RenderCommandEncoder &setColorStoreAction(MTLStoreAction action, uint32_t colorAttachmentIndex);
// Set store action for every color attachment. // Set store action for every color attachment.
RenderCommandEncoder &setColorStoreAction(MTLStoreAction action); RenderCommandEncoder &setColorStoreAction(MTLStoreAction action);
...@@ -457,6 +464,7 @@ class RenderCommandEncoder final : public CommandEncoder ...@@ -457,6 +464,7 @@ class RenderCommandEncoder final : public CommandEncoder
mtl::AutoObjCObj<MTLRenderPassDescriptor> mCachedRenderPassDescObjC; mtl::AutoObjCObj<MTLRenderPassDescriptor> mCachedRenderPassDescObjC;
MTLScissorRect mRenderPassMaxScissorRect; MTLScissorRect mRenderPassMaxScissorRect;
const OcclusionQueryPool &mOcclusionQueryPool;
bool mRecording = false; bool mRecording = false;
bool mHasDrawCalls = false; bool mHasDrawCalls = false;
IntermediateCommandStream mCommands; IntermediateCommandStream mCommands;
...@@ -480,6 +488,12 @@ class BlitCommandEncoder final : public CommandEncoder ...@@ -480,6 +488,12 @@ class BlitCommandEncoder final : public CommandEncoder
// NOTE: parent CommandBuffer's restart() must be called before this. // NOTE: parent CommandBuffer's restart() must be called before this.
BlitCommandEncoder &restart(); BlitCommandEncoder &restart();
BlitCommandEncoder &copyBuffer(const BufferRef &src,
size_t srcOffset,
const BufferRef &dst,
size_t dstOffset,
size_t size);
BlitCommandEncoder &copyBufferToTexture(const BufferRef &src, BlitCommandEncoder &copyBufferToTexture(const BufferRef &src,
size_t srcOffset, size_t srcOffset,
size_t srcBytesPerRow, size_t srcBytesPerRow,
...@@ -500,8 +514,11 @@ class BlitCommandEncoder final : public CommandEncoder ...@@ -500,8 +514,11 @@ class BlitCommandEncoder final : public CommandEncoder
uint32_t sliceCount, uint32_t sliceCount,
uint32_t levelCount); uint32_t levelCount);
BlitCommandEncoder &fillBuffer(const BufferRef &buffer, NSRange range, uint8_t value);
BlitCommandEncoder &generateMipmapsForTexture(const TextureRef &texture); BlitCommandEncoder &generateMipmapsForTexture(const TextureRef &texture);
BlitCommandEncoder &synchronizeResource(const TextureRef &texture); BlitCommandEncoder &synchronizeResource(Buffer *bufferPtr);
BlitCommandEncoder &synchronizeResource(Texture *texturePtr);
private: private:
id<MTLBlitCommandEncoder> get() id<MTLBlitCommandEncoder> get()
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <cassert> #include <cassert>
#include "common/debug.h" #include "common/debug.h"
#include "libANGLE/renderer/metal/mtl_occlusion_query_pool.h"
#include "libANGLE/renderer/metal/mtl_resources.h" #include "libANGLE/renderer/metal/mtl_resources.h"
// Use to compare the new values with the values already set in the command encoder: // Use to compare the new values with the values already set in the command encoder:
...@@ -34,33 +35,34 @@ namespace mtl ...@@ -34,33 +35,34 @@ namespace mtl
namespace namespace
{ {
#define ANGLE_MTL_CMD_X(PROC) \ #define ANGLE_MTL_CMD_X(PROC) \
PROC(Invalid) \ PROC(Invalid) \
PROC(SetRenderPipelineState) \ PROC(SetRenderPipelineState) \
PROC(SetTriangleFillMode) \ PROC(SetTriangleFillMode) \
PROC(SetFrontFacingWinding) \ PROC(SetFrontFacingWinding) \
PROC(SetCullMode) \ PROC(SetCullMode) \
PROC(SetDepthStencilState) \ PROC(SetDepthStencilState) \
PROC(SetDepthBias) \ PROC(SetDepthBias) \
PROC(SetStencilRefVals) \ PROC(SetStencilRefVals) \
PROC(SetViewport) \ PROC(SetViewport) \
PROC(SetScissorRect) \ PROC(SetScissorRect) \
PROC(SetBlendColor) \ PROC(SetBlendColor) \
PROC(SetVertexBuffer) \ PROC(SetVertexBuffer) \
PROC(SetVertexBufferOffset) \ PROC(SetVertexBufferOffset) \
PROC(SetVertexBytes) \ PROC(SetVertexBytes) \
PROC(SetVertexSamplerState) \ PROC(SetVertexSamplerState) \
PROC(SetVertexTexture) \ PROC(SetVertexTexture) \
PROC(SetFragmentBuffer) \ PROC(SetFragmentBuffer) \
PROC(SetFragmentBufferOffset) \ PROC(SetFragmentBufferOffset) \
PROC(SetFragmentBytes) \ PROC(SetFragmentBytes) \
PROC(SetFragmentSamplerState) \ PROC(SetFragmentSamplerState) \
PROC(SetFragmentTexture) \ PROC(SetFragmentTexture) \
PROC(Draw) \ PROC(Draw) \
PROC(DrawInstanced) \ PROC(DrawInstanced) \
PROC(DrawIndexed) \ PROC(DrawIndexed) \
PROC(DrawIndexedInstanced) \ PROC(DrawIndexedInstanced) \
PROC(DrawIndexedInstancedBaseVertex) PROC(DrawIndexedInstancedBaseVertex) \
PROC(SetVisibilityResultMode)
#define ANGLE_MTL_TYPE_DECL(CMD) CMD, #define ANGLE_MTL_TYPE_DECL(CMD) CMD,
...@@ -315,6 +317,14 @@ void DrawIndexedInstancedBaseVertexCmd(id<MTLRenderCommandEncoder> encoder, ...@@ -315,6 +317,14 @@ void DrawIndexedInstancedBaseVertexCmd(id<MTLRenderCommandEncoder> encoder,
[indexBuffer ANGLE_MTL_RELEASE]; [indexBuffer ANGLE_MTL_RELEASE];
} }
void SetVisibilityResultModeCmd(id<MTLRenderCommandEncoder> encoder,
IntermediateCommandStream *stream)
{
MTLVisibilityResultMode mode = stream->fetch<MTLVisibilityResultMode>();
size_t offset = stream->fetch<size_t>();
[encoder setVisibilityResultMode:mode offset:offset];
}
// Command encoder mapping // Command encoder mapping
#define ANGLE_MTL_CMD_MAP(CMD) CMD##Cmd, #define ANGLE_MTL_CMD_MAP(CMD) CMD##Cmd,
...@@ -532,6 +542,11 @@ void CommandBuffer::setWriteDependency(const ResourceRef &resource) ...@@ -532,6 +542,11 @@ void CommandBuffer::setWriteDependency(const ResourceRef &resource)
void CommandBuffer::setReadDependency(const ResourceRef &resource) void CommandBuffer::setReadDependency(const ResourceRef &resource)
{ {
setReadDependency(resource.get());
}
void CommandBuffer::setReadDependency(Resource *resource)
{
if (!resource) if (!resource)
{ {
return; return;
...@@ -733,8 +748,9 @@ void RenderCommandEncoderStates::reset() ...@@ -733,8 +748,9 @@ void RenderCommandEncoderStates::reset()
} }
// RenderCommandEncoder implemtation // RenderCommandEncoder implemtation
RenderCommandEncoder::RenderCommandEncoder(CommandBuffer *cmdBuffer) RenderCommandEncoder::RenderCommandEncoder(CommandBuffer *cmdBuffer,
: CommandEncoder(cmdBuffer, RENDER) const OcclusionQueryPool &queryPool)
: CommandEncoder(cmdBuffer, RENDER), mOcclusionQueryPool(queryPool)
{ {
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
{ {
...@@ -837,6 +853,17 @@ void RenderCommandEncoder::endEncoding() ...@@ -837,6 +853,17 @@ void RenderCommandEncoder::endEncoding()
mRenderPassDesc.stencilAttachment.storeAction; mRenderPassDesc.stencilAttachment.storeAction;
finalizeLoadStoreAction(objCRenderPassDesc.stencilAttachment); finalizeLoadStoreAction(objCRenderPassDesc.stencilAttachment);
// Set visibility result buffer
if (mOcclusionQueryPool.getNumRenderPassAllocatedQueries())
{
objCRenderPassDesc.visibilityResultBuffer =
mOcclusionQueryPool.getRenderPassVisibilityPoolBuffer()->get();
}
else
{
objCRenderPassDesc.visibilityResultBuffer = nil;
}
// Encode the actual encoder // Encode the actual encoder
encodeMetalEncoder(); encodeMetalEncoder();
...@@ -1380,6 +1407,21 @@ RenderCommandEncoder &RenderCommandEncoder::drawIndexedInstancedBaseVertex( ...@@ -1380,6 +1407,21 @@ RenderCommandEncoder &RenderCommandEncoder::drawIndexedInstancedBaseVertex(
return *this; return *this;
} }
RenderCommandEncoder &RenderCommandEncoder::setVisibilityResultMode(MTLVisibilityResultMode mode,
size_t offset)
{
if (mStateCache.visibilityResultMode == mode &&
mStateCache.visibilityResultBufferOffset == offset)
{
return *this;
}
mStateCache.visibilityResultMode = mode;
mStateCache.visibilityResultBufferOffset = offset;
mCommands.push(CmdType::SetVisibilityResultMode).push(mode).push(offset);
return *this;
}
RenderCommandEncoder &RenderCommandEncoder::setColorStoreAction(MTLStoreAction action, RenderCommandEncoder &RenderCommandEncoder::setColorStoreAction(MTLStoreAction action,
uint32_t colorAttachmentIndex) uint32_t colorAttachmentIndex)
{ {
...@@ -1507,6 +1549,29 @@ BlitCommandEncoder &BlitCommandEncoder::restart() ...@@ -1507,6 +1549,29 @@ BlitCommandEncoder &BlitCommandEncoder::restart()
} }
} }
BlitCommandEncoder &BlitCommandEncoder::copyBuffer(const BufferRef &src,
size_t srcOffset,
const BufferRef &dst,
size_t dstOffset,
size_t size)
{
if (!src || !dst)
{
return *this;
}
cmdBuffer().setReadDependency(src);
cmdBuffer().setWriteDependency(dst);
[get() copyFromBuffer:src->get()
sourceOffset:srcOffset
toBuffer:dst->get()
destinationOffset:dstOffset
size:size];
return *this;
}
BlitCommandEncoder &BlitCommandEncoder::copyBufferToTexture(const BufferRef &src, BlitCommandEncoder &BlitCommandEncoder::copyBufferToTexture(const BufferRef &src,
size_t srcOffset, size_t srcOffset,
size_t srcBytesPerRow, size_t srcBytesPerRow,
...@@ -1584,6 +1649,19 @@ BlitCommandEncoder &BlitCommandEncoder::copyTexture(const TextureRef &src, ...@@ -1584,6 +1649,19 @@ BlitCommandEncoder &BlitCommandEncoder::copyTexture(const TextureRef &src,
return *this; return *this;
} }
BlitCommandEncoder &BlitCommandEncoder::fillBuffer(const BufferRef &buffer,
NSRange range,
uint8_t value)
{
if (!buffer)
{
return *this;
}
[get() fillBuffer:buffer->get() range:range value:value];
return *this;
}
BlitCommandEncoder &BlitCommandEncoder::generateMipmapsForTexture(const TextureRef &texture) BlitCommandEncoder &BlitCommandEncoder::generateMipmapsForTexture(const TextureRef &texture)
{ {
if (!texture) if (!texture)
...@@ -1596,7 +1674,22 @@ BlitCommandEncoder &BlitCommandEncoder::generateMipmapsForTexture(const TextureR ...@@ -1596,7 +1674,22 @@ BlitCommandEncoder &BlitCommandEncoder::generateMipmapsForTexture(const TextureR
return *this; return *this;
} }
BlitCommandEncoder &BlitCommandEncoder::synchronizeResource(const TextureRef &texture) BlitCommandEncoder &BlitCommandEncoder::synchronizeResource(Buffer *buffer)
{
if (!buffer)
{
return *this;
}
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
// Only MacOS has separated storage for resource on CPU and GPU and needs explicit
// synchronization
cmdBuffer().setReadDependency(buffer);
[get() synchronizeResource:buffer->get()];
#endif
return *this;
}
BlitCommandEncoder &BlitCommandEncoder::synchronizeResource(Texture *texture)
{ {
if (!texture) if (!texture)
{ {
......
...@@ -133,6 +133,8 @@ constexpr uint32_t kStencilMaskAll = 0xff; // Only 8 bits stencil is supported ...@@ -133,6 +133,8 @@ constexpr uint32_t kStencilMaskAll = 0xff; // Only 8 bits stencil is supported
constexpr float kEmulatedAlphaValue = 1.0f; constexpr float kEmulatedAlphaValue = 1.0f;
constexpr size_t kOcclusionQueryResultSize = sizeof(uint64_t);
// NOTE(hqle): Support ES 3.0. // NOTE(hqle): Support ES 3.0.
constexpr gl::Version kMaxSupportedGLVersion = gl::Version(2, 0); constexpr gl::Version kMaxSupportedGLVersion = gl::Version(2, 0);
......
// //
// Copyright (c) 2019 The ANGLE Project Authors. All rights reserved. // Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
......
// //
// Copyright (c) 2019 The ANGLE Project Authors. All rights reserved. // Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
......
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// mtl_occlusion_query_pool: A pool for allocating visibility query within
// one render pass.
//
#ifndef LIBANGLE_RENDERER_METAL_MTL_OCCLUSION_QUERY_POOL_H_
#define LIBANGLE_RENDERER_METAL_MTL_OCCLUSION_QUERY_POOL_H_
#include <vector>
#include "libANGLE/Context.h"
#include "libANGLE/renderer/metal/mtl_common.h"
#include "libANGLE/renderer/metal/mtl_resources.h"
namespace rx
{
class ContextMtl;
class QueryMtl;
namespace mtl
{
class OcclusionQueryPool
{
public:
OcclusionQueryPool();
~OcclusionQueryPool();
void destroy(ContextMtl *contextMtl);
// Allocate an offset in visibility buffer for a query in a render pass.
// - clearOldValue = true, if the old value of query will be cleared before combining in the
// visibility resolve pass. This flag is only allowed to be false for the first allocation of
// the render pass or the query that already has an allocated offset.
// Note: a query might have more than one allocated offset. They will be combined in the final
// step.
angle::Result allocateQueryOffset(ContextMtl *contextMtl, QueryMtl *query, bool clearOldValue);
// Deallocate all offsets used for a query.
void deallocateQueryOffset(ContextMtl *contextMtl, QueryMtl *query);
// Retrieve a buffer that will contain the visibility results of all allocated queries for
// a render pass
const BufferRef &getRenderPassVisibilityPoolBuffer() const { return mRenderPassResultsPool; }
size_t getNumRenderPassAllocatedQueries() const { return mAllocatedQueries.size(); }
// This function is called at the end of render pass
void resolveVisibilityResults(ContextMtl *contextMtl);
private:
// Buffer to hold the visibility results for current render pass
BufferRef mRenderPassResultsPool;
// List of allocated queries per render pass
std::vector<QueryMtl *> mAllocatedQueries;
bool mResetFirstQuery = false;
};
} // namespace mtl
} // namespace rx
#endif /* LIBANGLE_RENDERER_METAL_MTL_OCCLUSION_QUERY_POOL_H_ */
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// mtl_occlusion_query_pool: A visibility pool for allocating visibility query within
// one render pass.
//
#include "libANGLE/renderer/metal/mtl_occlusion_query_pool.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
#include "libANGLE/renderer/metal/QueryMtl.h"
namespace rx
{
namespace mtl
{
// OcclusionQueryPool implementation
OcclusionQueryPool::OcclusionQueryPool() {}
OcclusionQueryPool::~OcclusionQueryPool() {}
void OcclusionQueryPool::destroy(ContextMtl *contextMtl)
{
mRenderPassResultsPool = nullptr;
for (QueryMtl *allocatedQuery : mAllocatedQueries)
{
if (!allocatedQuery)
{
continue;
}
allocatedQuery->clearAllocatedVisibilityOffsets();
}
mAllocatedQueries.clear();
}
angle::Result OcclusionQueryPool::allocateQueryOffset(ContextMtl *contextMtl,
QueryMtl *query,
bool clearOldValue)
{
// Only query that already has allocated offset or first query of the render pass is allowed to
// keep old value. Other queries must be reset to zero before counting the samples visibility in
// draw calls.
ASSERT(clearOldValue || mAllocatedQueries.empty() ||
!query->getAllocatedVisibilityOffsets().empty());
uint32_t currentOffset =
static_cast<uint32_t>(mAllocatedQueries.size()) * kOcclusionQueryResultSize;
if (!mRenderPassResultsPool)
{
// First allocation
ANGLE_TRY(Buffer::MakeBufferWithResOpt(contextMtl, MTLResourceStorageModePrivate,
kOcclusionQueryResultSize, nullptr,
&mRenderPassResultsPool));
mRenderPassResultsPool->get().label = @"OcclusionQueryPool";
}
else if (currentOffset + kOcclusionQueryResultSize > mRenderPassResultsPool->size())
{
// Double the capacity
ANGLE_TRY(Buffer::MakeBufferWithResOpt(contextMtl, MTLResourceStorageModePrivate,
mRenderPassResultsPool->size() * 2, nullptr,
&mRenderPassResultsPool));
mRenderPassResultsPool->get().label = @"OcclusionQueryPool";
}
if (clearOldValue)
{
// If old value is not needed, deallocate any offset previously allocated for this query.
deallocateQueryOffset(contextMtl, query);
}
if (query->getAllocatedVisibilityOffsets().empty())
{
mAllocatedQueries.push_back(query);
query->setFirstAllocatedVisibilityOffset(currentOffset);
}
else
{
// Additional offset allocated for a query is only allowed if it is a continuous region.
ASSERT(currentOffset ==
query->getAllocatedVisibilityOffsets().back() + kOcclusionQueryResultSize);
// Just reserve an empty slot in the allocated query array
mAllocatedQueries.push_back(nullptr);
query->addAllocatedVisibilityOffset();
}
if (currentOffset == 0)
{
mResetFirstQuery = clearOldValue;
}
return angle::Result::Continue;
}
void OcclusionQueryPool::deallocateQueryOffset(ContextMtl *contextMtl, QueryMtl *query)
{
if (query->getAllocatedVisibilityOffsets().empty())
{
return;
}
mAllocatedQueries[query->getAllocatedVisibilityOffsets().front() / kOcclusionQueryResultSize] =
nullptr;
query->clearAllocatedVisibilityOffsets();
}
void OcclusionQueryPool::resolveVisibilityResults(ContextMtl *contextMtl)
{
if (mAllocatedQueries.empty())
{
return;
}
RenderUtils &utils = contextMtl->getDisplay()->getUtils();
// Combine the values stored in the offsets allocated for each of the remaining queries
for (size_t i = 0; i < mAllocatedQueries.size(); ++i)
{
QueryMtl *query = mAllocatedQueries[i];
if (!query)
{
continue;
}
const BufferRef &dstBuf = mAllocatedQueries[i]->getVisibilityResultBuffer();
const VisibilityBufferOffsetsMtl &allocatedOffsets =
mAllocatedQueries[i]->getAllocatedVisibilityOffsets();
utils.combineVisibilityResult(contextMtl, /** keepOldValue */ i == 0 && !mResetFirstQuery,
allocatedOffsets, mRenderPassResultsPool, dstBuf);
}
// Request synchronization and cleanup
BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
for (size_t i = 0; i < mAllocatedQueries.size(); ++i)
{
QueryMtl *query = mAllocatedQueries[i];
if (!query)
{
continue;
}
const BufferRef &dstBuf = mAllocatedQueries[i]->getVisibilityResultBuffer();
dstBuf->syncContent(contextMtl, blitEncoder);
query->clearAllocatedVisibilityOffsets();
}
mAllocatedQueries.clear();
}
}
}
...@@ -25,6 +25,7 @@ namespace rx ...@@ -25,6 +25,7 @@ namespace rx
class BufferMtl; class BufferMtl;
class ContextMtl; class ContextMtl;
class DisplayMtl; class DisplayMtl;
class VisibilityBufferOffsetsMtl;
namespace mtl namespace mtl
{ {
...@@ -312,6 +313,27 @@ class IndexGeneratorUtils final : angle::NonCopyable ...@@ -312,6 +313,27 @@ class IndexGeneratorUtils final : angle::NonCopyable
AutoObjCPtr<id<MTLComputePipelineState>> mTriFanFromArraysGeneratorPipeline; AutoObjCPtr<id<MTLComputePipelineState>> mTriFanFromArraysGeneratorPipeline;
}; };
// Util class for handling visibility query result
class VisibilityResultUtils
{
public:
void onDestroy();
void combineVisibilityResult(ContextMtl *contextMtl,
bool keepOldValue,
const VisibilityBufferOffsetsMtl &renderPassResultBufOffsets,
const BufferRef &renderPassResultBuf,
const BufferRef &finalResultBuf);
private:
AutoObjCPtr<id<MTLComputePipelineState>> getVisibilityResultCombPipeline(ContextMtl *contextMtl,
bool keepOldValue);
// Visibility combination compute pipeline:
// - 0: This compute pipeline only combine the new values and discard old value.
// - 1: This compute pipeline keep the old value and combine with new values.
std::array<AutoObjCPtr<id<MTLComputePipelineState>>, 2> mVisibilityResultCombPipelines;
};
// Util class for handling mipmap generation // Util class for handling mipmap generation
class MipmapUtils final : angle::NonCopyable class MipmapUtils final : angle::NonCopyable
{ {
...@@ -377,6 +399,12 @@ class RenderUtils : public Context, angle::NonCopyable ...@@ -377,6 +399,12 @@ class RenderUtils : public Context, angle::NonCopyable
angle::Result generateLineLoopLastSegmentFromElementsArray(ContextMtl *contextMtl, angle::Result generateLineLoopLastSegmentFromElementsArray(ContextMtl *contextMtl,
const IndexGenerationParams &params); const IndexGenerationParams &params);
void combineVisibilityResult(ContextMtl *contextMtl,
bool keepOldValue,
const VisibilityBufferOffsetsMtl &renderPassResultBufOffsets,
const BufferRef &renderPassResultBuf,
const BufferRef &finalResultBuf);
// Compute based mipmap generation. Only possible for 3D texture for now. // Compute based mipmap generation. Only possible for 3D texture for now.
angle::Result generateMipmapCS(ContextMtl *contextMtl, angle::Result generateMipmapCS(ContextMtl *contextMtl,
const TextureRef &srcTexture, const TextureRef &srcTexture,
...@@ -398,6 +426,7 @@ class RenderUtils : public Context, angle::NonCopyable ...@@ -398,6 +426,7 @@ class RenderUtils : public Context, angle::NonCopyable
ColorBlitUtils mColorBlitUtils; ColorBlitUtils mColorBlitUtils;
DepthStencilBlitUtils mDepthStencilBlitUtils; DepthStencilBlitUtils mDepthStencilBlitUtils;
IndexGeneratorUtils mIndexUtils; IndexGeneratorUtils mIndexUtils;
VisibilityResultUtils mVisibilityResultUtils;
MipmapUtils mMipmapUtils; MipmapUtils mMipmapUtils;
}; };
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "libANGLE/renderer/metal/BufferMtl.h" #include "libANGLE/renderer/metal/BufferMtl.h"
#include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h"
#include "libANGLE/renderer/metal/QueryMtl.h"
#include "libANGLE/renderer/metal/mtl_common.h" #include "libANGLE/renderer/metal/mtl_common.h"
#include "libANGLE/renderer/metal/mtl_utils.h" #include "libANGLE/renderer/metal/mtl_utils.h"
...@@ -34,6 +35,7 @@ namespace ...@@ -34,6 +35,7 @@ namespace
#define UNMULTIPLY_ALPHA_CONSTANT_NAME @"kUnmultiplyAlpha" #define UNMULTIPLY_ALPHA_CONSTANT_NAME @"kUnmultiplyAlpha"
#define SOURCE_TEXTURE_TYPE_CONSTANT_NAME @"kSourceTextureType" #define SOURCE_TEXTURE_TYPE_CONSTANT_NAME @"kSourceTextureType"
#define SOURCE_TEXTURE2_TYPE_CONSTANT_NAME @"kSourceTexture2Type" #define SOURCE_TEXTURE2_TYPE_CONSTANT_NAME @"kSourceTexture2Type"
#define VISIBILITY_RESULT_KEEP_OLD_VAL_CONSTANT_NAME @"kCombineWithExistingResult"
// See libANGLE/renderer/metal/shaders/clear.metal // See libANGLE/renderer/metal/shaders/clear.metal
struct ClearParamsUniform struct ClearParamsUniform
...@@ -77,6 +79,14 @@ struct IndexConversionUniform ...@@ -77,6 +79,14 @@ struct IndexConversionUniform
uint32_t padding[2]; uint32_t padding[2];
}; };
// See libANGLE/renderer/metal/shaders/visibility.metal
struct CombineVisibilityResultUniform
{
uint32_t startOffset;
uint32_t numOffsets;
uint32_t padding[2];
};
// See libANGLE/renderer/metal/shaders/gen_mipmap.metal // See libANGLE/renderer/metal/shaders/gen_mipmap.metal
struct GenerateMipmapUniform struct GenerateMipmapUniform
{ {
...@@ -86,6 +96,27 @@ struct GenerateMipmapUniform ...@@ -86,6 +96,27 @@ struct GenerateMipmapUniform
uint8_t padding[7]; uint8_t padding[7];
}; };
// Class to automatically disable occlusion query upon entering block and re-able it upon
// exiting block.
struct ScopedDisableOcclusionQuery
{
ScopedDisableOcclusionQuery(ContextMtl *contextMtl, angle::Result *resultOut)
: mContextMtl(contextMtl), mResultOut(resultOut)
{
// temporarily disable occlusion query
contextMtl->disableActiveOcclusionQueryInRenderPass();
}
~ScopedDisableOcclusionQuery()
{
*mResultOut = mContextMtl->restartActiveOcclusionQueryInRenderPass();
}
private:
ContextMtl *mContextMtl;
angle::Result *mResultOut;
};
void GetBlitTexCoords(uint32_t srcWidth, void GetBlitTexCoords(uint32_t srcWidth,
uint32_t srcHeight, uint32_t srcHeight,
const gl::Rectangle &srcRect, const gl::Rectangle &srcRect,
...@@ -579,6 +610,17 @@ angle::Result RenderUtils::generateLineLoopLastSegmentFromElementsArray( ...@@ -579,6 +610,17 @@ angle::Result RenderUtils::generateLineLoopLastSegmentFromElementsArray(
return mIndexUtils.generateLineLoopLastSegmentFromElementsArray(contextMtl, params); return mIndexUtils.generateLineLoopLastSegmentFromElementsArray(contextMtl, params);
} }
void RenderUtils::combineVisibilityResult(
ContextMtl *contextMtl,
bool keepOldValue,
const VisibilityBufferOffsetsMtl &renderPassResultBufOffsets,
const BufferRef &renderPassResultBuf,
const BufferRef &finalResultBuf)
{
return mVisibilityResultUtils.combineVisibilityResult(
contextMtl, keepOldValue, renderPassResultBufOffsets, renderPassResultBuf, finalResultBuf);
}
// Compute based mipmap generation // Compute based mipmap generation
angle::Result RenderUtils::generateMipmapCS(ContextMtl *contextMtl, angle::Result RenderUtils::generateMipmapCS(ContextMtl *contextMtl,
const TextureRef &srcTexture, const TextureRef &srcTexture,
...@@ -759,17 +801,21 @@ angle::Result ClearUtils::clearWithDraw(const gl::Context *context, ...@@ -759,17 +801,21 @@ angle::Result ClearUtils::clearWithDraw(const gl::Context *context,
{ {
return angle::Result::Continue; return angle::Result::Continue;
} }
ContextMtl *contextMtl = GetImpl(context);
setupClearWithDraw(context, cmdEncoder, overridedParams); setupClearWithDraw(context, cmdEncoder, overridedParams);
// Draw the screen aligned triangle angle::Result result;
cmdEncoder->draw(MTLPrimitiveTypeTriangle, 0, 3); {
// Need to disable occlusion query, otherwise clearing will affect the occlusion counting
ScopedDisableOcclusionQuery disableOcclusionQuery(contextMtl, &result);
// Draw the screen aligned triangle
cmdEncoder->draw(MTLPrimitiveTypeTriangle, 0, 3);
}
// Invalidate current context's state // Invalidate current context's state
ContextMtl *contextMtl = GetImpl(context);
contextMtl->invalidateState(context); contextMtl->invalidateState(context);
return angle::Result::Continue; return result;
} }
// ColorBlitUtils implementation // ColorBlitUtils implementation
...@@ -926,13 +972,18 @@ angle::Result ColorBlitUtils::blitColorWithDraw(const gl::Context *context, ...@@ -926,13 +972,18 @@ angle::Result ColorBlitUtils::blitColorWithDraw(const gl::Context *context,
ContextMtl *contextMtl = GetImpl(context); ContextMtl *contextMtl = GetImpl(context);
setupColorBlitWithDraw(context, cmdEncoder, params); setupColorBlitWithDraw(context, cmdEncoder, params);
// Draw the screen aligned triangle angle::Result result;
cmdEncoder->draw(MTLPrimitiveTypeTriangle, 0, 3); {
// Need to disable occlusion query, otherwise blitting will affect the occlusion counting
ScopedDisableOcclusionQuery disableOcclusionQuery(contextMtl, &result);
// Draw the screen aligned triangle
cmdEncoder->draw(MTLPrimitiveTypeTriangle, 0, 3);
}
// Invalidate current context's state // Invalidate current context's state
contextMtl->invalidateState(context); contextMtl->invalidateState(context);
return angle::Result::Continue; return result;
} }
// DepthStencilBlitUtils implementation // DepthStencilBlitUtils implementation
...@@ -1143,13 +1194,18 @@ angle::Result DepthStencilBlitUtils::blitDepthStencilWithDraw(const gl::Context ...@@ -1143,13 +1194,18 @@ angle::Result DepthStencilBlitUtils::blitDepthStencilWithDraw(const gl::Context
setupDepthStencilBlitWithDraw(context, cmdEncoder, params); setupDepthStencilBlitWithDraw(context, cmdEncoder, params);
// Draw the screen aligned triangle angle::Result result;
cmdEncoder->draw(MTLPrimitiveTypeTriangle, 0, 3); {
// Need to disable occlusion query, otherwise blitting will affect the occlusion counting
ScopedDisableOcclusionQuery disableOcclusionQuery(contextMtl, &result);
// Draw the screen aligned triangle
cmdEncoder->draw(MTLPrimitiveTypeTriangle, 0, 3);
}
// Invalidate current context's state // Invalidate current context's state
contextMtl->invalidateState(context); contextMtl->invalidateState(context);
return angle::Result::Continue; return result;
} }
angle::Result DepthStencilBlitUtils::blitStencilViaCopyBuffer( angle::Result DepthStencilBlitUtils::blitStencilViaCopyBuffer(
...@@ -1584,6 +1640,77 @@ angle::Result IndexGeneratorUtils::generateLineLoopLastSegmentFromElementsArrayC ...@@ -1584,6 +1640,77 @@ angle::Result IndexGeneratorUtils::generateLineLoopLastSegmentFromElementsArrayC
return generateLineLoopLastSegment(contextMtl, first, last, params.dstBuffer, params.dstOffset); return generateLineLoopLastSegment(contextMtl, first, last, params.dstBuffer, params.dstOffset);
} }
// VisibilityResultUtils implementation
void VisibilityResultUtils::onDestroy()
{
ClearPipelineStateArray(&mVisibilityResultCombPipelines);
}
AutoObjCPtr<id<MTLComputePipelineState>> VisibilityResultUtils::getVisibilityResultCombPipeline(
ContextMtl *contextMtl,
bool keepOldValue)
{
// There is no guarantee Objective-C's BOOL is equal to bool, so casting just in case.
BOOL keepOldValueVal = keepOldValue;
AutoObjCPtr<id<MTLComputePipelineState>> &cache =
mVisibilityResultCombPipelines[keepOldValueVal];
if (cache)
{
return cache;
}
ANGLE_MTL_OBJC_SCOPE
{
auto funcConstants = [[[MTLFunctionConstantValues alloc] init] ANGLE_MTL_AUTORELEASE];
[funcConstants setConstantValue:&keepOldValueVal
type:MTLDataTypeBool
withName:VISIBILITY_RESULT_KEEP_OLD_VAL_CONSTANT_NAME];
EnsureSpecializedComputePipelineInitialized(
contextMtl->getDisplay(), @"combineVisibilityResult", funcConstants, &cache);
}
return cache;
}
void VisibilityResultUtils::combineVisibilityResult(
ContextMtl *contextMtl,
bool keepOldValue,
const VisibilityBufferOffsetsMtl &renderPassResultBufOffsets,
const BufferRef &renderPassResultBuf,
const BufferRef &finalResultBuf)
{
ASSERT(!renderPassResultBufOffsets.empty());
if (renderPassResultBufOffsets.size() == 1 && !keepOldValue)
{
// Use blit command to copy directly
BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->copyBuffer(renderPassResultBuf, renderPassResultBufOffsets.front(),
finalResultBuf, 0, kOcclusionQueryResultSize);
return;
}
ComputeCommandEncoder *cmdEncoder = contextMtl->getComputeCommandEncoder();
ASSERT(cmdEncoder);
id<MTLComputePipelineState> pipeline =
getVisibilityResultCombPipeline(contextMtl, keepOldValue);
cmdEncoder->setComputePipelineState(pipeline);
CombineVisibilityResultUniform options;
// Offset is viewed as 64 bit unit in compute shader.
options.startOffset = renderPassResultBufOffsets.front() / kOcclusionQueryResultSize;
options.numOffsets = renderPassResultBufOffsets.size();
cmdEncoder->setData(options, 0);
cmdEncoder->setBuffer(renderPassResultBuf, 0, 1);
cmdEncoder->setBufferForWrite(finalResultBuf, 0, 2);
DispatchCompute(contextMtl, cmdEncoder, pipeline, 1);
}
// MipmapUtils implementation // MipmapUtils implementation
void MipmapUtils::onDestroy() void MipmapUtils::onDestroy()
{ {
......
...@@ -49,7 +49,8 @@ class Resource : angle::NonCopyable ...@@ -49,7 +49,8 @@ class Resource : angle::NonCopyable
public: public:
virtual ~Resource() {} virtual ~Resource() {}
// Check whether the resource still being used by GPU // Check whether the resource still being used by GPU including the pending (uncommitted)
// command buffer.
bool isBeingUsedByGPU(Context *context) const; bool isBeingUsedByGPU(Context *context) const;
// Checks whether the last command buffer that uses the given resource has been committed or not // Checks whether the last command buffer that uses the given resource has been committed or not
bool hasPendingWorks(Context *context) const; bool hasPendingWorks(Context *context) const;
...@@ -63,6 +64,10 @@ class Resource : angle::NonCopyable ...@@ -63,6 +64,10 @@ class Resource : angle::NonCopyable
bool isCPUReadMemNeedSync() const { return mUsageRef->cpuReadMemNeedSync; } bool isCPUReadMemNeedSync() const { return mUsageRef->cpuReadMemNeedSync; }
void resetCPUReadMemNeedSync() { mUsageRef->cpuReadMemNeedSync = false; } void resetCPUReadMemNeedSync() { mUsageRef->cpuReadMemNeedSync = false; }
// These functions are useful for BufferMtl to know whether it should update the shadow copy
bool isCPUReadMemDirty() const { return mUsageRef->cpuReadMemDirty; }
void resetCPUReadMemDirty() { mUsageRef->cpuReadMemDirty = false; }
protected: protected:
Resource(); Resource();
// Share the GPU usage ref with other resource // Share the GPU usage ref with other resource
...@@ -79,6 +84,9 @@ class Resource : angle::NonCopyable ...@@ -79,6 +84,9 @@ class Resource : angle::NonCopyable
// This flag means the resource was issued to be modified by GPU, if CPU wants to read // This flag means the resource was issued to be modified by GPU, if CPU wants to read
// its content, explicit synchronization call must be invoked. // its content, explicit synchronization call must be invoked.
bool cpuReadMemNeedSync = false; bool cpuReadMemNeedSync = false;
// This flag is useful for BufferMtl to know whether it should update the shadow copy
bool cpuReadMemDirty = false;
}; };
// One resource object might just be a view of another resource. For example, a texture 2d // One resource object might just be a view of another resource. For example, a texture 2d
...@@ -228,7 +236,7 @@ class Texture final : public Resource, ...@@ -228,7 +236,7 @@ class Texture final : public Resource,
Texture(Texture *original, MTLPixelFormat format); Texture(Texture *original, MTLPixelFormat format);
Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, NSRange slices); Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, NSRange slices);
void syncContent(ContextMtl *context); void syncContentIfNeeded(ContextMtl *context);
AutoObjCObj<MTLTextureDescriptor> mCreationDesc; AutoObjCObj<MTLTextureDescriptor> mCreationDesc;
...@@ -249,15 +257,36 @@ class Buffer final : public Resource, public WrappedObject<id<MTLBuffer>> ...@@ -249,15 +257,36 @@ class Buffer final : public Resource, public WrappedObject<id<MTLBuffer>>
const uint8_t *data, const uint8_t *data,
BufferRef *bufferOut); BufferRef *bufferOut);
static angle::Result MakeBufferWithResOpt(ContextMtl *context,
MTLResourceOptions resourceOptions,
size_t size,
const uint8_t *data,
BufferRef *bufferOut);
angle::Result reset(ContextMtl *context, size_t size, const uint8_t *data); angle::Result reset(ContextMtl *context, size_t size, const uint8_t *data);
angle::Result resetWithResOpt(ContextMtl *context,
MTLResourceOptions resourceOptions,
size_t size,
const uint8_t *data);
const uint8_t *mapReadOnly(ContextMtl *context);
uint8_t *mapWithOpt(ContextMtl *context, bool readonly);
uint8_t *map(ContextMtl *context); uint8_t *map(ContextMtl *context);
void unmap(ContextMtl *context); void unmap(ContextMtl *context);
size_t size() const; size_t size() const;
// Explicitly sync content between CPU and GPU
void syncContent(ContextMtl *context, mtl::BlitCommandEncoder *encoder);
private: private:
Buffer(ContextMtl *context, size_t size, const uint8_t *data); Buffer(ContextMtl *context, size_t size, const uint8_t *data);
Buffer(ContextMtl *context,
MTLResourceOptions resourceOptions,
size_t size,
const uint8_t *data);
bool mMapReadOnly = true;
}; };
} // namespace mtl } // namespace mtl
......
...@@ -45,6 +45,37 @@ void SetTextureSwizzle(ContextMtl *context, ...@@ -45,6 +45,37 @@ void SetTextureSwizzle(ContextMtl *context,
} }
#endif #endif
} }
// Asynchronously synchronize the content of a resource between GPU memory and its CPU cache.
// NOTE: This operation doesn't finish immediately upon function's return.
template <class T>
void InvokeCPUMemSync(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder, T *resource)
{
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
if (blitEncoder)
{
blitEncoder->synchronizeResource(resource);
resource->resetCPUReadMemNeedSync();
}
#endif
}
// Ensure that a resource's CPU cache will be synchronized after GPU finishes its modifications on
// the resource.
template <class T>
void EnsureCPUMemWillBeSynced(ContextMtl *context, T *resource)
{
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
// Make sure GPU & CPU contents are synchronized.
// NOTE: Only MacOS has separated storage for resource on CPU and GPU and needs explicit
// synchronization
if (resource->get().storageMode == MTLStorageModeManaged && resource->isCPUReadMemNeedSync())
{
mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder();
InvokeCPUMemSync(context, blitEncoder, resource);
}
#endif
}
} // namespace } // namespace
// Resource implementation // Resource implementation
Resource::Resource() : mUsageRef(std::make_shared<UsageRef>()) {} Resource::Resource() : mUsageRef(std::make_shared<UsageRef>()) {}
...@@ -58,6 +89,7 @@ Resource::Resource(Resource *other) : mUsageRef(other->mUsageRef) ...@@ -58,6 +89,7 @@ Resource::Resource(Resource *other) : mUsageRef(other->mUsageRef)
void Resource::reset() void Resource::reset()
{ {
mUsageRef->cmdBufferQueueSerial = 0; mUsageRef->cmdBufferQueueSerial = 0;
resetCPUReadMemDirty();
resetCPUReadMemNeedSync(); resetCPUReadMemNeedSync();
} }
...@@ -76,6 +108,7 @@ void Resource::setUsedByCommandBufferWithQueueSerial(uint64_t serial, bool writi ...@@ -76,6 +108,7 @@ void Resource::setUsedByCommandBufferWithQueueSerial(uint64_t serial, bool writi
if (writing) if (writing)
{ {
mUsageRef->cpuReadMemNeedSync = true; mUsageRef->cpuReadMemNeedSync = true;
mUsageRef->cpuReadMemDirty = true;
} }
mUsageRef->cmdBufferQueueSerial = std::max(mUsageRef->cmdBufferQueueSerial, serial); mUsageRef->cmdBufferQueueSerial = std::max(mUsageRef->cmdBufferQueueSerial, serial);
...@@ -296,28 +329,12 @@ Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRang ...@@ -296,28 +329,12 @@ Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRang
void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder) void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder)
{ {
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST InvokeCPUMemSync(context, blitEncoder, this);
if (blitEncoder)
{
blitEncoder->synchronizeResource(shared_from_this());
this->resetCPUReadMemNeedSync();
}
#endif
} }
void Texture::syncContent(ContextMtl *context) void Texture::syncContentIfNeeded(ContextMtl *context)
{ {
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST EnsureCPUMemWillBeSynced(context, this);
// Make sure GPU & CPU contents are synchronized.
// NOTE: Only MacOS has separated storage for resource on CPU and GPU and needs explicit
// synchronization
if (this->isCPUReadMemNeedSync())
{
mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder();
syncContent(context, blitEncoder);
}
#endif
} }
bool Texture::isCPUAccessible() const bool Texture::isCPUAccessible() const
...@@ -364,7 +381,7 @@ void Texture::replaceRegion(ContextMtl *context, ...@@ -364,7 +381,7 @@ void Texture::replaceRegion(ContextMtl *context,
CommandQueue &cmdQueue = context->cmdQueue(); CommandQueue &cmdQueue = context->cmdQueue();
syncContent(context); syncContentIfNeeded(context);
// NOTE(hqle): what if multiple contexts on multiple threads are using this texture? // NOTE(hqle): what if multiple contexts on multiple threads are using this texture?
if (this->isBeingUsedByGPU(context)) if (this->isBeingUsedByGPU(context))
...@@ -399,7 +416,7 @@ void Texture::getBytes(ContextMtl *context, ...@@ -399,7 +416,7 @@ void Texture::getBytes(ContextMtl *context,
CommandQueue &cmdQueue = context->cmdQueue(); CommandQueue &cmdQueue = context->cmdQueue();
syncContent(context); syncContentIfNeeded(context);
// NOTE(hqle): what if multiple contexts on multiple threads are using this texture? // NOTE(hqle): what if multiple contexts on multiple threads are using this texture?
if (this->isBeingUsedByGPU(context)) if (this->isBeingUsedByGPU(context))
...@@ -647,25 +664,54 @@ angle::Result Buffer::MakeBuffer(ContextMtl *context, ...@@ -647,25 +664,54 @@ angle::Result Buffer::MakeBuffer(ContextMtl *context,
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result Buffer::MakeBufferWithResOpt(ContextMtl *context,
MTLResourceOptions options,
size_t size,
const uint8_t *data,
BufferRef *bufferOut)
{
bufferOut->reset(new Buffer(context, options, size, data));
if (!bufferOut || !bufferOut->get())
{
ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
}
return angle::Result::Continue;
}
Buffer::Buffer(ContextMtl *context, size_t size, const uint8_t *data) Buffer::Buffer(ContextMtl *context, size_t size, const uint8_t *data)
{ {
(void)reset(context, size, data); (void)reset(context, size, data);
} }
Buffer::Buffer(ContextMtl *context, MTLResourceOptions options, size_t size, const uint8_t *data)
{
(void)resetWithResOpt(context, options, size, data);
}
angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *data) angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *data)
{ {
MTLResourceOptions options;
options = 0;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
options |= MTLResourceStorageModeManaged;
#endif
return resetWithResOpt(context, options, size, data);
}
angle::Result Buffer::resetWithResOpt(ContextMtl *context,
MTLResourceOptions options,
size_t size,
const uint8_t *data)
{
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
{ {
MTLResourceOptions options;
id<MTLBuffer> newBuffer; id<MTLBuffer> newBuffer;
id<MTLDevice> metalDevice = context->getMetalDevice(); id<MTLDevice> metalDevice = context->getMetalDevice();
options = 0;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
options |= MTLResourceStorageModeManaged;
#endif
if (data) if (data)
{ {
newBuffer = [metalDevice newBufferWithBytes:data length:size options:options]; newBuffer = [metalDevice newBufferWithBytes:data length:size options:options];
...@@ -677,21 +723,41 @@ angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *dat ...@@ -677,21 +723,41 @@ angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *dat
set([newBuffer ANGLE_MTL_AUTORELEASE]); set([newBuffer ANGLE_MTL_AUTORELEASE]);
// Reset command buffer's reference serial
Resource::reset();
return angle::Result::Continue; return angle::Result::Continue;
} }
} }
void Buffer::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder)
{
InvokeCPUMemSync(context, blitEncoder, this);
}
const uint8_t *Buffer::mapReadOnly(ContextMtl *context)
{
return mapWithOpt(context, true);
}
uint8_t *Buffer::map(ContextMtl *context) uint8_t *Buffer::map(ContextMtl *context)
{ {
return mapWithOpt(context, false);
}
uint8_t *Buffer::mapWithOpt(ContextMtl *context, bool readonly)
{
mMapReadOnly = readonly;
CommandQueue &cmdQueue = context->cmdQueue(); CommandQueue &cmdQueue = context->cmdQueue();
// NOTE(hqle): what if multiple contexts on multiple threads are using this buffer? EnsureCPUMemWillBeSynced(context, this);
if (this->isBeingUsedByGPU(context)) if (this->isBeingUsedByGPU(context))
{ {
context->flushCommandBufer(); context->flushCommandBufer();
} }
// NOTE(hqle): currently not support reading data written by GPU
cmdQueue.ensureResourceReadyForCPU(this); cmdQueue.ensureResourceReadyForCPU(this);
return reinterpret_cast<uint8_t *>([get() contents]); return reinterpret_cast<uint8_t *>([get() contents]);
...@@ -700,8 +766,15 @@ uint8_t *Buffer::map(ContextMtl *context) ...@@ -700,8 +766,15 @@ uint8_t *Buffer::map(ContextMtl *context)
void Buffer::unmap(ContextMtl *context) void Buffer::unmap(ContextMtl *context)
{ {
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST #if TARGET_OS_OSX || TARGET_OS_MACCATALYST
[get() didModifyRange:NSMakeRange(0, size())]; if (!mMapReadOnly)
{
if (get().storageMode == MTLStorageModeManaged)
{
[get() didModifyRange:NSMakeRange(0, size())];
}
}
#endif #endif
mMapReadOnly = true;
} }
size_t Buffer::size() const size_t Buffer::size() const
......
...@@ -115,6 +115,200 @@ TEST_P(OcclusionQueriesTest, IsNotOccluded) ...@@ -115,6 +115,200 @@ TEST_P(OcclusionQueriesTest, IsNotOccluded)
EXPECT_GL_TRUE(result); EXPECT_GL_TRUE(result);
} }
// Test that glClear should not be counted by occlusion query.
TEST_P(OcclusionQueriesTest, ClearNotCounted)
{
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
!IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean"));
// TODO(syoussefi): Using render pass ops to clear the framebuffer attachment results in
// AMD/Windows misbehaving in this test. http://anglebug.com/3286
ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
// http://anglebug.com/4925
ANGLE_SKIP_TEST_IF(IsD3D11());
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
GLuint query[2] = {0};
glGenQueriesEXT(2, query);
// First query
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[0]);
// Full screen clear
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// View port clear
glViewport(0, 0, getWindowWidth() / 2, getWindowHeight());
glScissor(0, 0, getWindowWidth() / 2, getWindowHeight());
glEnable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
EXPECT_GL_NO_ERROR();
// Second query
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[1]);
// View port clear
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// View port clear
glViewport(0, 0, getWindowWidth() / 2, getWindowHeight());
glScissor(0, 0, getWindowWidth() / 2, getWindowHeight());
glEnable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// this quad should not be occluded
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f);
// Clear again
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// this quad should not be occluded
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 1.0);
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
EXPECT_GL_NO_ERROR();
swapBuffers();
GLuint result[2] = {GL_TRUE, GL_TRUE};
glGetQueryObjectuivEXT(query[0], GL_QUERY_RESULT_EXT,
&result[0]); // will block waiting for result
glGetQueryObjectuivEXT(query[1], GL_QUERY_RESULT_EXT,
&result[1]); // will block waiting for result
EXPECT_GL_NO_ERROR();
glDeleteQueriesEXT(2, query);
EXPECT_GL_FALSE(result[0]);
EXPECT_GL_TRUE(result[1]);
}
// Test multiple occlusion queries.
TEST_P(OcclusionQueriesTest, MultiQueries)
{
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
!IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean"));
// TODO(syoussefi): Using render pass ops to clear the framebuffer attachment results in
// AMD/Windows misbehaving in this test. http://anglebug.com/3286
ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
// http://anglebug.com/4925
ANGLE_SKIP_TEST_IF(IsOpenGL() || IsD3D11());
// http://anglebug.com/4925
ANGLE_SKIP_TEST_IF(IsMetal() && IsNVIDIA());
GLuint query[5] = {};
glGenQueriesEXT(5, query);
// First query
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[0]);
EXPECT_GL_NO_ERROR();
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); // this quad should not be occluded
EXPECT_GL_NO_ERROR();
// A flush shound't clear the query result
glFlush();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), -2, 0.25f); // this quad should be occluded
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
// First query ends
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f,
0.25f); // this quad should not be occluded
// Second query
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[1]);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f,
0.25f); // this quad should be occluded
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
// Third query
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[2]);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f,
0.5f); // this quad should not be occluded
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
// ------------
glFlush();
glViewport(0, 0, getWindowWidth() / 2, getWindowHeight());
glScissor(0, 0, getWindowWidth() / 2, getWindowHeight());
glEnable(GL_SCISSOR_TEST);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f,
0.5f); // this quad should not be occluded
// Fourth query: begin query then end then begin again
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[3]);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f,
1); // this quad should not be occluded
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[3]);
EXPECT_GL_NO_ERROR();
// glClear should not be counted toward query);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
// Fifth query spans across frames
glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[4]);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f,
0.25f); // this quad should not be occluded
swapBuffers();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f,
0.5f); // this quad should not be occluded
glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
GLuint result = GL_TRUE;
glGetQueryObjectuivEXT(query[0], GL_QUERY_RESULT_EXT,
&result); // will block waiting for result
EXPECT_GL_NO_ERROR();
EXPECT_GL_TRUE(result);
glGetQueryObjectuivEXT(query[1], GL_QUERY_RESULT_EXT,
&result); // will block waiting for result
EXPECT_GL_NO_ERROR();
EXPECT_GL_FALSE(result);
glGetQueryObjectuivEXT(query[2], GL_QUERY_RESULT_EXT,
&result); // will block waiting for result
EXPECT_GL_NO_ERROR();
EXPECT_GL_TRUE(result);
glGetQueryObjectuivEXT(query[3], GL_QUERY_RESULT_EXT,
&result); // will block waiting for result
EXPECT_GL_NO_ERROR();
EXPECT_GL_FALSE(result);
glGetQueryObjectuivEXT(query[4], GL_QUERY_RESULT_EXT,
&result); // will block waiting for result
EXPECT_GL_NO_ERROR();
EXPECT_GL_TRUE(result);
glDeleteQueriesEXT(5, query);
}
TEST_P(OcclusionQueriesTest, Errors) TEST_P(OcclusionQueriesTest, Errors)
{ {
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment