Browse Source

attempt to use polyline2d to draw lines with gsk gl shader

polyline2d
parent
commit
ed05e6fbbf
Signed by: alex
GPG Key ID: 022EAE42313D70F3
  1. 1
      ext/meson.build
  2. 26
      ext/polyline2d/LICENSE.md
  3. 82
      ext/polyline2d/LineSegment.h
  4. 443
      ext/polyline2d/Polyline2D.h
  5. 50
      ext/polyline2d/README.md
  6. 99
      ext/polyline2d/Vec2.h
  7. 24
      ext/polyline2d/meson.build
  8. 20
      ext/polyline2d/polyline2d_c.cpp
  9. 37
      ext/polyline2d/polyline2d_c.h
  10. 26
      inc/gui/widgets/arranger.h
  11. 19
      resources/gl/shaders/passthrough.frag
  12. 10
      resources/gl/shaders/passthrough.vert
  13. 5
      scripts/gen-gtk-resources-xml.scm
  14. 3
      src/gui/widgets/arranger.c
  15. 47
      src/gui/widgets/automation_point.c

1
ext/meson.build vendored

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
subdir ('midilib')
subdir ('nanovg')
subdir ('polyline2d')
subdir ('qm-dsp')
subdir ('weakjack')
subdir ('whereami')

26
ext/polyline2d/LICENSE.md vendored

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
The MIT License (MIT)
=====================
Copyright © 2019 Marius Metzger (CrushedPixel)
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

82
ext/polyline2d/LineSegment.h vendored

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
#pragma once
#include "Vec2.h"
#include <optional>
namespace crushedpixel {
template<typename Vec2>
struct LineSegment {
LineSegment(const Vec2 &a, const Vec2 &b) :
a(a), b(b) {}
Vec2 a, b;
/**
* @return A copy of the line segment, offset by the given vector.
*/
LineSegment operator+(const Vec2 &toAdd) const {
return {Vec2Maths::add(a, toAdd), Vec2Maths::add(b, toAdd)};
}
/**
* @return A copy of the line segment, offset by the given vector.
*/
LineSegment operator-(const Vec2 &toRemove) const {
return {Vec2Maths::subtract(a, toRemove), Vec2Maths::subtract(b, toRemove)};
}
/**
* @return The line segment's normal vector.
*/
Vec2 normal() const {
auto dir = direction();
// return the direction vector
// rotated by 90 degrees counter-clockwise
return {-dir.y, dir.x};
}
/**
* @return The line segment's direction vector.
*/
Vec2 direction(bool normalized = true) const {
auto vec = Vec2Maths::subtract(b, a);
return normalized
? Vec2Maths::normalized(vec)
: vec;
}
static std::optional<Vec2> intersection(const LineSegment &a, const LineSegment &b, bool infiniteLines) {
// calculate un-normalized direction vectors
auto r = a.direction(false);
auto s = b.direction(false);
auto originDist = Vec2Maths::subtract(b.a, a.a);
auto uNumerator = Vec2Maths::cross(originDist, r);
auto denominator = Vec2Maths::cross(r, s);
if (std::abs(denominator) < 0.0001f) {
// The lines are parallel
return std::nullopt;
}
// solve the intersection positions
auto u = uNumerator / denominator;
auto t = Vec2Maths::cross(originDist, s) / denominator;
if (!infiniteLines && (t < 0 || t > 1 || u < 0 || u > 1)) {
// the intersection lies outside of the line segments
return std::nullopt;
}
// calculate the intersection point
// a.a + r * t;
return Vec2Maths::add(a.a, Vec2Maths::multiply(r, t));
}
};
} // namespace crushedpixel

443
ext/polyline2d/Polyline2D.h vendored

@ -0,0 +1,443 @@ @@ -0,0 +1,443 @@
#pragma once
#include "LineSegment.h"
#include <vector>
#include <iterator>
#include <cassert>
namespace crushedpixel {
class Polyline2D {
public:
enum class JointStyle {
/**
* Corners are drawn with sharp joints.
* If the joint's outer angle is too large,
* the joint is drawn as beveled instead,
* to avoid the miter extending too far out.
*/
MITER,
/**
* Corners are flattened.
*/
BEVEL,
/**
* Corners are rounded off.
*/
ROUND
};
enum class EndCapStyle {
/**
* Path ends are drawn flat,
* and don't exceed the actual end point.
*/
BUTT, // lol
/**
* Path ends are drawn flat,
* but extended beyond the end point
* by half the line thickness.
*/
SQUARE,
/**
* Path ends are rounded off.
*/
ROUND,
/**
* Path ends are connected according to the JointStyle.
* When using this EndCapStyle, don't specify the common start/end point twice,
* as Polyline2D connects the first and last input point itself.
*/
JOINT
};
/**
* Creates a vector of vertices describing a solid path through the input points.
* @param points The points of the path.
* @param thickness The path's thickness.
* @param jointStyle The path's joint style.
* @param endCapStyle The path's end cap style.
* @param allowOverlap Whether to allow overlapping vertices.
* This yields better results when dealing with paths
* whose points have a distance smaller than the thickness,
* but may introduce overlapping vertices,
* which is undesirable when rendering transparent paths.
* @return The vertices describing the path.
* @tparam Vec2 The vector type to use for the vertices.
* Must have public non-const float fields "x" and "y".
* Must have a two-args constructor taking x and y values.
* See crushedpixel::Vec2 for a type that satisfies these requirements.
* @tparam InputCollection The collection type of the input points.
* Must contain elements of type Vec2.
* Must expose size() and operator[] functions.
*/
template<typename Vec2, typename InputCollection>
static std::vector<Vec2> create(const InputCollection &points, float thickness,
JointStyle jointStyle = JointStyle::MITER,
EndCapStyle endCapStyle = EndCapStyle::BUTT,
bool allowOverlap = false) {
std::vector<Vec2> vertices;
create(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap);
return vertices;
}
template<typename Vec2>
static std::vector<Vec2> create(const std::vector<Vec2> &points, float thickness,
JointStyle jointStyle = JointStyle::MITER,
EndCapStyle endCapStyle = EndCapStyle::BUTT,
bool allowOverlap = false) {
std::vector<Vec2> vertices;
create<Vec2, std::vector<Vec2>>(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap);
return vertices;
}
template<typename Vec2, typename InputCollection>
static size_t create(std::vector<Vec2> &vertices, const InputCollection &points, float thickness,
JointStyle jointStyle = JointStyle::MITER,
EndCapStyle endCapStyle = EndCapStyle::BUTT,
bool allowOverlap = false) {
auto numVerticesBefore = vertices.size();
create<Vec2, InputCollection>(std::back_inserter(vertices), points, thickness,
jointStyle, endCapStyle, allowOverlap);
return vertices.size() - numVerticesBefore;
}
template<typename Vec2, typename InputCollection, typename OutputIterator>
static OutputIterator create(OutputIterator vertices, const InputCollection &points, float thickness,
JointStyle jointStyle = JointStyle::MITER,
EndCapStyle endCapStyle = EndCapStyle::BUTT,
bool allowOverlap = false) {
// operate on half the thickness to make our lives easier
thickness /= 2;
// create poly segments from the points
std::vector<PolySegment<Vec2>> segments;
for (size_t i = 0; i + 1 < points.size(); i++) {
auto &point1 = points[i];
auto &point2 = points[i + 1];
// to avoid division-by-zero errors,
// only create a line segment for non-identical points
if (!Vec2Maths::equal(point1, point2)) {
segments.emplace_back(LineSegment<Vec2>(point1, point2), thickness);
}
}
if (endCapStyle == EndCapStyle::JOINT) {
// create a connecting segment from the last to the first point
auto &point1 = points[points.size() - 1];
auto &point2 = points[0];
// to avoid division-by-zero errors,
// only create a line segment for non-identical points
if (!Vec2Maths::equal(point1, point2)) {
segments.emplace_back(LineSegment<Vec2>(point1, point2), thickness);
}
}
if (segments.empty()) {
// handle the case of insufficient input points
return vertices;
}
Vec2 nextStart1{0, 0};
Vec2 nextStart2{0, 0};
Vec2 start1{0, 0};
Vec2 start2{0, 0};
Vec2 end1{0, 0};
Vec2 end2{0, 0};
// calculate the path's global start and end points
auto &firstSegment = segments[0];
auto &lastSegment = segments[segments.size() - 1];
auto pathStart1 = firstSegment.edge1.a;
auto pathStart2 = firstSegment.edge2.a;
auto pathEnd1 = lastSegment.edge1.b;
auto pathEnd2 = lastSegment.edge2.b;
// handle different end cap styles
if (endCapStyle == EndCapStyle::SQUARE) {
// extend the start/end points by half the thickness
pathStart1 = Vec2Maths::subtract(pathStart1, Vec2Maths::multiply(firstSegment.edge1.direction(), thickness));
pathStart2 = Vec2Maths::subtract(pathStart2, Vec2Maths::multiply(firstSegment.edge2.direction(), thickness));
pathEnd1 = Vec2Maths::add(pathEnd1, Vec2Maths::multiply(lastSegment.edge1.direction(), thickness));
pathEnd2 = Vec2Maths::add(pathEnd2, Vec2Maths::multiply(lastSegment.edge2.direction(), thickness));
} else if (endCapStyle == EndCapStyle::ROUND) {
// draw half circle end caps
createTriangleFan(vertices, firstSegment.center.a, firstSegment.center.a,
firstSegment.edge1.a, firstSegment.edge2.a, false);
createTriangleFan(vertices, lastSegment.center.b, lastSegment.center.b,
lastSegment.edge1.b, lastSegment.edge2.b, true);
} else if (endCapStyle == EndCapStyle::JOINT) {
// join the last (connecting) segment and the first segment
createJoint(vertices, lastSegment, firstSegment, jointStyle,
pathEnd1, pathEnd2, pathStart1, pathStart2, allowOverlap);
}
// generate mesh data for path segments
for (size_t i = 0; i < segments.size(); i++) {
auto &segment = segments[i];
// calculate start
if (i == 0) {
// this is the first segment
start1 = pathStart1;
start2 = pathStart2;
}
if (i + 1 == segments.size()) {
// this is the last segment
end1 = pathEnd1;
end2 = pathEnd2;
} else {
createJoint(vertices, segment, segments[i + 1], jointStyle,
end1, end2, nextStart1, nextStart2, allowOverlap);
}
// emit vertices
*vertices++ = start1;
*vertices++ = start2;
*vertices++ = end1;
*vertices++ = end1;
*vertices++ = start2;
*vertices++ = end2;
start1 = nextStart1;
start2 = nextStart2;
}
return vertices;
}
private:
static constexpr float pi = 3.14159265358979323846f;
/**
* The threshold for mitered joints.
* If the joint's angle is smaller than this angle,
* the joint will be drawn beveled instead.
*/
static constexpr float miterMinAngle = 0.349066; // ~20 degrees
/**
* The minimum angle of a round joint's triangles.
*/
static constexpr float roundMinAngle = 0.174533; // ~10 degrees
template<typename Vec2>
struct PolySegment {
PolySegment(const LineSegment<Vec2> &center, float thickness) :
center(center),
// calculate the segment's outer edges by offsetting
// the central line by the normal vector
// multiplied with the thickness
// center + center.normal() * thickness
edge1(center + Vec2Maths::multiply(center.normal(), thickness)),
edge2(center - Vec2Maths::multiply(center.normal(), thickness)) {}
LineSegment<Vec2> center, edge1, edge2;
};
template<typename Vec2, typename OutputIterator>
static OutputIterator createJoint(OutputIterator vertices,
const PolySegment<Vec2> &segment1, const PolySegment<Vec2> &segment2,
JointStyle jointStyle, Vec2 &end1, Vec2 &end2,
Vec2 &nextStart1, Vec2 &nextStart2,
bool allowOverlap) {
// calculate the angle between the two line segments
auto dir1 = segment1.center.direction();
auto dir2 = segment2.center.direction();
auto angle = Vec2Maths::angle(dir1, dir2);
// wrap the angle around the 180° mark if it exceeds 90°
// for minimum angle detection
auto wrappedAngle = angle;
if (wrappedAngle > pi / 2) {
wrappedAngle = pi - wrappedAngle;
}
if (jointStyle == JointStyle::MITER && wrappedAngle < miterMinAngle) {
// the minimum angle for mitered joints wasn't exceeded.
// to avoid the intersection point being extremely far out,
// thus producing an enormous joint like a rasta on 4/20,
// we render the joint beveled instead.
jointStyle = JointStyle::BEVEL;
}
if (jointStyle == JointStyle::MITER) {
// calculate each edge's intersection point
// with the next segment's central line
auto sec1 = LineSegment<Vec2>::intersection(segment1.edge1, segment2.edge1, true);
auto sec2 = LineSegment<Vec2>::intersection(segment1.edge2, segment2.edge2, true);
end1 = sec1 ? *sec1 : segment1.edge1.b;
end2 = sec2 ? *sec2 : segment1.edge2.b;
nextStart1 = end1;
nextStart2 = end2;
} else {
// joint style is either BEVEL or ROUND
// find out which are the inner edges for this joint
auto x1 = dir1.x;
auto x2 = dir2.x;
auto y1 = dir1.y;
auto y2 = dir2.y;
auto clockwise = x1 * y2 - x2 * y1 < 0;
const LineSegment<Vec2> *inner1, *inner2, *outer1, *outer2;
// as the normal vector is rotated counter-clockwise,
// the first edge lies to the left
// from the central line's perspective,
// and the second one to the right.
if (clockwise) {
outer1 = &segment1.edge1;
outer2 = &segment2.edge1;
inner1 = &segment1.edge2;
inner2 = &segment2.edge2;
} else {
outer1 = &segment1.edge2;
outer2 = &segment2.edge2;
inner1 = &segment1.edge1;
inner2 = &segment2.edge1;
}
// calculate the intersection point of the inner edges
auto innerSecOpt = LineSegment<Vec2>::intersection(*inner1, *inner2, allowOverlap);
auto innerSec = innerSecOpt
? *innerSecOpt
// for parallel lines, simply connect them directly
: inner1->b;
// if there's no inner intersection, flip
// the next start position for near-180° turns
Vec2 innerStart;
if (innerSecOpt) {
innerStart = innerSec;
} else if (angle > pi / 2) {
innerStart = outer1->b;
} else {
innerStart = inner1->b;
}
if (clockwise) {
end1 = outer1->b;
end2 = innerSec;
nextStart1 = outer2->a;
nextStart2 = innerStart;
} else {
end1 = innerSec;
end2 = outer1->b;
nextStart1 = innerStart;
nextStart2 = outer2->a;
}
// connect the intersection points according to the joint style
if (jointStyle == JointStyle::BEVEL) {
// simply connect the intersection points
*vertices++ = outer1->b;
*vertices++ = outer2->a;
*vertices++ = innerSec;
} else if (jointStyle == JointStyle::ROUND) {
// draw a circle between the ends of the outer edges,
// centered at the actual point
// with half the line thickness as the radius
createTriangleFan(vertices, innerSec, segment1.center.b, outer1->b, outer2->a, clockwise);
} else {
assert(false);
}
}
return vertices;
}
/**
* Creates a partial circle between two points.
* The points must be equally far away from the origin.
* @param vertices The vector to add vertices to.
* @param connectTo The position to connect the triangles to.
* @param origin The circle's origin.
* @param start The circle's starting point.
* @param end The circle's ending point.
* @param clockwise Whether the circle's rotation is clockwise.
*/
template<typename Vec2, typename OutputIterator>
static OutputIterator createTriangleFan(OutputIterator vertices, Vec2 connectTo, Vec2 origin,
Vec2 start, Vec2 end, bool clockwise) {
auto point1 = Vec2Maths::subtract(start, origin);
auto point2 = Vec2Maths::subtract(end, origin);
// calculate the angle between the two points
auto angle1 = atan2(point1.y, point1.x);
auto angle2 = atan2(point2.y, point2.x);
// ensure the outer angle is calculated
if (clockwise) {
if (angle2 > angle1) {
angle2 = angle2 - 2 * pi;
}
} else {
if (angle1 > angle2) {
angle1 = angle1 - 2 * pi;
}
}
auto jointAngle = angle2 - angle1;
// calculate the amount of triangles to use for the joint
auto numTriangles = std::max(1, (int) std::floor(std::abs(jointAngle) / roundMinAngle));
// calculate the angle of each triangle
auto triAngle = jointAngle / numTriangles;
Vec2 startPoint = start;
Vec2 endPoint;
for (int t = 0; t < numTriangles; t++) {
if (t + 1 == numTriangles) {
// it's the last triangle - ensure it perfectly
// connects to the next line
endPoint = end;
} else {
auto rot = (t + 1) * triAngle;
// rotate the original point around the origin
endPoint.x = std::cos(rot) * point1.x - std::sin(rot) * point1.y;
endPoint.y = std::sin(rot) * point1.x + std::cos(rot) * point1.y;
// re-add the rotation origin to the target point
endPoint = Vec2Maths::add(endPoint, origin);
}
// emit the triangle
*vertices++ = startPoint;
*vertices++ = endPoint;
*vertices++ = connectTo;
startPoint = endPoint;
}
return vertices;
}
};
} // namespace crushedpixel

50
ext/polyline2d/README.md vendored

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
# Polyline2D
*Polyline2D* is a header-only C++17 library that generates a triangle mesh from a list of points.
It can be used to draw thick lines with rendering APIs like *OpenGL*, which do not support line drawing out of the box.
It supports common joint and end cap styles.
## Example usage
```c++
#include <Polyline2D/Polyline2D.h>
using namespace crushedpixel;
std::vector<Vec2> points{
{ -0.25f, -0.5f },
{ -0.25f, 0.5f },
{ 0.25f, 0.25f },
{ 0.0f, 0.0f },
{ 0.25f, -0.25f },
{ -0.4f, -0.25f }
};
auto thickness = 0.1f;
auto vertices = Polyline2D::create(points, thickness,
Polyline2D::JointStyle::ROUND,
Polyline2D::EndCapStyle::SQUARE);
// render vertices, for example using OpenGL...
```
This code results in the following mesh:
![Result](https://i.imgur.com/D0lvyYT.png)
For demonstration purposes, the generated mesh is once rendered in wireframe mode (light green), and once in fill mode (transparent green).
The red points show the input points.
There is *no overdraw* within segments, only lines that overlap are filled twice.
For an example application using this software, visit [Polyline2DExample](https://github.com/CrushedPixel/Polyline2DExample).
## Installation
### Manual
To use *Polyline2D*, simply clone this repository and include the file `Polyline2D.h` from the `include` directory.
### Using CMake
To install the header files into your global header directory, you can use CMake:
```
mkdir build
cd build
cmake ..
make install
```
You can then include the header file using `#include <Polyline2D/Polyline2D.h>`.

99
ext/polyline2d/Vec2.h vendored

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
#pragma once
#include <cmath>
namespace crushedpixel {
/**
* A two-dimensional float vector.
* It exposes the x and y fields
* as required by the Polyline2D functions.
*/
struct Vec2 {
Vec2() :
Vec2(0, 0) {}
Vec2(float x, float y) :
x(x), y(y) {}
virtual ~Vec2() = default;
float x, y;
};
namespace Vec2Maths {
template<typename Vec2>
static bool equal(const Vec2 &a, const Vec2 &b) {
return a.x == b.x && a.y == b.y;
}
template<typename Vec2>
static Vec2 multiply(const Vec2 &a, const Vec2 &b) {
return {a.x * b.x, a.y * b.y};
}
template<typename Vec2>
static Vec2 multiply(const Vec2 &vec, float factor) {
return {vec.x * factor, vec.y * factor};
}
template<typename Vec2>
static Vec2 divide(const Vec2 &vec, float factor) {
return {vec.x / factor, vec.y / factor};
}
template<typename Vec2>
static Vec2 add(const Vec2 &a, const Vec2 &b) {
return {a.x + b.x, a.y + b.y};
}
template<typename Vec2>
static Vec2 subtract(const Vec2 &a, const Vec2 &b) {
return {a.x - b.x, a.y - b.y};
}
template<typename Vec2>
static float magnitude(const Vec2 &vec) {
return std::sqrt(vec.x * vec.x + vec.y * vec.y);
}
template<typename Vec2>
static Vec2 withLength(const Vec2 &vec, float len) {
auto mag = magnitude(vec);
auto factor = mag / len;
return divide(vec, factor);
}
template<typename Vec2>
static Vec2 normalized(const Vec2 &vec) {
return withLength(vec, 1);
}
/**
* Calculates the dot product of two vectors.
*/
template<typename Vec2>
static float dot(const Vec2 &a, const Vec2 &b) {
return a.x * b.x + a.y * b.y;
}
/**
* Calculates the cross product of two vectors.
*/
template<typename Vec2>
static float cross(const Vec2 &a, const Vec2 &b) {
return a.x * b.y - a.y * b.x;
}
/**
* Calculates the angle between two vectors.
*/
template<typename Vec2>
static float angle(const Vec2 &a, const Vec2 &b) {
return std::acos(dot(a, b) / (magnitude(a) * magnitude(b)));
}
} // namespace Vec2Maths
}

24
ext/polyline2d/meson.build vendored

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
# Copyright (C) 2022 Alexandros Theodotou <alex at zrythm dot org>
#
# This file is part of Zrythm
#
# Zrythm is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Zrythm is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
#polyline2d_lib = static_library (
#'polyline2d',
#'',
#c_args: [
#extra_optimizations_cflags,
#],
#)

20
ext/polyline2d/polyline2d_c.cpp vendored

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
/*
* Copyright (C) 2022 Alexandros Theodotou <alex at zrythm dot org>
*
* This file is part of Zrythm
*
* Zrythm is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zrythm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
*/
#include "polyline2d_c.h"

37
ext/polyline2d/polyline2d_c.h vendored

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Alexandros Theodotou <alex at zrythm dot org>
*
* This file is part of Zrythm
*
* Zrythm is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zrythm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* @file
*
* Polyline2D C interface.
*/
#ifndef __POLYLINE2D_POLYLINE2D_C_H__
#define __POLYLINE2D_POLYLINE2D_C_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

26
inc/gui/widgets/arranger.h

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
* Copyright (C) 2018-2022 Alexandros Theodotou <alex at zrythm dot org>
*
* This file is part of Zrythm
*
@ -423,28 +423,10 @@ typedef struct _ArrangerWidget @@ -423,28 +423,10 @@ typedef struct _ArrangerWidget
*/
PangoLayout * audio_layout;
#if 0
/**
* Dummy cairo surface to create new surfaces from.
*/
cairo_surface_t * dummy_surface;
/** Automation point shader. */
GskGLShader * ap_shader;
/**
* Thread pool for drawing in the background
* instead of in the UI thread.
*
* The result will be applied during draw in the
* UI thread.
*/
GThreadPool * draw_thread_pool;
/**
* Object pool for ArrangerDrawTaskData.
*
* Must only be accessed from the GTK thread.
*/
ObjectPool * draw_task_obj_pool;
#endif
bool ap_shader_compiled;
/**
* Cached playhead x to draw.

19
resources/gl/shaders/passthrough.frag

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
uniform vec4 color;
uniform float yvals[20];
void
mainImage (out vec4 fragColor,
in vec2 fragCoord,
in vec2 resolution,
in vec2 uv)
{
if (yvals[int (ceil (fragCoord.x))] == fragCoord.y
|| yvals[int (floor (fragCoord.x))] == fragCoord.y)
{
fragColor = color;
}
else
{
fragColor = vec4 (0, 0, 0, 0);
}
}

10
resources/gl/shaders/passthrough.vert

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
#version 150
// this shader simply passes the input vertices
// to the fragment shader.
in vec2 posIn;
void main() {
gl_Position = vec4(posIn.x, posIn.y, 0, 1.0);
}

5
scripts/gen-gtk-resources-xml.scm

@ -118,7 +118,10 @@ Args: @@ -118,7 +118,10 @@ Args:
(list resources-dir
gl-shaders-dir))
(lambda (f)
(string-suffix? ".glsl" f)))))
(or
(string-suffix? ".vert" f)
(string-suffix? ".frag" f)
(string-suffix? ".glsl" f))))))
;; Print UI files
(for-each

3
src/gui/widgets/arranger.c

@ -6769,6 +6769,9 @@ arranger_widget_setup ( @@ -6769,6 +6769,9 @@ arranger_widget_setup (
z_cairo_create_pango_layout_from_string (
GTK_WIDGET (self), "8",
PANGO_ELLIPSIZE_NONE, 0);
self->ap_shader =
gsk_gl_shader_new_from_resource (
"/org/zrythm/Zrythm/app/gl/shaders/passthrough.frag");
break;
case TYPE (MIDI_MODIFIER):
gtk_widget_add_css_class (

47
src/gui/widgets/automation_point.c

@ -74,6 +74,53 @@ automation_point_draw ( @@ -74,6 +74,53 @@ automation_point_draw (
automation_point_is_selected (ap), false,
false);
/* compile the shader */
if (arranger->ap_shader &&
!arranger->ap_shader_compiled)
{
GtkNative * native =
gtk_widget_get_native (
GTK_WIDGET (arranger));
GskRenderer * renderer =
gtk_native_get_renderer (native);
GError * err = NULL;
bool ret =
gsk_gl_shader_compile (
arranger->ap_shader, renderer,
&err);
if (!ret)
{
g_warning (
"failed to compile shader: %s",
err->message);
return;
}
arranger->ap_shader_compiled = true;
}
graphene_vec4_t color_vec4;
graphene_vec4_init (
&color_vec4, color.red, color.green,
color.blue, color.alpha);
float yvals[20] = {
1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 8.f, 9.f, 10.f,
11.f, 12.f, 13.f, 14.f, 15.f, 16.f, 18.f, 19.f, 20.f };
gtk_snapshot_push_gl_shader (
snapshot, arranger->ap_shader,
&GRAPHENE_RECT_INIT (
obj->full_rect.x - 1, obj->full_rect.y - 1,
obj->full_rect.width + 2,
obj->full_rect.height + 2),
gsk_gl_shader_format_args (
arranger->ap_shader,
"color", &color_vec4,
"yvals", yvals,
NULL));
gtk_snapshot_pop (snapshot);
return;
cairo_t * cr =
gtk_snapshot_append_cairo (
snapshot,

Loading…
Cancel
Save