FrameCapture: Implement capture of coherent bufferstorages.

Track buffers with the GL_MAP_COHERENT_BIT_EXT access flag in a
CoherentBufferTracker on a per page basis using the
PageFaultHandler and protection functions from system_utils.

Check for dirty memory and capture it on draw calls.

Bug: angleproject:5857
Change-Id: Ib098f96952db7583d3e6517570c179e7754f54b2
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3168629
Reviewed-by: Cody Northrop <cnorthrop@google.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
This commit is contained in:
Lubosz Sarnecki 2021-09-14 16:34:31 +02:00 коммит произвёл Angle LUCI CQ
Родитель f236c8f29b
Коммит d4b8cd5eac
3 изменённых файлов: 551 добавлений и 7 удалений

Просмотреть файл

@ -2637,9 +2637,11 @@ void CaptureShareGroupMidExecutionSetup(const gl::Context *context,
resourceTracker->setStartingBufferMapped(buffer->id().value, true);
frameCaptureShared->trackBufferMapping(
&setupCalls->back(), buffer->id(), static_cast<GLsizeiptr>(buffer->getMapOffset()),
&setupCalls->back(), buffer->id(), buffer,
static_cast<GLsizeiptr>(buffer->getMapOffset()),
static_cast<GLsizeiptr>(buffer->getMapLength()),
(buffer->getAccessFlags() & GL_MAP_WRITE_BIT) != 0);
(buffer->getAccessFlags() & GL_MAP_WRITE_BIT) != 0,
(buffer->getAccessFlags() & GL_MAP_COHERENT_BIT_EXT) != 0);
}
else
{
@ -4613,11 +4615,327 @@ void FrameCaptureShared::captureCompressedTextureData(const gl::Context *context
}
}
PageRange::PageRange(size_t start, size_t end) : start(start), end(end) {}
PageRange::~PageRange() = default;
AddressRange::AddressRange() {}
AddressRange::AddressRange(uintptr_t start, size_t size) : start(start), size(size) {}
AddressRange::~AddressRange() = default;
uintptr_t AddressRange::end()
{
return start + size;
}
CoherentBuffer::CoherentBuffer(uintptr_t start, size_t size, size_t pageSize) : mPageSize(pageSize)
{
mRange.start = start;
mRange.size = size;
mProtectionRange.start = rx::roundDownPow2(start, pageSize);
uintptr_t protectionEnd = rx::roundUpPow2(start + size, pageSize);
mProtectionRange.size = protectionEnd - mProtectionRange.start;
mPageCount = mProtectionRange.size / pageSize;
mProtectionStartPage = mProtectionRange.start / mPageSize;
mProtectionEndPage = mProtectionStartPage + mPageCount;
mDirtyPages = std::vector<bool>(mPageCount);
mDirtyPages.assign(mPageCount, true);
}
std::vector<PageRange> CoherentBuffer::getDirtyPageRanges()
{
std::vector<PageRange> dirtyPageRanges;
bool inDirty = false;
for (size_t i = 0; i < mPageCount; i++)
{
if (!inDirty && mDirtyPages[i])
{
// Found start of a dirty range
inDirty = true;
// Set end page as last page initially
dirtyPageRanges.push_back(PageRange(i, mPageCount));
}
else if (inDirty && !mDirtyPages[i])
{
// Found end of a dirty range
inDirty = false;
dirtyPageRanges.back().end = i;
}
}
return dirtyPageRanges;
}
AddressRange CoherentBuffer::getRange()
{
return mRange;
}
AddressRange CoherentBuffer::getDirtyAddressRange(const PageRange &dirtyPageRange)
{
AddressRange range;
if (dirtyPageRange.start == 0)
{
// First page, use non page aligned buffer start.
range.start = mRange.start;
}
else
{
range.start = mProtectionRange.start + dirtyPageRange.start * mPageSize;
}
if (dirtyPageRange.end == mPageCount)
{
// Last page, use non page aligned buffer end.
range.size = mRange.end() - range.start;
}
else
{
range.size = (dirtyPageRange.end - dirtyPageRange.start) * mPageSize;
// This occurs when a buffer occupies 2 pages, but is smaller than a page.
if (mRange.end() < range.end())
{
range.size = mRange.end() - range.start;
}
}
// Dirty range must be in buffer
ASSERT(range.start >= mRange.start && mRange.end() >= range.end());
return range;
}
CoherentBuffer::~CoherentBuffer() {}
bool CoherentBuffer::isDirty()
{
return std::find(mDirtyPages.begin(), mDirtyPages.end(), true) != mDirtyPages.end();
}
bool CoherentBuffer::contains(size_t page, size_t *relativePage)
{
bool isInProtectionRange = page >= mProtectionStartPage && page < mProtectionEndPage;
if (!isInProtectionRange)
{
return false;
}
*relativePage = page - mProtectionStartPage;
ASSERT(page >= mProtectionStartPage);
return true;
}
void CoherentBuffer::protectPageRange(const PageRange &pageRange)
{
for (size_t i = pageRange.start; i < pageRange.end; i++)
{
setDirty(i, false);
}
}
void CoherentBuffer::setDirty(size_t relativePage, bool dirty)
{
if (mDirtyPages[relativePage] == dirty)
{
// The page is already set.
// This can happen when tracked buffers overlap in a page.
return;
}
uintptr_t pageStart = mProtectionRange.start + relativePage * mPageSize;
// Last page end must be the same as protection end
if (relativePage + 1 == mPageCount)
{
ASSERT(mProtectionRange.end() == pageStart + mPageSize);
}
bool ret;
if (dirty)
{
ret = UnprotectMemory(pageStart, mPageSize);
}
else
{
ret = ProtectMemory(pageStart, mPageSize);
}
if (!ret)
{
ERR() << "Could not set protection for buffer page " << relativePage << " at "
<< reinterpret_cast<void *>(pageStart) << " with size " << mPageSize;
}
mDirtyPages[relativePage] = dirty;
}
void CoherentBuffer::removeProtection()
{
if (!UnprotectMemory(mProtectionRange.start, mProtectionRange.size))
{
ERR() << "Could not remove protection for buffer at " << mProtectionRange.start
<< " with size " << mProtectionRange.size;
}
}
CoherentBufferTracker::CoherentBufferTracker()
{
mPageSize = GetPageSize();
PageFaultCallback callback = [this](uintptr_t address) { return handleWrite(address); };
mPageFaultHandler = std::unique_ptr<PageFaultHandler>(CreatePageFaultHandler(callback));
}
CoherentBufferTracker::~CoherentBufferTracker()
{
disable();
}
PageFaultHandlerRangeType CoherentBufferTracker::handleWrite(uintptr_t address)
{
std::lock_guard<std::mutex> lock(mMutex);
auto pagesInBuffers = getBufferPagesForAddress(address);
if (pagesInBuffers.empty())
{
ERR() << "Didn't find a tracked buffer containing " << reinterpret_cast<void *>(address);
}
for (const auto &page : pagesInBuffers)
{
std::shared_ptr<CoherentBuffer> buffer = page.first;
size_t relativePage = page.second;
buffer->setDirty(relativePage, true);
}
return pagesInBuffers.empty() ? PageFaultHandlerRangeType::OutOfRange
: PageFaultHandlerRangeType::InRange;
}
HashMap<std::shared_ptr<CoherentBuffer>, size_t> CoherentBufferTracker::getBufferPagesForAddress(
uintptr_t address)
{
HashMap<std::shared_ptr<CoherentBuffer>, size_t> foundPages;
size_t page = address / mPageSize;
for (const auto &pair : mBuffers)
{
std::shared_ptr<CoherentBuffer> buffer = pair.second;
size_t relativePage;
if (buffer->contains(page, &relativePage))
{
foundPages.insert(std::make_pair(buffer, relativePage));
}
}
return foundPages;
}
bool CoherentBufferTracker::isDirty(gl::BufferID id)
{
return mBuffers[id.value]->isDirty();
}
void CoherentBufferTracker::enable()
{
if (mEnabled)
{
return;
}
bool ret = mPageFaultHandler->enable();
if (ret)
{
mEnabled = true;
}
else
{
ERR() << "Could not enable page fault handler.";
}
}
bool CoherentBufferTracker::haveBuffer(gl::BufferID id)
{
return mBuffers.find(id.value) != mBuffers.end();
}
void CoherentBufferTracker::onEndFrame()
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mEnabled)
{
return;
}
// Remove protection from all buffers
for (const auto &pair : mBuffers)
{
std::shared_ptr<CoherentBuffer> buffer = pair.second;
buffer->removeProtection();
}
disable();
}
void CoherentBufferTracker::disable()
{
if (!mEnabled)
{
return;
}
if (mPageFaultHandler->disable())
{
mEnabled = false;
}
else
{
ERR() << "Could not disable page fault handler.";
}
}
void CoherentBufferTracker::addBuffer(gl::BufferID id, uintptr_t start, size_t size)
{
std::lock_guard<std::mutex> lock(mMutex);
if (haveBuffer(id))
{
return;
}
auto buffer = std::make_shared<CoherentBuffer>(start, size, mPageSize);
mBuffers.insert(std::make_pair(id.value, std::move(buffer)));
}
void CoherentBufferTracker::removeBuffer(gl::BufferID id)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!haveBuffer(id))
{
return;
}
// Expecting that no buffer that overlapps in a page with another is removed
// while the other is kept and still written to. These writes would be missed.
mBuffers[id.value]->removeProtection();
mBuffers.erase(id.value);
}
void FrameCaptureShared::trackBufferMapping(CallCapture *call,
gl::BufferID id,
gl::Buffer *buffer,
GLintptr offset,
GLsizeiptr length,
bool writable)
bool writable,
bool coherent)
{
// Track that the buffer was mapped
mResourceTracker.setBufferMapped(id.value);
@ -4634,6 +4952,14 @@ void FrameCaptureShared::trackBufferMapping(CallCapture *call,
// Track the bufferID that was just mapped for use when writing return value
call->params.setMappedBufferID(id);
// Track coherent buffer
if (coherent)
{
mCoherentBufferTracker.enable();
uintptr_t data = reinterpret_cast<uintptr_t>(buffer->getMapPointer());
mCoherentBufferTracker.addBuffer(id, data, length);
}
}
}
@ -4850,6 +5176,25 @@ void FrameCaptureShared::maybeOverrideEntryPoint(const gl::Context *context,
}
}
void FrameCaptureShared::maybeCaptureCoherentBuffers(const gl::Context *context)
{
if (!isCaptureActive())
{
return;
}
std::lock_guard<std::mutex> lock(mCoherentBufferTracker.mMutex);
for (const auto &pair : mCoherentBufferTracker.mBuffers)
{
gl::BufferID id = {pair.first};
if (mCoherentBufferTracker.isDirty(id))
{
captureCoherentBufferSnapshot(context, id);
}
}
}
void FrameCaptureShared::maybeCaptureDrawArraysClientData(const gl::Context *context,
CallCapture &call,
size_t instanceCount)
@ -5080,6 +5425,7 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
case EntryPoint::GLDrawArrays:
{
maybeCaptureDrawArraysClientData(context, call, 1);
maybeCaptureCoherentBuffers(context);
break;
}
@ -5090,13 +5436,16 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
GLsizei instancecount =
call.params.getParamFlexName("instancecount", "primcount", ParamType::TGLsizei, 3)
.value.GLsizeiVal;
maybeCaptureDrawArraysClientData(context, call, instancecount);
maybeCaptureCoherentBuffers(context);
break;
}
case EntryPoint::GLDrawElements:
{
maybeCaptureDrawElementsClientData(context, call, 1);
maybeCaptureCoherentBuffers(context);
break;
}
@ -5107,7 +5456,9 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
GLsizei instancecount =
call.params.getParamFlexName("instancecount", "primcount", ParamType::TGLsizei, 4)
.value.GLsizeiVal;
maybeCaptureDrawElementsClientData(context, call, instancecount);
maybeCaptureCoherentBuffers(context);
break;
}
@ -5247,9 +5598,12 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
bool writable =
access == GL_WRITE_ONLY_OES || access == GL_WRITE_ONLY || access == GL_READ_WRITE;
bool coherent = access & GL_MAP_COHERENT_BIT_EXT;
FrameCaptureShared *frameCaptureShared =
context->getShareGroup()->getFrameCaptureShared();
frameCaptureShared->trackBufferMapping(&call, buffer->id(), offset, length, writable);
frameCaptureShared->trackBufferMapping(&call, buffer->id(), buffer, offset, length,
writable, coherent);
break;
}
@ -5276,8 +5630,9 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
FrameCaptureShared *frameCaptureShared =
context->getShareGroup()->getFrameCaptureShared();
frameCaptureShared->trackBufferMapping(&call, buffer->id(), offset, length,
access & GL_MAP_WRITE_BIT);
frameCaptureShared->trackBufferMapping(&call, buffer->id(), buffer, offset, length,
access & GL_MAP_WRITE_BIT,
access & GL_MAP_COHERENT_BIT_EXT);
break;
}
@ -5293,6 +5648,9 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
.value.BufferBindingVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
mResourceTracker.setBufferUnmapped(buffer->id().value);
// Remove from CoherentBufferTracker
mCoherentBufferTracker.removeBuffer(buffer->id());
break;
}
@ -5320,6 +5678,11 @@ void FrameCaptureShared::maybeCapturePreCallUpdates(
break;
}
case EntryPoint::GLCopyBufferSubData:
{
maybeCaptureCoherentBuffers(context);
break;
}
default:
break;
}
@ -5618,6 +5981,76 @@ void FrameCaptureShared::captureClientArraySnapshot(const gl::Context *context,
}
}
void FrameCaptureShared::captureCoherentBufferSnapshot(const gl::Context *context, gl::BufferID id)
{
if (!hasBufferData(id))
{
// This buffer was not marked writable
return;
}
const gl::State &apiState = context->getState();
const gl::BufferManager &buffers = apiState.getBufferManagerForCapture();
gl::Buffer *buffer = buffers.getBuffer(id);
if (!buffer)
{
// Could not find buffer binding
return;
}
ASSERT(buffer->isMapped());
std::shared_ptr<angle::CoherentBuffer> coherentBuffer =
mCoherentBufferTracker.mBuffers[id.value];
std::vector<PageRange> dirtyPageRanges = coherentBuffer->getDirtyPageRanges();
AddressRange wholeRange = coherentBuffer->getRange();
for (PageRange &pageRange : dirtyPageRanges)
{
// Write protect the memory already, so the app is blocked on writing during our capture
coherentBuffer->protectPageRange(pageRange);
// Create the parameters to our helper for use during replay
ParamBuffer dataParamBuffer;
// Pass in the target buffer ID
dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value);
// Capture the current buffer data with a binary param
ParamCapture captureData("source", ParamType::TvoidConstPointer);
AddressRange dirtyRange = coherentBuffer->getDirtyAddressRange(pageRange);
CaptureMemory(reinterpret_cast<void *>(dirtyRange.start), dirtyRange.size, &captureData);
dataParamBuffer.addParam(std::move(captureData));
// Also track its size for use with memcpy
dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr,
static_cast<GLsizeiptr>(dirtyRange.size));
if (wholeRange.start != dirtyRange.start)
{
// Capture with offset
GLsizeiptr offset = dirtyRange.start - wholeRange.start;
ASSERT(offset > 0);
// The dirty page range is not at the start of the buffer, track the offset.
dataParamBuffer.addValueParam<GLsizeiptr>("offset", ParamType::TGLsizeiptr, offset);
// Call the helper that populates the buffer with captured data
mFrameCalls.emplace_back("UpdateClientBufferData2WithOffset",
std::move(dataParamBuffer));
}
else
{
// Call the helper that populates the buffer with captured data
mFrameCalls.emplace_back("UpdateClientBufferData2", std::move(dataParamBuffer));
}
}
}
void FrameCaptureShared::captureMappedBufferSnapshot(const gl::Context *context,
const CallCapture &call)
{
@ -5786,6 +6219,7 @@ void FrameCaptureShared::onEndFrame(const gl::Context *context)
if (!enabled() || mFrameIndex > mCaptureEndFrame)
{
setCaptureInactive();
mCoherentBufferTracker.onEndFrame();
return;
}

Просмотреть файл

@ -11,6 +11,7 @@
#define LIBANGLE_FRAME_CAPTURE_H_
#include "common/PackedEnums.h"
#include "common/system_utils.h"
#include "libANGLE/Context.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/capture/frame_capture_utils_autogen.h"
@ -441,6 +442,105 @@ class FrameCapture final : angle::NonCopyable
std::vector<CallCapture> mSetupCalls;
};
// Page range inside a coherent buffer
struct PageRange
{
PageRange(size_t start, size_t end);
~PageRange();
// Relative start page
size_t start;
// First page after the relative end
size_t end;
};
// Memory address range defined by start and size
struct AddressRange
{
AddressRange();
AddressRange(uintptr_t start, size_t size);
~AddressRange();
uintptr_t end();
uintptr_t start;
size_t size;
};
class CoherentBuffer
{
public:
CoherentBuffer(uintptr_t start, size_t size, size_t pageSize);
~CoherentBuffer();
// Sets the a range in the buffer clean and protects a selected range
void protectPageRange(const PageRange &pageRange);
// Sets a page dirty state and sets it's protection
void setDirty(size_t relativePage, bool dirty);
// Removes protection completely
void removeProtection();
bool contains(size_t page, size_t *relativePage);
bool isDirty();
// Returns dirty page ranges
std::vector<PageRange> getDirtyPageRanges();
// Calculates address range from page range
AddressRange getDirtyAddressRange(const PageRange &dirtyPageRange);
AddressRange getRange();
private:
// Actual buffer start and size
AddressRange mRange;
// Start and size of page aligned protected area
AddressRange mProtectionRange;
// Start and end of protection in relative pages, calculated from mProtectionRange.
size_t mProtectionStartPage;
size_t mProtectionEndPage;
size_t mPageCount;
size_t mPageSize;
// Clean pages are protected
std::vector<bool> mDirtyPages;
};
class CoherentBufferTracker final : angle::NonCopyable
{
public:
CoherentBufferTracker();
~CoherentBufferTracker();
bool isDirty(gl::BufferID id);
void addBuffer(gl::BufferID id, uintptr_t start, size_t size);
void removeBuffer(gl::BufferID id);
void disable();
void enable();
void onEndFrame();
private:
// Returns a map to found buffers and the corresponding pages for a given address.
// For addresses that are in a page shared by 2 buffers, 2 results are returned.
HashMap<std::shared_ptr<CoherentBuffer>, size_t> getBufferPagesForAddress(uintptr_t address);
PageFaultHandlerRangeType handleWrite(uintptr_t address);
bool haveBuffer(gl::BufferID id);
public:
std::mutex mMutex;
HashMap<GLuint, std::shared_ptr<CoherentBuffer>> mBuffers;
private:
bool mEnabled = false;
std::unique_ptr<PageFaultHandler> mPageFaultHandler;
size_t mPageSize;
};
// Shared class for any items that need to be tracked by FrameCapture across shared contexts
class FrameCaptureShared final : angle::NonCopyable
{
@ -464,9 +564,11 @@ class FrameCaptureShared final : angle::NonCopyable
void trackBufferMapping(CallCapture *call,
gl::BufferID id,
gl::Buffer *buffer,
GLintptr offset,
GLsizeiptr length,
bool writable);
bool writable,
bool coherent);
void trackTextureUpdate(const gl::Context *context, const CallCapture &call);
@ -495,6 +597,9 @@ class FrameCaptureShared final : angle::NonCopyable
GLint level,
EntryPoint entryPoint);
// Capture coherent buffer storages
void captureCoherentBufferSnapshot(const gl::Context *context, gl::BufferID bufferID);
// Remove any cached texture levels on deletion
void deleteCachedTextureLevelData(gl::TextureID id);
@ -602,6 +707,7 @@ class FrameCaptureShared final : angle::NonCopyable
void maybeCaptureDrawElementsClientData(const gl::Context *context,
CallCapture &call,
size_t instanceCount);
void maybeCaptureCoherentBuffers(const gl::Context *context);
void updateCopyImageSubData(CallCapture &call);
void overrideProgramBinary(const gl::Context *context,
CallCapture &call,
@ -645,6 +751,7 @@ class FrameCaptureShared final : angle::NonCopyable
std::string mValidationExpression;
bool mTrimEnabled = true;
PackedEnumMap<ResourceIDType, uint32_t> mMaxAccessedResourceIDs;
CoherentBufferTracker mCoherentBufferTracker;
ResourceTracker mResourceTracker;
ReplayWriter mReplayWriter;

Просмотреть файл

@ -47,4 +47,7 @@ const ProgramSources &FrameCaptureShared::getProgramSources(gl::ShaderProgramID
return foundSources->second;
}
void FrameCaptureShared::setProgramSources(gl::ShaderProgramID id, ProgramSources sources) {}
CoherentBufferTracker::CoherentBufferTracker() {}
CoherentBufferTracker::~CoherentBufferTracker() {}
} // namespace angle