diff --git a/Core/Shared/Interfaces/IRenderingDevice.h b/Core/Shared/Interfaces/IRenderingDevice.h index f5d267998..acc8ad5f3 100644 --- a/Core/Shared/Interfaces/IRenderingDevice.h +++ b/Core/Shared/Interfaces/IRenderingDevice.h @@ -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) { @@ -25,6 +26,7 @@ struct RenderSurfaceInfo void Clear() { memset(Buffer, 0, Width * Height * sizeof(uint32_t)); + IsDirty = true; } ~RenderSurfaceInfo() diff --git a/Core/Shared/Video/DebugHud.cpp b/Core/Shared/Video/DebugHud.cpp index ef1a702b1..6553f6653 100644 --- a/Core/Shared/Video/DebugHud.cpp +++ b/Core/Shared/Video/DebugHud.cpp @@ -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 &command : _commands) { - command->Draw(argbBuffer, frameInfo, overscan, frameNumber, autoScale, forcedScale); + + bool isDirty = false; + if(clearAndUpdate) { + unordered_map drawPixels; + drawPixels.reserve(1000); + for(unique_ptr& 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& command : _commands) { + command->Draw(nullptr, argbBuffer, frameInfo, overscan, frameNumber, autoScale, forcedScale); + } } + _commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const unique_ptr& 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) diff --git a/Core/Shared/Video/DebugHud.h b/Core/Shared/Video/DebugHud.h index 81acee2b4..a75bdfd39 100644 --- a/Core/Shared/Video/DebugHud.h +++ b/Core/Shared/Video/DebugHud.h @@ -11,6 +11,7 @@ class DebugHud vector> _commands; atomic _commandCount; SimpleLock _commandLock; + unordered_map _drawPixels; public: DebugHud(); @@ -18,7 +19,7 @@ class 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); diff --git a/Core/Shared/Video/DrawCommand.h b/Core/Shared/Video/DrawCommand.h index 422443db7..8886e420f 100644 --- a/Core/Shared/Video/DrawCommand.h +++ b/Core/Shared/Video/DrawCommand.h @@ -10,6 +10,7 @@ class DrawCommand bool _disableAutoScale = false; protected: + unordered_map* _drawnPixels = nullptr; uint32_t* _argbBuffer = nullptr; FrameInfo _frameInfo = {}; OverscanDimensions _overscan = {}; @@ -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; + } } } @@ -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* 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 @@ -115,6 +133,7 @@ class DrawCommand if(_startFrame <= (int32_t)frameNumber) { _argbBuffer = argbBuffer; + _drawnPixels = drawnPixels; _frameInfo = frameInfo; _overscan = overscan; diff --git a/Core/Shared/Video/SoftwareRenderer.cpp b/Core/Shared/Video/SoftwareRenderer.cpp index 77762ae3b..916f4fd76 100644 --- a/Core/Shared/Video/SoftwareRenderer.cpp +++ b/Core/Shared/Video/SoftwareRenderer.cpp @@ -58,6 +58,7 @@ struct SoftwareRendererSurface uint32_t* Buffer = nullptr; uint32_t Width = 0; uint32_t Height = 0; + bool IsDirty = true; }; struct SoftwareRendererFrame @@ -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); diff --git a/Core/Shared/Video/VideoRenderer.cpp b/Core/Shared/Video/VideoRenderer.cpp index 413fa1ea1..502f3d71f 100644 --- a/Core/Shared/Video/VideoRenderer.cpp +++ b/Core/Shared/Video/VideoRenderer.cpp @@ -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); @@ -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); + } } } } @@ -116,8 +118,9 @@ 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.) @@ -125,6 +128,7 @@ void VideoRenderer::DrawScriptHud(RenderedFrame& frame) if(_needScriptHudClear) { _scriptHudSurface.Clear(); _needScriptHudClear = false; + needRedraw = true; } if(_emu->GetScriptHud()->HasCommands()) { @@ -132,8 +136,10 @@ void VideoRenderer::DrawScriptHud(RenderedFrame& frame) _emu->GetScriptHud()->Draw(_scriptHudSurface.Buffer, size, overscan, frame.FrameNumber, false); _needScriptHudClear = true; _lastScriptHudFrameNumber = frame.FrameNumber; + needRedraw = true; } } + return needRedraw; } std::pair VideoRenderer::GetScriptHudSize() @@ -163,6 +169,7 @@ void VideoRenderer::UpdateFrame(RenderedFrame& frame) if(_renderer) { _renderer->UpdateFrame(frame); + _needRedraw = true; _waitForRender.Signal(); } } diff --git a/Core/Shared/Video/VideoRenderer.h b/Core/Shared/Video/VideoRenderer.h index 178349302..274039ea8 100644 --- a/Core/Shared/Video/VideoRenderer.h +++ b/Core/Shared/Video/VideoRenderer.h @@ -52,6 +52,7 @@ class VideoRenderer bool _needScriptHudClear = false; uint32_t _scriptHudScale = 2; uint32_t _lastScriptHudFrameNumber = 0; + bool _needRedraw = true; RenderedFrame _lastFrame; SimpleLock _frameLock; @@ -59,7 +60,7 @@ class VideoRenderer safe_ptr _recorder; void RenderThread(); - void DrawScriptHud(RenderedFrame& frame); + bool DrawScriptHud(RenderedFrame& frame); FrameInfo GetEmuHudSize(FrameInfo baseFrameSize); diff --git a/Linux/SdlRenderer.cpp b/Linux/SdlRenderer.cpp index 5da8c7fbd..1e2699405 100644 --- a/Linux/SdlRenderer.cpp +++ b/Linux/SdlRenderer.cpp @@ -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 { @@ -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 }; diff --git a/UI/Controls/SoftwareRendererView.axaml.cs b/UI/Controls/SoftwareRendererView.axaml.cs index 0f760f046..ea72e1d33 100644 --- a/UI/Controls/SoftwareRendererView.axaml.cs +++ b/UI/Controls/SoftwareRendererView.axaml.cs @@ -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); diff --git a/UI/Interop/EmuApi.cs b/UI/Interop/EmuApi.cs index 3a18468e1..17aff0d8d 100644 --- a/UI/Interop/EmuApi.cs +++ b/UI/Interop/EmuApi.cs @@ -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 diff --git a/Utilities/AutoResetEvent.cpp b/Utilities/AutoResetEvent.cpp index 5b4647d1f..1a101b4e7 100644 --- a/Utilities/AutoResetEvent.cpp +++ b/Utilities/AutoResetEvent.cpp @@ -12,18 +12,21 @@ AutoResetEvent::~AutoResetEvent() //application is exiting. } -void AutoResetEvent::Wait(int timeoutDelay) +bool AutoResetEvent::Wait(int timeoutDelay) { std::unique_lock 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(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() diff --git a/Utilities/AutoResetEvent.h b/Utilities/AutoResetEvent.h index 79810ce0d..a1026b22f 100644 --- a/Utilities/AutoResetEvent.h +++ b/Utilities/AutoResetEvent.h @@ -16,6 +16,6 @@ class AutoResetEvent ~AutoResetEvent(); void Reset(); - void Wait(int timeoutDelay = 0); + bool Wait(int timeoutDelay = 0); void Signal(); }; diff --git a/Windows/Renderer.cpp b/Windows/Renderer.cpp index 42aba2955..1d0b590a0 100644 --- a/Windows/Renderer.cpp +++ b/Windows/Renderer.cpp @@ -454,10 +454,14 @@ void Renderer::DrawScreen() } uint8_t* surfacePointer = (uint8_t*)dd.pData; uint8_t* videoBuffer = _textureBuffer[1]; - for(uint32_t i = 0, iMax = _emuFrameHeight; i < iMax; i++) { - memcpy(surfacePointer, videoBuffer, rowPitch); - videoBuffer += rowPitch; - surfacePointer += dd.RowPitch; + if(rowPitch != dd.RowPitch) { + for(uint32_t i = 0, iMax = _emuFrameHeight; i < iMax; i++) { + memcpy(surfacePointer, videoBuffer, rowPitch); + videoBuffer += rowPitch; + surfacePointer += dd.RowPitch; + } + } else { + memcpy(surfacePointer, videoBuffer, rowPitch * _emuFrameHeight); } _pDeviceContext->Unmap(_pTexture, 0); @@ -496,35 +500,47 @@ bool Renderer::CreateHudTexture(HudRenderInfo& hud, uint32_t newWidth, uint32_t return true; } -void Renderer::DrawHud(HudRenderInfo& hud, uint32_t* hudBuffer, uint32_t newWidth, uint32_t newHeight) +void Renderer::DrawHud(HudRenderInfo& hud, RenderSurfaceInfo& hudSurface) { + uint32_t* hudBuffer = hudSurface.Buffer; + uint32_t newWidth = hudSurface.Width; + uint32_t newHeight = hudSurface.Height; + if(newWidth == 0 && newHeight == 0) { return; } + bool needRedraw = hudSurface.IsDirty; if(hud.Width != newWidth || hud.Height != newHeight || !hud.Texture || !hud.Shader) { + needRedraw = true; if(!CreateHudTexture(hud, newWidth, newHeight)) { return; } } - //Copy buffer to texture - uint32_t rowPitch = hud.Width * sizeof(uint32_t); - D3D11_MAPPED_SUBRESOURCE dd; - HRESULT hr = _pDeviceContext->Map(hud.Texture, 0, D3D11_MAP_WRITE_DISCARD, 0, &dd); - if(FAILED(hr)) { - MessageManager::Log("DeviceContext::Map() failed - Error:" + std::to_string(hr)); - return; - } - uint8_t* surfacePointer = (uint8_t*)dd.pData; - uint8_t* videoBuffer = (uint8_t*)hudBuffer; - for(uint32_t i = 0, iMax = hud.Height; i < iMax; i++) { - memcpy(surfacePointer, videoBuffer, rowPitch); - videoBuffer += rowPitch; - surfacePointer += dd.RowPitch; + if(needRedraw) { + //Copy buffer to texture + uint32_t rowPitch = hud.Width * sizeof(uint32_t); + D3D11_MAPPED_SUBRESOURCE dd; + HRESULT hr = _pDeviceContext->Map(hud.Texture, 0, D3D11_MAP_WRITE_DISCARD, 0, &dd); + if(FAILED(hr)) { + MessageManager::Log("DeviceContext::Map() failed - Error:" + std::to_string(hr)); + return; + } + uint8_t* surfacePointer = (uint8_t*)dd.pData; + uint8_t* videoBuffer = (uint8_t*)hudBuffer; + if(rowPitch != dd.RowPitch) { + for(uint32_t i = 0, iMax = hud.Height; i < iMax; i++) { + memcpy(surfacePointer, videoBuffer, rowPitch); + videoBuffer += rowPitch; + surfacePointer += dd.RowPitch; + } + } else { + memcpy(surfacePointer, videoBuffer, hud.Height * rowPitch); + } + _pDeviceContext->Unmap(hud.Texture, 0); } - _pDeviceContext->Unmap(hud.Texture, 0); - + RECT destRect; destRect.left = _leftMargin; destRect.top = _topMargin; @@ -562,8 +578,8 @@ void Renderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) //Draw HUD _spriteBatch->Begin(SpriteSortMode_Immediate, false); - DrawHud(_scriptHud, scriptHud.Buffer, scriptHud.Width, scriptHud.Height); - DrawHud(_emuHud, emuHud.Buffer, emuHud.Width, emuHud.Height); + DrawHud(_scriptHud, scriptHud); + DrawHud(_emuHud, emuHud); _spriteBatch->End(); // Present the information rendered to the back buffer to the front buffer (the screen) diff --git a/Windows/Renderer.h b/Windows/Renderer.h index 7d9a66b10..a214adb5c 100644 --- a/Windows/Renderer.h +++ b/Windows/Renderer.h @@ -82,7 +82,7 @@ class Renderer final : public IRenderingDevice void DrawScreen(); bool CreateHudTexture(HudRenderInfo& hud, uint32_t newWidth, uint32_t newHeight); - void DrawHud(HudRenderInfo& hud, uint32_t* hudBuffer, uint32_t newWidth, uint32_t newHeight); + void DrawHud(HudRenderInfo& hud, RenderSurfaceInfo& hudSurface); HRESULT CreateRenderTargetView(); void ReleaseRenderTargetView();