shaderflow 0.4.0.dev1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shaderflow might be problematic. Click here for more details.

Files changed (51) hide show
  1. ShaderFlow/Common/Imgui.py +0 -0
  2. ShaderFlow/Common/Notes.py +134 -0
  3. ShaderFlow/Common/__init__.py +0 -0
  4. ShaderFlow/Message.py +147 -0
  5. ShaderFlow/Module.py +162 -0
  6. ShaderFlow/Modules/Audio.py +431 -0
  7. ShaderFlow/Modules/Bouncing.py +94 -0
  8. ShaderFlow/Modules/Camera.py +451 -0
  9. ShaderFlow/Modules/Depth.py +0 -0
  10. ShaderFlow/Modules/Dynamics.py +288 -0
  11. ShaderFlow/Modules/Frametimer.py +79 -0
  12. ShaderFlow/Modules/Keyboard.py +43 -0
  13. ShaderFlow/Modules/Noise.py +80 -0
  14. ShaderFlow/Modules/OpticalFlow.py +1 -0
  15. ShaderFlow/Modules/Piano.py +362 -0
  16. ShaderFlow/Modules/Spectrogram.py +315 -0
  17. ShaderFlow/Modules/Video.py +234 -0
  18. ShaderFlow/Modules/Waveform.py +100 -0
  19. ShaderFlow/Modules/__init__.py +0 -0
  20. ShaderFlow/Resources/Images/ShaderFlow.png +0 -0
  21. ShaderFlow/Resources/Images/ShaderFlow.svg +1 -0
  22. ShaderFlow/Resources/Scenes/Examples/Demo.py +284 -0
  23. ShaderFlow/Resources/Scenes/Examples/GLSL/Bars.frag +20 -0
  24. ShaderFlow/Resources/Scenes/Examples/GLSL/Bouncing.frag +50 -0
  25. ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Simulation.glsl +49 -0
  26. ShaderFlow/Resources/Scenes/Examples/GLSL/Life/Visuals.glsl +31 -0
  27. ShaderFlow/Resources/Scenes/Examples/GLSL/MotionBlur.frag +20 -0
  28. ShaderFlow/Resources/Scenes/Examples/GLSL/Multipass.frag +43 -0
  29. ShaderFlow/Resources/Scenes/Examples/GLSL/RayMarch.frag +59 -0
  30. ShaderFlow/Resources/Scenes/Examples/GLSL/ShaderToy.frag +65 -0
  31. ShaderFlow/Resources/Scenes/Examples/GLSL/Video.frag +5 -0
  32. ShaderFlow/Resources/Scenes/Examples/GLSL/Visualizer.frag +75 -0
  33. ShaderFlow/Resources/Scenes/Examples/GLSL/Waveform.frag +18 -0
  34. ShaderFlow/Resources/Shaders/Fragment/Default.glsl +65 -0
  35. ShaderFlow/Resources/Shaders/Fragment/Final.glsl +7 -0
  36. ShaderFlow/Resources/Shaders/Fragment/Loading.frag +40 -0
  37. ShaderFlow/Resources/Shaders/Fragment/Missing.glsl +23 -0
  38. ShaderFlow/Resources/Shaders/Include/Camera.glsl +166 -0
  39. ShaderFlow/Resources/Shaders/Include/Complex.glsl +0 -0
  40. ShaderFlow/Resources/Shaders/Include/ShaderFlow.glsl +444 -0
  41. ShaderFlow/Resources/Shaders/Vertex/Default.glsl +21 -0
  42. ShaderFlow/Scene.py +1124 -0
  43. ShaderFlow/Shader.py +391 -0
  44. ShaderFlow/Texture.py +528 -0
  45. ShaderFlow/Variable.py +128 -0
  46. ShaderFlow/__init__.py +11 -0
  47. ShaderFlow/__main__.py +22 -0
  48. shaderflow-0.4.0.dev1.dist-info/METADATA +79 -0
  49. shaderflow-0.4.0.dev1.dist-info/RECORD +51 -0
  50. shaderflow-0.4.0.dev1.dist-info/WHEEL +5 -0
  51. shaderflow-0.4.0.dev1.dist-info/entry_points.txt +2 -0
ShaderFlow/Texture.py ADDED
@@ -0,0 +1,528 @@
1
+ from collections import deque
2
+ from typing import Any, Deque, Iterable, List, Optional, Self, Tuple, Type, Union
3
+
4
+ import moderngl
5
+ import numpy
6
+ import PIL
7
+ from attr import Factory, define, field
8
+
9
+ from Broken import BrokenEnum, Ignore
10
+ from Broken.Loaders import LoadableImage, LoaderImage
11
+ from ShaderFlow.Message import ShaderMessage
12
+ from ShaderFlow.Module import ShaderModule
13
+ from ShaderFlow.Variable import ShaderVariable
14
+
15
+
16
+ class TextureType(BrokenEnum):
17
+
18
+ # Float
19
+ f1 = numpy.uint8
20
+ """Alias for uint8, GL_UNSIGNED_BYTE"""
21
+ f2 = numpy.float16
22
+ """Float16 bits = 2 bytes, GL_HALF_FLOAT"""
23
+ f4 = numpy.float32
24
+ """Float32 bits = 4 bytes, GL_FLOAT"""
25
+
26
+ # # Integers
27
+
28
+ # Normal
29
+ u1 = numpy.uint8
30
+ u2 = numpy.uint16
31
+ u4 = numpy.uint32
32
+ i1 = numpy.int8
33
+ i2 = numpy.int16
34
+ i4 = numpy.int32
35
+
36
+ # Normalized
37
+ nu1 = numpy.uint8
38
+ nu2 = numpy.uint16
39
+ nu4 = numpy.uint32
40
+ ni1 = numpy.int8
41
+ ni2 = numpy.int16
42
+ ni4 = numpy.int32
43
+
44
+ class TextureFilter(BrokenEnum):
45
+ # Fixme: Disallow bad combinations of filter and types
46
+ Nearest = "nearest"
47
+ Linear = "linear"
48
+
49
+ class Anisotropy(BrokenEnum):
50
+ """Anisotropy levels to use on textures"""
51
+ x1 = 1
52
+ x2 = 2
53
+ x4 = 4
54
+ x8 = 8
55
+ x16 = 16
56
+
57
+ @define
58
+ class TextureBox:
59
+ """Holds a Framebuffer and bound Texture on the TextureMatrix"""
60
+ texture: moderngl.Texture = None
61
+ fbo: moderngl.Framebuffer = None
62
+ clear: bool = False
63
+ data: bytes = None
64
+ empty: bool = True
65
+
66
+ def release(self) -> Self:
67
+ (self.texture or Ignore()).release()
68
+ (self.fbo or Ignore()).release()
69
+
70
+ @define
71
+ class ShaderTexture(ShaderModule):
72
+ name: str = None
73
+
74
+ def __post__(self):
75
+ self.make()
76
+ self.apply()
77
+
78
+ # ------------------------------------------|
79
+ # Special
80
+
81
+ final: bool = field(default=False, converter=bool)
82
+ """Is this bound to the final FSSAA ShaderObject?"""
83
+
84
+ _track: bool = field(default=False, converter=bool)
85
+ """Should this ShaderTexture match the resolution of the Scene?"""
86
+
87
+ @property
88
+ def track(self) -> bool:
89
+ return self._track
90
+
91
+ @track.setter
92
+ def track(self, value: bool):
93
+ if (self._track == value):
94
+ return
95
+ self._track = value
96
+ self.make()
97
+
98
+ # ------------------------------------------|
99
+
100
+ # Filter
101
+
102
+ _filter: TextureFilter = TextureFilter.Linear
103
+
104
+ @property
105
+ def filter(self) -> TextureFilter:
106
+ return TextureFilter.get(self._filter)
107
+
108
+ @filter.setter
109
+ def filter(self, value: TextureFilter):
110
+ self._filter = TextureFilter.get(value)
111
+ self.apply()
112
+
113
+ # Anisotropy
114
+
115
+ _anisotropy: Anisotropy = Anisotropy.x16
116
+
117
+ @property
118
+ def anisotropy(self) -> Anisotropy:
119
+ return Anisotropy.get(self._anisotropy)
120
+
121
+ @anisotropy.setter
122
+ def anisotropy(self, value: Anisotropy):
123
+ self._anisotropy = Anisotropy.get(value)
124
+ self.apply()
125
+
126
+ # Mipmaps
127
+
128
+ _mipmaps: bool = field(default=False, converter=bool)
129
+
130
+ @property
131
+ def mipmaps(self) -> bool:
132
+ return self._mipmaps
133
+
134
+ @mipmaps.setter
135
+ def mipmaps(self, value: bool):
136
+ if (self._mipmaps == value):
137
+ return
138
+ self._mipmaps = value
139
+ self.apply()
140
+
141
+ # ModernGL Filter
142
+
143
+ @property
144
+ def moderngl_filter(self) -> int:
145
+ return dict(
146
+ linear=moderngl.LINEAR,
147
+ nearest=moderngl.NEAREST,
148
+ linear_mipmap=moderngl.LINEAR_MIPMAP_LINEAR,
149
+ nearest_mipmap=moderngl.NEAREST_MIPMAP_NEAREST,
150
+ ).get(self.filter.value + ("_mipmap"*self.mipmaps))
151
+
152
+ # Dtype
153
+
154
+ _dtype: TextureType = TextureType.f4
155
+
156
+ @property
157
+ def dtype(self) -> TextureType:
158
+ return TextureType.get(self._dtype)
159
+
160
+ @dtype.setter
161
+ def dtype(self, value: TextureType):
162
+ self._dtype = TextureType.get(value)
163
+ self.make()
164
+
165
+ # Repeat X
166
+
167
+ _repeat_x: bool = field(default=True, converter=bool)
168
+
169
+ @property
170
+ def repeat_x(self) -> bool:
171
+ return self._repeat_x
172
+
173
+ @repeat_x.setter
174
+ def repeat_x(self, value: bool):
175
+ if (self._repeat_x == value):
176
+ return
177
+ self._repeat_x = value
178
+ self.apply()
179
+
180
+ # Repeat Y
181
+
182
+ _repeat_y: bool = field(default=True, converter=bool)
183
+
184
+ @property
185
+ def repeat_y(self) -> bool:
186
+ return self._repeat_y
187
+
188
+ @repeat_y.setter
189
+ def repeat_y(self, value: bool):
190
+ if (self._repeat_y == value):
191
+ return
192
+ self._repeat_y = value
193
+ self.apply()
194
+
195
+ # Repeat XY
196
+
197
+ def repeat(self, value: bool) -> Self:
198
+ self.repeat_x = self.repeat_y = bool(value)
199
+ return self.apply()
200
+
201
+ # ------------------------------------------|
202
+
203
+ # Width
204
+
205
+ _width: int = field(default=1, converter=int)
206
+
207
+ @property
208
+ def width(self) -> int:
209
+ if not self.track:
210
+ return self._width
211
+ return self.resolution[0]
212
+
213
+ @width.setter
214
+ def width(self, value: int):
215
+ value = int(value)
216
+ if (self._width == value):
217
+ return
218
+ self._width = value
219
+ self.make()
220
+
221
+ # Height
222
+
223
+ _height: int = field(default=1, converter=int)
224
+
225
+ @property
226
+ def height(self) -> int:
227
+ if not self.track:
228
+ return self._height
229
+ return self.resolution[1]
230
+
231
+ @height.setter
232
+ def height(self, value: int):
233
+ value = int(value)
234
+ if (self._height == value):
235
+ return
236
+ self._height = value
237
+ self.make()
238
+
239
+ # Size (Width, Height)
240
+
241
+ @property
242
+ def resolution(self) -> Tuple[int, int]:
243
+ if not self.track:
244
+ return (self.width, self.height)
245
+ if self.final:
246
+ return self.scene.resolution
247
+ return self.scene.render_resolution
248
+
249
+ @resolution.setter
250
+ def resolution(self, value: Tuple[int, int]):
251
+ if not self.track:
252
+ self.width, self.height = value
253
+
254
+ @property
255
+ def size(self) -> Tuple[int, int]:
256
+ return self.resolution
257
+
258
+ @size.setter
259
+ def size(self, value: Tuple[int, int]):
260
+ self.resolution = value
261
+
262
+ @property
263
+ def aspect_ratio(self) -> float:
264
+ return self.width/(self.height or 1)
265
+
266
+ # Components
267
+
268
+ _components: int = field(default=4, converter=int)
269
+
270
+ @property
271
+ def components(self) -> int:
272
+ """Number of color channels per pixel (1 Grayscale, 2 RG, 3 RGB, 4 RGBA)"""
273
+ return self._components
274
+
275
+ @components.setter
276
+ def components(self, value: int):
277
+ if (self._components == value):
278
+ return
279
+ self._components = value
280
+ self.make()
281
+
282
+ # Bytes size and Zero filling
283
+
284
+ @property
285
+ def zeros(self) -> numpy.ndarray:
286
+ return numpy.zeros((*self.size, self.components), dtype=self.dtype.value)
287
+
288
+ @property
289
+ def size_t(self) -> int:
290
+ """Size of the texture data in bytes (width * height * components)"""
291
+ return self.width * self.height * self.components
292
+
293
+ # ------------------------------------------|
294
+
295
+ # Matrix
296
+
297
+ _matrix: Deque[Deque[TextureBox]] = Factory(deque)
298
+ """Matrix of previous frames (temporal) and their layers (layers)"""
299
+
300
+ @property
301
+ def matrix(self) -> Deque[Deque[TextureBox]]:
302
+ return self._matrix
303
+
304
+ # Temporal
305
+
306
+ _temporal: int = field(default=1, converter=int)
307
+ """Number of previous frames to be stored"""
308
+
309
+ @property
310
+ def temporal(self) -> int:
311
+ return self._temporal
312
+
313
+ @temporal.setter
314
+ def temporal(self, value: int):
315
+ if (self._temporal == value):
316
+ return
317
+ self._temporal = value
318
+ self.make()
319
+
320
+ # Layers
321
+
322
+ _layers: int = field(default=1, converter=int)
323
+ """Number of layers to be stored, useful in single-shader multipass"""
324
+
325
+ @property
326
+ def layers(self) -> int:
327
+ return self._layers
328
+
329
+ @layers.setter
330
+ def layers(self, value: int):
331
+ if (self._layers == value):
332
+ return
333
+ self._layers = value
334
+ self.make()
335
+
336
+ # ------------------------------------------|
337
+
338
+ def _pop_fill(self, list: Union[List, Deque], fill: Type[Any], length: int) -> List:
339
+ """Pop right or fill until a list's length is met"""
340
+ while len(list) > length:
341
+ list.pop()
342
+ while len(list) < length:
343
+ list.append(fill())
344
+ return list
345
+
346
+ def _populate(self) -> Self:
347
+ self._pop_fill(self.matrix, deque, self.temporal)
348
+ for row in self.matrix:
349
+ self._pop_fill(row, TextureBox, self.layers)
350
+
351
+ @property
352
+ def boxes(self) -> Iterable[Tuple[int, int, TextureBox]]:
353
+ for t, temporal in enumerate(self.matrix):
354
+ for b, box in enumerate(temporal):
355
+ yield (t, b, box)
356
+
357
+ def row(self, n: int=0) -> Iterable[TextureBox]:
358
+ yield from self.matrix[n]
359
+
360
+ def make(self) -> Self:
361
+ if (max(self.size) > 2**15):
362
+ raise Exception(f"Texture size likely too large {self.size}")
363
+
364
+ self._populate()
365
+ for (_, _, box) in self.boxes:
366
+ box.release()
367
+ box.texture = self.scene.opengl.texture(
368
+ components=self.components,
369
+ dtype=self.dtype.name,
370
+ size=self.size,
371
+ )
372
+ box.fbo = self.scene.opengl.framebuffer(
373
+ color_attachments=[box.texture]
374
+ )
375
+ return self.apply()
376
+
377
+ def apply(self) -> Self:
378
+ for (_, _, box) in self.boxes:
379
+ if self.mipmaps:
380
+ box.texture.build_mipmaps()
381
+ box.texture.filter = (self.moderngl_filter, self.moderngl_filter)
382
+ box.texture.anisotropy = self.anisotropy.value
383
+ box.texture.repeat_x = self.repeat_x
384
+ box.texture.repeat_y = self.repeat_y
385
+ return self
386
+
387
+ def box(self, temporal: int=0, layer: int=-1) -> Optional[TextureBox]:
388
+ """Note: Points to the current final box"""
389
+ if (self.temporal <= temporal):
390
+ return None
391
+ if (self.layers <= layer):
392
+ return None
393
+ return self.matrix[temporal][layer]
394
+
395
+ def fbo(self) -> moderngl.Framebuffer:
396
+ """Final and most Recent FBO of this Texture"""
397
+ if self.final and self.scene.realtime:
398
+ return self.scene.window.fbo
399
+ return self.box().fbo
400
+
401
+ def texture(self) -> moderngl.Texture:
402
+ """Final and most Recent Texture of this Texture"""
403
+ return self.box().texture
404
+
405
+ def roll(self, n: int=1) -> Self:
406
+ """Rotate the temporal layers by $n times"""
407
+ self.matrix.rotate(n)
408
+ return self
409
+
410
+ # ------------------------------------------|
411
+ # Input and Output
412
+
413
+ def from_image(self, image: LoadableImage) -> Self:
414
+ image = LoaderImage(image)
415
+ image = image.transpose(PIL.Image.FLIP_TOP_BOTTOM)
416
+ self.width, self.height = image.size
417
+ self.components = len(image.getbands())
418
+ self.dtype = TextureType.get(numpy.array(image).dtype.str[1:].replace("u", "f"))
419
+ self.make()
420
+ self.write(image.tobytes())
421
+ return self
422
+
423
+ def from_numpy(self, data: numpy.ndarray) -> Self:
424
+ size = data.shape
425
+ if len(size) == 3:
426
+ components = size[2]
427
+ size = size[:2][::-1]
428
+ else:
429
+ components = 1
430
+ self.dtype = TextureType.get(data.dtype)
431
+ self.width, self.height = size
432
+ self.components = components
433
+ self.make()
434
+ self.write(numpy.flip(data, axis=0).tobytes())
435
+ return self
436
+
437
+ def write(self,
438
+ data: bytes=None,
439
+ *,
440
+ temporal: int=0,
441
+ layer: int=-1,
442
+ viewport: Tuple[int, int, int, int]=None,
443
+ ) -> Self:
444
+ box = self.box(temporal, layer)
445
+ box.texture.write(data, viewport=viewport)
446
+ if not viewport:
447
+ box.data = bytes(data)
448
+ box.empty = False
449
+ return self
450
+
451
+ def clear(self, temporal: int=0, layer: int=-1) -> Self:
452
+ return self.write(self.zeros, temporal=temporal, layer=layer)
453
+
454
+ def is_empty(self, temporal: int=0, layer: int=-1) -> bool:
455
+ return self.box(temporal, layer).empty
456
+
457
+ # ------------------------------------------|
458
+
459
+ @property
460
+ def nbytes(self) -> int:
461
+ return self.dtype.value().nbytes * self.components
462
+
463
+ def sample_xy(self, x: float, y: float, temporal: int=0, layer: int=-1) -> numpy.ndarray:
464
+ """Get the Pixel at a XY coordinate: Origin at Top Right (0, 0); Bottom Left (width, height)"""
465
+ box = self.box(temporal=temporal, layer=layer)
466
+ data = (box.data or box.texture.read())
467
+ start = int((y*self.width + x) * self.nbytes)
468
+ return numpy.frombuffer(data, dtype=self.dtype.value)[start:start + self.nbytes]
469
+
470
+ def sample_stxy(self, x: float, y: float, temporal: int=0, layer: int=-1) -> numpy.ndarray:
471
+ """Get the Pixel at a XY coordinate: Origin at Bottom left (0, 0); Top right (width, height)"""
472
+ return self.sample_xy(x=x, y=(self.height - y - 1), temporal=temporal, layer=layer)
473
+
474
+ def sample_glxy(self, x: float, y: float, temporal: int=0, layer: int=-1) -> numpy.ndarray:
475
+ """Get the Pixel at a XY coordinate: Origin at Center (0, 0); Any Edge either (±w/2, 0), (0, ±h/2)"""
476
+ return self.sample_xy(x=int(x + (self.width/2)), y=int(y + (self.height/2)), temporal=temporal, layer=layer)
477
+
478
+ def sample_uv(self, u: float, v: float, temporal: int=0, layer: int=-1) -> numpy.ndarray:
479
+ """Get the Pixel at a UV coordinate: Origin at Top Right (0, 0); Bottom Left (1, 1)"""
480
+ return self.sample_xy(u*self.width, v*self.height, temporal=temporal, layer=layer)
481
+
482
+ def sample_stuv(self, u: float, v: float, temporal: int=0, layer: int=-1) -> numpy.ndarray:
483
+ """Get the Pixel at a UV coordinate: Origin at Bottom Left (0, 0); Top Right (1, 1)"""
484
+ return self.sample_uv(u=u, v=(1-v), temporal=temporal, layer=layer)
485
+
486
+ def sample_gluv(self, u: float, v: float, temporal: int=0, layer: int=-1) -> numpy.ndarray:
487
+ """Get the Pixel at a UV coordinate: Origin at Center (0, 0); Any Edge either (±1, 0), (0, ±1)"""
488
+ return self.sample_uv(u=(u/2 + 0.5), v=(v/2 + 0.5), temporal=temporal, layer=layer)
489
+
490
+ # ------------------------------------------|
491
+ # Module
492
+
493
+ def _coord2name(self, old: int, layer: int) -> str:
494
+ return f"{self.name}{old}x{layer}"
495
+
496
+ def defines(self) -> Iterable[str]:
497
+ if not self.name:
498
+ return
499
+
500
+ # Define last frames as plain name (iTex0x(-1) -> iTex, iTex1x(-1) -> iTex1)
501
+ for temporal in range(self.temporal):
502
+ yield f"#define {self.name}{temporal or ''} {self.name}{temporal}x{self.layers-1}"
503
+
504
+ # Function to sample a dynamic temporal, layer
505
+ yield f"\nvec4 {self.name}Texture(int temporal, int layer, vec2 astuv) {{"
506
+ yield " if (false) return vec4(0);"
507
+ for temporal in range(self.temporal):
508
+ for layer in range(self.layers):
509
+ yield f" else if (temporal == {temporal} && layer == {layer}) return texture({self._coord2name(temporal, layer)}, astuv);"
510
+ yield " else {return vec4(0);}"
511
+ yield "}"
512
+
513
+ def handle(self, message: ShaderMessage):
514
+ if self.track:
515
+ if isinstance(message, ShaderMessage.Shader.RecreateTextures):
516
+ self.make()
517
+
518
+ def pipeline(self) -> Iterable[ShaderVariable]:
519
+ if not self.name:
520
+ return
521
+ yield ShaderVariable("uniform", "int", "iLayer", None)
522
+ yield ShaderVariable("uniform", "vec2", f"{self.name}Size", self.size)
523
+ yield ShaderVariable("uniform", "float", f"{self.name}AspectRatio", self.aspect_ratio)
524
+ yield ShaderVariable("uniform", "int", f"{self.name}Layers", self.layers)
525
+ yield ShaderVariable("uniform", "int", f"{self.name}Temporal", self.temporal)
526
+ for (t, b, box) in self.boxes:
527
+ yield ShaderVariable("uniform", "sampler2D", self._coord2name(t, b), box.texture)
528
+
ShaderFlow/Variable.py ADDED
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from attr import define
6
+
7
+ from Broken import BrokenEnum, BrokenFluentBuilder, log
8
+
9
+
10
+ class ShaderVariableQualifier(BrokenEnum):
11
+ """Guidance enum for GLSL variable qualifiers options"""
12
+ Uniform = "uniform"
13
+ Attribute = "attribute"
14
+ Varying = "varying"
15
+
16
+ class ShaderVariableType(BrokenEnum):
17
+ """Guidance enum for GLSL variable types"""
18
+ Float = "float"
19
+ Int = "int"
20
+ Bool = "bool"
21
+ Vec2 = "vec2"
22
+ Vec3 = "vec3"
23
+ Vec4 = "vec4"
24
+ Mat2 = "mat2"
25
+ Mat3 = "mat3"
26
+ Mat4 = "mat4"
27
+ Sampler2D = "sampler2D"
28
+
29
+ class ShaderVariableInterpolation(BrokenEnum):
30
+ """Guidance enum for GLSL variable interpolation options"""
31
+ Flat = "flat"
32
+ Smooth = "smooth"
33
+ NoPerspective = "noperspective"
34
+
35
+ class ShaderVariableDirection(BrokenEnum):
36
+ """Guidance enum for GLSL variable direction options"""
37
+ In = "in"
38
+ Out = "out"
39
+
40
+ @define(eq=False)
41
+ class ShaderVariable(BrokenFluentBuilder):
42
+ """
43
+ Metaprogramming class to define a shader variable
44
+
45
+ "uniform vec2 resolution;"
46
+ • interpolation: "flat", "smooth", "noperspective" (smooth)
47
+ • qualifier: "uniform"
48
+ • type: "vec2"
49
+ • name: "resolution"
50
+ • value: Any (of self.size)
51
+ """
52
+ qualifier: ShaderVariableQualifier = None
53
+ type: ShaderVariableType = None
54
+ name: str = None
55
+ value: Any = None
56
+ direction: ShaderVariableDirection = None
57
+ interpolation: ShaderVariableInterpolation = None
58
+
59
+ def __hash__(self) -> int:
60
+ return hash(self.name)
61
+
62
+ def __eq__(self, other: ShaderVariable) -> bool:
63
+ return self.name == other.name
64
+
65
+ @property
66
+ def size_string(self) -> str:
67
+ """Get the size string for this variable"""
68
+ return {
69
+ ShaderVariableType.Float: "f",
70
+ ShaderVariableType.Int: "i",
71
+ ShaderVariableType.Bool: "i",
72
+ ShaderVariableType.Vec2: "2f",
73
+ ShaderVariableType.Vec3: "3f",
74
+ ShaderVariableType.Vec4: "4f",
75
+ }.get(ShaderVariableType.get(self.type))
76
+
77
+ # # To string methods
78
+
79
+ @property
80
+ def declaration(self) -> str:
81
+ """String to declared this variable in GLSL"""
82
+ return (" ".join(filter(None, (
83
+ self.interpolation,
84
+ self.direction,
85
+ self.qualifier,
86
+ self.type,
87
+ self.name,
88
+ )))).strip() + ";"
89
+
90
+ @staticmethod
91
+ def smart(definition: str | ShaderVariable) -> ShaderVariable:
92
+ """Smartly creates variables from item or list of variables or string definition"""
93
+
94
+ # Return if already instance of self
95
+ if isinstance(definition, ShaderVariable):
96
+ return definition
97
+
98
+ # Attempt to smartly parse the string
99
+ elif isinstance(definition, str):
100
+ variable = ShaderVariable()
101
+ string = definition.replace(";", "").strip().split()
102
+
103
+ # Iterate on each line item split, attribute if an option, else name
104
+ for i, item in enumerate(string):
105
+
106
+ # Match against known types on enums
107
+ if item in ShaderVariableDirection.values():
108
+ variable.direction = item
109
+ elif item in ShaderVariableQualifier.values():
110
+ variable.qualifier = item
111
+ elif item in ShaderVariableType.values():
112
+ variable.type = item
113
+ elif item in ShaderVariableInterpolation.values():
114
+ variable.interpolation = item
115
+
116
+ # Item -2 can be a custom type which is not in the enum
117
+ elif i == len(string) - 2:
118
+ variable.type = item
119
+
120
+ # Item -1 is assumed to be the name
121
+ elif i == len(string) - 1:
122
+ variable.name = item
123
+
124
+ # Unknown item
125
+ else:
126
+ log.warning(f"Unknown item ({item}) on ShaderVariable Smart from ({string})")
127
+
128
+ return variable
ShaderFlow/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ import ShaderFlow.Resources as ShaderFlowResources
2
+ from Broken import BrokenPath, BrokenProject
3
+
4
+ SHADERFLOW = BrokenProject(
5
+ PACKAGE=__file__,
6
+ APP_NAME="ShaderFlow",
7
+ APP_AUTHOR="BrokenSource",
8
+ RESOURCES=ShaderFlowResources,
9
+ )
10
+
11
+ BrokenPath.resetdir(SHADERFLOW.DIRECTORIES.DUMP, echo=False)
ShaderFlow/__main__.py ADDED
@@ -0,0 +1,22 @@
1
+ from Broken import BrokenApp
2
+ from ShaderFlow import SHADERFLOW
3
+
4
+ SHADERFLOW_ABOUT = """
5
+ 🌵 Imagine ShaderToy, on a Manim-like architecture. That's ShaderFlow.\n
6
+ • Tip: run "shaderflow (scene) --help" for More Options ✨
7
+ • Warn: Make sure you trust the file you are running
8
+
9
+ ©️ Broken Source Software, AGPL-3.0 License.
10
+ """
11
+
12
+ class ShaderFlowApp(BrokenApp):
13
+ def main(self):
14
+ self.typer.description = SHADERFLOW_ABOUT
15
+ self.find_projects(tag="Scene")
16
+ self.typer()
17
+
18
+ def main():
19
+ ShaderFlowApp(PROJECT=SHADERFLOW)
20
+
21
+ if __name__ == "__main__":
22
+ main()