blob: 9ee081ae7aecd1b0418a769dbeaa4b86e65dc568 [file] [edit]
/*
* Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// OPT: Optimization by using page-guard for speed up capture
// The speed is extremely slow when use vktrace to capture DOOM4. It took over half a day and 900G of trace for a capture from
// beginning to the game menu.
// The reason that caused such slow capture is DOOM updates a big mapped memory(over 67M) frequently, vktrace copies this memory
// block to harddrive when DOOM calls vkFlushmappedMemory to update it every time.
// Here we use page guard to record which page of big memory block has been changed and only save those changed pages, it make
// the capture time reduce to round 15 minutes, the trace file size is round 40G,
// The Playback time for these trace file is round 7 minutes(on Win10/AMDFury/32GRam/I5 system).
#include "vktrace_pageguard_memorycopy.h"
#include "vktrace_lib_pagestatusarray.h"
#include "vktrace_lib_pageguardmappedmemory.h"
#include "vktrace_lib_pageguardcapture.h"
#include "vktrace_lib_pageguard.h"
PageGuardCapture::PageGuardCapture() {
EmptyChangedInfoArray.offset = 0;
EmptyChangedInfoArray.length = 0;
#if defined(PLATFORM_LINUX) && !defined(ANDROID)
// Open the /proc/self/clear_refs file. We'll write to that file
// when we want to clear all the page dirty bits in /proc/self/pagemap.
clearRefsFd = open("/proc/self/clear_refs", O_WRONLY);
if (clearRefsFd < 0) VKTRACE_FATAL_ERROR("Open of /proc/self/clear_refs failed.");
// Clear the dirty bits, i.e. write a '4' to clear_refs. Some older
// kernels may require that '4' be written to it in order
// for /proc/self/pagemap to work as we we expect it to.
pageRefsDirtyClear();
#endif
}
std::unordered_map<VkDeviceMemory, PageGuardMappedMemory>& PageGuardCapture::getMapMemory() { return MapMemory; }
void PageGuardCapture::vkMapMemoryPageGuardHandle(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size,
VkFlags flags, void** ppData) {
PageGuardMappedMemory OPTmappedmem;
if (getPageGuardEnableFlag()) {
#ifdef PAGEGUARD_TARGET_RANGE_SIZE_CONTROL
if (size >= ref_target_range_size())
#endif
{
OPTmappedmem.vkMapMemoryPageGuardHandle(device, memory, offset, size, flags, ppData);
MapMemory[memory] = OPTmappedmem;
}
}
MapMemoryPtr[memory] = (PBYTE)(*ppData);
MapMemoryOffset[memory] = offset;
MapMemorySize[memory] = size;
}
void PageGuardCapture::vkUnmapMemoryPageGuardHandle(VkDevice device, VkDeviceMemory memory, void** MappedData,
vkFlushMappedMemoryRangesFunc pFunc) {
LPPageGuardMappedMemory lpOPTMemoryTemp = findMappedMemoryObject(device, memory);
if (lpOPTMemoryTemp) {
VkMappedMemoryRange memoryRange;
flushTargetChangedMappedMemory(lpOPTMemoryTemp, pFunc, &memoryRange);
lpOPTMemoryTemp->vkUnmapMemoryPageGuardHandle(device, memory, MappedData);
MapMemory.erase(memory);
}
MapMemoryPtr.erase(memory);
MapMemoryOffset.erase(memory);
MapMemorySize.erase(memory);
}
void* PageGuardCapture::getMappedMemoryPointer(VkDevice device, VkDeviceMemory memory) { return MapMemoryPtr[memory]; }
VkDeviceSize PageGuardCapture::getMappedMemoryOffset(VkDevice device, VkDeviceMemory memory) { return MapMemoryOffset[memory]; }
VkDeviceSize PageGuardCapture::getMappedMemorySize(VkDevice device, VkDeviceMemory memory) { return MapMemorySize[memory]; }
// return: if it's target mapped memory and no change at all;
// PBYTE *ppPackageDataforOutOfMap, must be an array include memoryRangeCount elements
bool PageGuardCapture::vkFlushMappedMemoryRangesPageGuardHandle(VkDevice device, uint32_t memoryRangeCount,
const VkMappedMemoryRange* pMemoryRanges,
PBYTE* ppPackageDataforOutOfMap) {
bool handleSuccessfully = false, bChanged = false;
std::unordered_map<VkDeviceMemory, PageGuardMappedMemory>::const_iterator mappedmem_it;
for (uint32_t i = 0; i < memoryRangeCount; i++) {
VkMappedMemoryRange* pRange = (VkMappedMemoryRange*)&pMemoryRanges[i];
ppPackageDataforOutOfMap[i] = nullptr;
LPPageGuardMappedMemory lpOPTMemoryTemp = findMappedMemoryObject(device, pRange->memory);
if (lpOPTMemoryTemp) {
if (pRange->size == VK_WHOLE_SIZE) {
pRange->size = lpOPTMemoryTemp->getMappedSize() - (pRange->offset - lpOPTMemoryTemp->MappedOffset);
}
if (lpOPTMemoryTemp->vkFlushMappedMemoryRangePageGuardHandle(device, pRange->memory, pRange->offset, pRange->size,
nullptr, nullptr, nullptr)) {
bChanged = true;
}
} else {
bChanged = true;
VkDeviceSize RealRangeSize = pRange->size;
if (RealRangeSize == VK_WHOLE_SIZE) {
RealRangeSize = MapMemorySize[pRange->memory] - (pRange->offset - MapMemoryOffset[pRange->memory]);
}
ppPackageDataforOutOfMap[i] = (PBYTE)pageguardAllocateMemory(RealRangeSize + 2 * sizeof(PageGuardChangedBlockInfo));
PageGuardChangedBlockInfo* pInfoTemp = (PageGuardChangedBlockInfo*)ppPackageDataforOutOfMap[i];
pInfoTemp[0].offset = 1;
pInfoTemp[0].length = (DWORD)RealRangeSize;
pInfoTemp[0].reserve0 = 0;
pInfoTemp[0].reserve1 = 0;
pInfoTemp[1].offset = pRange->offset - getMappedMemoryOffset(device, pRange->memory);
pInfoTemp[1].length = (DWORD)RealRangeSize;
pInfoTemp[1].reserve0 = 0;
pInfoTemp[1].reserve1 = 0;
PBYTE pDataInPackage = (PBYTE)(pInfoTemp + 2);
void* pDataMapped = getMappedMemoryPointer(device, pRange->memory);
vktrace_pageguard_memcpy(pDataInPackage, reinterpret_cast<PBYTE>(pDataMapped) + pInfoTemp[1].offset, RealRangeSize);
}
}
if (!bChanged) {
handleSuccessfully = true;
}
return handleSuccessfully;
}
LPPageGuardMappedMemory PageGuardCapture::findMappedMemoryObject(VkDevice device, VkDeviceMemory memory) {
LPPageGuardMappedMemory pMappedMemoryObject = nullptr;
std::unordered_map<VkDeviceMemory, PageGuardMappedMemory>::const_iterator mappedmem_it;
mappedmem_it = MapMemory.find(memory);
if (mappedmem_it != MapMemory.end()) {
pMappedMemoryObject = ((PageGuardMappedMemory*)&(mappedmem_it->second));
if (pMappedMemoryObject->MappedDevice != device) {
pMappedMemoryObject = nullptr;
}
}
return pMappedMemoryObject;
}
LPPageGuardMappedMemory PageGuardCapture::findMappedMemoryObject(PBYTE addr, VkDeviceSize* pOffsetOfAddr, PBYTE* ppBlock,
VkDeviceSize* pBlockSize) {
LPPageGuardMappedMemory pMappedMemoryObject = nullptr;
LPPageGuardMappedMemory pMappedMemoryTemp;
PBYTE pBlock = nullptr;
VkDeviceSize OffsetOfAddr = 0, BlockSize = 0;
for (std::unordered_map<VkDeviceMemory, PageGuardMappedMemory>::iterator it = MapMemory.begin(); it != MapMemory.end(); it++) {
pMappedMemoryTemp = &(it->second);
if ((addr >= pMappedMemoryTemp->pMappedData) && (addr < (pMappedMemoryTemp->pMappedData + pMappedMemoryTemp->MappedSize))) {
pMappedMemoryObject = pMappedMemoryTemp;
OffsetOfAddr = (VkDeviceSize)(addr - pMappedMemoryTemp->pMappedData);
BlockSize = pMappedMemoryTemp->PageGuardSize;
pBlock = addr - OffsetOfAddr % BlockSize;
if (ppBlock) {
*ppBlock = pBlock;
}
if (pBlockSize) {
*pBlockSize = BlockSize;
}
if (pOffsetOfAddr) {
*pOffsetOfAddr = OffsetOfAddr;
}
return pMappedMemoryObject;
}
}
return NULL;
}
LPPageGuardMappedMemory PageGuardCapture::findMappedMemoryObject(VkDevice device, const VkMappedMemoryRange* pMemoryRange) {
LPPageGuardMappedMemory pMappedMemoryObject = findMappedMemoryObject(device, pMemoryRange->memory);
return pMappedMemoryObject;
}
// get size of all changed package in array of pMemoryRanges
VkDeviceSize PageGuardCapture::getALLChangedPackageSizeInMappedMemory(VkDevice device, uint32_t memoryRangeCount,
const VkMappedMemoryRange* pMemoryRanges,
PBYTE* ppPackageDataforOutOfMap) {
VkDeviceSize allChangedPackageSize = 0, PackageSize = 0;
LPPageGuardMappedMemory pMappedMemoryTemp;
for (uint32_t i = 0; i < memoryRangeCount; i++) {
pMappedMemoryTemp = findMappedMemoryObject(device, pMemoryRanges + i);
if (pMappedMemoryTemp) {
pMappedMemoryTemp->getChangedDataPackage(&PackageSize);
} else {
PageGuardChangedBlockInfo* pInfoTemp = (PageGuardChangedBlockInfo*)ppPackageDataforOutOfMap[i];
PackageSize = pInfoTemp->length + 2 * sizeof(PageGuardChangedBlockInfo);
}
allChangedPackageSize += PackageSize;
}
return ROUNDUP_TO_4(allChangedPackageSize);
}
// get ptr and size of OPTChangedDataPackage;
PBYTE PageGuardCapture::getChangedDataPackageOutOfMap(PBYTE* ppPackageDataforOutOfMap, DWORD dwRangeIndex, VkDeviceSize* pSize) {
PBYTE pDataPackage = (PBYTE)ppPackageDataforOutOfMap[dwRangeIndex];
PageGuardChangedBlockInfo* pInfo = (PageGuardChangedBlockInfo*)pDataPackage;
if (pSize) {
*pSize = sizeof(PageGuardChangedBlockInfo) * 2 + pInfo->length;
}
return pDataPackage;
}
void PageGuardCapture::clearChangedDataPackageOutOfMap(PBYTE* ppPackageDataforOutOfMap, DWORD dwRangeIndex) {
pageguardFreeMemory(ppPackageDataforOutOfMap[dwRangeIndex]);
ppPackageDataforOutOfMap[dwRangeIndex] = nullptr;
}
bool PageGuardCapture::isHostWriteFlagSetInMemoryBarriers(uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers) {
bool flagSet = false;
if ((memoryBarrierCount != 0) && (pMemoryBarriers)) {
for (uint32_t i = 0; i < memoryBarrierCount; i++) {
if (pMemoryBarriers[i].srcAccessMask & VK_ACCESS_HOST_WRITE_BIT) {
flagSet = true;
}
}
}
return flagSet;
}
bool PageGuardCapture::isHostWriteFlagSetInBufferMemoryBarrier(uint32_t memoryBarrierCount,
const VkBufferMemoryBarrier* pMemoryBarriers) {
bool flagSet = false;
if ((memoryBarrierCount != 0) && (pMemoryBarriers)) {
for (uint32_t i = 0; i < memoryBarrierCount; i++) {
if (pMemoryBarriers[i].srcAccessMask & VK_ACCESS_HOST_WRITE_BIT) {
flagSet = true;
}
}
}
return flagSet;
}
bool PageGuardCapture::isHostWriteFlagSetInImageMemoryBarrier(uint32_t memoryBarrierCount,
const VkImageMemoryBarrier* pMemoryBarriers) {
bool flagSet = false;
if ((memoryBarrierCount != 0) && (pMemoryBarriers)) {
for (uint32_t i = 0; i < memoryBarrierCount; i++) {
if (pMemoryBarriers[i].srcAccessMask & VK_ACCESS_HOST_WRITE_BIT) {
flagSet = true;
}
}
}
return flagSet;
}
bool PageGuardCapture::isHostWriteFlagSet(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount,
const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier* pImageMemoryBarriers) {
bool flagSet = false, bWrite = isHostWriteFlagSetInMemoryBarriers(memoryBarrierCount, pMemoryBarriers) ||
isHostWriteFlagSetInBufferMemoryBarrier(bufferMemoryBarrierCount, pBufferMemoryBarriers) ||
isHostWriteFlagSetInImageMemoryBarrier(imageMemoryBarrierCount, pImageMemoryBarriers);
if (bWrite || (srcStageMask & VK_PIPELINE_STAGE_HOST_BIT)) {
flagSet = true;
}
return flagSet;
}
bool PageGuardCapture::isReadyForHostReadInMemoryBarriers(uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers) {
bool isReady = false;
if ((memoryBarrierCount != 0) && (pMemoryBarriers)) {
for (uint32_t i = 0; i < memoryBarrierCount; i++) {
if (pMemoryBarriers[i].dstAccessMask & VK_ACCESS_HOST_READ_BIT) {
isReady = true;
}
}
}
return isReady;
}
bool PageGuardCapture::isReadyForHostReadInBufferMemoryBarrier(uint32_t memoryBarrierCount,
const VkBufferMemoryBarrier* pMemoryBarriers) {
bool isReady = false;
if ((memoryBarrierCount != 0) && (pMemoryBarriers)) {
for (uint32_t i = 0; i < memoryBarrierCount; i++) {
if (pMemoryBarriers[i].dstAccessMask & VK_ACCESS_HOST_READ_BIT) {
isReady = true;
}
}
}
return isReady;
}
bool PageGuardCapture::isReadyForHostReadInImageMemoryBarrier(uint32_t memoryBarrierCount,
const VkImageMemoryBarrier* pMemoryBarriers) {
bool isReady = false;
if ((memoryBarrierCount != 0) && (pMemoryBarriers)) {
for (uint32_t i = 0; i < memoryBarrierCount; i++) {
if ((pMemoryBarriers[i].dstAccessMask & VK_ACCESS_HOST_READ_BIT)) {
isReady = true;
}
}
}
return isReady;
}
bool PageGuardCapture::isReadyForHostRead(VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount,
const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier* pImageMemoryBarriers) {
bool isReady = false, bRead = isReadyForHostReadInMemoryBarriers(memoryBarrierCount, pMemoryBarriers) ||
isReadyForHostReadInBufferMemoryBarrier(bufferMemoryBarrierCount, pBufferMemoryBarriers) ||
isReadyForHostReadInImageMemoryBarrier(imageMemoryBarrierCount, pImageMemoryBarriers);
if (bRead || (dstStageMask & VK_PIPELINE_STAGE_HOST_BIT)) {
isReady = true;
}
return isReady;
}
#if defined(PLATFORM_LINUX) && !defined(ANDROID)
void PageGuardCapture::pageRefsDirtyClear() {
char four = '4';
if (clearRefsFd >= 0) {
lseek(clearRefsFd, 0, SEEK_SET);
if (1 != write(clearRefsFd, &four, 1)) VKTRACE_FATAL_ERROR("Write to /proc/self/clear_refs failed.");
}
}
#endif