6414: Save screenshot in separate thread to avoid lags r=edg-l a=Robyt3

Encoding the image as PNG and saving it to a file comprises the majority of the time when taking a screenshot.

To avoid the client freezing while a screenshot is being saved, this task is moved to a separate background thread.

## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-03-11 16:49:15 +00:00 committed by GitHub
commit e05f88fb7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 23 deletions

View file

@ -11,11 +11,14 @@
#include <base/system.h>
#include <engine/console.h>
#include <engine/engine.h>
#include <engine/gfx/image_loader.h>
#include <engine/gfx/image_manipulation.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/shared/jobs.h>
#include <engine/storage.h>
#include <game/generated/client_data.h>
#include <game/generated/client_data7.h>
#include <game/localization.h>
@ -811,6 +814,55 @@ void CGraphics_Threaded::KickCommandBuffer()
m_pCommandBuffer->Reset();
}
class CScreenshotSaveJob : public IJob
{
IStorage *m_pStorage;
IConsole *m_pConsole;
char m_aName[IO_MAX_PATH_LENGTH];
int m_Width;
int m_Height;
void *m_pData;
void Run() override
{
char aWholePath[IO_MAX_PATH_LENGTH];
char aBuf[64 + IO_MAX_PATH_LENGTH];
IOHANDLE File = m_pStorage->OpenFile(m_aName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
if(File)
{
TImageByteBuffer ByteBuffer;
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)m_pData, ImageByteBuffer, m_Width, m_Height))
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
io_close(File);
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
}
else
{
str_format(aBuf, sizeof(aBuf), "failed to save screenshot to '%s'", aWholePath);
}
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA(1.0f, 0.6f, 0.3f, 1.0f));
}
public:
CScreenshotSaveJob(IStorage *pStorage, IConsole *pConsole, const char *pName, int Width, int Height, void *pData) :
m_pStorage(pStorage),
m_pConsole(pConsole),
m_Width(Width),
m_Height(Height),
m_pData(pData)
{
str_copy(m_aName, pName);
}
virtual ~CScreenshotSaveJob()
{
free(m_pData);
}
};
bool CGraphics_Threaded::ScreenshotDirect()
{
// add swap command
@ -831,28 +883,7 @@ bool CGraphics_Threaded::ScreenshotDirect()
if(Image.m_pData)
{
char aWholePath[IO_MAX_PATH_LENGTH];
char aBuf[64 + IO_MAX_PATH_LENGTH];
IOHANDLE File = m_pStorage->OpenFile(m_aScreenshotName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
if(File)
{
TImageByteBuffer ByteBuffer;
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
if(SavePNG(IMAGE_FORMAT_RGBA, (const uint8_t *)Image.m_pData, ImageByteBuffer, Image.m_Width, Image.m_Height))
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
io_close(File);
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
}
else
{
str_format(aBuf, sizeof(aBuf), "failed to save screenshot to '%s'", aWholePath);
}
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA(1.0f, 0.6f, 0.3f, 1.0f));
free(Image.m_pData);
m_pEngine->AddJob(std::make_shared<CScreenshotSaveJob>(m_pStorage, m_pConsole, m_aScreenshotName, Image.m_Width, Image.m_Height, Image.m_pData));
}
return DidSwap;
@ -2836,6 +2867,7 @@ int CGraphics_Threaded::Init()
// fetch pointers
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_pConsole = Kernel()->RequestInterface<IConsole>();
m_pEngine = Kernel()->RequestInterface<IEngine>();
// init textures
m_FirstFreeTexture = 0;

View file

@ -802,6 +802,7 @@ class CGraphics_Threaded : public IEngineGraphics
//
class IStorage *m_pStorage;
class IConsole *m_pConsole;
class IEngine *m_pEngine;
int m_CurIndex;
@ -817,7 +818,7 @@ class CGraphics_Threaded : public IEngineGraphics
float m_Rotation;
int m_Drawing;
bool m_DoScreenshot;
char m_aScreenshotName[128];
char m_aScreenshotName[IO_MAX_PATH_LENGTH];
CTextureHandle m_InvalidTexture;