Compare commits

..

No commits in common. "main" and "master" have entirely different histories.
main ... master

8 changed files with 88 additions and 194 deletions

View file

@ -1,21 +1,13 @@
.PHONY: default
default: dashing dashing.omp
default: dashing
.PHONY: bench
bench: dashing
perf stat --log-fd=2 ./dashing -b -s .1 data/HWOOD6E1.pat data/sf.seg
dashing: main.cc dashing.cc dashing.hh parse_numbers.hh contours_and_segments.hh
+g++ -W -Wall -O2 -g -std=c++20 $(filter %.cc, $^) -o $@ -flto -DNDEBUG
g++ -W -Wall -O2 -g -std=c++11 $(filter %.cc, $^) -o $@ -lboost_random
dashing-noopt: main.cc dashing.cc dashing.hh parse_numbers.hh contours_and_segments.hh
+g++ -W -Wall -O0 -g -std=c++20 $(filter %.cc, $^) -o $@
g++ -W -Wall -O0 -g -std=c++11 $(filter %.cc, $^) -o $@ -lboost_random
dashing-clang: main.cc dashing.cc dashing.hh parse_numbers.hh
+clang++ -W -Wall -O2 -g -std=c++20 $(filter %.cc, $^) -o $@ -flto -DNDEBUG
dashing.pgo.0: main.cc dashing.cc dashing.hh parse_numbers.hh contours_and_segments.hh
+g++ -W -Wall -O2 -g -std=c++20 $(filter %.cc, $^) -o $@ -flto -DNDEBUG -fprofile-generate
dashing.pgo.1: dashing.pgo.0 main.cc dashing.cc dashing.hh parse_numbers.hh contours_and_segments.hh
./dashing.pgo.0 -b -s .002 data/HWOOD6E1.pat data/sf.seg
+g++ -W -Wall -O2 -g -std=c++20 $(filter %.cc, $^) -o $@ -flto -DNDEBUG -fprofile-use
dashing.omp: main.cc dashing.cc dashing.hh parse_numbers.hh contours_and_segments.hh
+g++ -W -Wall -O2 -g -std=c++20 $(filter %.cc, $^) -o $@ -flto -DNDEBUG -fopenmp -DDASHING_OMP
clang++ -W -Wall -O2 -g -std=c++11 $(filter %.cc, $^) -o $@ -lboost_random

View file

@ -2,11 +2,11 @@
License: permissive (zlib); see source files for additional details.
On a i5-1235U in multithreaded benchmark mode, it runs at over 3.3 billion dashes per second:
On a i5-3320M in benchmark mode, it runs at over 175 million dashes per second:
```
$ time ./dashing.omp -b -s .002 data/HWOOD6E1.pat data/sf.seg
2822685873
real 0m0.851s
$ time ./dashing -b -s .01 data/HWOOD6E1.pat data/sf.seg
111430525
user 0m0.616s
```
![Example image](data/sf.png)
@ -31,7 +31,7 @@ I would be interested in passing this project to a new maintainer.
The winding rule `wr` defines which regions are in the interior of the contours.
For each dash or dot in the resulting hatch, `cb` is called with the output segment.
`xyhatch(const HatchPattern&, const C &segments, Cb cb, const char *wr)`:
`xyhatch(const HatchPattern&, const C &segments, Cb cb, const char \*wr)`:
The container C holds segments which must define a set of closed
contours.
The winding rule `wr` defines which regions are in the interior of the contours.
@ -39,7 +39,7 @@ I would be interested in passing this project to a new maintainer.
`HatchPattern::FromFile`: Read a hatch pattern from a file.
`parse_numbers(std::string line)`: Read a comma and/or space-separated
`parse\_numbers(std::string line)`: Read a comma and/or space-separated
sequence of numbers into a vector
`SegmentsFromFile`, `ContoursFromFile`, `ContourToSegments`, `ContoursToSegments`: Read and convert segments and contours
@ -51,22 +51,6 @@ Useful winding rules include:
but any predicate of a single integer may be used.
### Parallel API
These APIs are available if built with `-fopenmp -DDASHING_OMP`
`xyhatch_omp(const HatchPattern&, It start, It end, Cb cb, Wr wr)`:
Iterators `start`..`end` define a range of segments, which must define a set of closed contours.
The winding rule `wr` defines which regions are in the interior of the contours.
For each dash or dot in the resulting hatch, `cb` is called with the output segment and the thread ID.
`xyhatch_omp(const HatchPattern&, const C &segments, Cb cb, Wr wr)`:
The container C holds segments which must define a set of closed
contours.
The winding rule `wr` defines which regions are in the interior of the contours.
For each dash or dot in the resulting hatch, `cb` is called with the output segment and the thread ID.
### Other APIs
Other items in the header files are implementation details.
## Demo program

View file

@ -12,7 +12,7 @@ typedef std::vector<Contour> Contours;
void ContourToSegments(Segments &dest,
/* EXPLICIT COPY */ Contour src,
F jitter = 0)
double jitter = 0)
{
if(jitter)
{
@ -32,7 +32,7 @@ void ContourToSegments(Segments &dest,
dest.emplace_back(Segment{src[i-1], src[0], false});
}
void ContoursToSegments(Segments &dest, Contours const &src, F jitter=0)
void ContoursToSegments(Segments &dest, Contours const &src, double jitter=0)
{
dest.clear();
@ -62,14 +62,14 @@ Contours ContoursFromFile(std::istream &fi)
return result;
}
Segments SegmentsFromFile(std::istream &fi, F jitter) {
Segments SegmentsFromFile(std::istream &fi, double jitter) {
auto && contours = ContoursFromFile(fi);
auto result = Segments{};
ContoursToSegments(result, contours, jitter);
return result;
}
std::vector<Segment> SegmentsFromFile(const char *filename, F jitter) {
std::vector<Segment> SegmentsFromFile(const char *filename, double jitter) {
std::fstream fi(filename);
return SegmentsFromFile(fi, jitter);
}

View file

@ -2,41 +2,37 @@
#include "dashing.hh"
namespace dashing
{
std::vector<F> parse_numbers(std::string line) {
std::vector<double> parse_numbers(std::string line) {
boost::algorithm::replace_all(line, ",", " ");
std::istringstream fi(line);
std::vector<F> result;
F d;
while((fi >> d)) {
result.push_back(d);
for (auto p = fi.peek(); p == ',' || isspace(p); p = fi.peek()) {
fi.get();
}
}
std::vector<double> result;
double d;
while((fi >> d)) result.push_back(d);
return result;
}
PSMatrix PSMatrix::inverse() const {
auto i = F(1.) / determinant();
auto i = 1. / determinant();
return PSMatrix{d*i, -b*i, -c*i, a*i, i*(c*f-e*d), i*(b*e-a*f)};
}
PSMatrix Translation(F x, F y) {
PSMatrix Translation(double x, double y) {
PSMatrix r{1.,0.,0.,1.,x,y};
return r;
}
PSMatrix Rotation(F theta) {
F c = cos(theta), s = sin(theta);
PSMatrix Rotation(double theta) {
double c = cos(theta), s = sin(theta);
PSMatrix r{c,s,-s,c,0.,0.};
return r;
}
PSMatrix XSkew(F xk) {
PSMatrix XSkew(double xk) {
PSMatrix r{1.,0.,xk,1.,0.,0.};
return r;
}
PSMatrix YScale(F ys) {
PSMatrix YScale(double ys) {
PSMatrix r{1.,0.,0.,ys,0.,0.};
return r;
}
@ -52,31 +48,21 @@ PSMatrix operator*(const PSMatrix &m1, const PSMatrix m2) {
return r;
}
static F radians(F degrees) { return degrees * acos(0) / 90.; }
static double radians(double degrees) { return degrees * acos(0) / 90.; }
Dash::Dash(F th, F x0, F y0, F dx, F dy,
const std::vector<F>::const_iterator dbegin,
const std::vector<F>::const_iterator dend)
: dash{dbegin, dend} {
Dash::Dash(double th, double x0, double y0, double dx, double dy,
const std::vector<double>::const_iterator dbegin,
const std::vector<double>::const_iterator dend) : dash(dbegin, dend) {
auto s = 0.;
for(size_t i = 0; i < dash.size(); i++) {
bool is_negative = std::signbit(dash[i]);
bool index_is_odd = (i % 2) != 0;
if(is_negative != index_is_odd) {
throw std::invalid_argument("not a supported dash specification (but probably valid)");
}
dash[i] = std::abs(dash[i]);
}
if (dash.size() % 2) dash.push_back(0);
for(auto d : dash) { sum.push_back(s); s += d; }
for(auto d : dash) { sum.push_back(s); s += fabs(d); }
sum.push_back(s);
tr = Translation(x0, y0) * Rotation(th) * XSkew(dx / dy) * YScale(dy);
tf = tr.inverse();
}
Dash Dash::FromString(const std::string &line, F scale) {
std::vector<F> words = parse_numbers(line);
Dash Dash::FromString(const std::string &line, double scale) {
std::vector<double> words = parse_numbers(line);
if(words.size() < 5)
throw std::invalid_argument("not a valid dash specification");
for(auto i = words.begin() + 1; i != words.end(); i++) *i *= scale;

View file

@ -18,47 +18,43 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution.
*/
#pragma once
#ifndef DASHING_H
#define DASHING_H
#include <algorithm>
#include <cmath>
#include <cassert>
#include <vector>
#include <string>
#include <fstream>
#include <stdexcept>
#include <span>
#if defined(DASHING_OMP)
#include <omp.h>
#endif
#include "dashing_F.hh"
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/trim.hpp>
namespace dashing
{
struct PSMatrix {
F a, b, c, d, e, f;
double a, b, c, d, e, f;
PSMatrix inverse() const;
F determinant() const { return a * d - b * c; }
double determinant() const { return a * d - b * c; }
};
PSMatrix Translation(F x, F y);
PSMatrix Rotation(F theta);
PSMatrix XSkew(F xk);
PSMatrix YScale(F ys);
PSMatrix Translation(double x, double y);
PSMatrix Rotation(double theta);
PSMatrix XSkew(double xk);
PSMatrix YScale(double ys);
struct Point { F x, y; };
struct Point { double x, y; };
inline Point operator*(const Point &p, const PSMatrix &m) {
return Point{ p.x*m.a + p.y*m.c + m.e,
p.x*m.b + p.y*m.d + m.f };
}
inline Point operator*(const Point &p, F d)
inline Point operator*(const Point &p, double d)
{
return Point{ p.x * d, p.y * d };
}
inline Point operator*(F d, const Point &p)
inline Point operator*(double d, const Point &p)
{
return Point{ p.x * d, p.y * d };
}
@ -71,17 +67,17 @@ PSMatrix operator*(const PSMatrix &m1, const PSMatrix m2);
struct Dash {
PSMatrix tr, tf;
std::vector<F> dash, sum;
std::vector<double> dash, sum;
Dash(F th, F x0, F y0, F dx, F dy,
const std::vector<F>::const_iterator dbegin,
const std::vector<F>::const_iterator dend);
Dash(double th, double x0, double y0, double dx, double dy,
const std::vector<double>::const_iterator dbegin,
const std::vector<double>::const_iterator dend);
static Dash FromString(const std::string &line, F scale);
static Dash FromString(const std::string &line, double scale);
};
struct Segment { Point p, q; bool swapped; };
struct Intersection { F u; bool positive; };
struct Intersection { double u; bool positive; };
inline bool operator<(const Intersection &a, const Intersection &b)
{
return a.u < b.u;
@ -94,16 +90,16 @@ inline void ysort(Segment &s) {
std::swap(s.p, s.q);
}
inline int intceil(F x) { return int(ceil(x)); }
inline int intfloor(F x) { return int(floor(x)); }
inline double intceil(double x) { return int(ceil(x)); }
inline double intfloor(double x) { return int(floor(x)); }
inline F pythonmod(F a, F b) {
inline double pythonmod(double a, double b) {
auto r = a - floor(a / b) * b;
if(r == b) return 0;
return r;
}
inline size_t utoidx(const Dash &d, F u, F &o) {
inline size_t utoidx(const Dash &d, double u, double &o) {
u = pythonmod(u, d.sum.back());
for(size_t i = 1; i != d.sum.size(); i++) {
if(u < d.sum[i]) { o = u - d.sum[i-1]; return i-1; }
@ -112,30 +108,26 @@ inline size_t utoidx(const Dash &d, F u, F &o) {
}
template<class Cb>
void uvdraw(const Dash &pattern, F v, F u1, F u2, Cb cb) {
void uvdraw(const Dash &pattern, double v, double u1, double u2, Cb cb) {
if(pattern.dash.empty()) { cb(v, u1, u2); return; }
F o;
double o;
auto i = utoidx(pattern, u1, o);
const auto pi = pattern.dash[i];
if(i % 2 == 0) { cb(v, u1, std::min(u2, u1+pi-o)); u1 += pi-o; }
const auto &pi = pattern.dash[i];
if(pi >= 0) { cb(v, u1, std::min(u2, u1+pi-o)); u1 += pi-o; }
else { u1 -= pi+o; }
i++;
if(i % 2) {
u1 += pattern.dash[i];
i++;
}
i = i + 1;
if(i == pattern.dash.size()) i = 0;
for(auto u = u1; u < u2;) {
if(i >= pattern.dash.size()) i = 0;
const auto pi = pattern.dash[i];
cb(v, u, std::min(u2, u+pi));
u += pi;
u += pattern.dash[i+1];
i+=2;
const auto &pi = pattern.dash[i];
if(pi >= 0) { cb(v, u, std::min(u2, u+pi)); u += pi; }
else { u -= pi; }
i = i + 1;
if(i == pattern.dash.size()) i = 0;
}
}
template<class Cb, class Wr>
void uvspans(const Dash &pattern, std::vector<Segment> & segments, Cb cb, std::vector<Intersection> &uu, Wr wr) {
void uvspans(const Dash &pattern, std::vector<Segment> && segments, Cb cb, std::vector<Intersection> &uu, Wr wr) {
if(segments.empty()) return; // no segments
for(auto &s : segments) ysort(s);
@ -181,16 +173,16 @@ void uvspans(const Dash &pattern, std::vector<Segment> & segments, Cb cb, std::v
segments_begin ++;
}
for(const auto &s : std::span(heap_begin, heap_end)) {
for(const auto &s : boost::make_iterator_range(heap_begin, heap_end)) {
auto du = s.q.x - s.p.x;
auto dv = s.q.y - s.p.y;
assert(dv);
uu.push_back(
if(dv) uu.push_back(
Intersection{s.p.x + du * (v - s.p.y) / dv,s.swapped});
}
std::sort(uu.begin(), uu.end());
int winding = 0;
F old_u = -std::numeric_limits<F>::infinity();
double old_u = -std::numeric_limits<double>::infinity();
for(const auto &isect : uu) {
if(wr(winding)) uvdraw(pattern, v, old_u, isect.u, cb);
winding += 2*isect.positive - 1;
@ -201,16 +193,14 @@ void uvspans(const Dash &pattern, std::vector<Segment> & segments, Cb cb, std::v
struct HatchPattern {
std::vector<Dash> d;
static HatchPattern FromFile(std::istream &fi, F scale) {
static HatchPattern FromFile(std::istream &fi, double scale) {
HatchPattern result;
std::string line;
while(getline(fi, line)) {
auto i = line.find(";");
if(i != line.npos) line.erase(i, line.npos);
while(!line.empty() && isspace(line.back())) {
line.pop_back();
}
boost::algorithm::trim(line);
if(line.empty()) continue;
if(line[0] == '*') continue;
result.d.push_back(Dash::FromString(line, scale));
@ -218,34 +208,34 @@ struct HatchPattern {
return result;
}
static HatchPattern FromFile(const char *filename, F scale) {
static HatchPattern FromFile(const char *filename, double scale) {
std::ifstream fi(filename);
return FromFile(fi, scale);
}
};
template<class It, class Cb, class Wr>
void xyhatch(const Dash &pattern, It start, It end, Cb cb, Wr wr) {
std::vector<Segment> uvsegments;
uvsegments.reserve(end-start);
std::vector<Intersection> uu;
uu.reserve(8);
void xyhatch(const Dash &pattern, It start, It end, Cb cb, std::vector<Segment> &uvsegments, std::vector<Intersection> &uu, Wr wr) {
uvsegments.clear();
bool swapped = pattern.tf.determinant() < 0;
std::transform(start, end, std::back_inserter(uvsegments),
[&](const Segment &s)
{ return Segment{s.p * pattern.tf, s.q * pattern.tf, swapped != s.swapped };
});
uvspans(pattern, uvsegments, [&](F v, F u1, F u2) {
uvspans(pattern, std::move(uvsegments), [&](double v, double u1, double u2) {
Point p{u1, v}, q{u2, v};
cb(Segment{ p * pattern.tr, q * pattern.tr, false });
Segment xy{ p * pattern.tr, q * pattern.tr, false };
cb(xy);
}, uu, wr);
}
template<class It, class Cb, class Wr>
void xyhatch(const HatchPattern &pattern, It start, It end, Cb cb, Wr wr) {
for(const auto &i : pattern.d) xyhatch(i, start, end, cb, wr);
std::vector<Segment> uvsegments;
uvsegments.reserve(end-start);
std::vector<Intersection> uu;
uu.reserve(8);
for(const auto &i : pattern.d) xyhatch(i, start, end, cb, uvsegments, uu, wr);
}
template<class C, class Cb, class Wr>
@ -253,20 +243,5 @@ void xyhatch(const HatchPattern &pattern, const C &c, Cb cb, Wr wr) {
xyhatch(pattern, c.begin(), c.end(), cb, wr);
}
#if defined(DASHING_OMP)
template<class It, class Cb, class Wr>
void xyhatch_omp(const HatchPattern &pattern, It start, It end, Cb cb, Wr wr) {
#pragma omp parallel for
for(const auto &i : pattern.d) {
int iam = omp_get_thread_num();
xyhatch(i, start, end, [iam, &cb](Segment s) { cb(s, iam); }, wr);
}
}
template<class C, class Cb, class Wr>
void xyhatch_omp(const HatchPattern &pattern, const C &c, Cb cb, Wr wr) {
xyhatch_omp(pattern, c.begin(), c.end(), cb, wr);
}
#endif
}

View file

@ -1,26 +0,0 @@
/*
Copyright (c) 2015 Jeff Epler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgement in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#pragma once
namespace dashing
{
typedef float F;
}

23
main.cc
View file

@ -20,12 +20,6 @@ freely, subject to the following restrictions:
// demo and benchmark program for dashing
#include <new>
#include <atomic>
#include <cstring>
#if defined(DASHING_OMP)
#include <omp.h>
#endif
#include "dashing.hh"
#include "contours_and_segments.hh"
@ -91,20 +85,9 @@ int main(int argc, char **argv) {
if(xit) return 0;
if(bench) {
#if defined(DASHING_OMP)
struct per_thread_count_type {
alignas(std::hardware_destructive_interference_size) size_t n;
};
auto max_threads = omp_get_max_threads();
std::vector<per_thread_count_type> thread_nseg(max_threads, per_thread_count_type{0});
auto count_seg = [&thread_nseg](const Segment &s, int iam) { (void)s; thread_nseg[iam].n++; };
xyhatch_omp(h, s, count_seg, [](int i) { return i != 0; } );
size_t nseg = std::accumulate(thread_nseg.begin(), thread_nseg.end(), size_t{}, [](size_t a, const per_thread_count_type &b) { return a + b.n; });
#else
size_t nseg{};
auto count_seg = [&nseg](const Segment &s) { (void)s; nseg ++; };
xyhatch(h, s, count_seg, [](int i) { return i != 0; } );
#endif
int nseg = 0;
auto print_seg = [&nseg](const Segment &s) { (void)s; nseg ++; };
xyhatch(h, s, print_seg, [](int i) { return i != 0; } );
std::cout << nseg << "\n";
return 0;
}

View file

@ -3,9 +3,9 @@
#include <vector>
#include <string>
#include <sstream>
#include "dashing_F.hh"
#include <boost/algorithm/string/replace.hpp>
namespace dashing
{
std::vector<F> parse_numbers(std::string line);
std::vector<double> parse_numbers(std::string line);
}
#endif