From aed9270e52d0e585d5f9d6741cb6f82200b94db1 Mon Sep 17 00:00:00 2001 From: Sour Date: Wed, 13 Feb 2019 23:02:43 -0500 Subject: [PATCH] Imported video decode/renderer classes and related utilities from Mesen --- Core/BaseRenderer.h | 10 +- Core/BaseVideoFilter.cpp | 141 +++++++++++++++++++ Core/BaseVideoFilter.h | 37 +++++ Core/Console.cpp | 33 +++++ Core/Console.h | 14 ++ Core/Core.vcxproj | 17 +++ Core/Core.vcxproj.filters | 69 +++++++++- Core/DebugHud.cpp | 74 ++++++++++ Core/DebugHud.h | 27 ++++ Core/Debugger.cpp | 2 +- Core/DefaultVideoFilter.cpp | 136 +++++++++++++++++++ Core/DefaultVideoFilter.h | 28 ++++ Core/DrawCommand.h | 98 ++++++++++++++ Core/DrawLineCommand.h | 42 ++++++ Core/DrawPixelCommand.h | 23 ++++ Core/DrawRectangleCommand.h | 48 +++++++ Core/DrawScreenBufferCommand.h | 25 ++++ Core/DrawStringCommand.h | 161 ++++++++++++++++++++++ Core/SettingTypes.h | 129 ++++++++++++++++++ Core/VideoDecoder.cpp | 240 +++++++++++++++++++++++++++++++++ Core/VideoDecoder.h | 63 +++++++++ Core/VideoRenderer.cpp | 116 ++++++++++++++++ Core/VideoRenderer.h | 45 +++++++ Core/stdafx.h | 2 + InteropDLL/EmuApiWrapper.cpp | 1 + UI/Forms/frmMain.Designer.cs | 53 ++++---- UI/Forms/frmMain.cs | 8 +- Windows/Renderer.cpp | 25 ++-- 28 files changed, 1609 insertions(+), 58 deletions(-) create mode 100644 Core/BaseVideoFilter.cpp create mode 100644 Core/BaseVideoFilter.h create mode 100644 Core/DebugHud.cpp create mode 100644 Core/DebugHud.h create mode 100644 Core/DefaultVideoFilter.cpp create mode 100644 Core/DefaultVideoFilter.h create mode 100644 Core/DrawCommand.h create mode 100644 Core/DrawLineCommand.h create mode 100644 Core/DrawPixelCommand.h create mode 100644 Core/DrawRectangleCommand.h create mode 100644 Core/DrawScreenBufferCommand.h create mode 100644 Core/DrawStringCommand.h create mode 100644 Core/SettingTypes.h create mode 100644 Core/VideoDecoder.cpp create mode 100644 Core/VideoDecoder.h create mode 100644 Core/VideoRenderer.cpp create mode 100644 Core/VideoRenderer.h diff --git a/Core/BaseRenderer.h b/Core/BaseRenderer.h index 142c5b0..7ccc297 100644 --- a/Core/BaseRenderer.h +++ b/Core/BaseRenderer.h @@ -1,16 +1,10 @@ #pragma once -#include "../Core/IMessageManager.h" +#include "IMessageManager.h" +#include "SettingTypes.h" #include "../Utilities/Timer.h" class Console; -//TODO -enum class VideoResizeFilter -{ - NearestNeighbor = 0, - Bilinear = 1 -}; - class BaseRenderer : public IMessageManager { private: diff --git a/Core/BaseVideoFilter.cpp b/Core/BaseVideoFilter.cpp new file mode 100644 index 0000000..281d806 --- /dev/null +++ b/Core/BaseVideoFilter.cpp @@ -0,0 +1,141 @@ +#include "stdafx.h" +#include "BaseVideoFilter.h" +#include "MessageManager.h" +#include "../Utilities/PNGHelper.h" +#include "../Utilities/FolderUtilities.h" +#include "Console.h" + +BaseVideoFilter::BaseVideoFilter(shared_ptr console) +{ + _console = console; + _overscan = {}; //TODO _console->GetSettings()->GetOverscanDimensions(); +} + +BaseVideoFilter::~BaseVideoFilter() +{ + auto lock = _frameLock.AcquireSafe(); + if(_outputBuffer) { + delete[] _outputBuffer; + _outputBuffer = nullptr; + } +} + +void BaseVideoFilter::UpdateBufferSize() +{ + uint32_t newBufferSize = GetFrameInfo().Width*GetFrameInfo().Height; + if(_bufferSize != newBufferSize) { + _frameLock.Acquire(); + if(_outputBuffer) { + delete[] _outputBuffer; + } + + _bufferSize = newBufferSize; + _outputBuffer = new uint32_t[newBufferSize]; + _frameLock.Release(); + } +} + +OverscanDimensions BaseVideoFilter::GetOverscan() +{ + return _overscan; +} + +void BaseVideoFilter::OnBeforeApplyFilter() +{ +} + +bool BaseVideoFilter::IsOddFrame() +{ + return _isOddFrame; +} + +void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber) +{ + _frameLock.Acquire(); + _overscan = {}; //TODO _console->GetSettings()->GetOverscanDimensions(); + _isOddFrame = frameNumber % 2; + UpdateBufferSize(); + OnBeforeApplyFilter(); + ApplyFilter(ppuOutputBuffer); + + _frameLock.Release(); +} + +uint32_t* BaseVideoFilter::GetOutputBuffer() +{ + return _outputBuffer; +} + +void BaseVideoFilter::TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream) +{ + uint32_t* pngBuffer; + FrameInfo frameInfo; + uint32_t* frameBuffer = nullptr; + { + auto lock = _frameLock.AcquireSafe(); + if(_bufferSize == 0 || !GetOutputBuffer()) { + return; + } + + frameBuffer = new uint32_t[_bufferSize]; + memcpy(frameBuffer, GetOutputBuffer(), _bufferSize * sizeof(frameBuffer[0])); + frameInfo = GetFrameInfo(); + } + + pngBuffer = frameBuffer; + + //TODO + /* + uint32_t rotationAngle = _console->GetSettings()->GetScreenRotation(); + shared_ptr rotateFilter; + if(rotationAngle > 0) { + rotateFilter.reset(new RotateFilter(rotationAngle)); + pngBuffer = rotateFilter->ApplyFilter(pngBuffer, frameInfo.Width, frameInfo.Height); + frameInfo = rotateFilter->GetFrameInfo(frameInfo); + } + + shared_ptr scaleFilter = ScaleFilter::GetScaleFilter(filterType); + if(scaleFilter) { + pngBuffer = scaleFilter->ApplyFilter(pngBuffer, frameInfo.Width, frameInfo.Height, _console->GetSettings()->GetPictureSettings().ScanlineIntensity); + frameInfo = scaleFilter->GetFrameInfo(frameInfo); + } + + VideoHud hud; + hud.DrawHud(_console, pngBuffer, frameInfo, _console->GetSettings()->GetOverscanDimensions()); +*/ + if(!filename.empty()) { + PNGHelper::WritePNG(filename, pngBuffer, frameInfo.Width, frameInfo.Height); + } else { + PNGHelper::WritePNG(*stream, pngBuffer, frameInfo.Width, frameInfo.Height); + } + + delete[] frameBuffer; +} + +void BaseVideoFilter::TakeScreenshot(string romName, VideoFilterType filterType) +{ + string romFilename = FolderUtilities::GetFilename(romName, false); + + int counter = 0; + string baseFilename = FolderUtilities::CombinePath(FolderUtilities::GetScreenshotFolder(), romFilename); + string ssFilename; + while(true) { + string counterStr = std::to_string(counter); + while(counterStr.length() < 3) { + counterStr = "0" + counterStr; + } + ssFilename = baseFilename + "_" + counterStr + ".png"; + ifstream file(ssFilename, ios::in); + if(file) { + file.close(); + } else { + break; + } + counter++; + } + + TakeScreenshot(filterType, ssFilename); + + MessageManager::DisplayMessage("ScreenshotSaved", FolderUtilities::GetFilename(ssFilename, true)); +} + diff --git a/Core/BaseVideoFilter.h b/Core/BaseVideoFilter.h new file mode 100644 index 0000000..fc07cd4 --- /dev/null +++ b/Core/BaseVideoFilter.h @@ -0,0 +1,37 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/SimpleLock.h" +#include "SettingTypes.h" + +class Console; + +class BaseVideoFilter +{ +private: + uint32_t* _outputBuffer = nullptr; + uint32_t _bufferSize = 0; + SimpleLock _frameLock; + OverscanDimensions _overscan; + bool _isOddFrame; + + void UpdateBufferSize(); + +protected: + shared_ptr _console; + + virtual void ApplyFilter(uint16_t *ppuOutputBuffer) = 0; + virtual void OnBeforeApplyFilter(); + bool IsOddFrame(); + +public: + BaseVideoFilter(shared_ptr console); + virtual ~BaseVideoFilter(); + + uint32_t* GetOutputBuffer(); + void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber); + void TakeScreenshot(string romName, VideoFilterType filterType); + void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr); + + virtual OverscanDimensions GetOverscan(); + virtual FrameInfo GetFrameInfo() = 0; +}; \ No newline at end of file diff --git a/Core/Console.cpp b/Core/Console.cpp index abfe6fc..ba97efa 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -3,9 +3,22 @@ #include "Cpu.h" #include "MemoryManager.h" #include "Debugger.h" +#include "VideoDecoder.h" +#include "VideoRenderer.h" +#include "DebugHud.h" #include "../Utilities/Timer.h" #include "../Utilities/VirtualFile.h" +void Console::Initialize() +{ + _videoDecoder.reset(new VideoDecoder(shared_from_this())); + _videoRenderer.reset(new VideoRenderer(shared_from_this())); + _debugHud.reset(new DebugHud()); + + _videoDecoder->StartThread(); + _videoRenderer->StartThread(); +} + void Console::Run() { if(!_cpu) { @@ -50,6 +63,21 @@ void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile) } } +shared_ptr Console::GetVideoRenderer() +{ + return _videoRenderer; +} + +shared_ptr Console::GetVideoDecoder() +{ + return _videoDecoder; +} + +shared_ptr Console::GetDebugHud() +{ + return _debugHud; +} + shared_ptr Console::GetCpu() { return _cpu; @@ -60,6 +88,11 @@ shared_ptr Console::GetPpu() return _ppu; } +shared_ptr Console::GetMemoryManager() +{ + return _memoryManager; +} + shared_ptr Console::GetDebugger(bool allowStart) { return _debugger; diff --git a/Core/Console.h b/Core/Console.h index 51a08f0..6455a78 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -7,6 +7,9 @@ class Cpu; class Ppu; class MemoryManager; class Debugger; +class DebugHud; +class VideoRenderer; +class VideoDecoder; enum class MemoryOperationType; class Console : public std::enable_shared_from_this @@ -16,18 +19,29 @@ private: shared_ptr _ppu; shared_ptr _memoryManager; shared_ptr _debugger; + + shared_ptr _videoRenderer; + shared_ptr _videoDecoder; + shared_ptr _debugHud; SimpleLock _runLock; atomic _stopFlag; public: + void Initialize(); + void Run(); void Stop(); void LoadRom(VirtualFile romFile, VirtualFile patchFile); + shared_ptr GetVideoRenderer(); + shared_ptr GetVideoDecoder(); + shared_ptr GetDebugHud(); + shared_ptr GetCpu(); shared_ptr GetPpu(); + shared_ptr GetMemoryManager(); shared_ptr GetDebugger(bool allowStart = true); void ProcessCpuRead(uint32_t addr, uint8_t value, MemoryOperationType type); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index d6d66c4..dda4e94 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -45,11 +45,20 @@ + + + + + + + + + @@ -62,16 +71,22 @@ + + + + + + @@ -89,6 +104,8 @@ Create + + {78FEF1A1-6DF1-4CBB-A373-AE6FA7CE5CE0} diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 29d593c..977fbcd 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -44,9 +44,6 @@ Interfaces - - Misc - Misc @@ -62,6 +59,45 @@ SNES + + Video + + + Video + + + Video + + + Video + + + Video + + + Misc + + + Video\DebugHud + + + Video\DebugHud + + + Video\DebugHud + + + Video\DebugHud + + + Video\DebugHud + + + Video\DebugHud + + + Video\DebugHud + @@ -80,9 +116,6 @@ SNES - - Misc - Misc @@ -98,6 +131,24 @@ SNES + + Video + + + Video + + + Video + + + Video + + + Video + + + Video\DebugHud + @@ -112,5 +163,11 @@ {0da6a615-3092-40f7-936c-253eb03a8784} + + {7aa5124a-c2c1-418e-b7bb-5ba7846aa9b0} + + + {0d0cf24e-4126-4bfb-b391-27a015e7722a} + \ No newline at end of file diff --git a/Core/DebugHud.cpp b/Core/DebugHud.cpp new file mode 100644 index 0000000..14d1d60 --- /dev/null +++ b/Core/DebugHud.cpp @@ -0,0 +1,74 @@ +#include "stdafx.h" +#include +#include "DebugHud.h" +#include "DrawCommand.h" +#include "DrawLineCommand.h" +#include "DrawPixelCommand.h" +#include "DrawRectangleCommand.h" +#include "DrawStringCommand.h" +#include "DrawScreenBufferCommand.h" + +DebugHud::DebugHud() +{ +} + +DebugHud::~DebugHud() +{ + _commandLock.Acquire(); + _commandLock.Release(); +} + +void DebugHud::ClearScreen() +{ + auto lock = _commandLock.AcquireSafe(); + _commands.clear(); +} + +void DebugHud::Draw(uint32_t* argbBuffer, OverscanDimensions overscan, uint32_t lineWidth, uint32_t frameNumber) +{ + auto lock = _commandLock.AcquireSafe(); + for(unique_ptr &command : _commands) { + command->Draw(argbBuffer, overscan, lineWidth, frameNumber); + } + _commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const unique_ptr& c) { return c->Expired(); }), _commands.end()); +} + +void DebugHud::DrawPixel(int x, int y, int color, int frameCount, int startFrame) +{ + auto lock = _commandLock.AcquireSafe(); + if(_commands.size() < DebugHud::MaxCommandCount) { + _commands.push_back(unique_ptr(new DrawPixelCommand(x, y, color, frameCount, startFrame))); + } +} + +void DebugHud::DrawLine(int x, int y, int x2, int y2, int color, int frameCount, int startFrame) +{ + auto lock = _commandLock.AcquireSafe(); + if(_commands.size() < DebugHud::MaxCommandCount) { + _commands.push_back(unique_ptr(new DrawLineCommand(x, y, x2, y2, color, frameCount, startFrame))); + } +} + +void DebugHud::DrawRectangle(int x, int y, int width, int height, int color, bool fill, int frameCount, int startFrame) +{ + auto lock = _commandLock.AcquireSafe(); + if(_commands.size() < DebugHud::MaxCommandCount) { + _commands.push_back(unique_ptr(new DrawRectangleCommand(x, y, width, height, color, fill, frameCount, startFrame))); + } +} + +void DebugHud::DrawScreenBuffer(uint32_t* screenBuffer, int startFrame) +{ + auto lock = _commandLock.AcquireSafe(); + if(_commands.size() < DebugHud::MaxCommandCount) { + _commands.push_back(unique_ptr(new DrawScreenBufferCommand(screenBuffer, startFrame))); + } +} + +void DebugHud::DrawString(int x, int y, string text, int color, int backColor, int frameCount, int startFrame) +{ + auto lock = _commandLock.AcquireSafe(); + if(_commands.size() < DebugHud::MaxCommandCount) { + _commands.push_back(unique_ptr(new DrawStringCommand(x, y, text, color, backColor, frameCount, startFrame))); + } +} diff --git a/Core/DebugHud.h b/Core/DebugHud.h new file mode 100644 index 0000000..c6ecca1 --- /dev/null +++ b/Core/DebugHud.h @@ -0,0 +1,27 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/SimpleLock.h" +#include "SettingTypes.h" + +class DrawCommand; + +class DebugHud +{ +private: + static constexpr size_t MaxCommandCount = 500000; + vector> _commands; + SimpleLock _commandLock; + +public: + DebugHud(); + ~DebugHud(); + + void Draw(uint32_t* argbBuffer, OverscanDimensions overscan, uint32_t width, uint32_t frameNumber); + void ClearScreen(); + + void DrawPixel(int x, int y, int color, int frameCount, int startFrame); + void DrawLine(int x, int y, int x2, int y2, int color, int frameCount, int startFrame); + void DrawRectangle(int x, int y, int width, int height, int color, bool fill, int frameCount, int startFrame); + void DrawScreenBuffer(uint32_t* screenBuffer, int startFrame); + void DrawString(int x, int y, string text, int color, int backColor, int frameCount, int startFrame); +}; diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index a13ec71..2acb53a 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -14,7 +14,7 @@ Debugger::Debugger(shared_ptr cpu, shared_ptr ppu, shared_ptr +#include +#include "DebugHud.h" +#include "Console.h" + +DefaultVideoFilter::DefaultVideoFilter(shared_ptr console) : BaseVideoFilter(console) +{ + InitConversionMatrix(_pictureSettings.Hue, _pictureSettings.Saturation); +} + +void DefaultVideoFilter::InitConversionMatrix(double hueShift, double saturationShift) +{ + _pictureSettings.Hue = hueShift; + _pictureSettings.Saturation = saturationShift; + + double hue = hueShift * M_PI; + double sat = saturationShift + 1; + + double baseValues[6] = { 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f }; + + double s = sin(hue) * sat; + double c = cos(hue) * sat; + + double *output = _yiqToRgbMatrix; + double *input = baseValues; + for(int n = 0; n < 3; n++) { + double i = *input++; + double q = *input++; + *output++ = i * c - q * s; + *output++ = i * s + q * c; + } +} + +FrameInfo DefaultVideoFilter::GetFrameInfo() +{ + OverscanDimensions overscan = GetOverscan(); + return { overscan.GetScreenWidth(), overscan.GetScreenHeight(), OverscanDimensions::ScreenWidth, OverscanDimensions::ScreenHeight, 4 }; +} + +void DefaultVideoFilter::OnBeforeApplyFilter() +{ + /*PictureSettings currentSettings = _console->GetSettings()->GetPictureSettings(); + if(_pictureSettings.Hue != currentSettings.Hue || _pictureSettings.Saturation != currentSettings.Saturation) { + InitConversionMatrix(currentSettings.Hue, currentSettings.Saturation); + } + _pictureSettings = currentSettings; + _needToProcess = _pictureSettings.Hue != 0 || _pictureSettings.Saturation != 0 || _pictureSettings.Brightness || _pictureSettings.Contrast; + + if(_needToProcess) { + double y, i, q; + uint32_t* originalPalette = _console->GetSettings()->GetRgbPalette(); + + for(int pal = 0; pal < 512; pal++) { + uint32_t pixelOutput = originalPalette[pal]; + double redChannel = ((pixelOutput & 0xFF0000) >> 16) / 255.0; + double greenChannel = ((pixelOutput & 0xFF00) >> 8) / 255.0; + double blueChannel = (pixelOutput & 0xFF) / 255.0; + + //Apply brightness, contrast, hue & saturation + RgbToYiq(redChannel, greenChannel, blueChannel, y, i, q); + y *= _pictureSettings.Contrast * 0.5f + 1; + y += _pictureSettings.Brightness * 0.5f; + YiqToRgb(y, i, q, redChannel, greenChannel, blueChannel); + + int r = std::min(255, (int)(redChannel * 255)); + int g = std::min(255, (int)(greenChannel * 255)); + int b = std::min(255, (int)(blueChannel * 255)); + + _calculatedPalette[pal] = 0xFF000000 | (r << 16) | (g << 8) | b; + } + } else { + memcpy(_calculatedPalette, _console->GetSettings()->GetRgbPalette(), sizeof(_calculatedPalette)); + }*/ +} + +void DefaultVideoFilter::DecodePpuBuffer(uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, bool displayScanlines) +{ + /*uint32_t* out = outputBuffer; + OverscanDimensions overscan = GetOverscan(); + uint8_t scanlineIntensity = (uint8_t)((1.0 - _console->GetSettings()->GetPictureSettings().ScanlineIntensity) * 255); + for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) { + if(displayScanlines && (i + overscan.Top) % 2 == 0) { + for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { + *out = ApplyScanlineEffect(ppuOutputBuffer[i * 256 + j], scanlineIntensity); + out++; + } + } else { + for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { + *out = _calculatedPalette[ppuOutputBuffer[i * 256 + j]]; + out++; + } + } + }*/ +} + +void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer) +{ + uint32_t *out = GetOutputBuffer(); + for(int i = 0; i < 256 * 224; i++) { + uint16_t rgb555 = ppuOutputBuffer[i]; + uint8_t b = (rgb555 >> 10) * 256 / 32; + uint8_t g = ((rgb555 >> 5) & 0x1F) * 256 / 32; + uint8_t r = (rgb555 & 0x1F) * 256 / 32; + + out[i] = 0xFF000000 | (r << 16) | (g << 8) | b; + } + //DecodePpuBuffer(ppuOutputBuffer, GetOutputBuffer(), _console->GetSettings()->GetVideoFilterType() <= VideoFilterType::BisqwitNtsc); +} + +void DefaultVideoFilter::RgbToYiq(double r, double g, double b, double &y, double &i, double &q) +{ + y = r * 0.299f + g * 0.587f + b * 0.114f; + i = r * 0.596f - g * 0.275f - b * 0.321f; + q = r * 0.212f - g * 0.523f + b * 0.311f; +} + +void DefaultVideoFilter::YiqToRgb(double y, double i, double q, double &r, double &g, double &b) +{ + r = std::max(0.0, std::min(1.0, (y + _yiqToRgbMatrix[0] * i + _yiqToRgbMatrix[1] * q))); + g = std::max(0.0, std::min(1.0, (y + _yiqToRgbMatrix[2] * i + _yiqToRgbMatrix[3] * q))); + b = std::max(0.0, std::min(1.0, (y + _yiqToRgbMatrix[4] * i + _yiqToRgbMatrix[5] * q))); +} + +uint32_t DefaultVideoFilter::ApplyScanlineEffect(uint16_t ppuPixel, uint8_t scanlineIntensity) +{ + uint32_t pixelOutput = _calculatedPalette[ppuPixel]; + + uint8_t r = ((pixelOutput & 0xFF0000) >> 16) * scanlineIntensity / 255; + uint8_t g = ((pixelOutput & 0xFF00) >> 8) * scanlineIntensity / 255; + uint8_t b = (pixelOutput & 0xFF) * scanlineIntensity / 255; + + return 0xFF000000 | (r << 16) | (g << 8) | b; +} \ No newline at end of file diff --git a/Core/DefaultVideoFilter.h b/Core/DefaultVideoFilter.h new file mode 100644 index 0000000..4d3c44c --- /dev/null +++ b/Core/DefaultVideoFilter.h @@ -0,0 +1,28 @@ +#pragma once + +#include "stdafx.h" +#include "BaseVideoFilter.h" + +class DefaultVideoFilter : public BaseVideoFilter +{ +private: + double _yiqToRgbMatrix[6]; + uint32_t _calculatedPalette[512]; + PictureSettings _pictureSettings; + bool _needToProcess = false; + + void InitConversionMatrix(double hueShift, double saturationShift); + + void RgbToYiq(double r, double g, double b, double &y, double &i, double &q); + void YiqToRgb(double y, double i, double q, double &r, double &g, double &b); + +protected: + void DecodePpuBuffer(uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, bool displayScanlines); + uint32_t ApplyScanlineEffect(uint16_t ppuPixel, uint8_t scanlineIntensity); + void OnBeforeApplyFilter(); + +public: + DefaultVideoFilter(shared_ptr console); + void ApplyFilter(uint16_t *ppuOutputBuffer); + FrameInfo GetFrameInfo(); +}; \ No newline at end of file diff --git a/Core/DrawCommand.h b/Core/DrawCommand.h new file mode 100644 index 0000000..bacfc49 --- /dev/null +++ b/Core/DrawCommand.h @@ -0,0 +1,98 @@ +#pragma once +#include "stdafx.h" +#include "SettingTypes.h" +#include "Console.h" + +class DrawCommand +{ +private: + int _frameCount; + uint32_t* _argbBuffer; + OverscanDimensions _overscan; + uint32_t _lineWidth; + uint32_t _startFrame; + +protected: + bool _useIntegerScaling; + float _xScale; + int _yScale; + + virtual void InternalDraw() = 0; + __forceinline void DrawPixel(uint32_t x, uint32_t y, int color) + { + if(x < _overscan.Left || x >= _overscan.Left + _overscan.GetScreenWidth() || + y < _overscan.Top || y >= _overscan.Top + _overscan.GetScreenHeight()) { + //In overscan (out of bounds), skip drawing + return; + } + + uint32_t alpha = (color & 0xFF000000); + if(alpha > 0) { + if(_yScale == 1) { + if(alpha != 0xFF000000) { + BlendColors((uint8_t*)&_argbBuffer[(y - _overscan.Top)*_lineWidth + (x - _overscan.Left)], (uint8_t*)&color); + } else { + _argbBuffer[(y - _overscan.Top)*_lineWidth + (x - _overscan.Left)] = color; + } + } else { + int xPixelCount = _useIntegerScaling ? _yScale : (int)((x + 1)*_xScale) - (int)(x*_xScale); + x = (int)(x * (_useIntegerScaling ? _yScale : _xScale)); + y = (int)(y * _yScale); + int top = (int)(_overscan.Top * _yScale); + int left = (int)(_overscan.Left * _xScale); + + for(int i = 0; i < _yScale; i++) { + for(int j = 0; j < xPixelCount; j++) { + if(alpha != 0xFF000000) { + BlendColors((uint8_t*)&_argbBuffer[(y - top)*_lineWidth + i*_lineWidth + (x - left)+j], (uint8_t*)&color); + } else { + _argbBuffer[(y - top)*_lineWidth + i*_lineWidth + (x - left) +j] = color; + } + } + } + } + } + } + + __forceinline void BlendColors(uint8_t output[4], uint8_t input[4]) + { + uint8_t alpha = input[3] + 1; + uint8_t invertedAlpha = 256 - input[3]; + output[0] = (uint8_t)((alpha * input[0] + invertedAlpha * output[0]) >> 8); + output[1] = (uint8_t)((alpha * input[1] + invertedAlpha * output[1]) >> 8); + output[2] = (uint8_t)((alpha * input[2] + invertedAlpha * output[2]) >> 8); + output[3] = 0xFF; + } + +public: + DrawCommand(int startFrame, int frameCount, bool useIntegerScaling = false) + { + _frameCount = frameCount > 0 ? frameCount : 1; + _startFrame = startFrame; + _useIntegerScaling = useIntegerScaling; + } + + virtual ~DrawCommand() + { + } + + void Draw(uint32_t* argbBuffer, OverscanDimensions &overscan, uint32_t lineWidth, uint32_t frameNumber) + { + if(_startFrame <= frameNumber) { + _argbBuffer = argbBuffer; + _overscan = overscan; + _lineWidth = lineWidth; + _yScale = lineWidth / overscan.GetScreenWidth(); + _xScale = (float)lineWidth / overscan.GetScreenWidth(); + + InternalDraw(); + + _frameCount--; + } + } + + bool Expired() + { + return _frameCount <= 0; + } +}; diff --git a/Core/DrawLineCommand.h b/Core/DrawLineCommand.h new file mode 100644 index 0000000..28daaf7 --- /dev/null +++ b/Core/DrawLineCommand.h @@ -0,0 +1,42 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawLineCommand : public DrawCommand +{ +private: + int _x, _y, _x2, _y2, _color; + +protected: + void InternalDraw() + { + int x = _x; + int y = _y; + int dx = abs(_x2 - x), sx = x < _x2 ? 1 : -1; + int dy = abs(_y2 - y), sy = y < _y2 ? 1 : -1; + int err = (dx > dy ? dx : -dy) / 2, e2; + + while(true) { + DrawPixel(x, y, _color); + if(x == _x2 && y == _y2) { + break; + } + + e2 = err; + if(e2 > -dx) { + err -= dy; x += sx; + } + if(e2 < dy) { + err += dx; y += sy; + } + } + } + +public: + DrawLineCommand(int x, int y, int x2, int y2, int color, int frameCount, int startFrame) : + DrawCommand(startFrame, frameCount), _x(x), _y(y), _x2(x2), _y2(y2), _color(color) + { + //Invert alpha byte - 0 = opaque, 255 = transparent (this way, no need to specifiy alpha channel all the time) + _color = (~color & 0xFF000000) | (color & 0xFFFFFF); + } +}; diff --git a/Core/DrawPixelCommand.h b/Core/DrawPixelCommand.h new file mode 100644 index 0000000..5da1ce8 --- /dev/null +++ b/Core/DrawPixelCommand.h @@ -0,0 +1,23 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawPixelCommand : public DrawCommand +{ +private: + int _x, _y, _color; + +protected: + void InternalDraw() + { + DrawPixel(_x, _y, _color); + } + +public: + DrawPixelCommand(int x, int y, int color, int frameCount, int startFrame) : + DrawCommand(startFrame, frameCount), _x(x), _y(y), _color(color) + { + //Invert alpha byte - 0 = opaque, 255 = transparent (this way, no need to specifiy alpha channel all the time) + _color = (~color & 0xFF000000) | (color & 0xFFFFFF); + } +}; diff --git a/Core/DrawRectangleCommand.h b/Core/DrawRectangleCommand.h new file mode 100644 index 0000000..2062626 --- /dev/null +++ b/Core/DrawRectangleCommand.h @@ -0,0 +1,48 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawRectangleCommand : public DrawCommand +{ +private: + int _x, _y, _width, _height, _color; + bool _fill; + +protected: + void InternalDraw() + { + if(_fill) { + for(int j = 0; j < _height; j++) { + for(int i = 0; i < _width; i++) { + DrawPixel(_x + i, _y + j, _color); + } + } + } else { + for(int i = 0; i < _width; i++) { + DrawPixel(_x + i, _y, _color); + DrawPixel(_x + i, _y + _height - 1, _color); + } + for(int i = 1; i < _height - 1; i++) { + DrawPixel(_x, _y + i, _color); + DrawPixel(_x + _width - 1, _y + i, _color); + } + } + } + +public: + DrawRectangleCommand(int x, int y, int width, int height, int color, bool fill, int frameCount, int startFrame) : + DrawCommand(startFrame, frameCount), _x(x), _y(y), _width(width), _height(height), _color(color), _fill(fill) + { + if(width < 0) { + _x += width + 1; + _width = -width; + } + if(height < 0) { + _y += height + 1; + _height = -height; + } + + //Invert alpha byte - 0 = opaque, 255 = transparent (this way, no need to specifiy alpha channel all the time) + _color = (~color & 0xFF000000) | (color & 0xFFFFFF); + } +}; \ No newline at end of file diff --git a/Core/DrawScreenBufferCommand.h b/Core/DrawScreenBufferCommand.h new file mode 100644 index 0000000..2cff980 --- /dev/null +++ b/Core/DrawScreenBufferCommand.h @@ -0,0 +1,25 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawScreenBufferCommand : public DrawCommand +{ +private: + uint32_t _screenBuffer[256*240]; + +protected: + void InternalDraw() + { + for(int y = 0; y < 240; y++) { + for(int x = 0; x < 256; x++) { + DrawPixel(x, y, _screenBuffer[(y << 8) + x]); + } + } + } + +public: + DrawScreenBufferCommand(uint32_t* screenBuffer, int startFrame) : DrawCommand(startFrame, 1) + { + memcpy(_screenBuffer, screenBuffer, 256 * 240 * sizeof(uint32_t)); + } +}; diff --git a/Core/DrawStringCommand.h b/Core/DrawStringCommand.h new file mode 100644 index 0000000..6aea0a8 --- /dev/null +++ b/Core/DrawStringCommand.h @@ -0,0 +1,161 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawStringCommand : public DrawCommand +{ +private: + int _x, _y, _color, _backColor; + string _text; + + //Taken from FCEUX's LUA code + const int _tabSpace = 4; + const uint8_t _font[792] = { + 6, 0, 0, 0, 0, 0, 0, 0, // 0x20 - Spacebar + 3, 64, 64, 64, 64, 64, 0, 64, + 5, 80, 80, 80, 0, 0, 0, 0, + 6, 80, 80,248, 80,248, 80, 80, + 6, 32,120,160,112, 40,240, 32, + 6, 64,168, 80, 32, 80,168, 16, + 6, 96,144,160, 64,168,144,104, + 3, 64, 64, 0, 0, 0, 0, 0, + 4, 32, 64, 64, 64, 64, 64, 32, + 4, 64, 32, 32, 32, 32, 32, 64, + 6, 0, 80, 32,248, 32, 80, 0, + 6, 0, 32, 32,248, 32, 32, 0, + 3, 0, 0, 0, 0, 0, 64,128, + 5, 0, 0, 0,240, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 64, + 5, 16, 16, 32, 32, 32, 64, 64, + 6,112,136,136,136,136,136,112, // 0x30 - 0 + 6, 32, 96, 32, 32, 32, 32, 32, + 6,112,136, 8, 48, 64,128,248, + 6,112,136, 8, 48, 8,136,112, + 6, 16, 48, 80,144,248, 16, 16, + 6,248,128,128,240, 8, 8,240, + 6, 48, 64,128,240,136,136,112, + 6,248, 8, 16, 16, 32, 32, 32, + 6,112,136,136,112,136,136,112, + 6,112,136,136,120, 8, 16, 96, + 3, 0, 0, 64, 0, 0, 64, 0, + 3, 0, 0, 64, 0, 0, 64,128, + 4, 0, 32, 64,128, 64, 32, 0, + 5, 0, 0,240, 0,240, 0, 0, + 4, 0,128, 64, 32, 64,128, 0, + 6,112,136, 8, 16, 32, 0, 32, // 0x3F - ? + 6,112,136,136,184,176,128,112, // 0x40 - @ + 6,112,136,136,248,136,136,136, // 0x41 - A + 6,240,136,136,240,136,136,240, + 6,112,136,128,128,128,136,112, + 6,224,144,136,136,136,144,224, + 6,248,128,128,240,128,128,248, + 6,248,128,128,240,128,128,128, + 6,112,136,128,184,136,136,120, + 6,136,136,136,248,136,136,136, + 4,224, 64, 64, 64, 64, 64,224, + 6, 8, 8, 8, 8, 8,136,112, + 6,136,144,160,192,160,144,136, + 6,128,128,128,128,128,128,248, + 6,136,216,168,168,136,136,136, + 6,136,136,200,168,152,136,136, + 7, 48, 72,132,132,132, 72, 48, + 6,240,136,136,240,128,128,128, + 6,112,136,136,136,168,144,104, + 6,240,136,136,240,144,136,136, + 6,112,136,128,112, 8,136,112, + 6,248, 32, 32, 32, 32, 32, 32, + 6,136,136,136,136,136,136,112, + 6,136,136,136, 80, 80, 32, 32, + 6,136,136,136,136,168,168, 80, + 6,136,136, 80, 32, 80,136,136, + 6,136,136, 80, 32, 32, 32, 32, + 6,248, 8, 16, 32, 64,128,248, + 3,192,128,128,128,128,128,192, + 5, 64, 64, 32, 32, 32, 16, 16, + 3,192, 64, 64, 64, 64, 64,192, + 4, 64,160, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 0, 0, 0,248, + 3,128, 64, 0, 0, 0, 0, 0, + 5, 0, 0, 96, 16,112,144,112, // 0x61 - a + 5,128,128,224,144,144,144,224, + 5, 0, 0,112,128,128,128,112, + 5, 16, 16,112,144,144,144,112, + 5, 0, 0, 96,144,240,128,112, + 5, 48, 64,224, 64, 64, 64, 64, + 5, 0,112,144,144,112, 16,224, + 5,128,128,224,144,144,144,144, + 2,128, 0,128,128,128,128,128, + 4, 32, 0, 32, 32, 32, 32,192, + 5,128,128,144,160,192,160,144, + 2,128,128,128,128,128,128,128, + 6, 0, 0,208,168,168,168,168, + 5, 0, 0,224,144,144,144,144, + 5, 0, 0, 96,144,144,144, 96, + 5, 0,224,144,144,224,128,128, + 5, 0,112,144,144,112, 16, 16, + 5, 0, 0,176,192,128,128,128, + 5, 0, 0,112,128, 96, 16,224, + 4, 64, 64,224, 64, 64, 64, 32, + 5, 0, 0,144,144,144,144,112, + 5, 0, 0,144,144,144,160,192, + 6, 0, 0,136,136,168,168, 80, + 5, 0, 0,144,144, 96,144,144, + 5, 0,144,144,144,112, 16, 96, + 5, 0, 0,240, 32, 64,128,240, + 4, 32, 64, 64,128, 64, 64, 32, + 3, 64, 64, 64, 64, 64, 64, 64, + 4,128, 64, 64, 32, 64, 64,128, + 6, 0,104,176, 0, 0, 0, 0 + }; + + int GetCharNumber(char ch) + { + ch -= 32; + return (ch < 0 || ch > 98) ? 0 : ch; + } + + int GetCharWidth(char ch) + { + return _font[GetCharNumber(ch) * 8]; + } + +protected: + void InternalDraw() + { + int startX = (int)(_x * _xScale / _yScale); + int x = startX; + int y = _y; + for(char c : _text) { + if(c == '\n') { + x = startX; + y += 9; + } else if(c == '\t') { + x += (_tabSpace - (((x - startX) / 8) % _tabSpace)) * 8; + } else { + int ch = GetCharNumber(c); + int width = GetCharWidth(c); + int rowOffset = (c == 'y' || c == 'g' || c == 'p' || c == 'q') ? 1 : 0; + for(int j = 0; j < 8; j++) { + uint8_t rowData = ((j == 7 && rowOffset == 0) || (j == 0 && rowOffset == 1)) ? 0 : _font[ch * 8 + 1 + j - rowOffset]; + for(int i = 0; i < width; i++) { + int drawFg = (rowData >> (7 - i)) & 0x01; + DrawPixel(x + i, y + j, drawFg ? _color : _backColor); + } + } + for(int i = 0; i < width; i++) { + DrawPixel(x + i, y - 1, _backColor); + } + x += width; + } + } + } + +public: + DrawStringCommand(int x, int y, string text, int color, int backColor, int frameCount, int startFrame) : + DrawCommand(startFrame, frameCount, true), _x(x), _y(y), _color(color), _backColor(backColor), _text(text) + { + //Invert alpha byte - 0 = opaque, 255 = transparent (this way, no need to specifiy alpha channel all the time) + _color = (~color & 0xFF000000) | (color & 0xFFFFFF); + _backColor = (~backColor & 0xFF000000) | (backColor & 0xFFFFFF); + } +}; diff --git a/Core/SettingTypes.h b/Core/SettingTypes.h new file mode 100644 index 0000000..c0338ec --- /dev/null +++ b/Core/SettingTypes.h @@ -0,0 +1,129 @@ +#pragma once +#include "stdafx.h" + +enum class ScaleFilterType +{ + xBRZ = 0, + HQX = 1, + Scale2x = 2, + _2xSai = 3, + Super2xSai = 4, + SuperEagle = 5, + Prescale = 6, +}; + +enum class VideoFilterType +{ + None = 0, + NTSC = 1, + BisqwitNtscQuarterRes = 2, + BisqwitNtscHalfRes = 3, + BisqwitNtsc = 4, + xBRZ2x = 5, + xBRZ3x = 6, + xBRZ4x = 7, + xBRZ5x = 8, + xBRZ6x = 9, + HQ2x = 10, + HQ3x = 11, + HQ4x = 12, + Scale2x = 13, + Scale3x = 14, + Scale4x = 15, + _2xSai = 16, + Super2xSai = 17, + SuperEagle = 18, + Prescale2x = 19, + Prescale3x = 20, + Prescale4x = 21, + Prescale6x = 22, + Prescale8x = 23, + Prescale10x = 24, + Raw = 25, + HdPack = 999 +}; + +enum class VideoResizeFilter +{ + NearestNeighbor = 0, + Bilinear = 1 +}; + +enum class VideoAspectRatio +{ + NoStretching = 0, + Auto = 1, + NTSC = 2, + PAL = 3, + Standard = 4, + Widescreen = 5, + Custom = 6 +}; + +struct OverscanDimensions +{ + static const int ScreenWidth = 256; + static const int ScreenHeight = 224; + + uint32_t Left = 0; + uint32_t Right = 0; + uint32_t Top = 0; + uint32_t Bottom = 0; + + uint32_t GetPixelCount() + { + return GetScreenWidth() * GetScreenHeight(); + } + + uint32_t GetScreenWidth() + { + return 256 - Left - Right; + } + + uint32_t GetScreenHeight() + { + return 224 - Top - Bottom; + } +}; + +struct PictureSettings +{ + double Brightness = 0; + double Contrast = 0; + double Saturation = 0; + double Hue = 0; + double ScanlineIntensity = 0; +}; + +struct NtscFilterSettings +{ + double Sharpness = 0; + double Gamma = 0; + double Resolution = 0; + double Artifacts = 0; + double Fringing = 0; + double Bleed = 0; + bool MergeFields = false; + bool VerticalBlend = false; + bool KeepVerticalResolution = false; + + double YFilterLength = 0; + double IFilterLength = 0; + double QFilterLength = 0; +}; + +struct FrameInfo +{ + uint32_t Width; + uint32_t Height; + uint32_t OriginalWidth; + uint32_t OriginalHeight; + uint32_t BitsPerPixel; +}; + +struct ScreenSize +{ + int32_t Width; + int32_t Height; + double Scale; +}; diff --git a/Core/VideoDecoder.cpp b/Core/VideoDecoder.cpp new file mode 100644 index 0000000..86b523a --- /dev/null +++ b/Core/VideoDecoder.cpp @@ -0,0 +1,240 @@ +#include "stdafx.h" +#include "IRenderingDevice.h" +#include "VideoDecoder.h" +#include "VideoRenderer.h" +#include "DefaultVideoFilter.h" +#include "Console.h" +#include "Ppu.h" +#include "DebugHud.h" + +VideoDecoder::VideoDecoder(shared_ptr console) +{ + _console = console; + _frameChanged = false; + _stopFlag = false; + UpdateVideoFilter(); +} + +VideoDecoder::~VideoDecoder() +{ + StopThread(); +} + +FrameInfo VideoDecoder::GetFrameInfo() +{ + return _lastFrameInfo; +} + +void VideoDecoder::GetScreenSize(ScreenSize &size, bool ignoreScale) +{ + if(_videoFilter) { + /*OverscanDimensions overscan = ignoreScale ? _videoFilter->GetOverscan() : _console->GetSettings()->GetOverscanDimensions(); + FrameInfo frameInfo{ overscan.GetScreenWidth(), overscan.GetScreenHeight(), PPU::ScreenWidth, PPU::ScreenHeight, 4 }; + double aspectRatio = _console->GetSettings()->GetAspectRatio(_console); + double scale = (ignoreScale ? 1 : _console->GetSettings()->GetVideoScale()); + size.Width = (int32_t)(frameInfo.Width * scale); + size.Height = (int32_t)(frameInfo.Height * scale); + if(aspectRatio != 0.0) { + size.Width = (uint32_t)(frameInfo.OriginalHeight * scale * aspectRatio * ((double)frameInfo.Width / frameInfo.OriginalWidth)); + } + + if(_console->GetSettings()->GetScreenRotation() % 180) { + std::swap(size.Width, size.Height); + } + + size.Scale = scale;*/ + + if(true || ignoreScale) { + size.Width = 256; + size.Height = 224; + } else { + size.Width = 512; + size.Height = 448; + } + size.Scale = 2; + } +} + +void VideoDecoder::UpdateVideoFilter() +{ + VideoFilterType newFilter = VideoFilterType::None; + + if(_videoFilterType != newFilter || _videoFilter == nullptr) { + _videoFilterType = newFilter; + _videoFilter.reset(new DefaultVideoFilter(_console)); + //_scaleFilter.reset(); + + switch(_videoFilterType) { + case VideoFilterType::None: break; + + //TODO + /* + case VideoFilterType::NTSC: _videoFilter.reset(new NtscFilter(_console)); break; + case VideoFilterType::BisqwitNtsc: _videoFilter.reset(new BisqwitNtscFilter(_console, 1)); break; + case VideoFilterType::BisqwitNtscHalfRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 2)); break; + case VideoFilterType::BisqwitNtscQuarterRes: _videoFilter.reset(new BisqwitNtscFilter(_console, 4)); break; + case VideoFilterType::Raw: _videoFilter.reset(new RawVideoFilter(_console)); break; + default: _scaleFilter = ScaleFilter::GetScaleFilter(_videoFilterType); break;*/ + } + } + + //TODO + /* + if(_console->GetSettings()->GetScreenRotation() == 0 && _rotateFilter) { + _rotateFilter.reset(); + } else if(_console->GetSettings()->GetScreenRotation() > 0) { + if(!_rotateFilter || _rotateFilter->GetAngle() != _console->GetSettings()->GetScreenRotation()) { + _rotateFilter.reset(new RotateFilter(_console->GetSettings()->GetScreenRotation())); + } + }*/ +} + +void VideoDecoder::DecodeFrame(bool synchronous) +{ + UpdateVideoFilter(); + + _videoFilter->SendFrame(_ppuOutputBuffer, _frameNumber); + + uint32_t* outputBuffer = _videoFilter->GetOutputBuffer(); + FrameInfo frameInfo = _videoFilter->GetFrameInfo(); + _console->GetDebugHud()->Draw(outputBuffer, _videoFilter->GetOverscan(), frameInfo.Width, _frameNumber); + + /* + if(_rotateFilter) { + outputBuffer = _rotateFilter->ApplyFilter(outputBuffer, frameInfo.Width, frameInfo.Height); + frameInfo = _rotateFilter->GetFrameInfo(frameInfo); + } + + if(_scaleFilter) { + outputBuffer = _scaleFilter->ApplyFilter(outputBuffer, frameInfo.Width, frameInfo.Height, _console->GetSettings()->GetPictureSettings().ScanlineIntensity); + frameInfo = _scaleFilter->GetFrameInfo(frameInfo); + } + + if(_hud) { + _hud->DrawHud(_console, outputBuffer, frameInfo, _videoFilter->GetOverscan()); + }*/ + + ScreenSize screenSize; + GetScreenSize(screenSize, true); + /*if(_previousScale != _console->GetSettings()->GetVideoScale() || screenSize.Height != _previousScreenSize.Height || screenSize.Width != _previousScreenSize.Width) { + _console->GetNotificationManager()->SendNotification(ConsoleNotificationType::ResolutionChanged); + }*/ + _previousScale = 1; // _console->GetSettings()->GetVideoScale(); + _previousScreenSize = screenSize; + _lastFrameInfo = frameInfo; + + _frameChanged = false; + + //Rewind manager will take care of sending the correct frame to the video renderer + _console->GetVideoRenderer()->UpdateFrame(outputBuffer, frameInfo.Width, frameInfo.Height); +} + +void VideoDecoder::DecodeThread() +{ + //This thread will decode the PPU's output (color ID to RGB, intensify r/g/b and produce a HD version of the frame if needed) + while(!_stopFlag.load()) { + //DecodeFrame returns the final ARGB frame we want to display in the emulator window + while(!_frameChanged) { + _waitForFrame.Wait(); + if(_stopFlag.load()) { + return; + } + } + + DecodeFrame(); + } +} + +uint32_t VideoDecoder::GetFrameCount() +{ + return _frameCount; +} + +void VideoDecoder::UpdateFrameSync(uint16_t *ppuOutputBuffer, uint32_t frameNumber) +{ + _frameNumber = frameNumber; + _ppuOutputBuffer = ppuOutputBuffer; + DecodeFrame(true); + _frameCount++; +} + +void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber) +{ + if(_frameChanged) { + //Last frame isn't done decoding yet - sometimes Signal() introduces a 25-30ms delay + while(_frameChanged) { + //Spin until decode is done + } + //At this point, we are sure that the decode thread is no longer busy + } + + _frameNumber = frameNumber; + _ppuOutputBuffer = ppuOutputBuffer; + _frameChanged = true; + _waitForFrame.Signal(); + + _frameCount++; +} + +void VideoDecoder::StartThread() +{ +#ifndef LIBRETRO + if(!_decodeThread) { + _stopFlag = false; + _frameChanged = false; + _frameCount = 0; + _waitForFrame.Reset(); + + //TODO + //_hud.reset(new VideoHud()); + + _decodeThread.reset(new thread(&VideoDecoder::DecodeThread, this)); + } +#endif +} + +void VideoDecoder::StopThread() +{ +#ifndef LIBRETRO + _stopFlag = true; + if(_decodeThread) { + _waitForFrame.Signal(); + _decodeThread->join(); + + _decodeThread.reset(); + + + //TODO + //_hud.reset(); + /*UpdateVideoFilter(); + if(_ppuOutputBuffer != nullptr) { + //Clear whole screen + for(uint32_t i = 0; i < PPU::PixelCount; i++) { + _ppuOutputBuffer[i] = 14; //Black + } + DecodeFrame(); + }*/ + _ppuOutputBuffer = nullptr; + } +#endif +} + +bool VideoDecoder::IsRunning() +{ + return _decodeThread != nullptr; +} + +void VideoDecoder::TakeScreenshot() +{ + if(_videoFilter) { + //TODO + //_videoFilter->TakeScreenshot(_console->GetRomPath().GetFileName(), _videoFilterType); + } +} + +void VideoDecoder::TakeScreenshot(std::stringstream &stream) +{ + if(_videoFilter) { + _videoFilter->TakeScreenshot(_videoFilterType, "", &stream); + } +} diff --git a/Core/VideoDecoder.h b/Core/VideoDecoder.h new file mode 100644 index 0000000..d0a3d21 --- /dev/null +++ b/Core/VideoDecoder.h @@ -0,0 +1,63 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/SimpleLock.h" +#include "../Utilities/AutoResetEvent.h" +#include "SettingTypes.h" + +class BaseVideoFilter; +//class ScaleFilter; +//class RotateFilter; +class IRenderingDevice; +//class VideoHud; +class Console; + +class VideoDecoder +{ +private: + shared_ptr _console; + + uint16_t *_ppuOutputBuffer = nullptr; + uint32_t _frameNumber = 0; + + unique_ptr _decodeThread; + //unique_ptr _hud; + + AutoResetEvent _waitForFrame; + + atomic _frameChanged; + atomic _stopFlag; + uint32_t _frameCount = 0; + + ScreenSize _previousScreenSize = {}; + double _previousScale = 0; + FrameInfo _lastFrameInfo; + + VideoFilterType _videoFilterType = VideoFilterType::None; + unique_ptr _videoFilter; + //shared_ptr _scaleFilter; + //shared_ptr _rotateFilter; + + void UpdateVideoFilter(); + + void DecodeThread(); + +public: + VideoDecoder(shared_ptr console); + ~VideoDecoder(); + + void DecodeFrame(bool synchronous = false); + void TakeScreenshot(); + void TakeScreenshot(std::stringstream &stream); + + uint32_t GetFrameCount(); + + FrameInfo GetFrameInfo(); + void GetScreenSize(ScreenSize &size, bool ignoreScale); + + void UpdateFrameSync(uint16_t *ppuOutputBuffer, uint32_t frameNumber); + void UpdateFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber); + + bool IsRunning(); + void StartThread(); + void StopThread(); +}; \ No newline at end of file diff --git a/Core/VideoRenderer.cpp b/Core/VideoRenderer.cpp new file mode 100644 index 0000000..900a801 --- /dev/null +++ b/Core/VideoRenderer.cpp @@ -0,0 +1,116 @@ +#include "stdafx.h" +#include "IRenderingDevice.h" +#include "VideoRenderer.h" +//#include "AviRecorder.h" +#include "VideoDecoder.h" +#include "Console.h" + +VideoRenderer::VideoRenderer(shared_ptr console) +{ + _console = console; + _stopFlag = false; + StartThread(); +} + +VideoRenderer::~VideoRenderer() +{ + _stopFlag = true; + StopThread(); +} + +void VideoRenderer::StartThread() +{ +#ifndef LIBRETRO + if(!_renderThread) { + _stopFlag = false; + _waitForRender.Reset(); + + _renderThread.reset(new std::thread(&VideoRenderer::RenderThread, this)); + } +#endif +} + +void VideoRenderer::StopThread() +{ +#ifndef LIBRETRO + _stopFlag = true; + if(_renderThread) { + _renderThread->join(); + _renderThread.reset(); + } +#endif +} + +void VideoRenderer::RenderThread() +{ + if(_renderer) { + _renderer->Reset(); + } + + while(!_stopFlag.load()) { + //Wait until a frame is ready, or until 16ms have passed (to allow UI to run at a minimum of 60fps) + _waitForRender.Wait(16); + if(_renderer) { + _renderer->Render(); + } + } +} + +void VideoRenderer::UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height) +{ + //TODO + /*shared_ptr aviRecorder = _aviRecorder; + if(aviRecorder) { + aviRecorder->AddFrame(frameBuffer, width, height); + }*/ + + if(_renderer) { + _renderer->UpdateFrame(frameBuffer, width, height); + _waitForRender.Signal(); + } +} + +void VideoRenderer::RegisterRenderingDevice(IRenderingDevice *renderer) +{ + _renderer = renderer; + StartThread(); +} + +void VideoRenderer::UnregisterRenderingDevice(IRenderingDevice *renderer) +{ + if(_renderer == renderer) { + StopThread(); + _renderer = nullptr; + } +} + +//TODO +/* +void VideoRenderer::StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel) +{ + shared_ptr recorder(new AviRecorder(_console)); + + FrameInfo frameInfo = _console->GetVideoDecoder()->GetFrameInfo(); + if(recorder->StartRecording(filename, codec, frameInfo.Width, frameInfo.Height, frameInfo.BitsPerPixel, _console->GetSettings()->GetSampleRate(), compressionLevel)) { + _aviRecorder = recorder; + } +} + +void VideoRenderer::AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate) +{ + shared_ptr aviRecorder = _aviRecorder; + if(aviRecorder) { + aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate); + } +} + +void VideoRenderer::StopRecording() +{ + _aviRecorder.reset(); +} + +bool VideoRenderer::IsRecording() +{ + return _aviRecorder != nullptr && _aviRecorder->IsRecording(); +} +*/ \ No newline at end of file diff --git a/Core/VideoRenderer.h b/Core/VideoRenderer.h new file mode 100644 index 0000000..9d6683f --- /dev/null +++ b/Core/VideoRenderer.h @@ -0,0 +1,45 @@ +#pragma once +#include "stdafx.h" +#include +#include "../Utilities/AutoResetEvent.h" + +class IRenderingDevice; +class Console; + +//TODO +//class AviRecorder; +//enum class VideoCodec; + +class VideoRenderer +{ +private: + shared_ptr _console; + + AutoResetEvent _waitForRender; + unique_ptr _renderThread; + IRenderingDevice* _renderer = nullptr; + atomic _stopFlag; + + //TODO + //shared_ptr _aviRecorder; + + void RenderThread(); + +public: + VideoRenderer(shared_ptr console); + ~VideoRenderer(); + + void StartThread(); + void StopThread(); + + void UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height); + void RegisterRenderingDevice(IRenderingDevice *renderer); + void UnregisterRenderingDevice(IRenderingDevice *renderer); + + //TODO + /* + void StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel); + void AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); + void StopRecording(); + bool IsRecording();*/ +}; \ No newline at end of file diff --git a/Core/stdafx.h b/Core/stdafx.h index e0e5a34..3598d11 100644 --- a/Core/stdafx.h +++ b/Core/stdafx.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "../Utilities/UTF8Util.h" @@ -42,6 +43,7 @@ using std::max; using std::string; using std::atomic_flag; using std::atomic; +using std::thread; #ifdef _DEBUG #pragma comment(lib, "C:\\Code\\Mesen-S\\bin\\x64\\Debug\\Utilities.lib") diff --git a/InteropDLL/EmuApiWrapper.cpp b/InteropDLL/EmuApiWrapper.cpp index 2488312..e185c55 100644 --- a/InteropDLL/EmuApiWrapper.cpp +++ b/InteropDLL/EmuApiWrapper.cpp @@ -62,6 +62,7 @@ namespace InteropEmu { DllExport void __stdcall InitDll() { _console.reset(new Console()); + _console->Initialize(); } DllExport void __stdcall InitializeEmu(const char* homeFolder, void *windowHandle, void *viewerHandle, bool noAudio, bool noVideo, bool noInput) diff --git a/UI/Forms/frmMain.Designer.cs b/UI/Forms/frmMain.Designer.cs index 2cf7c97..36aa617 100644 --- a/UI/Forms/frmMain.Designer.cs +++ b/UI/Forms/frmMain.Designer.cs @@ -34,10 +34,10 @@ this.debugToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.mnuRun = new System.Windows.Forms.ToolStripMenuItem(); this.mnuStep = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuRun100Instructions = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); this.mnuDebugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuTraceLogger = new System.Windows.Forms.ToolStripMenuItem(); - this.mnuRun100Instructions = new System.Windows.Forms.ToolStripMenuItem(); this.mnuMain.SuspendLayout(); this.SuspendLayout(); // @@ -55,7 +55,7 @@ this.debugToolStripMenuItem}); this.mnuMain.Location = new System.Drawing.Point(0, 0); this.mnuMain.Name = "mnuMain"; - this.mnuMain.Size = new System.Drawing.Size(514, 24); + this.mnuMain.Size = new System.Drawing.Size(512, 24); this.mnuMain.TabIndex = 1; this.mnuMain.Text = "ctrlMesenMenuStrip1"; // @@ -71,7 +71,8 @@ // this.mnuOpen.Image = global::Mesen.GUI.Properties.Resources.Folder; this.mnuOpen.Name = "mnuOpen"; - this.mnuOpen.Size = new System.Drawing.Size(103, 22); + this.mnuOpen.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.mnuOpen.Size = new System.Drawing.Size(152, 22); this.mnuOpen.Text = "Open"; this.mnuOpen.Click += new System.EventHandler(this.mnuOpen_Click); // @@ -92,7 +93,7 @@ // this.mnuRun.Name = "mnuRun"; this.mnuRun.ShortcutKeys = System.Windows.Forms.Keys.F5; - this.mnuRun.Size = new System.Drawing.Size(157, 22); + this.mnuRun.Size = new System.Drawing.Size(163, 22); this.mnuRun.Text = "Run"; this.mnuRun.Click += new System.EventHandler(this.mnuRun_Click); // @@ -100,41 +101,41 @@ // this.mnuStep.Name = "mnuStep"; this.mnuStep.ShortcutKeys = System.Windows.Forms.Keys.F11; - this.mnuStep.Size = new System.Drawing.Size(157, 22); + this.mnuStep.Size = new System.Drawing.Size(163, 22); this.mnuStep.Text = "Step"; this.mnuStep.Click += new System.EventHandler(this.mnuStep_Click); // - // toolStripMenuItem1 - // - this.toolStripMenuItem1.Name = "toolStripMenuItem1"; - this.toolStripMenuItem1.Size = new System.Drawing.Size(154, 6); - // - // mnuDebugger - // - this.mnuDebugger.Name = "mnuDebugger"; - this.mnuDebugger.Size = new System.Drawing.Size(157, 22); - this.mnuDebugger.Text = "Debugger"; - // - // mnuTraceLogger - // - this.mnuTraceLogger.Name = "mnuTraceLogger"; - this.mnuTraceLogger.Size = new System.Drawing.Size(157, 22); - this.mnuTraceLogger.Text = "Trace Logger"; - this.mnuTraceLogger.Click += new System.EventHandler(this.mnuTraceLogger_Click); - // // mnuRun100Instructions // this.mnuRun100Instructions.Name = "mnuRun100Instructions"; this.mnuRun100Instructions.ShortcutKeys = System.Windows.Forms.Keys.F6; - this.mnuRun100Instructions.Size = new System.Drawing.Size(157, 22); - this.mnuRun100Instructions.Text = "Run 100 ops"; + this.mnuRun100Instructions.Size = new System.Drawing.Size(163, 22); + this.mnuRun100Instructions.Text = "Run 1000 ops"; this.mnuRun100Instructions.Click += new System.EventHandler(this.mnuRun100Instructions_Click); // + // toolStripMenuItem1 + // + this.toolStripMenuItem1.Name = "toolStripMenuItem1"; + this.toolStripMenuItem1.Size = new System.Drawing.Size(160, 6); + // + // mnuDebugger + // + this.mnuDebugger.Name = "mnuDebugger"; + this.mnuDebugger.Size = new System.Drawing.Size(163, 22); + this.mnuDebugger.Text = "Debugger"; + // + // mnuTraceLogger + // + this.mnuTraceLogger.Name = "mnuTraceLogger"; + this.mnuTraceLogger.Size = new System.Drawing.Size(163, 22); + this.mnuTraceLogger.Text = "Trace Logger"; + this.mnuTraceLogger.Click += new System.EventHandler(this.mnuTraceLogger_Click); + // // frmMain // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(514, 476); + this.ClientSize = new System.Drawing.Size(512, 475); this.Controls.Add(this.ctrlRenderer); this.Controls.Add(this.mnuMain); this.MainMenuStrip = this.mnuMain; diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index 0a35597..ee6caee 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -30,7 +30,8 @@ namespace Mesen.GUI.Forms private void mnuTraceLogger_Click(object sender, EventArgs e) { - + frmTraceLogger frm = new frmTraceLogger(); + frm.Show(); } private void mnuStep_Click(object sender, EventArgs e) @@ -47,9 +48,6 @@ namespace Mesen.GUI.Forms Task.Run(() => { EmuApi.Run(); }); - - frmTraceLogger frm = new frmTraceLogger(); - frm.Show(); } } } @@ -61,7 +59,7 @@ namespace Mesen.GUI.Forms private void mnuRun100Instructions_Click(object sender, EventArgs e) { - DebugApi.Step(100); + DebugApi.Step(1000); } } } diff --git a/Windows/Renderer.cpp b/Windows/Renderer.cpp index 0157a6c..84f0012 100644 --- a/Windows/Renderer.cpp +++ b/Windows/Renderer.cpp @@ -3,8 +3,11 @@ #include "DirectXTK/SpriteBatch.h" #include "DirectXTK/SpriteFont.h" #include "../Core/Console.h" +#include "../Core/VideoDecoder.h" +#include "../Core/VideoRenderer.h" #include "../Core/Debugger.h" #include "../Core/MessageManager.h" +#include "../Core/SettingTypes.h" #include "../Utilities/UTF8Util.h" using namespace DirectX; @@ -13,16 +16,15 @@ Renderer::Renderer(shared_ptr console, HWND hWnd, bool registerAsMessag { _hWnd = hWnd; - SetScreenSize(256, 240); + SetScreenSize(256, 224); } Renderer::~Renderer() { - //TODO - /*shared_ptr videoRenderer = _console->GetVideoRenderer(); + shared_ptr videoRenderer = _console->GetVideoRenderer(); if(videoRenderer) { videoRenderer->UnregisterRenderingDevice(this); - }*/ + } CleanupDevice(); } @@ -38,20 +40,20 @@ void Renderer::SetFullscreenMode(bool fullscreen, void* windowHandle, uint32_t m void Renderer::SetScreenSize(uint32_t width, uint32_t height) { - //TODO - /*ScreenSize screenSize; + ScreenSize screenSize; _console->GetVideoDecoder()->GetScreenSize(screenSize, false); - if(_screenHeight != screenSize.Height || _screenWidth != screenSize.Width || _nesFrameHeight != height || _nesFrameWidth != width || _resizeFilter != _console->GetSettings()->GetVideoResizeFilter() || _newFullscreen != _fullscreen) { + //TODO _resizeFilter != _console->GetSettings()->GetVideoResizeFilter() + if(_screenHeight != screenSize.Height || _screenWidth != screenSize.Width || _nesFrameHeight != height || _nesFrameWidth != width || _newFullscreen != _fullscreen) { auto frameLock = _frameLock.AcquireSafe(); auto textureLock = _textureLock.AcquireSafe(); _console->GetVideoDecoder()->GetScreenSize(screenSize, false); - if(_screenHeight != screenSize.Height || _screenWidth != screenSize.Width || _nesFrameHeight != height || _nesFrameWidth != width || _resizeFilter != _console->GetSettings()->GetVideoResizeFilter() || _newFullscreen != _fullscreen) { + if(_screenHeight != screenSize.Height || _screenWidth != screenSize.Width || _nesFrameHeight != height || _nesFrameWidth != width || _newFullscreen != _fullscreen) { _nesFrameHeight = height; _nesFrameWidth = width; _newFrameBufferSize = width*height; - bool needReset = _fullscreen != _newFullscreen || _resizeFilter != _console->GetSettings()->GetVideoResizeFilter(); + bool needReset = _fullscreen != _newFullscreen;//TODO || _resizeFilter != _console->GetSettings()->GetVideoResizeFilter(); bool fullscreenResizeMode = _fullscreen && _newFullscreen; if(_pSwapChain && _fullscreen && !_newFullscreen) { @@ -94,7 +96,7 @@ void Renderer::SetScreenSize(uint32_t width, uint32_t height) } } } - }*/ + } } void Renderer::Reset() @@ -104,8 +106,7 @@ void Renderer::Reset() if(FAILED(InitDevice())) { CleanupDevice(); } else { - //TODO - //_console->GetVideoRenderer()->RegisterRenderingDevice(this); + _console->GetVideoRenderer()->RegisterRenderingDevice(this); } }