mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-16 03:38:30 +12:00
164 lines
7.2 KiB
C++
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();
|
|
}
|