Mesen/Linux/SdlRenderer.cpp

260 lines
7.7 KiB
C++
Executable file

#include "SdlRenderer.h"
#include "../Core/Console.h"
#include "../Core/Debugger.h"
#include "../Core/VideoRenderer.h"
#include "../Core/VideoDecoder.h"
#include "../Core/EmulationSettings.h"
SimpleLock SdlRenderer::_frameLock;
SdlRenderer::SdlRenderer(shared_ptr<Console> console, void* windowHandle, bool registerAsMessageManager) : BaseRenderer(console, registerAsMessageManager), _windowHandle(windowHandle)
{
_frameBuffer = nullptr;
_requiredWidth = 256;
_requiredHeight = 240;
shared_ptr<VideoRenderer> videoRenderer = _console->GetVideoRenderer();
if(videoRenderer) {
_console->GetVideoRenderer()->RegisterRenderingDevice(this);
}
}
SdlRenderer::~SdlRenderer()
{
shared_ptr<VideoRenderer> videoRenderer = _console->GetVideoRenderer();
if(videoRenderer) {
videoRenderer->UnregisterRenderingDevice(this);
}
auto lock = _frameLock.AcquireSafe();
Cleanup();
delete[] _frameBuffer;
}
void SdlRenderer::SetFullscreenMode(bool fullscreen, void* windowHandle, uint32_t monitorWidth, uint32_t monitorHeight)
{
//TODO: Implement exclusive fullscreen for Linux
}
bool SdlRenderer::Init()
{
auto log = [](const char* msg) {
MessageManager::Log(msg);
MessageManager::Log(SDL_GetError());
};
if(SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
log("[SDL] Failed to initialize video subsystem.");
return false;
};
_sdlWindow = SDL_CreateWindowFrom(_windowHandle);
if(!_sdlWindow) {
log("[SDL] Failed to create window from handle.");
return false;
}
//Hack to make this work properly - otherwise SDL_CreateRenderer never returns
_sdlWindow->flags |= SDL_WINDOW_OPENGL;
if(SDL_GL_LoadLibrary(NULL) != 0) {
log("[SDL] Failed to initialize OpenGL, attempting to continue with initialization.");
}
uint32_t baseFlags = _vsyncEnabled ? SDL_RENDERER_PRESENTVSYNC : 0;
_sdlRenderer = SDL_CreateRenderer(_sdlWindow, -1, baseFlags | SDL_RENDERER_ACCELERATED);
if(!_sdlRenderer) {
log("[SDL] Failed to create accelerated renderer.");
MessageManager::Log("[SDL] Attempting to create software renderer...");
_sdlRenderer = SDL_CreateRenderer(_sdlWindow, -1, baseFlags | SDL_RENDERER_SOFTWARE);
if(!_sdlRenderer) {
log("[SDL] Failed to create software renderer.");
return false;
}
}
_sdlTexture = SDL_CreateTexture(_sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, _nesFrameWidth, _nesFrameHeight);
if(!_sdlTexture) {
string msg = "[SDL] Failed to create texture: " + std::to_string(_nesFrameWidth) + "x" + std::to_string(_nesFrameHeight);
log(msg.c_str());
return false;
}
_spriteFont.reset(new SpriteFont(_sdlRenderer, "Resources/Font.24.spritefont"));
_largeFont.reset(new SpriteFont(_sdlRenderer, "Resources/Font.64.spritefont"));
SDL_SetWindowSize(_sdlWindow, _screenWidth, _screenHeight);
return true;
}
void SdlRenderer::Cleanup()
{
if(_sdlTexture) {
SDL_DestroyTexture(_sdlTexture);
_sdlTexture = nullptr;
}
if(_sdlRenderer) {
SDL_DestroyRenderer(_sdlRenderer);
_sdlRenderer = nullptr;
}
}
void SdlRenderer::Reset()
{
Cleanup();
if(Init()) {
_console->GetVideoRenderer()->RegisterRenderingDevice(this);
} else {
Cleanup();
}
}
void SdlRenderer::SetScreenSize(uint32_t width, uint32_t height)
{
ScreenSize screenSize;
_console->GetVideoDecoder()->GetScreenSize(screenSize, false);
if(_screenHeight != (uint32_t)screenSize.Height || _screenWidth != (uint32_t)screenSize.Width || _nesFrameHeight != height || _nesFrameWidth != width || _resizeFilter != _console->GetSettings()->GetVideoResizeFilter() || _vsyncEnabled != _console->GetSettings()->CheckFlag(EmulationFlags::VerticalSync)) {
_vsyncEnabled = _console->GetSettings()->CheckFlag(EmulationFlags::VerticalSync);
_nesFrameHeight = height;
_nesFrameWidth = width;
_newFrameBufferSize = width*height;
_screenHeight = screenSize.Height;
_screenWidth = screenSize.Width;
_resizeFilter = _console->GetSettings()->GetVideoResizeFilter();
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, _resizeFilter == VideoResizeFilter::Bilinear ? "1" : "0");
_screenBufferSize = _screenHeight*_screenWidth;
Reset();
}
}
void SdlRenderer::UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height)
{
_frameLock.Acquire();
if(_frameBuffer == nullptr || _requiredWidth != width || _requiredHeight != height) {
_requiredWidth = width;
_requiredHeight = height;
delete[] _frameBuffer;
_frameBuffer = new uint32_t[width*height];
memset(_frameBuffer, 0, width*height*4);
}
memcpy(_frameBuffer, frameBuffer, width*height*_bytesPerPixel);
_frameChanged = true;
_frameLock.Release();
}
void SdlRenderer::Render()
{
SetScreenSize(_requiredWidth, _requiredHeight);
if(!_sdlRenderer || !_sdlTexture) {
return;
}
bool paused = _console->IsPaused() && _console->IsRunning();
bool disableOverlay = _console->GetSettings()->CheckFlag(EmulationFlags::HidePauseOverlay);
shared_ptr<Debugger> debugger = _console->GetDebugger(false);
if(debugger && debugger->IsExecutionStopped()) {
paused = debugger->IsPauseIconShown();
disableOverlay = true;
}
if(_noUpdateCount > 10 || _frameChanged || paused || IsMessageShown()) {
SDL_RenderClear(_sdlRenderer);
uint8_t *textureBuffer;
int rowPitch;
SDL_LockTexture(_sdlTexture, nullptr, (void**)&textureBuffer, &rowPitch);
{
auto frameLock = _frameLock.AcquireSafe();
if(_frameBuffer && _nesFrameWidth == _requiredWidth && _nesFrameHeight == _requiredHeight) {
uint32_t* ppuFrameBuffer = _frameBuffer;
for(uint32_t i = 0, iMax = _nesFrameHeight; i < iMax; i++) {
memcpy(textureBuffer, ppuFrameBuffer, _nesFrameWidth*_bytesPerPixel);
ppuFrameBuffer += _nesFrameWidth;
textureBuffer += rowPitch;
}
}
}
SDL_UnlockTexture(_sdlTexture);
if(_frameChanged) {
_renderedFrameCount++;
_frameChanged = false;
}
SDL_Rect source = {0, 0, (int)_nesFrameWidth, (int)_nesFrameHeight };
SDL_Rect dest = {0, 0, (int)_screenWidth, (int)_screenHeight };
SDL_RenderCopy(_sdlRenderer, _sdlTexture, &source, &dest);
if(paused && !_console->GetSettings()->CheckFlag(EmulationFlags::HidePauseOverlay)) {
DrawPauseScreen(disableOverlay);
} else if(_console->GetVideoDecoder()->IsRunning()) {
DrawCounters();
}
DrawToasts();
SDL_RenderPresent(_sdlRenderer);
} else {
_noUpdateCount++;
}
}
void SdlRenderer::DrawPauseScreen(bool disableOverlay)
{
if(disableOverlay) {
DrawString(L"I", 15, 15, 106, 90, 205, 168);
DrawString(L"I", 23, 15, 106, 90, 205, 168);
} else {
uint32_t textureData = 0x222222AA;
SDL_Surface* surf = SDL_CreateRGBSurfaceFrom((void*)&textureData, 1, 1, 32, 4, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
if(surf) {
SDL_Texture* texture = SDL_CreateTextureFromSurface(_sdlRenderer, surf);
if(texture) {
SDL_Rect source = { 0, 0, 1, 1 };
SDL_Rect dest = { 0, 0, (int)_screenWidth, (int)_screenHeight };
SDL_RenderCopy(_sdlRenderer, texture, &source, &dest);
SDL_DestroyTexture(texture);
}
SDL_FreeSurface(surf);
}
XMFLOAT2 size = _largeFont->MeasureString(L"PAUSE");
_largeFont->DrawString(_sdlRenderer, L"PAUSE", (int)(_screenWidth / 2 - size.x / 2), (int)(_screenHeight / 2 - size.y / 2 - 8), 250, 235, 215);
string utf8Message = _console->GetSettings()->GetPauseScreenMessage();
if(utf8Message.size() > 0) {
std::wstring message = utf8::utf8::decode(utf8Message);
float width = MeasureString(message);
DrawString(message, _screenWidth - width - 20, _screenHeight - 40);
}
}
}
void SdlRenderer::DrawString(std::wstring message, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t opacity)
{
const wchar_t *text = message.c_str();
_spriteFont->DrawString(_sdlRenderer, text, x, y, r, g, b);
}
float SdlRenderer::MeasureString(std::wstring text)
{
return _spriteFont->MeasureString(text.c_str()).x;
}
bool SdlRenderer::ContainsCharacter(wchar_t character)
{
return _spriteFont->ContainsCharacter(character);
}