Skip to content

Commit

Permalink
Reduced CPU usage for HUDs and when paused
Browse files Browse the repository at this point in the history
  • Loading branch information
SourMesen committed Oct 14, 2023
1 parent 3b72d78 commit af2afc4
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 59 deletions.
2 changes: 2 additions & 0 deletions Core/Shared/Interfaces/IRenderingDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct RenderSurfaceInfo
uint32_t* Buffer = nullptr;
uint32_t Width = 0;
uint32_t Height = 0;
bool IsDirty = true;

void UpdateSize(uint32_t width, uint32_t height)
{
Expand All @@ -25,6 +26,7 @@ struct RenderSurfaceInfo
void Clear()
{
memset(Buffer, 0, Width * Height * sizeof(uint32_t));
IsDirty = true;
}

~RenderSurfaceInfo()
Expand Down
44 changes: 41 additions & 3 deletions Core/Shared/Video/DebugHud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,52 @@ void DebugHud::ClearScreen()
_commands.clear();
}

void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale, float forcedScale)
bool DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale, float forcedScale, bool clearAndUpdate)
{
auto lock = _commandLock.AcquireSafe();
for(unique_ptr<DrawCommand> &command : _commands) {
command->Draw(argbBuffer, frameInfo, overscan, frameNumber, autoScale, forcedScale);

bool isDirty = false;
if(clearAndUpdate) {
unordered_map<uint32_t, uint32_t> drawPixels;
drawPixels.reserve(1000);
for(unique_ptr<DrawCommand>& command : _commands) {
command->Draw(&drawPixels, argbBuffer, frameInfo, overscan, frameNumber, autoScale, forcedScale);
}

isDirty = drawPixels.size() != _drawPixels.size();
if(!isDirty) {
for(auto keyValue : drawPixels) {
auto match = _drawPixels.find(keyValue.first);
if(match != _drawPixels.end()) {
if(keyValue.second != match->second) {
isDirty = true;
break;
}
} else {
isDirty = true;
break;
}
}
}

if(isDirty) {
memset(argbBuffer, 0, frameInfo.Height * frameInfo.Width * sizeof(uint32_t));
for(auto keyValue : drawPixels) {
argbBuffer[keyValue.first] = keyValue.second;
}
_drawPixels = drawPixels;
}
} else {
isDirty = true;
for(unique_ptr<DrawCommand>& command : _commands) {
command->Draw(nullptr, argbBuffer, frameInfo, overscan, frameNumber, autoScale, forcedScale);
}
}

_commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const unique_ptr<DrawCommand>& c) { return c->Expired(); }), _commands.end());
_commandCount = (uint32_t)_commands.size();

return isDirty;
}

void DebugHud::DrawPixel(int x, int y, int color, int frameCount, int startFrame)
Expand Down
3 changes: 2 additions & 1 deletion Core/Shared/Video/DebugHud.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ class DebugHud
vector<unique_ptr<DrawCommand>> _commands;
atomic<uint32_t> _commandCount;
SimpleLock _commandLock;
unordered_map<uint32_t, uint32_t> _drawPixels;

public:
DebugHud();
~DebugHud();

bool HasCommands() { return _commandCount > 0; }

void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale, float forcedScale = 0);
bool Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale, float forcedScale = 0, bool clearAndUpdate = false);
void ClearScreen();

void DrawPixel(int x, int y, int color, int frameCount, int startFrame = -1);
Expand Down
35 changes: 27 additions & 8 deletions Core/Shared/Video/DrawCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class DrawCommand
bool _disableAutoScale = false;

protected:
unordered_map<uint32_t, uint32_t>* _drawnPixels = nullptr;
uint32_t* _argbBuffer = nullptr;
FrameInfo _frameInfo = {};
OverscanDimensions _overscan = {};
Expand All @@ -21,16 +22,33 @@ class DrawCommand

__forceinline void InternalDrawPixel(int32_t offset, int color, uint32_t alpha)
{
if(alpha != 0xFF000000) {
if(_argbBuffer[offset] == 0) {
//When drawing on an empty background, premultiply channels & preserve alpha value
//This is needed for hardware blending between the HUD and the game screen
BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color, true);
if(_drawnPixels) {
//Log modified pixels
if(alpha != 0xFF000000) {
if(_drawnPixels->find(offset) == _drawnPixels->end()) {
//When drawing on an empty background, premultiply channels & preserve alpha value
//This is needed for hardware blending between the HUD and the game screen
(*_drawnPixels)[offset] = color;
BlendColors((uint8_t*)&(*_drawnPixels)[offset], (uint8_t*)&color, true);
} else {
BlendColors((uint8_t*)&(*_drawnPixels)[offset], (uint8_t*)&color);
}
} else {
BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color);
(*_drawnPixels)[offset] = color;
}
} else {
_argbBuffer[offset] = color;
//Draw pixels directly to the buffer
if(alpha != 0xFF000000) {
if(_argbBuffer[offset] == 0) {
//When drawing on an empty background, premultiply channels & preserve alpha value
//This is needed for hardware blending between the HUD and the game screen
BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color, true);
} else {
BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color);
}
} else {
_argbBuffer[offset] = color;
}
}
}

Expand Down Expand Up @@ -106,7 +124,7 @@ class DrawCommand
{
}

void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t frameNumber, bool autoScale, float forcedScale = 0)
void Draw(unordered_map<uint32_t, uint32_t>* drawnPixels, uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t frameNumber, bool autoScale, float forcedScale = 0)
{
if(_startFrame < 0) {
//When no start frame was specified, start on the next drawn frame
Expand All @@ -115,6 +133,7 @@ class DrawCommand

if(_startFrame <= (int32_t)frameNumber) {
_argbBuffer = argbBuffer;
_drawnPixels = drawnPixels;
_frameInfo = frameInfo;
_overscan = overscan;

Expand Down
7 changes: 4 additions & 3 deletions Core/Shared/Video/SoftwareRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct SoftwareRendererSurface
uint32_t* Buffer = nullptr;
uint32_t Width = 0;
uint32_t Height = 0;
bool IsDirty = true;
};

struct SoftwareRendererFrame
Expand All @@ -78,9 +79,9 @@ void SoftwareRenderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scri
}

SoftwareRendererFrame frame = {
{ _textureBuffer[1], _frameWidth, _frameHeight },
{ emuHud.Buffer, emuHud.Width, emuHud.Height },
{ scriptHud.Buffer, scriptHud.Width, scriptHud.Height }
{ _textureBuffer[1], _frameWidth, _frameHeight, true },
{ emuHud.Buffer, emuHud.Width, emuHud.Height, emuHud.IsDirty },
{ scriptHud.Buffer, scriptHud.Width, scriptHud.Height, scriptHud.IsDirty }
};

_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::RefreshSoftwareRenderer, &frame);
Expand Down
21 changes: 14 additions & 7 deletions Core/Shared/Video/VideoRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void VideoRenderer::RenderThread()
{
while(!_stopFlag.load()) {
//Wait until a frame is ready, or until 32ms have passed (to allow HUD to update at ~30fps when paused)
_waitForRender.Wait(32);
bool forceRender = _waitForRender.Wait(32);
if(_renderer) {
FrameInfo size = _emu->GetVideoDecoder()->GetBaseFrameInfo(true);
_scriptHudSurface.UpdateSize(size.Width * _scriptHudScale, size.Height * _scriptHudScale);
Expand All @@ -85,17 +85,19 @@ void VideoRenderer::RenderThread()
frame = _lastFrame;
}

_emuHudSurface.Clear();
_inputHud->DrawControllers(size, frame.InputData);
{
auto lock = _hudLock.AcquireSafe();
_systemHud->Draw(_rendererHud.get(), size.Width, size.Height);
}
_rendererHud->Draw(_emuHudSurface.Buffer, size, {}, 0, false);

DrawScriptHud(frame);

_emuHudSurface.IsDirty = _rendererHud->Draw(_emuHudSurface.Buffer, size, {}, 0, false, 0, true);
_scriptHudSurface.IsDirty = DrawScriptHud(frame);

_renderer->Render(_emuHudSurface, _scriptHudSurface);
if(forceRender || _needRedraw || _emuHudSurface.IsDirty || _scriptHudSurface.IsDirty) {
_needRedraw = false;
_renderer->Render(_emuHudSurface, _scriptHudSurface);
}
}
}
}
Expand All @@ -116,24 +118,28 @@ FrameInfo VideoRenderer::GetEmuHudSize(FrameInfo baseFrameSize)
return size;
}

void VideoRenderer::DrawScriptHud(RenderedFrame& frame)
bool VideoRenderer::DrawScriptHud(RenderedFrame& frame)
{
bool needRedraw = false;
if(_lastScriptHudFrameNumber != frame.FrameNumber) {
//Clear+draw HUD for scripts
//-Only when frame number changes (to prevent the HUD from disappearing when paused, etc.)
//-Only when commands are queued, otherwise skip drawing/clearing to avoid wasting CPU time
if(_needScriptHudClear) {
_scriptHudSurface.Clear();
_needScriptHudClear = false;
needRedraw = true;
}

if(_emu->GetScriptHud()->HasCommands()) {
auto [size, overscan] = GetScriptHudSize();
_emu->GetScriptHud()->Draw(_scriptHudSurface.Buffer, size, overscan, frame.FrameNumber, false);
_needScriptHudClear = true;
_lastScriptHudFrameNumber = frame.FrameNumber;
needRedraw = true;
}
}
return needRedraw;
}

std::pair<FrameInfo, OverscanDimensions> VideoRenderer::GetScriptHudSize()
Expand Down Expand Up @@ -163,6 +169,7 @@ void VideoRenderer::UpdateFrame(RenderedFrame& frame)

if(_renderer) {
_renderer->UpdateFrame(frame);
_needRedraw = true;
_waitForRender.Signal();
}
}
Expand Down
3 changes: 2 additions & 1 deletion Core/Shared/Video/VideoRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ class VideoRenderer
bool _needScriptHudClear = false;
uint32_t _scriptHudScale = 2;
uint32_t _lastScriptHudFrameNumber = 0;
bool _needRedraw = true;

RenderedFrame _lastFrame;
SimpleLock _frameLock;

safe_ptr<IVideoRecorder> _recorder;

void RenderThread();
void DrawScriptHud(RenderedFrame& frame);
bool DrawScriptHud(RenderedFrame& frame);

FrameInfo GetEmuHudSize(FrameInfo baseFrameSize);

Expand Down
22 changes: 15 additions & 7 deletions Linux/SdlRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,14 @@ void SdlRenderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud
auto frameLock = _frameLock.AcquireSafe();
if(_frameBuffer && _frameWidth == _requiredWidth && _frameHeight == _requiredHeight) {
uint32_t* ppuFrameBuffer = _frameBuffer;
for(uint32_t i = 0, iMax = _frameHeight; i < iMax; i++) {
memcpy(textureBuffer, ppuFrameBuffer, _frameWidth*_bytesPerPixel);
ppuFrameBuffer += _frameWidth;
textureBuffer += rowPitch;
if(rowPitch != _frameWidth) {
for(uint32_t i = 0, iMax = _frameHeight; i < iMax; i++) {
memcpy(textureBuffer, ppuFrameBuffer, _frameWidth*_bytesPerPixel);
ppuFrameBuffer += _frameWidth;
textureBuffer += rowPitch;
}
} else {
memcpy(textureBuffer, ppuFrameBuffer, _frameHeight * _frameWidth * _bytesPerPixel);
}
}
} else {
Expand All @@ -236,14 +240,18 @@ void SdlRenderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud

SDL_UnlockTexture(_sdlTexture);

UpdateHudTexture(_emuHud, emuHud.Buffer);
UpdateHudTexture(_scriptHud, scriptHud.Buffer);
if(emuHud.IsDirty) {
UpdateHudTexture(_emuHud, emuHud.Buffer);
}
if(scriptHud.IsDirty) {
UpdateHudTexture(_scriptHud, scriptHud.Buffer);
}

SDL_Rect source = {0, 0, (int)_frameWidth, (int)_frameHeight };
SDL_Rect dest = {0, 0, (int)_screenWidth, (int)_screenHeight };

if(SDL_RenderCopy(_sdlRenderer, _sdlTexture, &source, &dest) != 0) {
LogSdlError("SDL_RenderCopy failed");
LogSdlError("SDL_RenderCopy failed");
}

SDL_Rect scriptHudSource = { 0, 0, (int)_scriptHud.Width, (int)_scriptHud.Height };
Expand Down
8 changes: 6 additions & 2 deletions UI/Controls/SoftwareRendererView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ private unsafe void UpdateSurface(SoftwareRendererSurface frame, WriteableBitmap
public unsafe void UpdateSoftwareRenderer(SoftwareRendererFrame frameInfo)
{
UpdateSurface(frameInfo.Frame, _model.FrameSurface, s => _model.FrameSurface = s);
UpdateSurface(frameInfo.EmuHud, _model.EmuHudSurface, s => _model.EmuHudSurface = s);
UpdateSurface(frameInfo.ScriptHud, _model.ScriptHudSurface, s => _model.ScriptHudSurface = s);
if(frameInfo.EmuHud.IsDirty) {
UpdateSurface(frameInfo.EmuHud, _model.EmuHudSurface, s => _model.EmuHudSurface = s);
}
if(frameInfo.ScriptHud.IsDirty) {
UpdateSurface(frameInfo.ScriptHud, _model.ScriptHudSurface, s => _model.ScriptHudSurface = s);
}

Dispatcher.UIThread.Post(() => {
RenderOptions.SetBitmapInterpolationMode(_frame, ConfigManager.Config.Video.UseBilinearInterpolation ? BitmapInterpolationMode.LowQuality : BitmapInterpolationMode.None);
Expand Down
1 change: 1 addition & 0 deletions UI/Interop/EmuApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ public struct SoftwareRendererSurface
public IntPtr FrameBuffer;
public UInt32 Width;
public UInt32 Height;
[MarshalAs(UnmanagedType.I1)] public bool IsDirty;
}

public struct SoftwareRendererFrame
Expand Down
7 changes: 5 additions & 2 deletions Utilities/AutoResetEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ AutoResetEvent::~AutoResetEvent()
//application is exiting.
}

void AutoResetEvent::Wait(int timeoutDelay)
bool AutoResetEvent::Wait(int timeoutDelay)
{
std::unique_lock<std::mutex> lock(_mutex);
bool receivedSignal = false;
if(timeoutDelay == 0) {
//Wait until signaled
_signal.wait(lock, [this] { return _signaled; });
receivedSignal = true;
} else {
//Wait until signaled or timeout
auto timeoutTime = std::chrono::system_clock::now() + std::chrono::duration<int, std::milli>(timeoutDelay);
_signal.wait_until(lock, timeoutTime, [this] { return _signaled; });
receivedSignal = _signal.wait_until(lock, timeoutTime, [this] { return _signaled; });
}
_signaled = false;
return receivedSignal;
}

void AutoResetEvent::Reset()
Expand Down
2 changes: 1 addition & 1 deletion Utilities/AutoResetEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ class AutoResetEvent
~AutoResetEvent();

void Reset();
void Wait(int timeoutDelay = 0);
bool Wait(int timeoutDelay = 0);
void Signal();
};
Loading

0 comments on commit af2afc4

Please sign in to comment.