From 1e991708468b795f0d0b68fb4979f75ce483250d Mon Sep 17 00:00:00 2001 From: Lemmy Date: Wed, 6 May 2026 20:25:15 -0400 Subject: [PATCH] fix(screen-corners): sharper without dedicated shader for more control: no more faint lines on the outside --- meson.build | 2 + src/render/backend/gles_render_backend.cpp | 8 ++ src/render/backend/gles_render_backend.h | 4 + src/render/backend/render_backend.h | 3 + src/render/core/render_styles.h | 19 +++ src/render/programs/screen_corner_program.cpp | 131 ++++++++++++++++++ src/render/programs/screen_corner_program.h | 34 +++++ src/render/render_context.cpp | 10 ++ src/render/scene/node.h | 1 + src/render/scene/screen_corner_node.h | 54 ++++++++ src/shell/screen_corners/screen_corners.cpp | 46 +++--- src/shell/screen_corners/screen_corners.h | 4 +- src/ui/controls/screen_corner.cpp | 28 ++++ src/ui/controls/screen_corner.h | 23 +++ 14 files changed, 345 insertions(+), 22 deletions(-) create mode 100644 src/render/programs/screen_corner_program.cpp create mode 100644 src/render/programs/screen_corner_program.h create mode 100644 src/render/scene/screen_corner_node.h create mode 100644 src/ui/controls/screen_corner.cpp create mode 100644 src/ui/controls/screen_corner.h diff --git a/meson.build b/meson.build index c71871c5c..e7451d1d7 100644 --- a/meson.build +++ b/meson.build @@ -390,6 +390,7 @@ _noctalia_sources = files( 'src/render/programs/image_program.cpp', 'src/render/programs/linear_gradient_program.cpp', 'src/render/programs/rect_program.cpp', + 'src/render/programs/screen_corner_program.cpp', 'src/render/programs/spinner_program.cpp', 'src/render/programs/audio_spectrum_program.cpp', 'src/render/programs/effect_program.cpp', @@ -552,6 +553,7 @@ _noctalia_sources = files( 'src/ui/controls/radio_button.cpp', 'src/ui/controls/scroll_view.cpp', 'src/ui/controls/search_picker.cpp', + 'src/ui/controls/screen_corner.cpp', 'src/ui/controls/segmented.cpp', 'src/ui/controls/select.cpp', 'src/ui/controls/separator.cpp', diff --git a/src/render/backend/gles_render_backend.cpp b/src/render/backend/gles_render_backend.cpp index e339a9689..d36880207 100644 --- a/src/render/backend/gles_render_backend.cpp +++ b/src/render/backend/gles_render_backend.cpp @@ -340,6 +340,13 @@ void GlesRenderBackend::drawSpinner(float surfaceWidth, float surfaceHeight, flo m_spinnerProgram.draw(surfaceWidth, surfaceHeight, width, height, style, transform); } +void GlesRenderBackend::drawScreenCorner(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, + float width, float height, const ScreenCornerStyle& style, + const Mat3& transform) { + m_screenCornerProgram.ensureInitialized(); + m_screenCornerProgram.draw(surfaceWidth, surfaceHeight, pixelScaleX, pixelScaleY, width, height, style, transform); +} + void GlesRenderBackend::drawAudioSpectrum(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, float width, float height, const AudioSpectrumStyle& style, std::span values, const Mat3& transform) { @@ -429,6 +436,7 @@ void GlesRenderBackend::cleanup() { m_imageProgram.destroy(); m_glyphProgram.destroy(); m_spinnerProgram.destroy(); + m_screenCornerProgram.destroy(); m_audioSpectrumProgram.destroy(); m_effectProgram.destroy(); m_graphProgram.destroy(); diff --git a/src/render/backend/gles_render_backend.h b/src/render/backend/gles_render_backend.h index 9f3fb973c..a89806594 100644 --- a/src/render/backend/gles_render_backend.h +++ b/src/render/backend/gles_render_backend.h @@ -10,6 +10,7 @@ #include "render/programs/graph_program.h" #include "render/programs/image_program.h" #include "render/programs/rect_program.h" +#include "render/programs/screen_corner_program.h" #include "render/programs/spinner_program.h" #include "render/programs/wallpaper_program.h" @@ -48,6 +49,8 @@ public: void drawGlyph(const RenderGlyphDraw& draw) override; void drawSpinner(float surfaceWidth, float surfaceHeight, float width, float height, const SpinnerStyle& style, const Mat3& transform) override; + void drawScreenCorner(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, float width, + float height, const ScreenCornerStyle& style, const Mat3& transform) override; void drawAudioSpectrum(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, float width, float height, const AudioSpectrumStyle& style, std::span values, const Mat3& transform) override; @@ -82,6 +85,7 @@ private: ImageProgram m_imageProgram; GlyphProgram m_glyphProgram; SpinnerProgram m_spinnerProgram; + ScreenCornerProgram m_screenCornerProgram; AudioSpectrumProgram m_audioSpectrumProgram; EffectProgram m_effectProgram; GraphProgram m_graphProgram; diff --git a/src/render/backend/render_backend.h b/src/render/backend/render_backend.h index 57975dbdb..3622812e5 100644 --- a/src/render/backend/render_backend.h +++ b/src/render/backend/render_backend.h @@ -18,6 +18,7 @@ struct AudioSpectrumStyle; struct EffectStyle; struct GraphStyle; struct RoundedRectStyle; +struct ScreenCornerStyle; struct SpinnerStyle; struct TransitionParams; @@ -122,6 +123,8 @@ public: virtual void drawGlyph(const RenderGlyphDraw& draw) = 0; virtual void drawSpinner(float surfaceWidth, float surfaceHeight, float width, float height, const SpinnerStyle& style, const Mat3& transform) = 0; + virtual void drawScreenCorner(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, + float width, float height, const ScreenCornerStyle& style, const Mat3& transform) = 0; virtual void drawAudioSpectrum(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, float width, float height, const AudioSpectrumStyle& style, std::span values, const Mat3& transform) = 0; diff --git a/src/render/core/render_styles.h b/src/render/core/render_styles.h index 35730c0d6..786fe0812 100644 --- a/src/render/core/render_styles.h +++ b/src/render/core/render_styles.h @@ -117,6 +117,25 @@ struct SpinnerStyle { float thickness = 2.0f; }; +enum class ScreenCornerPosition : std::uint8_t { + TopLeft, + TopRight, + BottomRight, + BottomLeft, +}; + +struct ScreenCornerStyle { + Color color = rgba(0.0f, 0.0f, 0.0f, 1.0f); + ScreenCornerPosition position = ScreenCornerPosition::TopLeft; + float exponent = 4.0f; + float softness = 1.0f; +}; + +constexpr bool operator==(const ScreenCornerStyle& lhs, const ScreenCornerStyle& rhs) noexcept { + return lhs.color == rhs.color && lhs.position == rhs.position && lhs.exponent == rhs.exponent && + lhs.softness == rhs.softness; +} + enum class AudioSpectrumOrientation : std::uint8_t { Horizontal, Vertical, diff --git a/src/render/programs/screen_corner_program.cpp b/src/render/programs/screen_corner_program.cpp new file mode 100644 index 000000000..97e67a68a --- /dev/null +++ b/src/render/programs/screen_corner_program.cpp @@ -0,0 +1,131 @@ +#include "render/programs/screen_corner_program.h" + +#include +#include +#include + +namespace { + + constexpr char kVertexShaderSource[] = R"( +precision highp float; + +attribute vec2 a_position; +uniform vec2 u_surface_size; +uniform vec2 u_size; +uniform mat3 u_transform; +varying vec2 v_local; + +vec2 to_ndc(vec2 pixel_pos) { + vec2 normalized = pixel_pos / u_surface_size; + return vec2(normalized.x * 2.0 - 1.0, 1.0 - normalized.y * 2.0); +} + +void main() { + vec2 local = a_position * u_size; + vec3 pixel = u_transform * vec3(local, 1.0); + v_local = local; + gl_Position = vec4(to_ndc(pixel.xy), 0.0, 1.0); +} +)"; + + constexpr char kFragmentShaderSource[] = R"( +precision highp float; + +uniform vec2 u_size; +uniform vec2 u_pixel_scale; +uniform vec4 u_color; +uniform int u_corner; +uniform float u_exponent; +uniform float u_softness; +varying vec2 v_local; + +vec2 corner_center() { + if (u_corner == 1) { + return vec2(0.0, u_size.y); + } + if (u_corner == 2) { + return vec2(0.0, 0.0); + } + if (u_corner == 3) { + return vec2(u_size.x, 0.0); + } + return u_size; +} + +void main() { + vec2 radius = max(u_size, vec2(1.0)); + vec2 normalized = abs(v_local - corner_center()) / radius; + float shape = pow(normalized.x, u_exponent) + pow(normalized.y, u_exponent) - 1.0; + float pixel_scale = max(min(u_pixel_scale.x, u_pixel_scale.y), 1.0); + float aa = max(u_softness / (min(radius.x, radius.y) * pixel_scale), 0.0001); + float coverage = smoothstep(-aa, aa, shape); + float alpha = u_color.a * coverage; + if (alpha <= 0.0) { + discard; + } + gl_FragColor = vec4(u_color.rgb * alpha, alpha); +} +)"; + +} // namespace + +void ScreenCornerProgram::ensureInitialized() { + if (m_program.isValid()) { + return; + } + + m_program.create(kVertexShaderSource, kFragmentShaderSource); + m_positionLocation = glGetAttribLocation(m_program.id(), "a_position"); + m_surfaceSizeLocation = glGetUniformLocation(m_program.id(), "u_surface_size"); + m_sizeLocation = glGetUniformLocation(m_program.id(), "u_size"); + m_pixelScaleLocation = glGetUniformLocation(m_program.id(), "u_pixel_scale"); + m_colorLocation = glGetUniformLocation(m_program.id(), "u_color"); + m_cornerLocation = glGetUniformLocation(m_program.id(), "u_corner"); + m_exponentLocation = glGetUniformLocation(m_program.id(), "u_exponent"); + m_softnessLocation = glGetUniformLocation(m_program.id(), "u_softness"); + m_transformLocation = glGetUniformLocation(m_program.id(), "u_transform"); + + if (m_positionLocation < 0 || m_surfaceSizeLocation < 0 || m_sizeLocation < 0 || m_pixelScaleLocation < 0 || + m_colorLocation < 0 || m_cornerLocation < 0 || m_exponentLocation < 0 || m_softnessLocation < 0 || + m_transformLocation < 0) { + throw std::runtime_error("failed to query screen-corner shader locations"); + } +} + +void ScreenCornerProgram::destroy() { + m_program.destroy(); + m_positionLocation = -1; + m_surfaceSizeLocation = -1; + m_sizeLocation = -1; + m_pixelScaleLocation = -1; + m_colorLocation = -1; + m_cornerLocation = -1; + m_exponentLocation = -1; + m_softnessLocation = -1; + m_transformLocation = -1; +} + +void ScreenCornerProgram::draw(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, + float width, float height, const ScreenCornerStyle& style, const Mat3& transform) const { + if (!m_program.isValid() || width <= 0.0f || height <= 0.0f || style.color.a <= 0.0f) { + return; + } + + const std::array vertices = { + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + }; + + glUseProgram(m_program.id()); + glUniform2f(m_surfaceSizeLocation, surfaceWidth, surfaceHeight); + glUniform2f(m_sizeLocation, width, height); + glUniform2f(m_pixelScaleLocation, std::max(1.0f, pixelScaleX), std::max(1.0f, pixelScaleY)); + glUniform4f(m_colorLocation, style.color.r, style.color.g, style.color.b, style.color.a); + glUniform1i(m_cornerLocation, static_cast(style.position)); + glUniform1f(m_exponentLocation, std::max(1.0f, style.exponent)); + glUniform1f(m_softnessLocation, std::max(0.0f, style.softness)); + glUniformMatrix3fv(m_transformLocation, 1, GL_FALSE, transform.m.data()); + glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, vertices.data()); + glEnableVertexAttribArray(m_positionLocation); + glDrawArrays(GL_TRIANGLES, 0, 6); + glDisableVertexAttribArray(m_positionLocation); +} diff --git a/src/render/programs/screen_corner_program.h b/src/render/programs/screen_corner_program.h new file mode 100644 index 000000000..7109bbfa0 --- /dev/null +++ b/src/render/programs/screen_corner_program.h @@ -0,0 +1,34 @@ +#pragma once + +#include "render/core/mat3.h" +#include "render/core/render_styles.h" +#include "render/core/shader_program.h" + +#include + +class ScreenCornerProgram { +public: + ScreenCornerProgram() = default; + ~ScreenCornerProgram() = default; + + ScreenCornerProgram(const ScreenCornerProgram&) = delete; + ScreenCornerProgram& operator=(const ScreenCornerProgram&) = delete; + + void ensureInitialized(); + void destroy(); + + void draw(float surfaceWidth, float surfaceHeight, float pixelScaleX, float pixelScaleY, float width, float height, + const ScreenCornerStyle& style, const Mat3& transform = Mat3::identity()) const; + +private: + ShaderProgram m_program; + GLint m_positionLocation = -1; + GLint m_surfaceSizeLocation = -1; + GLint m_sizeLocation = -1; + GLint m_pixelScaleLocation = -1; + GLint m_colorLocation = -1; + GLint m_cornerLocation = -1; + GLint m_exponentLocation = -1; + GLint m_softnessLocation = -1; + GLint m_transformLocation = -1; +}; diff --git a/src/render/render_context.cpp b/src/render/render_context.cpp index 35b71133a..832820f97 100644 --- a/src/render/render_context.cpp +++ b/src/render/render_context.cpp @@ -15,6 +15,7 @@ #include "render/scene/image_node.h" #include "render/scene/node.h" #include "render/scene/rect_node.h" +#include "render/scene/screen_corner_node.h" #include "render/scene/spinner_node.h" #include "render/scene/text_node.h" #include "render/scene/wallpaper_node.h" @@ -320,6 +321,15 @@ void RenderContext::renderNode(const Node* node, const Mat3& parentTransform, fl m_backend->drawSpinner(sw, sh, node->width(), node->height(), style, worldTransform); break; } + case NodeType::ScreenCorner: { + const auto* corner = static_cast(node); + auto style = corner->style(); + style.color.a *= effectiveOpacity; + const float pixelScaleX = sw > 0.0f ? bw / sw : 1.0f; + const float pixelScaleY = sh > 0.0f ? bh / sh : 1.0f; + m_backend->drawScreenCorner(sw, sh, pixelScaleX, pixelScaleY, node->width(), node->height(), style, worldTransform); + break; + } case NodeType::AudioSpectrum: { const auto* spectrum = static_cast(node); auto style = spectrum->style(); diff --git a/src/render/scene/node.h b/src/render/scene/node.h index eff3e9517..676a672da 100644 --- a/src/render/scene/node.h +++ b/src/render/scene/node.h @@ -17,6 +17,7 @@ enum class NodeType : std::uint8_t { Image, Glyph, Spinner, + ScreenCorner, AudioSpectrum, Effect, Graph, diff --git a/src/render/scene/screen_corner_node.h b/src/render/scene/screen_corner_node.h new file mode 100644 index 000000000..2ed100be7 --- /dev/null +++ b/src/render/scene/screen_corner_node.h @@ -0,0 +1,54 @@ +#pragma once + +#include "render/core/render_styles.h" +#include "render/scene/node.h" + +class ScreenCornerNode : public Node { +public: + ScreenCornerNode() : Node(NodeType::ScreenCorner) {} + + [[nodiscard]] const ScreenCornerStyle& style() const noexcept { return m_style; } + + void setStyle(const ScreenCornerStyle& style) { + if (m_style == style) { + return; + } + m_style = style; + markPaintDirty(); + } + + void setColor(const Color& color) { + if (m_style.color == color) { + return; + } + m_style.color = color; + markPaintDirty(); + } + + void setCorner(ScreenCornerPosition position) { + if (m_style.position == position) { + return; + } + m_style.position = position; + markPaintDirty(); + } + + void setExponent(float exponent) { + if (m_style.exponent == exponent) { + return; + } + m_style.exponent = exponent; + markPaintDirty(); + } + + void setSoftness(float softness) { + if (m_style.softness == softness) { + return; + } + m_style.softness = softness; + markPaintDirty(); + } + +private: + ScreenCornerStyle m_style; +}; diff --git a/src/shell/screen_corners/screen_corners.cpp b/src/shell/screen_corners/screen_corners.cpp index 777e7f686..35a57c539 100644 --- a/src/shell/screen_corners/screen_corners.cpp +++ b/src/shell/screen_corners/screen_corners.cpp @@ -3,7 +3,7 @@ #include "config/config_service.h" #include "core/ui_phase.h" #include "render/render_context.h" -#include "ui/controls/box.h" +#include "ui/controls/screen_corner.h" #include "wayland/wayland_connection.h" #include @@ -17,18 +17,18 @@ namespace { LayerShellAnchor::Bottom | LayerShellAnchor::Left, }; - Radii cornerRadii(int cornerIndex, float size) { + ScreenCornerPosition cornerPosition(int cornerIndex) { switch (cornerIndex) { case 0: - return Radii{size, 0.0f, 0.0f, 0.0f}; + return ScreenCornerPosition::TopLeft; case 1: - return Radii{0.0f, size, 0.0f, 0.0f}; + return ScreenCornerPosition::TopRight; case 2: - return Radii{0.0f, 0.0f, size, 0.0f}; + return ScreenCornerPosition::BottomRight; case 3: - return Radii{0.0f, 0.0f, 0.0f, size}; + return ScreenCornerPosition::BottomLeft; default: - return Radii{}; + return ScreenCornerPosition::TopLeft; } } @@ -105,14 +105,16 @@ void ScreenCorners::ensureSurfaces() { auto* cornerPtr = &corner; const int cornerIndex = i; - const float cornerSize = static_cast(size); corner.surface->setConfigureCallback( [cornerPtr](std::uint32_t, std::uint32_t) { cornerPtr->surface->requestLayout(); }); - corner.surface->setPrepareFrameCallback([this, cornerPtr, cornerSize, cornerIndex](bool, bool) { - if (cornerPtr->sceneRoot == nullptr) { + corner.surface->setPrepareFrameCallback([this, cornerPtr, size, cornerIndex](bool, bool) { + auto& target = cornerPtr->surface->renderTarget(); + const auto width = target.logicalWidth() == 0 ? size : target.logicalWidth(); + const auto height = target.logicalHeight() == 0 ? size : target.logicalHeight(); + if (cornerPtr->sceneRoot == nullptr || cornerPtr->builtWidth != width || cornerPtr->builtHeight != height) { UiPhaseScope layoutPhase(UiPhase::Layout); - buildCornerScene(*cornerPtr, cornerSize, cornerIndex); + buildCornerScene(*cornerPtr, width, height, cornerIndex); } }); corner.surface->setRenderContext(m_renderContext); @@ -132,17 +134,19 @@ void ScreenCorners::ensureSurfaces() { void ScreenCorners::destroySurfaces() { m_instances.clear(); } -void ScreenCorners::buildCornerScene(Corner& corner, float size, int cornerIndex) { - auto root = std::make_unique(); - root->setSize(size, size); - root->setStyle(RoundedRectStyle{ - .fill = Color{0.0f, 0.0f, 0.0f, 1.0f}, - .fillMode = FillMode::Solid, - .radius = cornerRadii(cornerIndex, size), - .softness = 0.5f, - .invertFill = true, - }); +void ScreenCorners::buildCornerScene(Corner& corner, std::uint32_t width, std::uint32_t height, int cornerIndex) { + const float logicalWidth = static_cast(std::max(1, width)); + const float logicalHeight = static_cast(std::max(1, height)); + + auto root = std::make_unique(); + root->setSize(logicalWidth, logicalHeight); + root->setColor(Color{0.0f, 0.0f, 0.0f, 1.0f}); + root->setCorner(cornerPosition(cornerIndex)); + root->setExponent(4.0f); + root->setSoftness(1.5f); corner.sceneRoot = std::move(root); + corner.builtWidth = width; + corner.builtHeight = height; corner.surface->setSceneRoot(corner.sceneRoot.get()); } diff --git a/src/shell/screen_corners/screen_corners.h b/src/shell/screen_corners/screen_corners.h index d4e6d8ce0..651192fec 100644 --- a/src/shell/screen_corners/screen_corners.h +++ b/src/shell/screen_corners/screen_corners.h @@ -27,6 +27,8 @@ private: struct Corner { std::unique_ptr surface; std::unique_ptr sceneRoot; + std::uint32_t builtWidth = 0; + std::uint32_t builtHeight = 0; }; struct OutputInstance { @@ -36,7 +38,7 @@ private: void ensureSurfaces(); void destroySurfaces(); - void buildCornerScene(Corner& corner, float size, int cornerIndex); + void buildCornerScene(Corner& corner, std::uint32_t width, std::uint32_t height, int cornerIndex); WaylandConnection* m_wayland = nullptr; ConfigService* m_config = nullptr; diff --git a/src/ui/controls/screen_corner.cpp b/src/ui/controls/screen_corner.cpp new file mode 100644 index 000000000..a638f8b43 --- /dev/null +++ b/src/ui/controls/screen_corner.cpp @@ -0,0 +1,28 @@ +#include "ui/controls/screen_corner.h" + +#include "render/scene/screen_corner_node.h" + +#include + +ScreenCorner::ScreenCorner() { + auto corner = std::make_unique(); + m_corner = static_cast(addChild(std::move(corner))); +} + +void ScreenCorner::setColor(const Color& color) { m_corner->setColor(color); } + +void ScreenCorner::setCorner(ScreenCornerPosition position) { m_corner->setCorner(position); } + +void ScreenCorner::setExponent(float exponent) { m_corner->setExponent(exponent); } + +void ScreenCorner::setSoftness(float softness) { m_corner->setSoftness(softness); } + +void ScreenCorner::setSize(float width, float height) { + Node::setSize(width, height); + m_corner->setFrameSize(width, height); +} + +void ScreenCorner::setFrameSize(float width, float height) { + Node::setFrameSize(width, height); + m_corner->setFrameSize(width, height); +} diff --git a/src/ui/controls/screen_corner.h b/src/ui/controls/screen_corner.h new file mode 100644 index 000000000..01adfda53 --- /dev/null +++ b/src/ui/controls/screen_corner.h @@ -0,0 +1,23 @@ +#pragma once + +#include "render/core/color.h" +#include "render/core/render_styles.h" +#include "render/scene/node.h" + +class ScreenCornerNode; + +class ScreenCorner : public Node { +public: + ScreenCorner(); + + void setColor(const Color& color); + void setCorner(ScreenCornerPosition position); + void setExponent(float exponent); + void setSoftness(float softness); + + void setSize(float width, float height) override; + void setFrameSize(float width, float height); + +private: + ScreenCornerNode* m_corner = nullptr; +};