Cemu/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp
Exzap f9f6206929 Vulkan: Add profiler for Vulkan API CPU cost
Disabled by default. Set VULKAN_API_CPU_BENCHMARK to 1 to enable
2023-09-29 06:48:10 +02:00

164 lines
7.2 KiB
C++

#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Common/GLInclude/GLInclude.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/Core/LatteTexture.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h"
#define LOG_READBACK_TIME
struct LatteTextureReadbackQueueEntry
{
HRTick initiateTime;
uint32 lastUpdateDrawcallIndex;
LatteTextureView* textureView;
};
std::vector<LatteTextureReadbackQueueEntry> sTextureScheduledReadbacks; // readbacks that have been queued but the actual transfer has not yet been started
std::queue<LatteTextureReadbackInfo*> sTextureActiveReadbackQueue; // readbacks in flight
void LatteTextureReadback_StartTransfer(LatteTextureView* textureView)
{
cemuLog_log(LogType::TextureReadback, "[TextureReadback-Start] PhysAddr {:08x} Res {}x{} Fmt {} Slice {} Mip {}", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->format, textureView->firstSlice, textureView->firstMip);
HRTick currentTick = HighResolutionTimer().now().getTick();
// create info entry and store in ordered linked list
LatteTextureReadbackInfo* readbackInfo = g_renderer->texture_createReadback(textureView);
sTextureActiveReadbackQueue.push(readbackInfo);
readbackInfo->StartTransfer();
readbackInfo->transferStartTime = currentTick;
}
/*
* Checks for queued transfers and starts them if at least five drawcalls have passed since the last write
* Called after a draw sequence is completed
* Returns true if at least one transfer was started
*/
bool LatteTextureReadback_Update(bool forceStart)
{
bool hasStartedTransfer = false;
for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++)
{
LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i];
uint32 numElapsedDrawcalls = LatteGPUState.drawCallCounter - entry.lastUpdateDrawcallIndex;
if (forceStart || numElapsedDrawcalls >= 5)
{
#ifdef LOG_READBACK_TIME
double elapsedSecondsSinceInitiate = HighResolutionTimer::getTimeDiff(entry.initiateTime, HighResolutionTimer().now().getTick());
cemuLog_log(LogType::TextureReadback, "[TextureReadback-Update] Starting transfer for {:08x} after {} elapsed drawcalls. Time since initiate: {:.4} Force-start: {}", entry.textureView->baseTexture->physAddress, numElapsedDrawcalls, elapsedSecondsSinceInitiate, forceStart?"yes":"no");
#endif
LatteTextureReadback_StartTransfer(entry.textureView);
// remove element
vectorRemoveByIndex(sTextureScheduledReadbacks, i);
i--;
hasStartedTransfer = true;
}
}
return hasStartedTransfer;
}
/*
* Called when a texture is deleted
*/
void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture)
{
// delete from queue
for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++)
{
LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i];
if (entry.textureView->baseTexture == texture)
{
vectorRemoveByIndex(sTextureScheduledReadbacks, i);
break;
}
}
}
void LatteTextureReadback_Initate(LatteTextureView* textureView)
{
// currently we don't support readback for resized textures
if (textureView->baseTexture->overwriteInfo.hasResolutionOverwrite)
{
cemuLog_log(LogType::Force, "Texture readback is not supported for textures with modified resolution. Texture: {:08x} {}x{}", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height);
return;
}
// check if texture isn't already queued for transfer
for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++)
{
LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i];
if (entry.textureView == textureView)
{
entry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter;
return;
}
}
// queue
LatteTextureReadbackQueueEntry queueEntry;
queueEntry.initiateTime = HighResolutionTimer().now().getTick();
queueEntry.textureView = textureView;
queueEntry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter;
sTextureScheduledReadbacks.emplace_back(queueEntry);
}
void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish)
{
if (forceFinish)
{
// start any delayed transfers
LatteTextureReadback_Update(true);
}
performanceMonitor.gpuTime_waitForAsync.beginMeasuring();
while (!sTextureActiveReadbackQueue.empty())
{
LatteTextureReadbackInfo* readbackInfo = sTextureActiveReadbackQueue.front();
if (forceFinish)
{
if (!readbackInfo->IsFinished())
{
readbackInfo->waitStartTime = HighResolutionTimer().now().getTick();
#ifdef LOG_READBACK_TIME
if (cemuLog_isLoggingEnabled(LogType::TextureReadback))
{
double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, HighResolutionTimer().now().getTick());
cemuLog_log(LogType::TextureReadback, "[Texture-Readback] Force-finish: {:08x} Res {:}/{:} TM {:} FMT {:04x} Transfer time so far: {:.4}ms", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0);
}
#endif
readbackInfo->forceFinish = true;
readbackInfo->ForceFinish();
// rerun logic since ->ForceFinish() can recurively call this function and thus modify the queue
continue;
}
}
else
{
if (!readbackInfo->IsFinished())
break;
readbackInfo->waitStartTime = HighResolutionTimer().now().getTick();
}
// performance testing
#ifdef LOG_READBACK_TIME
if (cemuLog_isLoggingEnabled(LogType::TextureReadback))
{
HRTick currentTick = HighResolutionTimer().now().getTick();
double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, currentTick);
double elapsedSecondsWaiting = HighResolutionTimer::getTimeDiff(readbackInfo->waitStartTime, currentTick);
cemuLog_log(LogType::TextureReadback, "[Texture-Readback] {:08x} Res {}/{} TM {} FMT {:04x} ReadbackLatency: {:6.3}ms WaitTime: {:6.3}ms ForcedWait {}", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0, elapsedSecondsWaiting * 1000.0, readbackInfo->forceFinish ? "yes" : "no");
}
#endif
uint8* pixelData = readbackInfo->GetData();
LatteTextureLoader_writeReadbackTextureToMemory(&readbackInfo->hostTextureCopy, 0, 0, pixelData);
readbackInfo->ReleaseData();
// get the original texture if it still exists and invalidate the current data hash
LatteTextureView* origTexView = LatteTextureViewLookupCache::lookupSlice(readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.pitch, 0, 0, readbackInfo->hostTextureCopy.format);
if (origTexView)
LatteTC_ResetTextureChangeTracker(origTexView->baseTexture, true);
delete readbackInfo;
// remove from queue
cemu_assert_debug(!sTextureActiveReadbackQueue.empty());
cemu_assert_debug(readbackInfo == sTextureActiveReadbackQueue.front());
sTextureActiveReadbackQueue.pop();
}
performanceMonitor.gpuTime_waitForAsync.endMeasuring();
}