fix(screen-corners): sharper without dedicated shader for more control: no more faint lines on the outside

This commit is contained in:
Lemmy
2026-05-06 20:25:15 -04:00
parent 7adb666b3f
commit 1e99170846
14 changed files with 345 additions and 22 deletions
+2
View File
@@ -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',
@@ -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<const float> 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();
+4
View File
@@ -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<const float> 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;
+3
View File
@@ -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<const float> values, const Mat3& transform) = 0;
+19
View File
@@ -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,
@@ -0,0 +1,131 @@
#include "render/programs/screen_corner_program.h"
#include <algorithm>
#include <array>
#include <stdexcept>
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<GLfloat, 12> 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<GLint>(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);
}
@@ -0,0 +1,34 @@
#pragma once
#include "render/core/mat3.h"
#include "render/core/render_styles.h"
#include "render/core/shader_program.h"
#include <GLES2/gl2.h>
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;
};
+10
View File
@@ -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<const ScreenCornerNode*>(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<const AudioSpectrumNode*>(node);
auto style = spectrum->style();
+1
View File
@@ -17,6 +17,7 @@ enum class NodeType : std::uint8_t {
Image,
Glyph,
Spinner,
ScreenCorner,
AudioSpectrum,
Effect,
Graph,
+54
View File
@@ -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;
};
+25 -21
View File
@@ -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 <algorithm>
@@ -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<float>(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<Box>();
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<float>(std::max<std::uint32_t>(1, width));
const float logicalHeight = static_cast<float>(std::max<std::uint32_t>(1, height));
auto root = std::make_unique<ScreenCorner>();
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());
}
+3 -1
View File
@@ -27,6 +27,8 @@ private:
struct Corner {
std::unique_ptr<LayerSurface> surface;
std::unique_ptr<Node> 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;
+28
View File
@@ -0,0 +1,28 @@
#include "ui/controls/screen_corner.h"
#include "render/scene/screen_corner_node.h"
#include <memory>
ScreenCorner::ScreenCorner() {
auto corner = std::make_unique<ScreenCornerNode>();
m_corner = static_cast<ScreenCornerNode*>(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);
}
+23
View File
@@ -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;
};