OXYGENHUB
◆ main openmsx / src / imgui / VectorPathDsl.hh
10.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef VECTOR_PATH_DSL_HH
2
#define VECTOR_PATH_DSL_HH
3
4
// Compile-time DSL for building VectorPath wire-format byte arrays.
5
//
6
// See VectorPath.hh for the binary layout and draw API. This header is
7
// independent, it only produces std::array<uint8_t, N> blobs.
8
//
9
// Example:
10
//   using namespace VectorPath::dsl;
11
//   static constexpr auto outline = path<int16_t>(
12
//       from(10, 20),
13
//       line(30, 40),
14
//       curve(12, 13, 14, 15, 16, 17));
15
//   static constexpr auto panel = strokeCollection<int16_t>(
16
//       openPath(from(0, 0), line(10, 0)),
17
//       closedPath(from(0, 0), line(10, 0), line(10, 10), line(0, 10)));
18
19
#include <algorithm>
20
#include <array>
21
#include <bit>
22
#include <cassert>
23
#include <cstddef>
24
#include <cstdint>
25
#include <cstring>
26
#include <span>
27
#include <tuple>
28
#include <type_traits>
29
#include <utility>
30
31
namespace VectorPath::dsl {
32
33
// --- Segment types ---
34
35
template<typename T>
36
struct From {
37
	T x, y;
38
};
39
40
template<typename T>
41
struct Line {
42
	T x, y;
43
};
44
45
template<typename T>
46
struct Curve {
47
	T cx1, cy1, cx2, cy2, x, y;
48
};
49
50
template<typename Anchor, typename... Segs>
51
struct OpenStrokePath {
52
	Anchor anchor;
53
	std::tuple<Segs...> segments;
54
};
55
56
template<typename Anchor, typename... Segs>
57
struct ClosedStrokePath {
58
	Anchor anchor;
59
	std::tuple<Segs...> segments;
60
};
61
62
// --- Segment factories ---
63
64
template<typename T>
65
[[nodiscard]] consteval From<T> from(T x, T y)
66
{
67
	return {x, y};
68
}
69
70
template<typename T>
71
[[nodiscard]] consteval Line<T> line(T x, T y)
72
{
73
	return {x, y};
74
}
75
76
template<typename T>
77
[[nodiscard]] consteval Curve<T> curve(T cx1, T cy1, T cx2, T cy2, T x, T y)
78
{
79
	return {cx1, cy1, cx2, cy2, x, y};
80
}
81
82
namespace detail {
83
84
class ByteWriter {
85
public:
86
	consteval explicit ByteWriter(std::span<uint8_t> data_)
87
		: data(data_) {}
88
89
	template<typename T>
90
	consteval void put(T value)
91
	{
92
		static_assert(std::is_trivially_copyable_v<T>);
93
		if (pos + sizeof(T) > data.size()) {
94
			throw "VectorPath DSL: buffer overflow";
95
		}
96
		auto a = std::bit_cast<std::array<uint8_t, sizeof(T)>>(value);
97
		std::copy_n(a.data(), a.size(), &data[pos]);
98
		pos += sizeof(T);
99
	}
100
101
	consteval void putBytes(std::span<const uint8_t> bytes)
102
	{
103
		std::copy_n(bytes.data(), bytes.size(), &data[pos]);
104
		pos += bytes.size();
105
	}
106
107
	[[nodiscard]] constexpr size_t size() const { return pos; }
108
109
private:
110
	std::span<uint8_t> data;
111
	size_t pos = 0;
112
};
113
114
template<typename T> inline constexpr bool is_from_v = false;
115
template<typename T> inline constexpr bool is_from_v<From<T>> = true;
116
template<typename T> inline constexpr bool is_line_v = false;
117
template<typename T> inline constexpr bool is_line_v<Line<T>> = true;
118
template<typename T> inline constexpr bool is_curve_v = false;
119
template<typename T> inline constexpr bool is_curve_v<Curve<T>> = true;
120
121
template<typename T> concept FromSeg = is_from_v<T>;
122
template<typename T> concept LineSeg = is_line_v<T>;
123
template<typename T> concept CurveSeg = is_curve_v<T>;
124
template<typename T> concept LineOrCurveSeg = is_line_v<T> || is_curve_v<T>;
125
126
127
template<typename CoordT, typename T>
128
[[nodiscard]] consteval CoordT toCoord(T v)
129
{
130
	return CoordT(v);
131
}
132
133
template<typename CoordT, typename T>
134
consteval void writeFrom(ByteWriter& w, From<T> f)
135
{
136
	w.put(toCoord<CoordT>(f.x));
137
	w.put(toCoord<CoordT>(f.y));
138
}
139
140
template<typename CoordT, LineSeg Seg>
141
consteval void writeSegment(ByteWriter& w, Seg seg)
142
{
143
	w.put(toCoord<CoordT>(seg.x));
144
	w.put(toCoord<CoordT>(seg.y));
145
}
146
147
template<typename CoordT, CurveSeg Seg>
148
consteval void writeSegment(ByteWriter& w, Seg seg)
149
{
150
	w.put(toCoord<CoordT>(seg.cx1));
151
	w.put(toCoord<CoordT>(seg.cy1));
152
	w.put(toCoord<CoordT>(seg.cx2));
153
	w.put(toCoord<CoordT>(seg.cy2));
154
	w.put(toCoord<CoordT>(seg.x));
155
	w.put(toCoord<CoordT>(seg.y));
156
}
157
158
template<typename CoordT, LineOrCurveSeg Seg>
159
[[nodiscard]] consteval size_t segmentWireSize()
160
{
161
	if constexpr (CurveSeg<Seg>) {
162
		return 6 * sizeof(CoordT);
163
	} else {
164
		return 2 * sizeof(CoordT);
165
	}
166
}
167
168
template<typename CoordT, LineOrCurveSeg... Segs>
169
[[nodiscard]] consteval size_t pathWireSize()
170
{
171
	constexpr size_t segmentCount = sizeof...(Segs);
172
	return 1 + 2 * sizeof(CoordT)
173
	     + (segmentCount + 7) / 8
174
	     + (0 + ... + segmentWireSize<CoordT, Segs>());
175
}
176
177
template<size_t I, typename CoordT, size_t N, typename Tuple>
178
consteval void writeSegmentAt(ByteWriter& w,
179
                              const std::array<bool, N>& curveFlags,
180
                              Tuple segments)
181
{
182
	if ((I % 8) == 0) {
183
		uint8_t flags = 0;
184
		for (size_t j = 0; j < 8 && (I + j) < N; ++j) {
185
			if (curveFlags[I + j]) {
186
				flags |= uint8_t(1 << j);
187
			}
188
		}
189
		w.put(flags);
190
	}
191
	writeSegment<CoordT>(w, std::get<I>(segments));
192
}
193
194
template<typename CoordT, size_t N, typename Tuple, size_t... I>
195
consteval void writeAllSegments(ByteWriter& w,
196
                                const std::array<bool, N>& curveFlags,
197
                                Tuple segments,
198
                                std::index_sequence<I...>)
199
{
200
	(writeSegmentAt<I, CoordT, N>(w, curveFlags, segments), ...);
201
}
202
203
template<typename CoordT, FromSeg Anchor, LineOrCurveSeg... Segs>
204
[[nodiscard]] consteval auto packPath(Anchor anchor, Segs... segments)
205
{
206
	constexpr size_t segmentCount = sizeof...(Segs);
207
	static_assert(segmentCount >= 1, "path requires at least one segment after from()");
208
	static_assert(segmentCount <= 255, "path segment count must fit in uint8_t");
209
210
	std::array<uint8_t, pathWireSize<CoordT, Segs...>()> result{};
211
	ByteWriter w{std::span<uint8_t>{result.data(), result.size()}};
212
213
	w.put(uint8_t(segmentCount));
214
	writeFrom<CoordT>(w, anchor);
215
216
	constexpr std::array<bool, segmentCount> curveFlags = {is_curve_v<Segs>...};
217
	const auto segTuple = std::tuple{segments...};
218
	writeAllSegments<CoordT, segmentCount>(
219
		w, curveFlags, segTuple, std::make_index_sequence<segmentCount>{});
220
221
	if (w.size() != result.size()) {
222
		throw "VectorPath DSL: path size mismatch";
223
	}
224
	return result;
225
}
226
227
template<typename CoordT, typename Anchor, typename... Segs, size_t... I>
228
consteval auto packOpenDescriptor(OpenStrokePath<Anchor, Segs...> desc,
229
                                  std::index_sequence<I...>)
230
{
231
	return packPath<CoordT>(desc.anchor, std::get<I>(desc.segments)...);
232
}
233
234
template<typename CoordT, typename Anchor, typename... Segs, size_t... I>
235
consteval auto packClosedDescriptor(ClosedStrokePath<Anchor, Segs...> desc,
236
                                    std::index_sequence<I...>)
237
{
238
	return packPath<CoordT>(desc.anchor, std::get<I>(desc.segments)...);
239
}
240
241
template<typename CoordT, typename Anchor, typename... Segs>
242
consteval auto packDescriptor(OpenStrokePath<Anchor, Segs...> desc)
243
{
244
	return packOpenDescriptor<CoordT, Anchor, Segs...>(
245
		desc, std::make_index_sequence<sizeof...(Segs)>{});
246
}
247
248
template<typename CoordT, typename Anchor, typename... Segs>
249
consteval auto packDescriptor(ClosedStrokePath<Anchor, Segs...> desc)
250
{
251
	return packClosedDescriptor<CoordT, Anchor, Segs...>(
252
		desc, std::make_index_sequence<sizeof...(Segs)>{});
253
}
254
255
template<typename T>
256
struct is_closed_stroke : std::false_type {};
257
258
template<typename Anchor, typename... Segs>
259
struct is_closed_stroke<ClosedStrokePath<Anchor, Segs...>> : std::true_type {};
260
261
template<typename T>
262
inline constexpr bool is_closed_stroke_v = is_closed_stroke<T>::value;
263
264
template<typename CoordT, typename Desc, size_t... I>
265
consteval size_t descriptorWireSizeImpl(std::index_sequence<I...>)
266
{
267
	return pathWireSize<CoordT, std::tuple_element_t<I, decltype(Desc::segments)>...>();
268
}
269
270
template<typename CoordT, typename Desc>
271
consteval size_t descriptorWireSize()
272
{
273
	constexpr size_t n = std::tuple_size_v<decltype(Desc::segments)>;
274
	return descriptorWireSizeImpl<CoordT, Desc>(std::make_index_sequence<n>{});
275
}
276
277
// Initially `writeOne()` was a local lambda inside `packStrokeCollection()`.
278
// Extracted to a standalone function to work around an MSVC compiler bug:
279
// calling consteval functions inside local generic lambdas triggers false C7595 errors.
280
template<typename CoordT, typename Desc, size_t N>
281
consteval void writeOne(
282
	ByteWriter& w, const Desc& desc, size_t& index,
283
	const std::array<bool, N>& closed)
284
{
285
	if ((index % 8) == 0) {
286
		uint8_t flags = 0;
287
		for (size_t j = 0; j < 8 && (index + j) < N; ++j) {
288
			if (closed[index + j]) {
289
				flags |= uint8_t(1 << j);
290
			}
291
		}
292
		w.put(flags);
293
	}
294
	auto bytes = packDescriptor<CoordT>(desc);
295
	w.putBytes(std::span<const uint8_t>(bytes));
296
	++index;
297
}
298
299
template<typename CoordT, typename... Desc>
300
[[nodiscard]] consteval auto packStrokeCollection(Desc... desc)
301
{
302
	constexpr size_t pathCount = sizeof...(Desc);
303
	static_assert(pathCount >= 1, "stroke collection requires at least one path");
304
	static_assert(pathCount <= 255, "stroke collection path count must fit in uint8_t");
305
306
	constexpr std::array<bool, pathCount> closed = {is_closed_stroke_v<Desc>...};
307
	constexpr size_t headerSize = 1 + (pathCount + 7) / 8;
308
	constexpr size_t pathsSize = (descriptorWireSize<CoordT, Desc>() + ...);
309
	constexpr size_t totalSize = headerSize + pathsSize;
310
311
	std::array<uint8_t, totalSize> result{};
312
	ByteWriter w{std::span<uint8_t>{result.data(), result.size()}};
313
	w.put(uint8_t(pathCount));
314
315
	// Fold over the descriptors using a mutable tracking index passed by reference
316
	//   Initially we wrote 'writeOne' as a local lambda function, but apparently msvc has known bugs
317
	size_t index = 0;
318
	(writeOne<CoordT>(w, desc, index, closed), ...);
319
320
	if (w.size() != totalSize) {
321
		throw "VectorPath DSL: stroke collection size mismatch";
322
	}
323
	return result;
324
}
325
326
} // namespace detail
327
328
// --- Pack API ---
329
330
template<typename CoordT, detail::FromSeg Anchor, detail::LineOrCurveSeg... Segs>
331
[[nodiscard]] consteval auto path(Anchor anchor, Segs... segments)
332
{
333
	return detail::packPath<CoordT>(anchor, segments...);
334
}
335
336
template<typename Anchor, detail::LineOrCurveSeg... Segs>
337
[[nodiscard]] consteval OpenStrokePath<Anchor, Segs...> openPath(Anchor anchor, Segs... segments)
338
{
339
	return {anchor, std::tuple{segments...}};
340
}
341
342
template<typename Anchor, detail::LineOrCurveSeg... Segs>
343
[[nodiscard]] consteval ClosedStrokePath<Anchor, Segs...> closedPath(
344
	Anchor anchor, Segs... segments)
345
{
346
	return {anchor, std::tuple{segments...}};
347
}
348
349
template<typename CoordT, typename... Desc>
350
[[nodiscard]] consteval auto strokeCollection(Desc... desc)
351
{
352
	return detail::packStrokeCollection<CoordT>(desc...);
353
}
354
355
} // namespace VectorPath::dsl
356
357
#endif