MayaFlux 0.5.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} // namespace detail
39
40/**
41 * @struct DualField
42 * @brief One authored expression carried as both a host callable and shader text.
43 * @tparam D Domain type.
44 * @tparam R Range type.
45 *
46 * The cpu member is an ordinary Tendency and is accepted anywhere a Tendency is,
47 * including FieldOperator::bind. It composes with combine, chain, scale, clamp,
48 * threshold, invert, lerp and select as any Tendency does. Those free functions
49 * build closures, so a composed Tendency has no shader half: composition on the
50 * GPU is textual and happens in the kernel body, where several emitted functions
51 * are called with whatever arithmetic is wanted.
52 *
53 * Field bodies must be authored outside namespace Kinesis. See the authoring
54 * rules in ShaderCompat.hpp.
55 *
56 * @code
57 * namespace MayaFlux::Fields {
58 * using namespace MayaFlux::ShaderCompat;
59 *
60 * const auto swirl = MF_FIELD(swirl, [](vec3 p) -> vec3 {
61 * return cross(vec3(0.0f, 1.0f, 0.0f), p) * 2.0f;
62 * });
63 * }
64 *
65 * field_op->bind(FieldTarget::POSITION, Fields::swirl.cpu);
66 * @endcode
67 */
68template <typename D, typename R>
69struct DualField {
72
73 /**
74 * @brief Evaluate the host half.
75 */
76 R operator()(const D& d) const { return cpu.fn(d); }
77
78 /**
79 * @brief Whether the shader half parsed into a usable function definition.
80 */
81 [[nodiscard]] bool has_source() const noexcept { return source.valid(); }
82};
83
88
89/**
90 * @struct TemporalField
91 * @brief An authored expression of position and time, shader-only.
92 * @tparam D Domain type.
93 * @tparam R Range type.
94 *
95 * Carries no Tendency. Tendency<D, R> is a function of the domain alone, so a
96 * two-argument callable has no CPU half to hand to FieldOperator: the value
97 * depends on when it is asked, which a stateless Tendency cannot express.
98 *
99 * The second argument is supplied by whoever dispatches the shader. For
100 * VertexFieldProcessor it is seconds since the processor attached.
101 *
102 * @code
103 * namespace MayaFlux::Fields {
104 * using namespace MayaFlux::ShaderCompat;
105 *
106 * const auto breathe = MF_FIELD_T(breathe, [](vec3 p, float t) -> vec3 {
107 * float d = length(p);
108 * if (d < 0.001f) { return vec3(0.0f); }
109 * return (p / d) * (sin(d * 2.5f - t * 2.0f) * 0.25f);
110 * });
111 * }
112 * @endcode
113 */
114template <typename D, typename R>
117
118 /**
119 * @brief Whether the shader half parsed into a usable function definition.
120 */
121 [[nodiscard]] bool has_source() const noexcept { return source.valid(); }
122};
123
127
128/**
129 * @brief Build a DualField from a lambda and its stringified text.
130 * @param name Name the emitted GLSL function will carry.
131 * @param fn The lambda itself, compiled by the host.
132 * @param text The same lambda stringified, parsed into GLSL.
133 *
134 * Domain and range are deduced from the lambda's call operator, so the returned
135 * type follows the authored signature rather than being spelled at the call
136 * site. Prefer MF_FIELD: this function cannot check that fn and text correspond,
137 * and passing a mismatched pair produces a field whose halves disagree silently.
138 */
139template <typename Lambda>
140[[nodiscard]] auto dual_field(std::string name, Lambda&& fn, std::string_view text)
141{
143 using D = typename Sig::domain;
144 using R = typename Sig::range;
145
146 return DualField<D, R> {
147 .cpu = Tendency<D, R> { .fn = std::forward<Lambda>(fn) },
148 .source = FieldSource::parse(std::move(name), text),
149 };
150}
151
152/**
153 * @brief Build a TemporalField from a lambda and its stringified text.
154 *
155 * The auxiliary parameter must be float. Domain and range are deduced from the
156 * call operator, so the returned type follows the authored signature. Prefer
157 * MF_FIELD_T: this cannot check that fn and text correspond.
158 */
159template <typename Lambda>
160[[nodiscard]] auto temporal_field(std::string name, Lambda&&, std::string_view text)
161{
163 using D = typename Sig::domain;
164 using R = typename Sig::range;
165
166 static_assert(std::is_same_v<typename Sig::aux, float>,
167 "MF_FIELD_T: the second parameter must be float");
168
169 return TemporalField<D, R> {
170 .source = FieldSource::parse(std::move(name), text),
171 };
172}
173
174} // namespace MayaFlux::Kinesis
175
176/**
177 * @brief Bind one authored expression to both the host and shader backends.
178 *
179 * The lambda is compiled by the host compiler and its text is parsed into a GLSL
180 * function definition. Both halves come from the same characters and cannot
181 * drift.
182 *
183 * The lambda must carry a trailing return type, take its argument by value, and
184 * stay inside the intersection documented in ShaderCompat.hpp.
185 *
186 * @code
187 * const auto falloff = MF_FIELD(falloff, [](vec3 p) -> float {
188 * float d = length(p) / 3.0f;
189 * return 1.0f - smoothstep(0.2f, 0.9f, clamp(d, 0.0f, 1.0f));
190 * });
191 * @endcode
192 */
193#define MF_FIELD(name, lambda) \
194 MayaFlux::Kinesis::dual_field(#name, lambda, #lambda)
195
196/**
197 * @brief Bind an authored expression of position and time to the shader backend.
198 *
199 * Same authoring rules as MF_FIELD, with one addition: the lambda takes a
200 * second float parameter and has no CPU half. Use MF_FIELD when the expression
201 * depends on position alone, so it remains bindable to FieldOperator.
202 */
203#define MF_FIELD_T(name, lambda) \
204 MayaFlux::Kinesis::temporal_field(#name, lambda, #lambda)
GLSL type and function spellings made valid as C++.
std::string name
Definition VKDevice.cpp:143
auto temporal_field(std::string name, Lambda &&, std::string_view text)
Build a TemporalField 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:76
bool has_source() const noexcept
Whether the shader half parsed into a usable function definition.
Definition DualField.hpp:81
One authored expression carried as both a host callable and shader text.
Definition DualField.hpp:69
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 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