srctree

Mikael Larsson parent 64e735fd 31ad50c6
gfx: Add implementation of new fragment shader

This commit adds a new fragment shader for drawing rectangles with borders. This shader also has some support for borders with radius.
gfx/BUILD added: 231, removed: 77, total 154
@@ -32,9 +32,9 @@ genrule(
)
 
genrule(
name = "border_fragment_shader",
srcs = ["border_shader.frag"],
outs = ["border_fragment_shader.h"],
name = "rect_fragment_shader",
srcs = ["rect_shader.frag"],
outs = ["rect_fragment_shader.h"],
cmd = "xxd -i $< >$@",
)
 
@@ -43,7 +43,7 @@ cc_library(
srcs = [
"sfml_canvas.cpp",
":basic_vertex_shader",
":border_fragment_shader",
":rect_fragment_shader",
],
hdrs = ["sfml_canvas.h"],
visibility = ["//visibility:public"],
 
ev/null added: 231, removed: 77, total 154
@@ -1,50 +0,0 @@
// SPDX-FileCopyrightText: 2022 Mikael Larsson <c.mikael.larsson@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
 
uniform vec2 resolution;
uniform vec3 inner_top_left;
uniform vec3 inner_top_right;
uniform vec3 inner_bottom_left;
uniform vec3 inner_bottom_right;
uniform vec3 outer_top_left;
uniform vec3 outer_top_right;
uniform vec3 outer_bottom_left;
uniform vec3 outer_bottom_right;
uniform vec4 left_border_color;
uniform vec4 right_border_color;
uniform vec4 top_border_color;
uniform vec4 bottom_border_color;
 
// Gets the position of the fragment in screen coordinates
vec3 get_frag_pos() {
vec2 p = vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y);
return vec3(p.x, p.y, 0.0);
}
 
// Checks if a point is inside a quadrilateral
bool is_point_inside(vec3 p, vec3 a, vec3 b, vec3 c, vec3 d) {
return dot(cross(p - a, b - a), cross(p - d, c - d)) <= 0.0 &&
dot(cross(p - a, d - a), cross(p - b, c - b)) <= 0.0;
}
 
void main() {
vec3 p = get_frag_pos();
if (is_point_inside(p, inner_top_left, inner_top_right, inner_bottom_right, inner_bottom_left)) {
// The fragment is not on a border, set fully transparent color
gl_FragColor = vec4(1.0, 1.0, 1.0, 0.0);
} else {
if (is_point_inside(p, outer_top_left, outer_top_right, inner_top_right, inner_top_left)) {
gl_FragColor = top_border_color;
} else if (is_point_inside(p, outer_top_right, outer_bottom_right, inner_bottom_right, inner_top_right)) {
gl_FragColor = right_border_color;
} else if (is_point_inside(p, outer_bottom_right, outer_bottom_left, inner_bottom_left, inner_bottom_right)) {
gl_FragColor = bottom_border_color;
} else if (is_point_inside(p, outer_bottom_left, outer_top_left, inner_top_left, inner_bottom_left)) {
gl_FragColor = left_border_color;
} else {
// The fragment is not on a border, set fully transparent color
gl_FragColor = vec4(1.0, 1.0, 1.0, 0.0);
}
}
}
 
filename was Deleted added: 231, removed: 77, total 154
@@ -0,0 +1,200 @@
// SPDX-FileCopyrightText: 2022 Mikael Larsson <c.mikael.larsson@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
 
uniform vec2 resolution;
 
// Corner position, inner rectangle
uniform vec2 inner_top_left;
uniform vec2 inner_top_right;
uniform vec2 inner_bottom_left;
uniform vec2 inner_bottom_right;
 
// Corner position, outer rectangle
uniform vec2 outer_top_left;
uniform vec2 outer_top_right;
uniform vec2 outer_bottom_left;
uniform vec2 outer_bottom_right;
 
// Corner radii of outer rectangle
uniform vec2 top_left_radii;
uniform vec2 top_right_radii;
uniform vec2 bottom_left_radii;
uniform vec2 bottom_right_radii;
 
// Colors
uniform vec4 left_border_color;
uniform vec4 right_border_color;
uniform vec4 top_border_color;
uniform vec4 bottom_border_color;
uniform vec4 inner_rect_color;
 
// Gets the position of the fragment in screen coordinates
vec2 get_frag_pos() {
return vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y);
}
 
vec2 top_left_inward_pos(vec2 origin, vec2 offset) {
return vec2(origin.x + offset.x, origin.y + offset.y);
}
 
vec2 top_right_inward_pos(vec2 origin, vec2 offset) {
return vec2(origin.x - offset.x, origin.y + offset.y);
}
 
vec2 bottom_left_inward_pos(vec2 origin, vec2 offset) {
return vec2(origin.x + offset.x, origin.y - offset.y);
}
 
vec2 bottom_right_inward_pos(vec2 origin, vec2 offset) {
return vec2(origin.x - offset.x, origin.y - offset.y);
}
 
bool is_point_inside_quadrilateral(vec2 p2, vec2 a2, vec2 b2, vec2 c2, vec2 d2) {
vec3 p = vec3(p2.xy, 0.0);
vec3 a = vec3(a2.xy, 0.0);
vec3 b = vec3(b2.xy, 0.0);
vec3 c = vec3(c2.xy, 0.0);
vec3 d = vec3(d2.xy, 0.0);
return dot(cross(p - a, b - a), cross(p - d, c - d)) <= 0.0 &&
dot(cross(p - a, d - a), cross(p - b, c - b)) <= 0.0;
}
 
bool is_point_inside_ellipse(vec2 p, vec2 origin, vec2 radii) {
return (pow(p.x - origin.x, 2.0) / pow(radii.x, 2.0) + pow(p.y - origin.y, 2.0) / pow(radii.y, 2.0)) <= 1.0;
}
 
bool is_inside_rounded_rect(vec2 p,
vec2 top_left_pos,
vec2 top_right_pos,
vec2 bottom_left_pos,
vec2 bottom_right_pos,
vec2 top_left_radii,
vec2 top_right_radii,
vec2 bottom_left_radii,
vec2 bottom_right_radii) {
if (is_point_inside_quadrilateral(p,
top_left_pos,
top_right_pos,
bottom_right_pos,
bottom_left_pos)) {
if (top_left_radii.x > 0.0 || top_left_radii.y > 0.0) {
vec2 c = top_left_inward_pos(top_left_pos, top_left_radii);
if (p.x <= c.x && p.y <= c.y && !is_point_inside_ellipse(p, c, top_left_radii)) {
return false;
}
}
if (top_right_radii.x > 0.0 || top_right_radii.y > 0.0) {
vec2 c = top_right_inward_pos(top_right_pos, top_right_radii);
if (p.x >= c.x && p.y <= c.y && !is_point_inside_ellipse(p, c, top_right_radii)) {
return false;
}
}
if (bottom_left_radii.x > 0.0 || bottom_left_radii.y > 0.0) {
vec2 c = bottom_left_inward_pos(bottom_left_pos, bottom_left_radii);
if (p.x <= c.x && p.y >= c.y && !is_point_inside_ellipse(p, c, bottom_left_radii)) {
return false;
}
}
if (bottom_right_radii.x > 0.0 || bottom_right_radii.y > 0.0) {
vec2 c = bottom_right_inward_pos(bottom_right_pos, bottom_right_radii);
if (p.x >= c.x && p.y >= c.y && !is_point_inside_ellipse(p, c, bottom_right_radii)) {
return false;
}
}
return true;
}
return false;
}
 
vec2 max_radius(vec2 r) {
float lr = max(r.x, r.y);
return vec2(lr, lr);
}
 
void main() {
vec4 no_color = vec4(1.0, 1.0, 1.0, 0.0);
vec2 p = get_frag_pos();
 
// Calculate radius for all corners of the inner rectangle
vec2 inner_top_left_radii = vec2(max(top_left_radii.x - (inner_top_left.x - outer_top_left.x), 0.0),
max(top_left_radii.y - (inner_top_left.y - outer_top_left.y), 0.0));
vec2 inner_top_right_radii = vec2(max(top_right_radii.x - (outer_top_right.x - inner_top_right.x), 0.0),
max(top_right_radii.y - (inner_top_right.y - outer_top_right.y), 0.0));
vec2 inner_bottom_left_radii = vec2(max(bottom_left_radii.x - (inner_bottom_left.x - outer_bottom_left.x), 0.0),
max(bottom_left_radii.y - (outer_bottom_left.y - inner_bottom_left.y), 0.0));
vec2 inner_bottom_right_radii = vec2(max(bottom_right_radii.x - (outer_bottom_right.x - inner_bottom_right.x), 0.0),
max(bottom_right_radii.y - (outer_bottom_right.y - inner_bottom_right.y), 0.0));
 
// Check if the point is inside the inner rectangle
if (is_inside_rounded_rect(p,
inner_top_left,
inner_top_right,
inner_bottom_left,
inner_bottom_right,
inner_top_left_radii,
inner_top_right_radii,
inner_bottom_left_radii,
inner_bottom_right_radii)) {
gl_FragColor = inner_rect_color;
// Otherwise, check if the point is inside the outer rectangle
} else if (is_inside_rounded_rect(p,
outer_top_left,
outer_top_right,
outer_bottom_left,
outer_bottom_right,
top_left_radii,
top_right_radii,
bottom_left_radii,
bottom_right_radii)) {
// Extract the largest inner radius for each corner
vec2 max_inner_top_left_radii = max_radius(inner_top_left_radii);
vec2 max_inner_top_right_radii = max_radius(inner_top_right_radii);
vec2 max_inner_bottom_left_radii = max_radius(inner_bottom_left_radii);
vec2 max_inner_bottom_right_radii = max_radius(inner_bottom_right_radii);
 
// Adjust position for corners of inner rectangle with respect to the max radius
// This is needed to get a good transition between colors
vec2 inner_top_left_inward = top_left_inward_pos(inner_top_left, max_inner_bottom_left_radii);
vec2 inner_top_right_inward = top_right_inward_pos(inner_top_right, max_inner_top_right_radii);
vec2 inner_bottom_left_inward = bottom_left_inward_pos(inner_bottom_left, max_inner_bottom_left_radii);
vec2 inner_bottom_right_inward = bottom_right_inward_pos(inner_bottom_right, max_inner_bottom_right_radii);
 
// Check if point is on the top border
if (is_point_inside_quadrilateral(p,
outer_top_left,
outer_top_right,
inner_top_right_inward,
inner_top_left_inward)) {
gl_FragColor = top_border_color;
// Otherwise, check if point is on the right border
} else if (is_point_inside_quadrilateral(p,
outer_top_right,
outer_bottom_right,
inner_bottom_right_inward,
inner_top_right_inward)) {
gl_FragColor = right_border_color;
// Otherwise, check if point is on the bottom border
} else if (is_point_inside_quadrilateral(p,
outer_bottom_right,
outer_bottom_left,
inner_bottom_left_inward,
inner_bottom_right_inward)) {
gl_FragColor = bottom_border_color;
// Otherwise, check if point is on the left border
} else if (is_point_inside_quadrilateral(p,
outer_bottom_left,
outer_top_left,
inner_top_left_inward,
inner_bottom_left_inward)) {
gl_FragColor = left_border_color;
// Otherwise, set transparent color
} else {
gl_FragColor = no_color;
}
}
// Otherwise, set transparent color
else {
gl_FragColor = no_color;
}
}
 
gfx/sfml_canvas.cpp added: 231, removed: 77, total 154
@@ -25,7 +25,7 @@ namespace gfx {
namespace {
 
#include "gfx/basic_vertex_shader.h"
#include "gfx/border_fragment_shader.h"
#include "gfx/rect_fragment_shader.h"
 
std::filesystem::recursive_directory_iterator get_font_dir_iterator(std::filesystem::path const &path) {
std::error_code errc;
@@ -62,15 +62,15 @@ std::optional<std::string> find_path_to_font(std::string_view font_filename) {
return std::nullopt;
}
 
sf::Glsl::Vec3 to_vec3(int x, int y) {
return sf::Glsl::Vec3(static_cast<float>(x), static_cast<float>(y), 0.0);
sf::Glsl::Vec2 to_vec2(int x, int y) {
return {static_cast<float>(x), static_cast<float>(y)};
}
 
sf::Glsl::Vec4 to_vec4(Color const &color) {
return sf::Glsl::Vec4(static_cast<float>(color.r) / 0xFF,
return {static_cast<float>(color.r) / 0xFF,
static_cast<float>(color.g) / 0xFF,
static_cast<float>(color.b) / 0xFF,
static_cast<float>(color.a) / 0xFF);
static_cast<float>(color.a) / 0xFF};
}
 
} // namespace
@@ -78,7 +78,7 @@ sf::Glsl::Vec4 to_vec4(Color const &color) {
SfmlCanvas::SfmlCanvas(sf::RenderTarget &target) : target_{target} {
border_shader_.loadFromMemory(
std::string{reinterpret_cast<char const *>(gfx_basic_shader_vert), gfx_basic_shader_vert_len},
std::string{reinterpret_cast<char const *>(gfx_border_shader_frag), gfx_border_shader_frag_len});
std::string{reinterpret_cast<char const *>(gfx_rect_shader_frag), gfx_rect_shader_frag_len});
}
 
void SfmlCanvas::set_viewport_size(int width, int height) {
@@ -99,31 +99,35 @@ void SfmlCanvas::fill_rect(geom::Rect const &rect, Color color) {
void SfmlCanvas::draw_rect(geom::Rect const &rect, Color const &color, Borders const &borders) {
auto translated{rect.translated(tx_, ty_)};
auto inner_rect{translated.scaled(scale_)};
 
fill_rect(rect, color);
 
auto outer_rect =
inner_rect.expanded({borders.left.size, borders.right.size, borders.top.size, borders.bottom.size});
auto outer_rect{
inner_rect.expanded({borders.left.size, borders.right.size, borders.top.size, borders.bottom.size})};
 
sf::RectangleShape drawable{{static_cast<float>(outer_rect.width), static_cast<float>(outer_rect.height)}};
drawable.setPosition(static_cast<float>(outer_rect.x), static_cast<float>(outer_rect.y));
 
border_shader_.setUniform("resolution", target_.getView().getSize());
 
border_shader_.setUniform("inner_top_left", to_vec3(inner_rect.left(), inner_rect.top()));
border_shader_.setUniform("inner_top_right", to_vec3(inner_rect.right(), inner_rect.top()));
border_shader_.setUniform("inner_bottom_left", to_vec3(inner_rect.left(), inner_rect.bottom()));
border_shader_.setUniform("inner_bottom_right", to_vec3(inner_rect.right(), inner_rect.bottom()));
border_shader_.setUniform("inner_top_left", to_vec2(inner_rect.left(), inner_rect.top()));
border_shader_.setUniform("inner_top_right", to_vec2(inner_rect.right(), inner_rect.top()));
border_shader_.setUniform("inner_bottom_left", to_vec2(inner_rect.left(), inner_rect.bottom()));
border_shader_.setUniform("inner_bottom_right", to_vec2(inner_rect.right(), inner_rect.bottom()));
 
border_shader_.setUniform("outer_top_left", to_vec3(outer_rect.left(), outer_rect.top()));
border_shader_.setUniform("outer_top_right", to_vec3(outer_rect.right(), outer_rect.top()));
border_shader_.setUniform("outer_bottom_left", to_vec3(outer_rect.left(), outer_rect.bottom()));
border_shader_.setUniform("outer_bottom_right", to_vec3(outer_rect.right(), outer_rect.bottom()));
border_shader_.setUniform("outer_top_left", to_vec2(outer_rect.left(), outer_rect.top()));
border_shader_.setUniform("outer_top_right", to_vec2(outer_rect.right(), outer_rect.top()));
border_shader_.setUniform("outer_bottom_left", to_vec2(outer_rect.left(), outer_rect.bottom()));
border_shader_.setUniform("outer_bottom_right", to_vec2(outer_rect.right(), outer_rect.bottom()));
 
// TODO(mkiael): Set radii when support for parsing it from stylesheet has been added
border_shader_.setUniform("top_left_radii", to_vec2(0, 0));
border_shader_.setUniform("top_right_radii", to_vec2(0, 0));
border_shader_.setUniform("bottom_left_radii", to_vec2(0, 0));
border_shader_.setUniform("bottom_right_radii", to_vec2(0, 0));
 
border_shader_.setUniform("left_border_color", to_vec4(borders.left.color));
border_shader_.setUniform("right_border_color", to_vec4(borders.right.color));
border_shader_.setUniform("top_border_color", to_vec4(borders.top.color));
border_shader_.setUniform("bottom_border_color", to_vec4(borders.bottom.color));
border_shader_.setUniform("inner_rect_color", to_vec4(color));
 
target_.draw(drawable, &border_shader_);
}