ES-DE/es-app/src/MediaViewer.cpp
Leon Styhre 9e277ed1ff Improved the blur shaders to run faster at higher resolutions and to look identical regardless of display resolution
Also improved the blur shaders rendering quality when rotating the screen 90 or 270 degrees
2023-08-18 20:22:08 +02:00

451 lines
14 KiB
C++

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// MediaViewer.cpp
//
// Fullscreen game media viewer.
//
#include "MediaViewer.h"
#include "Sound.h"
#include "components/VideoFFmpegComponent.h"
#include "views/ViewController.h"
#define KEY_REPEAT_START_DELAY 600
#define KEY_REPEAT_SPEED 250
MediaViewer::MediaViewer()
: mRenderer {Renderer::getInstance()}
, mGame {nullptr}
, mHasVideo {false}
, mHasImages {false}
, mDisplayingImage {false}
, mHasManual {false}
, mShowMediaTypes {false}
, mFrameHeight {0.0f}
, mCurrentImageIndex {0}
, mScreenshotIndex {0}
, mTitleScreenIndex {0}
, mKeyRepeatDir {0}
, mKeyRepeatTimer {0}
, mHelpInfoPosition {HelpInfoPosition::TOP}
{
Window::getInstance()->setMediaViewer(this);
}
bool MediaViewer::startMediaViewer(FileData* game)
{
mHasVideo = false;
mHasImages = false;
mCurrentImageIndex = 0;
mScreenshotIndex = -1;
mTitleScreenIndex = -1;
mKeyRepeatDir = 0;
mKeyRepeatTimer = 0;
ViewController::getInstance()->pauseViewVideos();
mShowMediaTypes = Settings::getInstance()->getBool("MediaViewerShowTypes");
if (Settings::getInstance()->getString("MediaViewerHelpPrompts") == "disabled")
mHelpInfoPosition = HelpInfoPosition::DISABLED;
else if (Settings::getInstance()->getString("MediaViewerHelpPrompts") == "bottom")
mHelpInfoPosition = HelpInfoPosition::BOTTOM;
else
mHelpInfoPosition = HelpInfoPosition::TOP;
if (mHelpInfoPosition == HelpInfoPosition::DISABLED)
mFrameHeight = 0.0f;
else
mFrameHeight = Font::get(FONT_SIZE_MINI)->getLetterHeight() * 1.9f;
mGame = game;
mHasManual = (mGame->getManualPath() != "");
initiateViewer();
if (!mHasVideo && !mHasImages)
return false;
Window::getInstance()->stopInfoPopup();
HelpStyle style;
style.font = Font::get(FONT_SIZE_MINI);
style.origin = {0.5f, 0.5f};
style.iconColor = 0xAAAAAAFF;
style.textColor = 0xAAAAAAFF;
mEntryCount = std::to_string(mImages.size() + (mVideo == nullptr ? 0 : 1));
mMediaType =
std::make_unique<TextComponent>((mHasVideo ? "VIDEO" : mImageFiles[0].second.mediaType),
Font::get(FONT_SIZE_MINI, FONT_PATH_REGULAR), 0xAAAAAAFF);
mMediaType->setOrigin(0.0f, 0.5f);
if (mHelpInfoPosition == HelpInfoPosition::TOP) {
mMediaType->setPosition(mRenderer->getScreenWidth() * 0.01f, mFrameHeight / 2.0f);
style.position = glm::vec2 {mRenderer->getScreenWidth() / 2.0f, mFrameHeight / 2.0f};
}
else if (mHelpInfoPosition == HelpInfoPosition::BOTTOM) {
mMediaType->setPosition(mRenderer->getScreenWidth() * 0.01f,
mRenderer->getScreenHeight() - (mFrameHeight / 2.0f));
style.position = glm::vec2 {mRenderer->getScreenWidth() / 2.0f,
mRenderer->getScreenHeight() - (mFrameHeight / 2.0f)};
}
mHelp = std::make_unique<HelpComponent>();
mHelp->setStyle(style);
mHelp->setPrompts(getHelpPrompts());
return true;
}
void MediaViewer::stopMediaViewer()
{
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
ViewController::getInstance()->startViewVideos();
mVideoFile = "";
mVideo.reset();
mImageFiles.clear();
mImages.clear();
}
void MediaViewer::launchPDFViewer()
{
if (mHasManual) {
Window::getInstance()->stopMediaViewer();
Window::getInstance()->startPDFViewer(mGame);
}
}
void MediaViewer::input(InputConfig* config, Input input)
{
if (config->isMappedLike("down", input) && input.value != 0) {
mKeyRepeatDir = 0;
return;
}
else if (config->isMappedLike("up", input) && input.value != 0) {
mKeyRepeatDir = 0;
launchPDFViewer();
return;
}
else if (config->isMappedLike("left", input)) {
if (input.value) {
mKeyRepeatDir = -1;
mKeyRepeatTimer = -(KEY_REPEAT_START_DELAY - KEY_REPEAT_SPEED);
showPrevious();
}
else {
mKeyRepeatDir = 0;
}
}
else if (config->isMappedLike("right", input)) {
if (input.value) {
mKeyRepeatDir = 1;
mKeyRepeatTimer = -(KEY_REPEAT_START_DELAY - KEY_REPEAT_SPEED);
showNext();
}
else {
mKeyRepeatDir = 0;
}
}
else if (config->isMappedLike("lefttrigger", input) && input.value != 0) {
mKeyRepeatDir = 0;
showFirst();
}
else if (config->isMappedLike("righttrigger", input) && input.value != 0) {
mKeyRepeatDir = 0;
showLast();
}
else if (input.value != 0) {
// Any other input stops the media viewer.
Window::getInstance()->stopMediaViewer();
}
}
void MediaViewer::update(int deltaTime)
{
if (mKeyRepeatDir != 0) {
mKeyRepeatTimer += deltaTime;
while (mKeyRepeatTimer >= KEY_REPEAT_SPEED) {
mKeyRepeatTimer -= KEY_REPEAT_SPEED;
if (mKeyRepeatDir == 1)
showNext();
else
showPrevious();
}
}
if (mVideo)
mVideo->update(deltaTime);
}
void MediaViewer::render(const glm::mat4& /*parentTrans*/)
{
const glm::mat4 trans {mRenderer->getIdentity()};
mRenderer->setMatrix(trans);
// Render a black background below the game media.
mRenderer->drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
0x000000FF, 0x000000FF);
if (mVideo && !mDisplayingImage) {
mVideo->render(trans);
Renderer::postProcessingParams videoParameters;
unsigned int shaders {0};
if (Settings::getInstance()->getBool("MediaViewerVideoScanlines"))
shaders = Renderer::Shader::SCANLINES;
if (Settings::getInstance()->getBool("MediaViewerVideoBlur")) {
if (mRenderer->getScreenRotation() == 90 || mRenderer->getScreenRotation() == 270)
shaders |= Renderer::Shader::BLUR_VERTICAL;
else
shaders |= Renderer::Shader::BLUR_HORIZONTAL;
}
// We run two passes to make the blur smoother.
videoParameters.blurPasses = 2;
videoParameters.blurStrength = 1.35f;
if (shaders != 0)
mRenderer->shaderPostprocessing(shaders, videoParameters);
}
else if (mImages[mCurrentImageIndex]->hasImage() &&
mImages[mCurrentImageIndex]->getSize() != glm::vec2 {0.0f, 0.0f}) {
mImages[mCurrentImageIndex]->render(trans);
if (mCurrentImageIndex == mScreenshotIndex &&
Settings::getInstance()->getBool("MediaViewerScreenshotScanlines"))
mRenderer->shaderPostprocessing(Renderer::Shader::SCANLINES);
else if (mCurrentImageIndex == mTitleScreenIndex &&
Settings::getInstance()->getBool("MediaViewerScreenshotScanlines"))
mRenderer->shaderPostprocessing(Renderer::Shader::SCANLINES);
// This is necessary so that the video loops if viewing an image when
// the video ends.
if (mVideo)
mVideo->handleLooping();
}
if (mHelpInfoPosition != HelpInfoPosition::DISABLED) {
// Render a dark gray frame behind the help info.
mRenderer->setMatrix(mRenderer->getIdentity());
mRenderer->drawRect(0.0f,
(mHelpInfoPosition == HelpInfoPosition::TOP ?
0.0f :
Renderer::getScreenHeight() - mFrameHeight),
Renderer::getScreenWidth(), mFrameHeight, 0x222222FF, 0x222222FF);
mHelp->render(trans);
if (mShowMediaTypes)
mMediaType->render(trans);
}
}
std::vector<HelpPrompt> MediaViewer::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("left/right", "browse"));
if (mHasManual)
prompts.push_back(HelpPrompt("up", "pdf manual"));
prompts.push_back(HelpPrompt("lt", "first"));
prompts.push_back(HelpPrompt("rt", "last"));
return prompts;
}
void MediaViewer::initiateViewer()
{
if (mGame->getType() == PLACEHOLDER)
return;
findMedia();
loadImages();
if (!mHasVideo && !mHasImages)
return;
if (mHasVideo)
playVideo();
}
void MediaViewer::findMedia()
{
std::string mediaFile;
if ((mediaFile = mGame->getVideoPath()) != "") {
mVideoFile = mediaFile;
mHasVideo = true;
}
if (!mHasVideo && (mediaFile = mGame->getScreenshotPath()) != "") {
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("SCREENSHOT", false)));
mScreenshotIndex = 0;
}
if ((mediaFile = mGame->getCoverPath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("BOX COVER", true)));
if ((mediaFile = mGame->getBackCoverPath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("BOX BACK COVER", true)));
if ((mediaFile = mGame->getTitleScreenPath()) != "") {
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("TITLE SCREEN", false)));
mTitleScreenIndex = static_cast<int>(mImageFiles.size() - 1);
}
if (mHasVideo && (mediaFile = mGame->getScreenshotPath()) != "") {
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("SCREENSHOT", false)));
mScreenshotIndex = static_cast<int>(mImageFiles.size() - 1);
}
if ((mediaFile = mGame->getFanArtPath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("FAN ART", true)));
if ((mediaFile = mGame->getMiximagePath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("MIXIMAGE", true)));
if (!mImageFiles.empty())
mHasImages = true;
}
void MediaViewer::loadImages()
{
for (auto& file : mImageFiles) {
mImages.emplace_back(std::make_unique<ImageComponent>(false, false));
mImages.back()->setOrigin(0.5f, 0.5f);
if (mHelpInfoPosition == HelpInfoPosition::TOP) {
mImages.back()->setPosition(Renderer::getScreenWidth() / 2.0f,
(Renderer::getScreenHeight() / 2.0f) +
(mFrameHeight / 2.0f));
}
else if (mHelpInfoPosition == HelpInfoPosition::BOTTOM) {
mImages.back()->setPosition(Renderer::getScreenWidth() / 2.0f,
(Renderer::getScreenHeight() / 2.0f) -
(mFrameHeight / 2.0f));
}
else {
mImages.back()->setPosition(Renderer::getScreenWidth() / 2.0f,
Renderer::getScreenHeight() / 2.0f);
}
mImages.back()->setLinearInterpolation(file.second.linearInterpolation);
mImages.back()->setMaxSize(Renderer::getScreenWidth(),
Renderer::getScreenHeight() - mFrameHeight);
mImages.back()->setImage(file.first);
}
}
void MediaViewer::playVideo()
{
if (mVideo || mVideoFile == "")
return;
mDisplayingImage = false;
mVideo = std::make_unique<VideoFFmpegComponent>();
mVideo->setOrigin(0.5f, 0.5f);
if (mHelpInfoPosition == HelpInfoPosition::TOP) {
mVideo->setPosition(Renderer::getScreenWidth() / 2.0f,
(Renderer::getScreenHeight() / 2.0f + (mFrameHeight / 2.0f)));
}
else if (mHelpInfoPosition == HelpInfoPosition::BOTTOM) {
mVideo->setPosition(Renderer::getScreenWidth() / 2.0f,
(Renderer::getScreenHeight() / 2.0f - (mFrameHeight / 2.0f)));
}
else {
mVideo->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f);
}
if (Settings::getInstance()->getBool("MediaViewerStretchVideos"))
mVideo->setResize(Renderer::getScreenWidth(), Renderer::getScreenHeight() - mFrameHeight);
else
mVideo->setMaxSize(Renderer::getScreenWidth(), Renderer::getScreenHeight() - mFrameHeight);
mVideo->setVideo(mVideoFile);
mVideo->setMediaViewerMode(true);
mVideo->startVideoPlayer();
}
void MediaViewer::showNext()
{
if (mHasImages && ((mCurrentImageIndex != static_cast<int>(mImageFiles.size()) - 1) ||
(!mDisplayingImage && mCurrentImageIndex == 0 && mImageFiles.size() == 1)))
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
bool showedVideo {false};
if (mVideo && !mHasImages) {
return;
}
else if (mVideo && !Settings::getInstance()->getBool("MediaViewerKeepVideoRunning")) {
mVideo.reset();
showedVideo = true;
}
if ((mVideo || showedVideo) && !mDisplayingImage)
mCurrentImageIndex = 0;
else if (static_cast<int>(mImageFiles.size()) > mCurrentImageIndex + 1)
++mCurrentImageIndex;
mDisplayingImage = true;
mMediaType->setText(mImageFiles[mCurrentImageIndex].second.mediaType);
}
void MediaViewer::showPrevious()
{
if ((mHasVideo && mDisplayingImage) || (!mHasVideo && mCurrentImageIndex != 0))
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
if (mCurrentImageIndex == 0 && !mHasVideo) {
return;
}
else if (mCurrentImageIndex == 0 && mHasVideo) {
mDisplayingImage = false;
mMediaType->setText("VIDEO");
playVideo();
return;
}
mMediaType->setText(mImageFiles[mCurrentImageIndex - 1].second.mediaType);
--mCurrentImageIndex;
}
void MediaViewer::showFirst()
{
if (!mHasImages)
return;
else if (mCurrentImageIndex == 0 && !mHasVideo)
return;
else if (mCurrentImageIndex == 0 && !mDisplayingImage)
return;
mCurrentImageIndex = 0;
mMediaType->setText((mHasVideo ? "VIDEO" : mImageFiles[mCurrentImageIndex].second.mediaType));
if (mHasVideo) {
mDisplayingImage = false;
playVideo();
}
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}
void MediaViewer::showLast()
{
if (!mHasImages)
return;
else if (mCurrentImageIndex == static_cast<int>(mImages.size() - 1))
return;
mCurrentImageIndex = static_cast<int>(mImages.size()) - 1;
mMediaType->setText(mImageFiles[mCurrentImageIndex].second.mediaType);
mDisplayingImage = true;
if (mVideo && !Settings::getInstance()->getBool("MediaViewerKeepVideoRunning"))
mVideo.reset();
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}