From 170d8e37914d315c580bef6590d7a8219d9242b5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Aug 2023 19:41:07 +0200 Subject: [PATCH] Added rounded corner support to the image, video, animation, carousel and grid elements --- es-core/src/ThemeData.cpp | 8 ++ es-core/src/components/GIFAnimComponent.cpp | 10 ++ es-core/src/components/GIFAnimComponent.h | 1 + es-core/src/components/ImageComponent.cpp | 19 +++- es-core/src/components/ImageComponent.h | 4 + .../src/components/LottieAnimComponent.cpp | 10 ++ es-core/src/components/LottieAnimComponent.h | 1 + es-core/src/components/VideoComponent.cpp | 24 ++++- es-core/src/components/VideoComponent.h | 3 + .../src/components/VideoFFmpegComponent.cpp | 88 +++++++++--------- es-core/src/components/VideoFFmpegComponent.h | 10 +- .../components/primary/CarouselComponent.h | 9 ++ .../src/components/primary/GridComponent.h | 21 +++++ es-core/src/renderers/Renderer.h | 15 ++- es-core/src/renderers/RendererOpenGL.cpp | 5 + es-core/src/renderers/ShaderOpenGL.cpp | 8 ++ es-core/src/renderers/ShaderOpenGL.h | 2 + resources/graphics/white.png | Bin 0 -> 5122 bytes resources/shaders/glsl/blur_horizontal.glsl | 2 + resources/shaders/glsl/blur_vertical.glsl | 2 + resources/shaders/glsl/core.glsl | 35 ++++++- resources/shaders/glsl/scanlines.glsl | 2 + 22 files changed, 219 insertions(+), 60 deletions(-) create mode 100644 resources/graphics/white.png diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index dcd553d3c..c684edccd 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -118,6 +118,7 @@ std::map> {"itemAxisRotation", FLOAT}, {"imageFit", STRING}, {"imageInterpolation", STRING}, + {"imageCornerRadius", FLOAT}, {"imageColor", COLOR}, {"imageColorEnd", COLOR}, {"imageGradientType", STRING}, @@ -184,6 +185,7 @@ std::map> {"unfocusedItemDimming", FLOAT}, {"imageFit", STRING}, {"imageRelativeScale", FLOAT}, + {"imageCornerRadius", FLOAT}, {"imageColor", COLOR}, {"imageColorEnd", COLOR}, {"imageGradientType", STRING}, @@ -194,11 +196,13 @@ std::map> {"imageSaturation", FLOAT}, {"backgroundImage", PATH}, {"backgroundRelativeScale", FLOAT}, + {"backgroundCornerRadius", FLOAT}, {"backgroundColor", COLOR}, {"backgroundColorEnd", COLOR}, {"backgroundGradientType", STRING}, {"selectorImage", PATH}, {"selectorRelativeScale", FLOAT}, + {"selectorCornerRadius", FLOAT}, {"selectorLayer", STRING}, {"selectorColor", COLOR}, {"selectorColorEnd", COLOR}, @@ -282,6 +286,7 @@ std::map> {"tileHorizontalAlignment", STRING}, {"tileVerticalAlignment", STRING}, {"interpolation", STRING}, + {"cornerRadius", FLOAT}, {"color", COLOR}, {"colorEnd", COLOR}, {"gradientType", STRING}, @@ -309,6 +314,8 @@ std::map> {"onIterationsDone", STRING}, {"audio", BOOLEAN}, {"interpolation", STRING}, + {"imageCornerRadius", FLOAT}, + {"videoCornerRadius", FLOAT}, {"color", COLOR}, {"colorEnd", COLOR}, {"gradientType", STRING}, @@ -337,6 +344,7 @@ std::map> {"direction", STRING}, {"iterationCount", UNSIGNED_INTEGER}, {"interpolation", STRING}, + {"cornerRadius", FLOAT}, {"color", COLOR}, {"colorEnd", COLOR}, {"gradientType", STRING}, diff --git a/es-core/src/components/GIFAnimComponent.cpp b/es-core/src/components/GIFAnimComponent.cpp index ad22af7c9..894a44d84 100644 --- a/es-core/src/components/GIFAnimComponent.cpp +++ b/es-core/src/components/GIFAnimComponent.cpp @@ -45,6 +45,7 @@ GIFAnimComponent::GIFAnimComponent() , mIterationCount {0} , mPlayCount {0} , mTargetIsMax {false} + , mCornerRadius {0.0f} , mColorShift {0xFFFFFFFF} , mColorShiftEnd {0xFFFFFFFF} , mColorGradientHorizontal {true} @@ -372,6 +373,10 @@ void GIFAnimComponent::applyTheme(const std::shared_ptr& theme, mIterationCount *= 2; } + if (elem->has("cornerRadius")) + mCornerRadius = + glm::clamp(elem->get("cornerRadius"), 0.0f, 0.5f) * mRenderer->getScreenWidth(); + if (elem->has("interpolation")) { const std::string& interpolation {elem->get("interpolation")}; if (interpolation == "linear") { @@ -588,6 +593,11 @@ void GIFAnimComponent::render(const glm::mat4& parentTrans) vertices->dimming = mDimming; vertices->shaderFlags = Renderer::ShaderFlags::PREMULTIPLIED; + if (mCornerRadius > 0.0f) { + vertices->cornerRadius = mCornerRadius; + vertices->shaderFlags = vertices->shaderFlags | Renderer::ShaderFlags::ROUNDED_CORNERS; + } + // Render it. mRenderer->drawTriangleStrips(&vertices[0], 4); } diff --git a/es-core/src/components/GIFAnimComponent.h b/es-core/src/components/GIFAnimComponent.h index f3bfe067a..bf9fdacaf 100644 --- a/es-core/src/components/GIFAnimComponent.h +++ b/es-core/src/components/GIFAnimComponent.h @@ -104,6 +104,7 @@ private: int mPlayCount; bool mTargetIsMax; + float mCornerRadius; unsigned int mColorShift; unsigned int mColorShiftEnd; bool mColorGradientHorizontal; diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index e2f66a660..2cf294cae 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -29,7 +29,9 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic) , mColorShiftEnd {0xFFFFFFFF} , mColorGradientHorizontal {true} , mFadeOpacity {0.0f} + , mCornerRadius {0.0f} , mReflectionsFalloff {0.0f} + , mCornerAntiAliasing {true} , mFading {false} , mForceLoad {forceLoad} , mDynamic {dynamic} @@ -332,7 +334,6 @@ void ImageComponent::setSaturation(float saturation) void ImageComponent::setDimming(float dimming) { - // Set dimming value. mDimming = dimming; } @@ -432,6 +433,18 @@ void ImageComponent::render(const glm::mat4& parentTrans) mVertices->dimming = mDimming; mVertices->reflectionsFalloff = mReflectionsFalloff; + if (mCornerRadius > 0.0f) { + mVertices->cornerRadius = mCornerRadius; + if (mCornerAntiAliasing) { + mVertices->shaderFlags = + mVertices->shaderFlags | Renderer::ShaderFlags::ROUNDED_CORNERS; + } + else { + mVertices->shaderFlags = + mVertices->shaderFlags | Renderer::ShaderFlags::ROUNDED_CORNERS_NO_AA; + } + } + mVertices->shaderFlags = mVertices->shaderFlags | Renderer::ShaderFlags::PREMULTIPLIED; mRenderer->drawTriangleStrips(&mVertices[0], 4); } @@ -535,6 +548,10 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, } } + if (elem->has("cornerRadius")) + mCornerRadius = + glm::clamp(elem->get("cornerRadius"), 0.0f, 0.5f) * mRenderer->getScreenWidth(); + if (properties && elem->has("imageType")) { std::string imageTypes {elem->get("imageType")}; for (auto& character : imageTypes) { diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index fc35d2fad..4ba4121f2 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -89,6 +89,8 @@ public: void setSaturation(float saturation) override; void setDimming(float dimming) override; void setClipRegion(const glm::vec4& clipRegion); + void setCornerRadius(float radius) { mCornerRadius = radius; } + void setCornerAntiAliasing(bool state) { mCornerAntiAliasing = state; } void setReflectionsFalloff(float falloff) override { mReflectionsFalloff = falloff; } void setFlipX(bool state) override; // Mirror on the X axis. @@ -159,7 +161,9 @@ private: std::shared_ptr mTexture; float mFadeOpacity; + float mCornerRadius; float mReflectionsFalloff; + bool mCornerAntiAliasing; bool mFading; bool mForceLoad; bool mDynamic; diff --git a/es-core/src/components/LottieAnimComponent.cpp b/es-core/src/components/LottieAnimComponent.cpp index 4fe6433a4..993500df7 100644 --- a/es-core/src/components/LottieAnimComponent.cpp +++ b/es-core/src/components/LottieAnimComponent.cpp @@ -40,6 +40,7 @@ LottieAnimComponent::LottieAnimComponent() , mIterationCount {0} , mPlayCount {0} , mTargetIsMax {false} + , mCornerRadius {0.0f} , mColorShift {0xFFFFFFFF} , mColorShiftEnd {0xFFFFFFFF} , mColorGradientHorizontal {true} @@ -342,6 +343,10 @@ void LottieAnimComponent::applyTheme(const std::shared_ptr& theme, mIterationCount *= 2; } + if (elem->has("cornerRadius")) + mCornerRadius = + glm::clamp(elem->get("cornerRadius"), 0.0f, 0.5f) * mRenderer->getScreenWidth(); + if (properties & COLOR) { if (elem->has("color")) { mColorShift = elem->get("color"); @@ -579,6 +584,11 @@ void LottieAnimComponent::render(const glm::mat4& parentTrans) vertices->dimming = mDimming; vertices->shaderFlags = Renderer::ShaderFlags::PREMULTIPLIED; + if (mCornerRadius > 0.0f) { + vertices->cornerRadius = mCornerRadius; + vertices->shaderFlags = vertices->shaderFlags | Renderer::ShaderFlags::ROUNDED_CORNERS; + } + // Render it. mRenderer->drawTriangleStrips(&vertices[0], 4); } diff --git a/es-core/src/components/LottieAnimComponent.h b/es-core/src/components/LottieAnimComponent.h index d1a1a170b..bae80e309 100644 --- a/es-core/src/components/LottieAnimComponent.h +++ b/es-core/src/components/LottieAnimComponent.h @@ -90,6 +90,7 @@ private: int mPlayCount; bool mTargetIsMax; + float mCornerRadius; unsigned int mColorShift; unsigned int mColorShiftEnd; bool mColorGradientHorizontal; diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 05027bb73..272d697cd 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -16,14 +16,16 @@ #include -#define SCREENSAVER_FADE_IN_TIME 1100 +#define SCREENSAVER_FADE_IN_TIME 900 #define MEDIA_VIEWER_FADE_IN_TIME 600 VideoComponent::VideoComponent() - : mVideoWidth {0} + : mRenderer {Renderer::getInstance()} + , mVideoWidth {0} , mVideoHeight {0} , mColorShift {0xFFFFFFFF} , mColorShiftEnd {0xFFFFFFFF} + , mVideoCornerRadius {0.0f} , mColorGradientHorizontal {true} , mTargetSize {0.0f, 0.0f} , mVideoAreaPos {0.0f, 0.0f} @@ -131,7 +133,7 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, glm::vec2 scale {getParent() ? getParent()->getSize() : - glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}}; + glm::vec2 {mRenderer->getScreenWidth(), mRenderer->getScreenHeight()}}; if (properties & ThemeFlags::SIZE) { if (elem->has("size")) { @@ -225,6 +227,14 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, } } + if (elem->has("imageCornerRadius")) + mStaticImage.setCornerRadius(glm::clamp(elem->get("imageCornerRadius"), 0.0f, 0.5f) * + mRenderer->getScreenWidth()); + + if (elem->has("videoCornerRadius")) + mVideoCornerRadius = glm::clamp(elem->get("videoCornerRadius"), 0.0f, 0.5f) * + mRenderer->getScreenWidth(); + if (elem->has("default")) { const std::string defaultVideo {elem->get("default")}; if (ResourceManager::getInstance().fileExists(defaultVideo)) { @@ -332,6 +342,14 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("pillarboxes")) mDrawPillarboxes = elem->get("pillarboxes"); + // The black frame is rendered behind all videos and may be expanded to render pillarboxes + // or letterboxes. + mBlackFrame.setZIndex(mZIndex); + mBlackFrame.setCornerRadius(mVideoCornerRadius); + mBlackFrame.setCornerAntiAliasing(false); + mBlackFrame.setColorShift(0x000000FF); + mBlackFrame.setImage(":/graphics/white.png"); + if (elem->has("pillarboxThreshold")) { const glm::vec2 pillarboxThreshold {elem->get("pillarboxThreshold")}; mPillarboxThreshold.x = glm::clamp(pillarboxThreshold.x, 0.2f, 1.0f); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 40ccc8fb7..c8e700ca6 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -99,12 +99,15 @@ protected: IMAGE }; + Renderer* mRenderer; ImageComponent mStaticImage; + ImageComponent mBlackFrame; unsigned mVideoWidth; unsigned mVideoHeight; unsigned int mColorShift; unsigned int mColorShiftEnd; + float mVideoCornerRadius; bool mColorGradientHorizontal; glm::vec2 mTargetSize; glm::vec2 mVideoAreaPos; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 9367fb7d8..bebe69b18 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -30,8 +30,7 @@ #endif VideoFFmpegComponent::VideoFFmpegComponent() - : mRenderer {Renderer::getInstance()} - , mRectangleOffset {0.0f, 0.0f} + : mBlackFrameOffset {0.0f, 0.0f} , mFrameProcessingThread {nullptr} , mFormatContext {nullptr} , mVideoStream {nullptr} @@ -176,31 +175,23 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) if (mIsPlaying && mFormatContext) { Renderer::Vertex vertices[4]; - mRenderer->setMatrix(trans); - unsigned int rectColor {0x000000FF}; - - if (!mGeneralFade && mThemeOpacity != 1.0f) - rectColor = static_cast(mThemeOpacity * 255.0f); - if (mGeneralFade && (mOpacity != 1.0f || mThemeOpacity != 1.0f)) - rectColor = static_cast(mFadeIn * mOpacity * mThemeOpacity * 255.0f); - - // Render the black rectangle behind the video. - if (mVideoRectangleCoords.size() == 4) { - mRenderer->drawRect(mVideoRectangleCoords[0], mVideoRectangleCoords[1], - mVideoRectangleCoords[2], mVideoRectangleCoords[3], // Line break. - rectColor, rectColor); + if (!mScreensaverMode && !mMediaViewerMode) { + mBlackFrame.setOpacity(mOpacity * mThemeOpacity); + mBlackFrame.render(trans); } + mRenderer->setMatrix(trans); + // This is needed to avoid a slight gap before the video starts playing. if (!mDecodedFrame) return; // clang-format off - vertices[0] = {{0.0f + mRectangleOffset.x, 0.0f + mRectangleOffset.y }, {mTopLeftCrop.x, 1.0f - mBottomRightCrop.y}, 0xFFFFFFFF}; - vertices[1] = {{0.0f + mRectangleOffset.x, mSize.y + mRectangleOffset.y }, {mTopLeftCrop.x, 1.0f - mTopLeftCrop.y }, 0xFFFFFFFF}; - vertices[2] = {{mSize.x + mRectangleOffset.x, 0.0f + + mRectangleOffset.y }, {mBottomRightCrop.x * 1.0f, 1.0f - mBottomRightCrop.y}, 0xFFFFFFFF}; - vertices[3] = {{mSize.x + mRectangleOffset.x, mSize.y + + mRectangleOffset.y}, {mBottomRightCrop.x * 1.0f, 1.0f - mTopLeftCrop.y }, 0xFFFFFFFF}; + vertices[0] = {{0.0f + mBlackFrameOffset.x, 0.0f + mBlackFrameOffset.y }, {mTopLeftCrop.x, 1.0f - mBottomRightCrop.y}, 0xFFFFFFFF}; + vertices[1] = {{0.0f + mBlackFrameOffset.x, mSize.y + mBlackFrameOffset.y }, {mTopLeftCrop.x, 1.0f - mTopLeftCrop.y }, 0xFFFFFFFF}; + vertices[2] = {{mSize.x + mBlackFrameOffset.x, 0.0f + + mBlackFrameOffset.y }, {mBottomRightCrop.x * 1.0f, 1.0f - mBottomRightCrop.y}, 0xFFFFFFFF}; + vertices[3] = {{mSize.x + mBlackFrameOffset.x, mSize.y + + mBlackFrameOffset.y}, {mBottomRightCrop.x * 1.0f, 1.0f - mTopLeftCrop.y }, 0xFFFFFFFF}; // clang-format on vertices[0].color = mColorShift; @@ -213,12 +204,22 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) vertices[i].position = glm::round(vertices[i].position); if (mFadeIn < 1.0f || mThemeOpacity < 1.0f) - vertices->opacity = mFadeIn * mThemeOpacity; + vertices->opacity = mOpacity * mThemeOpacity; vertices->brightness = mBrightness; vertices->saturation = mSaturation * mThemeSaturation; + vertices->saturation = 1.0f; vertices->dimming = mDimming; + if (mVideoCornerRadius > 0.0f) { + // We don't want to apply anti-aliasing to rounded corners as the black frame is + // rendered behind the video and that would generate ugly edge artifacts for any + // videos with lighter content. + vertices->cornerRadius = mVideoCornerRadius; + vertices->shaderFlags = + vertices->shaderFlags | Renderer::ShaderFlags::ROUNDED_CORNERS_NO_AA; + } + std::unique_lock pictureLock {mPictureMutex}; if (!mOutputPicture.hasBeenRendered) { @@ -266,6 +267,9 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) if (mRenderScanlines) vertices[0].shaders = Renderer::Shader::SCANLINES; } + else { + vertices[0].opacity = mFadeIn; + } mRenderer->drawTriangleStrips(&vertices[0], 4, Renderer::BlendFactor::SRC_ALPHA, Renderer::BlendFactor::ONE_MINUS_SRC_ALPHA); @@ -985,22 +989,24 @@ void VideoFFmpegComponent::outputFrames() mEndOfVideo = true; } -void VideoFFmpegComponent::calculateBlackRectangle() +void VideoFFmpegComponent::calculateBlackFrame() { - // Calculate the position and size for the black rectangle that will be rendered behind + // Calculate the position and size for the black frame image that will be rendered behind // videos. If the option to display pillarboxes (and letterboxes) is enabled, then this - // would extend to the entire video area (if above the threshold as defined below) or - // otherwise it will exactly match the video size. The reason to add a black rectangle - // behind videos in this second instance is that the scanline rendering will make the - // video partially transparent so this may avoid some unforseen issues with some themes. + // would extend to the entire video area (if above the threshold as explained below) or + // otherwise it will exactly match the video size. The reason to add a black frame behind + // videos in this second instance is that the scanline rendering will make the video + // partially transparent so this may avoid some unforseen issues with some themes. + // Another reason is that it always take a short while to initiate the video player which + // means no video texture is rendered for that brief moment, and it looks better to draw + // the black frame during this time period as most game videos also fade in from black. // In general, adding very narrow pillarboxes or letterboxes doesn't look good, so by // default this is not done unless the size of the video vs the overall video area is // above the threshold defined by mPillarboxThreshold. By default this is set to 0.85 // for the X axis and 0.90 for the Y axis, but this is theme-controllable via the // pillarboxThreshold property. if (mVideoAreaPos != glm::vec2 {0.0f, 0.0f} && mVideoAreaSize != glm::vec2 {0.0f, 0.0f}) { - mVideoRectangleCoords.clear(); - mRectangleOffset = {0.0f, 0.0f}; + mBlackFrameOffset = {0.0f, 0.0f}; if (mDrawPillarboxes) { float rectHeight {0.0f}; @@ -1037,26 +1043,23 @@ void VideoFFmpegComponent::calculateBlackRectangle() // the video correctly. if (mOrigin != glm::vec2 {0.5f, 0.5f}) { if (rectWidth > mSize.x) - mRectangleOffset.x -= (rectWidth - mSize.x) * (mOrigin.x - 0.5f); + mBlackFrameOffset.x -= (rectWidth - mSize.x) * (mOrigin.x - 0.5f); else if (rectHeight > mSize.y) - mRectangleOffset.y -= (rectHeight - mSize.y) * (mOrigin.y - 0.5f); + mBlackFrameOffset.y -= (rectHeight - mSize.y) * (mOrigin.y - 0.5f); } - // Populate the rectangle coordinates to be used in render(). + // Set the black frame position and size. const float offsetX {rectWidth - mSize.x}; const float offsetY {rectHeight - mSize.y}; - mVideoRectangleCoords.emplace_back(std::round((-offsetX / 2.0f) + mRectangleOffset.x)); - mVideoRectangleCoords.emplace_back(std::round((-offsetY / 2.0f) + mRectangleOffset.y)); - mVideoRectangleCoords.emplace_back(std::round(rectWidth)); - mVideoRectangleCoords.emplace_back(std::round(rectHeight)); + mBlackFrame.setPosition((-offsetX / 2.0f) + mBlackFrameOffset.x, + (-offsetY / 2.0f) + mBlackFrameOffset.y); + mBlackFrame.setResize(rectWidth, rectHeight); } - // If the option to display pillarboxes is disabled, then make the rectangle equivalent - // to the size of the video. else { - mVideoRectangleCoords.emplace_back(0.0f); - mVideoRectangleCoords.emplace_back(0.0f); - mVideoRectangleCoords.emplace_back(std::round(mSize.x)); - mVideoRectangleCoords.emplace_back(std::round(mSize.y)); + // If the option to display pillarboxes is disabled, then set the black frame to the + // same position and size as the video. + mBlackFrame.setPosition(0.0f, 0.0f); + mBlackFrame.setResize(mSize.x, mSize.y); } } } @@ -1553,8 +1556,7 @@ void VideoFFmpegComponent::startVideoStream() // the video screeensaver. resize(); - // Calculate pillarbox/letterbox sizes. - calculateBlackRectangle(); + calculateBlackFrame(); mFadeIn = 0.0f; } diff --git a/es-core/src/components/VideoFFmpegComponent.h b/es-core/src/components/VideoFFmpegComponent.h index 4c2e78db4..e2294ce69 100644 --- a/es-core/src/components/VideoFFmpegComponent.h +++ b/es-core/src/components/VideoFFmpegComponent.h @@ -82,15 +82,14 @@ private: // Output frames to AudioManager and to the video surface (via the main thread). void outputFrames(); - // Calculate the black rectangle that is shown behind videos with non-standard aspect ratios. - void calculateBlackRectangle(); + // Calculate the black frame that is rendered behind all videos and which may also be + // adding pillarboxes/letterboxes. + void calculateBlackFrame(); // Detect and initialize the hardware decoder. static void detectHWDecoder(); bool decoderInitHW(); - Renderer* mRenderer; - // clang-format off static inline enum AVHWDeviceType sDeviceType {AV_HWDEVICE_TYPE_NONE}; static inline enum AVPixelFormat sPixelFormat {AV_PIX_FMT_NONE}; @@ -99,8 +98,7 @@ private: static inline std::vector sHWDecodedVideos; std::shared_ptr mTexture; - std::vector mVideoRectangleCoords; - glm::vec2 mRectangleOffset; + glm::vec2 mBlackFrameOffset; std::unique_ptr mFrameProcessingThread; std::mutex mPictureMutex; diff --git a/es-core/src/components/primary/CarouselComponent.h b/es-core/src/components/primary/CarouselComponent.h index debe3db45..9b03e6fda 100644 --- a/es-core/src/components/primary/CarouselComponent.h +++ b/es-core/src/components/primary/CarouselComponent.h @@ -166,6 +166,7 @@ private: bool mItemAxisHorizontal; float mItemAxisRotation; bool mLinearInterpolation; + float mImageCornerRadius; unsigned int mImageColorShift; unsigned int mImageColorShiftEnd; bool mImageColorGradientHorizontal; @@ -241,6 +242,7 @@ CarouselComponent::CarouselComponent() , mItemAxisHorizontal {false} , mItemAxisRotation {0.0f} , mLinearInterpolation {false} + , mImageCornerRadius {0.0f} , mImageColorShift {0xFFFFFFFF} , mImageColorShiftEnd {0xFFFFFFFF} , mImageColorGradientHorizontal {true} @@ -309,6 +311,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptrsetResize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); else if (mImagefit == ImageFit::COVER) item->setCroppedSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); + item->setCornerRadius(mImageCornerRadius); item->setImage(entry.data.imagePath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); if (mImageBrightness != 0.0) @@ -339,6 +342,7 @@ void CarouselComponent::addEntry(Entry& entry, const std::shared_ptrsetCroppedSize( glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); + mDefaultImage->setCornerRadius(mImageCornerRadius); mDefaultImage->setImage(entry.data.defaultImagePath); mDefaultImage->applyTheme(theme, "system", "", ThemeFlags::ALL); if (mImageBrightness != 0.0) @@ -413,6 +417,7 @@ void CarouselComponent::updateEntry(Entry& entry, const std::shared_ptrsetResize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); else if (mImagefit == ImageFit::COVER) item->setCroppedSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f))); + item->setCornerRadius(mImageCornerRadius); item->setImage(entry.data.imagePath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); if (mImageBrightness != 0.0) @@ -1429,6 +1434,10 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, } } + if (elem->has("imageCornerRadius")) + mImageCornerRadius = glm::clamp(elem->get("imageCornerRadius"), 0.0f, 0.5f) * + (mItemScale >= 1.0f ? mItemScale : 1.0f) * mRenderer->getScreenWidth(); + mImageSelectedColor = mImageColorShift; mImageSelectedColorEnd = mImageColorShiftEnd; diff --git a/es-core/src/components/primary/GridComponent.h b/es-core/src/components/primary/GridComponent.h index ad6d545b8..3eb51786d 100644 --- a/es-core/src/components/primary/GridComponent.h +++ b/es-core/src/components/primary/GridComponent.h @@ -162,6 +162,7 @@ private: float mUnfocusedItemDimming; ImageFit mImagefit; float mImageRelativeScale; + float mImageCornerRadius; unsigned int mImageColor; unsigned int mImageColorEnd; bool mImageColorGradientHorizontal; @@ -238,6 +239,7 @@ GridComponent::GridComponent() , mUnfocusedItemDimming {1.0f} , mImagefit {ImageFit::CONTAIN} , mImageRelativeScale {1.0f} + , mImageCornerRadius {0.0f} , mImageColor {0xFFFFFFFF} , mImageColorEnd {0xFFFFFFFF} , mImageColorGradientHorizontal {true} @@ -306,6 +308,7 @@ void GridComponent::addEntry(Entry& entry, const std::shared_ptr& item->setResize(mItemSize * mImageRelativeScale); else if (mImagefit == ImageFit::COVER) item->setCroppedSize(mItemSize * mImageRelativeScale); + item->setCornerRadius(mImageCornerRadius); item->setImage(entry.data.imagePath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); if (mImageBrightness != 0.0) @@ -338,6 +341,7 @@ void GridComponent::addEntry(Entry& entry, const std::shared_ptr& mDefaultImage->setResize(mItemSize * mImageRelativeScale); else if (mImagefit == ImageFit::COVER) mDefaultImage->setCroppedSize(mItemSize * mImageRelativeScale); + mDefaultImage->setCornerRadius(mImageCornerRadius); mDefaultImage->setImage(entry.data.defaultImagePath); mDefaultImage->applyTheme(theme, "system", "", ThemeFlags::ALL); if (mImageBrightness != 0.0) @@ -395,6 +399,7 @@ void GridComponent::updateEntry(Entry& entry, const std::shared_ptrsetResize(mItemSize * mImageRelativeScale); else if (mImagefit == ImageFit::COVER) item->setCroppedSize(mItemSize * mImageRelativeScale); + item->setCornerRadius(mImageCornerRadius); item->setImage(entry.data.imagePath); item->applyTheme(theme, "system", "", ThemeFlags::ALL); if (mImageBrightness != 0.0) @@ -1121,6 +1126,12 @@ void GridComponent::applyTheme(const std::shared_ptr& theme, mBackgroundImage->setColorGradientHorizontal(false); } } + float backgroundCornerRadius {0.0f}; + if (elem->has("backgroundCornerRadius")) + backgroundCornerRadius = + glm::clamp(elem->get("backgroundCornerRadius"), 0.0f, 0.5f) * + (mItemScale >= 1.0f ? mItemScale : 1.0f) * mRenderer->getScreenWidth(); + mBackgroundImage->setCornerRadius(backgroundCornerRadius); mBackgroundImage->setImage(elem->get("backgroundImage")); mBackgroundImagePath = path; } @@ -1146,6 +1157,12 @@ void GridComponent::applyTheme(const std::shared_ptr& theme, mSelectorImage->setColorGradientHorizontal(false); } } + float selectorCornerRadius {0.0f}; + if (elem->has("selectorCornerRadius")) + selectorCornerRadius = + glm::clamp(elem->get("selectorCornerRadius"), 0.0f, 0.5f) * + (mItemScale >= 1.0f ? mItemScale : 1.0f) * mRenderer->getScreenWidth(); + mSelectorImage->setCornerRadius(selectorCornerRadius); mSelectorImage->setImage(elem->get("selectorImage")); mSelectorImagePath = path; } @@ -1236,6 +1253,10 @@ void GridComponent::applyTheme(const std::shared_ptr& theme, mItemSpacing.y = ((mItemSize.y * mItemScale) - mItemSize.y) / 2.0f; } + if (elem->has("imageCornerRadius")) + mImageCornerRadius = glm::clamp(elem->get("imageCornerRadius"), 0.0f, 0.5f) * + (mItemScale >= 1.0f ? mItemScale : 1.0f) * mRenderer->getScreenWidth(); + if (elem->has("imageColor")) { mImageColor = elem->get("imageColor"); mImageColorEnd = mImageColor; diff --git a/es-core/src/renderers/Renderer.h b/es-core/src/renderers/Renderer.h index a66586337..5c65e274d 100644 --- a/es-core/src/renderers/Renderer.h +++ b/es-core/src/renderers/Renderer.h @@ -49,11 +49,13 @@ public: }; enum ShaderFlags { - PREMULTIPLIED = 0x00000001, - FONT_TEXTURE = 0x00000002, - POST_PROCESSING = 0x00000004, - CLIPPING = 0x00000008, - ROTATED = 0x00000010 // Screen rotated 90 or 270 degrees. + PREMULTIPLIED = 0x00000001, + FONT_TEXTURE = 0x00000002, + POST_PROCESSING = 0x00000004, + CLIPPING = 0x00000008, + ROTATED = 0x00000010, // Screen rotated 90 or 270 degrees. + ROUNDED_CORNERS = 0x00000020, + ROUNDED_CORNERS_NO_AA = 0x00000040 }; // clang-format on @@ -66,6 +68,7 @@ public: float opacity; float saturation; float dimming; + float cornerRadius; float reflectionsFalloff; float blurStrength; unsigned int shaders; @@ -76,6 +79,7 @@ public: , opacity {1.0f} , saturation {1.0f} , dimming {1.0f} + , cornerRadius {0.0f} , reflectionsFalloff {0.0f} , blurStrength {0.0f} , shaders {0} @@ -95,6 +99,7 @@ public: , opacity {1.0f} , saturation {1.0f} , dimming {1.0f} + , cornerRadius {0.0f} , reflectionsFalloff {0.0f} , blurStrength {0.0f} , shaders {0} diff --git a/es-core/src/renderers/RendererOpenGL.cpp b/es-core/src/renderers/RendererOpenGL.cpp index e62da151e..188f39985 100644 --- a/es-core/src/renderers/RendererOpenGL.cpp +++ b/es-core/src/renderers/RendererOpenGL.cpp @@ -505,6 +505,11 @@ void RendererOpenGL::drawTriangleStrips(const Vertex* vertices, mCoreShader->setOpacity(vertices->opacity); mCoreShader->setSaturation(vertices->saturation); mCoreShader->setDimming(vertices->dimming); + if (vertices->shaderFlags & ShaderFlags::ROUNDED_CORNERS || + vertices->shaderFlags & ShaderFlags::ROUNDED_CORNERS_NO_AA) { + mCoreShader->setTextureSize({width, height}); + mCoreShader->setCornerRadius(vertices->cornerRadius); + } mCoreShader->setReflectionsFalloff(vertices->reflectionsFalloff); mCoreShader->setFlags(vertices->shaderFlags); GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices)); diff --git a/es-core/src/renderers/ShaderOpenGL.cpp b/es-core/src/renderers/ShaderOpenGL.cpp index 2b34aca58..3c7a3a13e 100644 --- a/es-core/src/renderers/ShaderOpenGL.cpp +++ b/es-core/src/renderers/ShaderOpenGL.cpp @@ -23,6 +23,7 @@ ShaderOpenGL::ShaderOpenGL() , mShaderOpacity {0} , mShaderSaturation {0} , mShaderDimming {0} + , mCornerRadius {0} , mShaderReflectionsFalloff {0} , mBlurStrength {0} , mShaderFlags {0} @@ -128,6 +129,7 @@ void ShaderOpenGL::getVariableLocations(GLuint programID) mShaderOpacity = glGetUniformLocation(mProgramID, "opacity"); mShaderSaturation = glGetUniformLocation(mProgramID, "saturation"); mShaderDimming = glGetUniformLocation(mProgramID, "dimming"); + mCornerRadius = glGetUniformLocation(mProgramID, "cornerRadius"); mShaderReflectionsFalloff = glGetUniformLocation(mProgramID, "reflectionsFalloff"); mBlurStrength = glGetUniformLocation(mProgramID, "blurStrength"); mShaderFlags = glGetUniformLocation(mProgramID, "shaderFlags"); @@ -194,6 +196,12 @@ void ShaderOpenGL::setDimming(GLfloat dimming) GL_CHECK_ERROR(glUniform1f(mShaderDimming, dimming)); } +void ShaderOpenGL::setCornerRadius(GLfloat cornerRadius) +{ + if (mCornerRadius != -1) + GL_CHECK_ERROR(glUniform1f(mCornerRadius, cornerRadius)); +} + void ShaderOpenGL::setReflectionsFalloff(GLfloat falloff) { if (mShaderReflectionsFalloff != -1) diff --git a/es-core/src/renderers/ShaderOpenGL.h b/es-core/src/renderers/ShaderOpenGL.h index d50d6268f..c995100c4 100644 --- a/es-core/src/renderers/ShaderOpenGL.h +++ b/es-core/src/renderers/ShaderOpenGL.h @@ -73,6 +73,7 @@ public: void setOpacity(GLfloat opacity); void setSaturation(GLfloat saturation); void setDimming(GLfloat dimming); + void setCornerRadius(GLfloat cornerRadius); void setReflectionsFalloff(GLfloat falloff); void setBlurStrength(GLfloat blurStrength); void setFlags(GLuint flags); @@ -101,6 +102,7 @@ private: GLint mShaderOpacity; GLint mShaderSaturation; GLint mShaderDimming; + GLint mCornerRadius; GLint mShaderReflectionsFalloff; GLint mBlurStrength; GLint mShaderFlags; diff --git a/resources/graphics/white.png b/resources/graphics/white.png new file mode 100644 index 0000000000000000000000000000000000000000..cdc1d18325c3fe311c5c828500a55243980b0038 GIT binary patch literal 5122 zcmeHKXIK;28V-t(MP(HM5!)DLl`_dB8A38kP$9Hs11umQ*EX2~ku;J}ttg6MK}A=^ z!dkexe@ev%OI%QSqLX5hQ(+lr+OMz5zSVUhX}JQ31p)wEneLHcA31yMIZ&~ojG!Ofl zwC4>q1Lyt{{K|S;PX4C4R4d)NDag~iPSgQ?)#YhT0^h7xn%o)dO^+zr_rF&s9PB$~ z>M_4cX7Y_eFXLFJ3!eme&Kk90AlGb8-zIp>Z0SYYaSbIa*4(U6ZQT-4nOj-%a8~S= z2$#^-oLO(h_Qef0|6x9N9Ah4}YWVzfS*DJ5!YI2&WacJ~J&uL99(|9a<{-w6M zmQ`1jlHHGPZwSBlh!x}?`1(VhcxA;ui$mI}rU$dCvJ$cboQ4Hm%gyB;%sLR=x~(cL zEdNNsjqoeXxi$gk^28H3!xv|H9uGgib4BP)jrH5w6`9w^4O&?Bi{EC8{#eBCJ4aRh zJ9@^6`Q>|Ewl9rq{w{m^rnP1p$rh-(&i2)~*;$LO6u*hV3!5X$2Tj=k`6S(YWPfYU zjAd_}1`MBDV{=Eg<&2MA_HT;YW-K%zB;KJdJh%Fu6SR|G{oLk^pW?`vf7Rt~oM0Q1 zx_w*at%=1IeO9tu3Ym*dO(!guIM(9le9|Ns!zS&5Rrvx&f!25BFmJ1?!kWBiOKPME+R)O+;@8@`jP3eUp*8yJEzus;M}Hb> z5@+toxj80g+E{FZiCwxMq#9&mi3ZW>q08LQNNL|wFUOu`hK$`8m~F>Al^tn*^J<@p zC4t)ZrT^H|c652Wb2(Cb#Q*X4r4g6jzO$dX^y*973p++EDE-!B(eUwYMQ?AVS4t1x zn3v|^IdW^(Mc)UbUkrSkm_{~crN5cpsJ>f~pS*Z)VHks9vPkab73}Zj_0jvl@tfwX z68oK)Fzn>?&^&J^7R-9Maw|4@fKA*bC+n@AJ8h!kk@6Q_F?B2VmkBM3`k0Tm-DSzX za1blASkP#`WY^N{ZA;g*RqnXRI<@fb+{1@nm(4O~zO<_Ubp>}qy)*4Ja4`Rx4fkm# zbo{{yD21Yj>|4Di?S9+A^sL>9+$~LW9@o?sHoKU$N$w1x*W28Qzf*8N^H_4O@OWnN z53>W^XV&$}7+GFa_U=@9&;G_|6#0`fb#vL-ujQ^#`nuA9Q&rJQ;Q~*mNt6?j+~FiuRUyHX_>mJ z7qvV2@b_15-KdPtdH-G}RRDT$l5h=&%SJd#C8v9aPVb!nKsqP% zml?V+u%~lED4jZ9OHkekluAFkI|NB|>0P)zqCavs27@T66qBflc*FGkC1RFge2i2R zauVyf@Q~V*vO}r zUK*V$93I!;Ny72`(-lw2&N~;4-G+=TOHopVc zP!ZcmmZ?5s=r>Ma1B$XbP&1cgZvii`Ln3PoH+MjBn{IyJ3N#I@AKXuuS(0R}R%fyNo7bnF?O7(*F!LU?=_ z5yCtq3_&n17vl<@5gvvhEY6pwNRu*z&nIYD~YPn92DHvj+t literal 0 HcmV?d00001 diff --git a/resources/shaders/glsl/blur_horizontal.glsl b/resources/shaders/glsl/blur_horizontal.glsl index 4428bb308..1747804fc 100644 --- a/resources/shaders/glsl/blur_horizontal.glsl +++ b/resources/shaders/glsl/blur_horizontal.glsl @@ -39,6 +39,8 @@ out vec4 FragColor; // 0x00000004 - Post processing // 0x00000008 - Clipping // 0x00000010 - Screen rotated 90 or 270 degrees +// 0x00000020 - Rounded corners +// 0x00000040 - Rounded corners with no anti-aliasing void main() { diff --git a/resources/shaders/glsl/blur_vertical.glsl b/resources/shaders/glsl/blur_vertical.glsl index ce7e551d7..31e522ced 100644 --- a/resources/shaders/glsl/blur_vertical.glsl +++ b/resources/shaders/glsl/blur_vertical.glsl @@ -39,6 +39,8 @@ out vec4 FragColor; // 0x00000004 - Post processing // 0x00000008 - Clipping // 0x00000010 - Screen rotated 90 or 270 degrees +// 0x00000020 - Rounded corners +// 0x00000040 - Rounded corners with no anti-aliasing void main() { diff --git a/resources/shaders/glsl/core.glsl b/resources/shaders/glsl/core.glsl index da5609e47..361d7dc26 100644 --- a/resources/shaders/glsl/core.glsl +++ b/resources/shaders/glsl/core.glsl @@ -3,8 +3,7 @@ // EmulationStation Desktop Edition // core.glsl // -// Core shader functionality: -// Clipping, brightness, saturation, opacity, dimming and reflections falloff. +// Core shader functionality. // // Vertex section of code: @@ -38,11 +37,13 @@ in vec2 position; in vec2 texCoord; in vec4 color; +uniform vec2 textureSize; uniform vec4 clipRegion; uniform float brightness; uniform float saturation; uniform float opacity; uniform float dimming; +uniform float cornerRadius; uniform float reflectionsFalloff; uniform uint shaderFlags; @@ -55,6 +56,8 @@ out vec4 FragColor; // 0x00000004 - Post processing // 0x00000008 - Clipping // 0x00000010 - Screen rotated 90 or 270 degrees +// 0x00000020 - Rounded corners +// 0x00000040 - Rounded corners with no anti-aliasing void main() { @@ -72,6 +75,34 @@ void main() vec4 sampledColor = texture(textureSampler, texCoord); + // Rounded corners. + if (0x0u != (shaderFlags & 0x20u) || 0x0u != (shaderFlags & 0x40u)) { + float radius = cornerRadius; + // Don't go beyond half the width and height. + if (radius > textureSize.x / 2.0) + radius = textureSize.x / 2.0; + if (radius > textureSize.y / 2.0) + radius = textureSize.y / 2.0; + + vec2 q = abs(position - textureSize / 2.0) - + (vec2(textureSize.x / 2.0, textureSize.y / 2.0) - radius); + float pixelDistance = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; + + if (pixelDistance > 0.0) { + discard; + } + else { + float pixelValue; + if (0x0u != (shaderFlags & 0x20u)) + pixelValue = 1.0 - smoothstep(-0.75, 0.5, pixelDistance); + else + pixelValue = 1.0; + + sampledColor.a *= pixelValue; + sampledColor.rgb *= pixelValue; + } + } + // Brightness. if (brightness != 0.0) { sampledColor.rgb /= sampledColor.a; diff --git a/resources/shaders/glsl/scanlines.glsl b/resources/shaders/glsl/scanlines.glsl index cb68ee18f..fb4fc1422 100644 --- a/resources/shaders/glsl/scanlines.glsl +++ b/resources/shaders/glsl/scanlines.glsl @@ -100,6 +100,8 @@ uniform float OutputGamma; // 0x00000004 - Post processing // 0x00000008 - Clipping // 0x00000010 - Screen rotated 90 or 270 degrees +// 0x00000020 - Rounded corners +// 0x00000040 - Rounded corners with no anti-aliasing void main() {