MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DualField.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "FieldSource.hpp"
4#include "ShaderCompat.hpp"
5#include "Tendency.hpp"
6
7#include <type_traits>
8
9namespace MayaFlux::Kinesis {
10
11namespace detail {
12
13 /**
14 * @struct FieldSignature
15 * @brief Extracts domain and range types from a non-generic lambda.
16 *
17 * Only const-qualified single-argument call operators are matched, which is
18 * the shape MF_FIELD accepts. A mutable lambda, a multi-argument lambda or a
19 * generic lambda produces no specialisation and fails against the incomplete
20 * primary template.
21 */
22 template <typename T>
23 struct FieldSignature : FieldSignature<decltype(&T::operator())> { };
24
25 template <typename C, typename R, typename A>
26 struct FieldSignature<R (C::*)(A) const> {
27 using domain = std::decay_t<A>;
28 using range = R;
29 };
30
31 template <typename C, typename R, typename A, typename B>
32 struct FieldSignature<R (C::*)(A, B) const> {
33 using domain = std::decay_t<A>;
34 using range = R;
35 using aux = std::decay_t<B>;
36 };
37
38 template <typename C, typename R, typename A, typename B, typename P>
39 struct FieldSignature<R (C::*)(A, B, P) const> {
40 using domain = std::decay_t<A>;
41 using range = R;
42 using aux = std::decay_t<B>;
43 using params = std::decay_t<P>;
44 };
45
46} // namespace detail
47
48/**
49 * @struct DualField
50 * @brief One authored expression carried as both a host callable and shader text.
51 * @tparam D Domain type.
52 * @tparam R Range type.
53 *
54 * The cpu member is an ordinary Tendency and is accepted anywhere a Tendency is,
55 * including FieldOperator::bind. It composes with combine, chain, scale, clamp,
56 * threshold, invert, lerp and select as any Tendency does. Those free functions
57 * build closures, so a composed Tendency has no shader half: composition on the
58 * GPU is textual and happens in the kernel body, where several emitted functions
59 * are called with whatever arithmetic is wanted.
60 *
61 * Field bodies must be authored outside namespace Kinesis. See the authoring
62 * rules in ShaderCompat.hpp.
63 *
64 * @code
65 * namespace MayaFlux::Fields {
66 * using namespace MayaFlux::ShaderCompat;
67 *
68 * const auto swirl = MF_FIELD(swirl, [](vec3 p) -> vec3 {
69 * return cross(vec3(0.0f, 1.0f, 0.0f), p) * 2.0f;
70 * });
71 * }
72 *
73 * field_op->bind(FieldTarget::POSITION, Fields::swirl.cpu);
74 * @endcode
75 */
76template <typename D, typename R>
77struct DualField {
80
81 /**
82 * @brief Evaluate the host half.
83 */
84 R operator()(const D& d) const { return cpu.fn(d); }
85
86 /**
87 * @brief Whether the shader half parsed into a usable function definition.
88 */
89 [[nodiscard]] bool has_source() const noexcept { return source.valid(); }
90};
91
96
97/**
98 * @struct TemporalField
99 * @brief An authored expression of position and time, shader-only.
100 * @tparam D Domain type.
101 * @tparam R Range type.
102 *
103 * Carries no Tendency. Tendency<D, R> is a function of the domain alone, so a
104 * two-argument callable has no CPU half to hand to FieldOperator: the value
105 * depends on when it is asked, which a stateless Tendency cannot express.
106 *
107 * The second argument is supplied by whoever dispatches the shader. For
108 * VertexFieldProcessor it is seconds since the processor attached.
109 *
110 * @code
111 * namespace MayaFlux::Fields {
112 * using namespace MayaFlux::ShaderCompat;
113 *
114 * const auto breathe = MF_FIELD_T(breathe, [](vec3 p, float t) -> vec3 {
115 * float d = length(p);
116 * if (d < 0.001f) { return vec3(0.0f); }
117 * return (p / d) * (sin(d * 2.5f - t * 2.0f) * 0.25f);
118 * });
119 * }
120 * @endcode
121 */
122template <typename D, typename R>
125
126 /**
127 * @brief Whether the shader half parsed into a usable function definition.
128 */
129 [[nodiscard]] bool has_source() const noexcept { return source.valid(); }
130};
131
135
136/**
137 * @struct ParametricField
138 * @brief An authored expression of position, time and a parameter block, shader-only.
139 * @tparam D Domain type.
140 * @tparam R Range type.
141 *
142 * Carries no Tendency, for the same reason TemporalField does not. The third
143 * argument is a vec4 supplied by whoever dispatches the shader, and it is what
144 * lets a field depend on state the shader text cannot hold, such as an Agent's
145 * position and radius. For GpuFieldOperator it is set with set_params() and
146 * pushed every dispatch, so changing it recompiles nothing.
147 *
148 * @code
149 * namespace MayaFlux::Fields {
150 * using namespace MayaFlux::ShaderCompat;
151 *
152 * const auto pull = MF_FIELD_P(pull, [](vec3 p, float t, vec4 a) -> vec3 {
153 * vec3 d = vec3(a.x, a.y, a.z) - p;
154 * float r = length(d);
155 * return r > 0.001f ? (d / r) * (a.w / (r * r + 0.1f)) : vec3(0.0f);
156 * });
157 * }
158 * @endcode
159 */
160template <typename D, typename R>
163
164 /**
165 * @brief Whether the shader half parsed into a usable function definition.
166 */
167 [[nodiscard]] bool has_source() const noexcept { return source.valid(); }
168};
169
173
174/**
175 * @brief Build a DualField from a lambda and its stringified text.
176 * @param name Name the emitted GLSL function will carry.
177 * @param fn The lambda itself, compiled by the host.
178 * @param text The same lambda stringified, parsed into GLSL.
179 *
180 * Domain and range are deduced from the lambda's call operator, so the returned
181 * type follows the authored signature rather than being spelled at the call
182 * site. Prefer MF_FIELD: this function cannot check that fn and text correspond,
183 * and passing a mismatched pair produces a field whose halves disagree silently.
184 */
185template <typename Lambda>
186[[nodiscard]] auto dual_field(std::string name, Lambda&& fn, std::string_view text)
187{
189 using D = typename Sig::domain;
190 using R = typename Sig::range;
191
192 return DualField<D, R> {
193 .cpu = Tendency<D, R> { .fn = std::forward<Lambda>(fn) },
194 .source = FieldSource::parse(std::move(name), text),
195 };
196}
197
198/**
199 * @brief Build a TemporalField from a lambda and its stringified text.
200 *
201 * The auxiliary parameter must be float. Domain and range are deduced from the
202 * call operator, so the returned type follows the authored signature. Prefer
203 * MF_FIELD_T: this cannot check that fn and text correspond.
204 */
205template <typename Lambda>
206[[nodiscard]] auto temporal_field(std::string name, Lambda&&, std::string_view text)
207{
209 using D = typename Sig::domain;
210 using R = typename Sig::range;
211
212 static_assert(std::is_same_v<typename Sig::aux, float>,
213 "MF_FIELD_T: the second parameter must be float");
214
215 return TemporalField<D, R> {
216 .source = FieldSource::parse(std::move(name), text),
217 };
218}
219
220/**
221 * @brief Build a ParametricField from a lambda and its stringified text.
222 *
223 * The second parameter must be float and the third glm::vec4. Domain and range
224 * are deduced from the call operator. Prefer MF_FIELD_P: this cannot check that
225 * fn and text correspond.
226 */
227template <typename Lambda>
228[[nodiscard]] auto parametric_field(std::string name, Lambda&&, std::string_view text)
229{
231 using D = typename Sig::domain;
232 using R = typename Sig::range;
233
234 static_assert(std::is_same_v<typename Sig::aux, float>,
235 "MF_FIELD_P: the second parameter must be float");
236 static_assert(std::is_same_v<typename Sig::params, glm::vec4>,
237 "MF_FIELD_P: the third parameter must be vec4");
238
239 return ParametricField<D, R> {
240 .source = FieldSource::parse(std::move(name), text),
241 };
242}
243
244} // namespace MayaFlux::Kinesis
245
246/**
247 * @brief Bind one authored expression to both the host and shader backends.
248 *
249 * The lambda is compiled by the host compiler and its text is parsed into a GLSL
250 * function definition. Both halves come from the same characters and cannot
251 * drift.
252 *
253 * The lambda must carry a trailing return type, take its argument by value, and
254 * stay inside the intersection documented in ShaderCompat.hpp.
255 *
256 * @code
257 * const auto falloff = MF_FIELD(falloff, [](vec3 p) -> float {
258 * float d = length(p) / 3.0f;
259 * return 1.0f - smoothstep(0.2f, 0.9f, clamp(d, 0.0f, 1.0f));
260 * });
261 * @endcode
262 */
263#define MF_FIELD(name, lambda) \
264 MayaFlux::Kinesis::dual_field(#name, lambda, #lambda)
265
266/**
267 * @brief Bind an authored expression of position and time to the shader backend.
268 *
269 * Same authoring rules as MF_FIELD, with one addition: the lambda takes a
270 * second float parameter and has no CPU half. Use MF_FIELD when the expression
271 * depends on position alone, so it remains bindable to FieldOperator.
272 */
273#define MF_FIELD_T(name, lambda) \
274 MayaFlux::Kinesis::temporal_field(#name, lambda, #lambda)
275
276/**
277 * @brief Bind an authored expression of position, time and a vec4 parameter block to the shader backend.
278 *
279 * Same authoring rules as MF_FIELD_T, with a third vec4 parameter that carries
280 * live state in from outside the shader. Has no CPU half.
281 */
282#define MF_FIELD_P(name, lambda) \
283 MayaFlux::Kinesis::parametric_field(#name, lambda, #lambda)
GLSL type and function spellings made valid as C++.
std::string name
Definition VKDevice.cpp:123
auto temporal_field(std::string name, Lambda &&, std::string_view text)
Build a TemporalField from a lambda and its stringified text.
auto parametric_field(std::string name, Lambda &&, std::string_view text)
Build a ParametricField from a lambda and its stringified text.
auto dual_field(std::string name, Lambda &&fn, std::string_view text)
Build a DualField from a lambda and its stringified text.
R operator()(const D &d) const
Evaluate the host half.
Definition DualField.hpp:84
bool has_source() const noexcept
Whether the shader half parsed into a usable function definition.
Definition DualField.hpp:89
One authored expression carried as both a host callable and shader text.
Definition DualField.hpp:77
static FieldSource parse(std::string fn_name, std::string_view text)
Extract return type, parameter list and body from lambda text.
bool valid() const noexcept
Whether parsing produced a usable function definition.
Parsed representation of a stringified field lambda.
bool has_source() const noexcept
Whether the shader half parsed into a usable function definition.
An authored expression of position, time and a parameter block, shader-only.
bool has_source() const noexcept
Whether the shader half parsed into a usable function definition.
An authored expression of position and time, shader-only.
std::function< R(const D &)> fn
Definition Tendency.hpp:23
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22
Extracts domain and range types from a non-generic lambda.
Definition DualField.hpp:23