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/Shader.py ADDED
@@ -0,0 +1,391 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import errno
5
+ import itertools
6
+ import os
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Any, Iterable, List, Self, Set, Tuple, Union
10
+
11
+ import _moderngl
12
+ import imgui
13
+ import moderngl
14
+ import numpy
15
+ import rich
16
+ import watchdog
17
+ import watchdog.observers
18
+ from attr import Factory, define
19
+ from rich.panel import Panel
20
+ from rich.syntax import Syntax
21
+
22
+ import Broken
23
+ from Broken import BrokenPath, denum, log
24
+ from Broken.Loaders import LoaderString
25
+ from ShaderFlow import SHADERFLOW
26
+ from ShaderFlow.Message import ShaderMessage
27
+ from ShaderFlow.Module import ShaderModule
28
+ from ShaderFlow.Texture import ShaderTexture
29
+ from ShaderFlow.Variable import ShaderVariable, ShaderVariableDirection
30
+
31
+ WATCHDOG = watchdog.observers.Observer()
32
+ WATCHDOG.start()
33
+
34
+ @define
35
+ class ShaderDumper:
36
+ shader: ShaderObject # Fixme: Extending a parent class with refactored functionality
37
+ """Parent ShaderObject instance"""
38
+
39
+ error: str
40
+ """str(_moderngl.Error) exception"""
41
+
42
+ fragment: str
43
+ """Potentially faulty Fragment shader"""
44
+
45
+ vertex: str
46
+ """Potentially faulty Vertex shader"""
47
+
48
+ context: int = 5
49
+ """Number of lines to show before and after the faulty line"""
50
+
51
+ _parser = re.compile(r"^0\((\d+)\)\s*:\s*error\s* (\w+):\s(.*)", re.MULTILINE)
52
+
53
+ @property
54
+ def code(self) -> str:
55
+ """Simple heuristic to choose what shader cause the error"""
56
+ if ("fragment_shader" in self.error):
57
+ return self.fragment
58
+ elif ("vertex_shader" in self.error):
59
+ return self.vertex
60
+ raise RuntimeError(f"Cannot determine shader from error: {self.error}")
61
+
62
+ @property
63
+ def lines(self) -> List[str]:
64
+ return self.code.splitlines()
65
+
66
+ def dump(self):
67
+ directory = Broken.PROJECT.DIRECTORIES.DUMP
68
+ log.error(f"{self.shader.who} Dumping shaders to {directory}")
69
+ (directory/f"{self.shader.uuid}.frag").write_text(self.fragment, encoding="utf-8")
70
+ (directory/f"{self.shader.uuid}.vert").write_text(self.vertex, encoding="utf-8")
71
+ (directory/f"{self.shader.uuid}-error.md" ).write_text(self.error, encoding="utf-8")
72
+
73
+ # Visual only: Print highlighted code panels of all errors
74
+ for match in ShaderDumper._parser.finditer(self.error):
75
+ lineno, errno, message = match.groups()
76
+ lineno = int(lineno)
77
+ start = max(0, lineno - self.context - 1)
78
+ end = min(len(self.lines), lineno + self.context)
79
+ code = []
80
+
81
+ for i, line in enumerate(self.lines[start:end]):
82
+ div = (">" if (i == lineno) else "|")
83
+ code.append(f"({i+start+1:3d}) {div} {line}")
84
+
85
+ rich.print(Panel(
86
+ Syntax(code='\n'.join(code), lexer="glsl"),
87
+ title=f"({errno} at Module #{self.shader.uuid}, Line {lineno}): {message}",
88
+ ))
89
+
90
+ @define
91
+ class ShaderObject(ShaderModule):
92
+ version: int = 330
93
+ """OpenGL Version to use for the shader. Must be <= than the Window Backend version"""
94
+
95
+ program: moderngl.Program = None
96
+ """ModernGL 'Compiled Shaders' object"""
97
+
98
+ vao: moderngl.VertexArray = None
99
+ """Buffer object for the vertices of the shader"""
100
+
101
+ vbo: moderngl.Buffer = None
102
+ """Buffer object for the vertices of the shader"""
103
+
104
+ texture: ShaderTexture = None
105
+ """ShaderTexture Module that this Shader renders to in layers and temporal"""
106
+
107
+ clear: bool = False
108
+ """Clear the Final Texture before rendering"""
109
+
110
+ instances: int = 1
111
+ """Number of gl_InstanceID's to render"""
112
+
113
+ vertices: List[float] = Factory(list)
114
+ """Vertices of the shader. More often than not, a Fullscreen Quad"""
115
+
116
+ vertex_variables: Set[ShaderVariable] = Factory(set)
117
+ """Variables metaprogramming that will be added to the Vertex Shader"""
118
+
119
+ fragment_variables: Set[ShaderVariable] = Factory(set)
120
+ """Variables metaprogramming that will be added to the Fragment Shader"""
121
+
122
+ def __post__(self):
123
+ """Set default values for some variables"""
124
+ self.texture = ShaderTexture(scene=self.scene, name=self.name, track=True)
125
+ self.fragment_variable("out vec4 fragColor")
126
+ self.vertex_variable("in vec2 vertex_position")
127
+ self.vertex_variable("in vec2 vertex_gluv")
128
+ self.passthrough("flat int instance")
129
+ self.passthrough("vec2 gluv")
130
+ self.passthrough("vec2 stuv")
131
+ self.passthrough("vec2 astuv")
132
+ self.passthrough("vec2 agluv")
133
+ self.passthrough("vec2 fragCoord")
134
+ self.passthrough("vec2 glxy")
135
+ self.passthrough("vec2 stxy")
136
+
137
+ # Add a fullscreen center-(0, 0) uv rectangle
138
+ for x, y in itertools.product((-1, 1), (-1, 1)):
139
+ self.add_vertice(x=x, y=y, u=x, v=y)
140
+
141
+ # Load default vertex and fragment shaders
142
+ self.vertex = (SHADERFLOW.RESOURCES.VERTEX/ "Default.glsl")
143
+ self.fragment = (SHADERFLOW.RESOURCES.FRAGMENT/"Default.glsl")
144
+
145
+ def add_vertice(self, x: float=0, y: float=0, u: float=0, v: float=0) -> Self:
146
+ self.vertices.extend((x, y, u, v))
147
+ return self
148
+
149
+ def clear_vertices(self) -> Self:
150
+ self.vertices = []
151
+
152
+ def vertex_variable(self, variable: ShaderVariable) -> Self:
153
+ self.vertex_variables.add(ShaderVariable.smart(variable))
154
+
155
+ def fragment_variable(self, variable: ShaderVariable) -> Self:
156
+ self.fragment_variables.add(ShaderVariable.smart(variable))
157
+
158
+ def common_variable(self, variable: ShaderVariable) -> Self:
159
+ self.vertex_variable(variable)
160
+ self.fragment_variable(variable)
161
+
162
+ def passthrough(self, variable: ShaderVariable) -> Self:
163
+ variable = ShaderVariable.smart(variable)
164
+ self.vertex_variable(variable.copy(direction="out"))
165
+ self.fragment_variable(variable.copy(direction="in"))
166
+
167
+ @property
168
+ def vao_definition(self) -> Tuple[str]:
169
+ """("2f 2f", "render_vertex", "coords_vertex")"""
170
+ sizes, names = [], []
171
+ for variable in self.vertex_variables:
172
+ if variable.direction == ShaderVariableDirection.In.value:
173
+ sizes.append(variable.size_string)
174
+ names.append(variable.name)
175
+ return (" ".join(sizes), *names)
176
+
177
+ def _build_shader(self, content: str, variables: Iterable[ShaderVariable]) -> str:
178
+ """Build the final shader from the contents provided"""
179
+ shader = []
180
+
181
+ @contextlib.contextmanager
182
+ def section(name: str=""):
183
+ shader.append("\n\n// " + "-"*96 + "|")
184
+ shader.append(f"// ShaderFlow Section: ({name})\n")
185
+ yield
186
+
187
+ shader.append(f"#version {self.version}")
188
+
189
+ # Add variable definitions
190
+ with section("Variables"):
191
+ for variable in variables:
192
+ shader.append(variable.declaration)
193
+
194
+ with section("Include - ShaderFlow"):
195
+ shader.append(SHADERFLOW.RESOURCES.SHADERS_INCLUDE/"ShaderFlow.glsl")
196
+
197
+ # Add all modules includes to the shader
198
+ for module in self.scene.modules:
199
+ for defines in module.defines():
200
+ shader.append(defines)
201
+
202
+ for include in filter(None, module.includes()):
203
+ with section(f"Include - {module.who}"):
204
+ shader.append(include)
205
+
206
+ # Add shader content itself
207
+ with section("Content"):
208
+ shader.append(content)
209
+
210
+ return '\n'.join(map(LoaderString, shader))
211
+
212
+ # # Vertex shader content
213
+
214
+ def _watchshader(self, path: Path) -> None:
215
+
216
+ @define(eq=False)
217
+ class Handler(watchdog.events.FileSystemEventHandler):
218
+ shader: ShaderObject
219
+ def on_modified(self, event):
220
+ if self.shader.scene.rendering:
221
+ return
222
+ self.shader.scene.scheduler.once(self.shader.compile)
223
+
224
+ # Add the Shader Path to the watchdog for changes. Only ignore 'File Too Long'
225
+ # exceptions when non-path strings as we can't get max len easily per system
226
+ try:
227
+ if (path := BrokenPath(path).valid()):
228
+ WATCHDOG.schedule(Handler(self), path)
229
+ except OSError as error:
230
+ if error.errno != errno.ENAMETOOLONG:
231
+ raise error
232
+
233
+ _vertex: Union[Path, str] = ""
234
+ """The 'User Content' of the Vertex Shader, interted after the Metaprogramming.
235
+ A Path value will be watched for changes and shaders will be automatically reloaded"""
236
+
237
+ def make_vertex(self, content: str) -> Self:
238
+ return self._build_shader(LoaderString(content), self.vertex_variables)
239
+
240
+ @property
241
+ def vertex(self) -> str:
242
+ return self.make_vertex(self._vertex)
243
+
244
+ @vertex.setter
245
+ def vertex(self, value: Union[Path, str]):
246
+ self._watchshader(value)
247
+ self._vertex = value
248
+
249
+ # # Fragment shader content
250
+
251
+ _fragment: Union[Path, str] = ""
252
+ """The 'User Content' of the Fragment Shader, interted after the Metaprogramming.
253
+ A Path value will be watched for changes and shaders will be automatically reloaded"""
254
+
255
+ def make_fragment(self, content: str) -> Self:
256
+ return self._build_shader(LoaderString(content), self.fragment_variables)
257
+
258
+ @property
259
+ def fragment(self) -> str:
260
+ return self.make_fragment(self._fragment)
261
+
262
+ @fragment.setter
263
+ def fragment(self, value: Union[Path, str]):
264
+ self._watchshader(value)
265
+ self._fragment = value
266
+
267
+ # # Uniforms
268
+
269
+ def set_uniform(self, name: str, value: Any=None) -> None:
270
+ if (self.program is None):
271
+ raise RuntimeError(f"Shader {self.who} hasn't been compiled yet")
272
+ if (value is not None) and (uniform := self.program.get(name, None)):
273
+ uniform.value = denum(value)
274
+
275
+ def get_uniform(self, name: str) -> Any | None:
276
+ return self.program.get(name, None)
277
+
278
+ # # Rendering
279
+
280
+ def _full_pipeline(self) -> Iterable[ShaderVariable]:
281
+ for module in self.scene.modules:
282
+ yield from module.pipeline()
283
+
284
+ def compile(self, _vertex: str=None, _fragment: str=None) -> Self:
285
+ log.info(f"{self.who} Compiling shaders")
286
+
287
+ # Add pipeline variable definitions
288
+ for variable in self._full_pipeline():
289
+ self.common_variable(variable)
290
+
291
+ # Metaprogram either injected or proper shaders
292
+ fragment = self.make_fragment(_fragment or self._fragment)
293
+ vertex = self.make_vertex(_vertex or self._vertex)
294
+
295
+ try:
296
+ self.program = self.scene.opengl.program(vertex, fragment)
297
+ except _moderngl.Error as error:
298
+ ShaderDumper(
299
+ shader=self,
300
+ error=str(error),
301
+ vertex=vertex,
302
+ fragment=fragment
303
+ ).dump()
304
+
305
+ if (_vertex or _fragment):
306
+ raise RuntimeError(log.error("Recursion on Missing Texture Shader Loading"))
307
+
308
+ log.error(f"{self.who} Error compiling shaders, loading missing texture shader")
309
+ self.compile(
310
+ _vertex =LoaderString(SHADERFLOW.RESOURCES.VERTEX/"Default.glsl"),
311
+ _fragment=LoaderString(SHADERFLOW.RESOURCES.FRAGMENT/"Missing.glsl")
312
+ )
313
+
314
+ # Render the vertices that are defined on the shader
315
+ self.vbo = self.scene.opengl.buffer(numpy.array(self.vertices, dtype="f4"))
316
+ self.vao = self.scene.opengl.vertex_array(
317
+ self.program, [(self.vbo, *self.vao_definition)],
318
+ skip_errors=True
319
+ )
320
+
321
+ return self
322
+
323
+ # # Module
324
+
325
+ def update(self) -> None:
326
+ if (self.program is None):
327
+ self.compile()
328
+ self.render()
329
+
330
+ SKIP_GPU: bool = (os.environ.get("SKIP_GPU", "0") == "1")
331
+ """Do not render shaders, useful for benchmarking raw Python performance"""
332
+
333
+ def render_fbo(self, fbo: moderngl.Framebuffer, clear: bool=True) -> None:
334
+ if self.SKIP_GPU:
335
+ return
336
+ fbo.use()
337
+ clear or fbo.clear()
338
+ self.vao.render(
339
+ moderngl.TRIANGLE_STRIP,
340
+ instances=self.instances
341
+ )
342
+
343
+ def use_pipeline(self, pipeline: Iterable[ShaderVariable]) -> None:
344
+ for index, variable in enumerate(pipeline):
345
+ # if variable not in self.fragment_variables:
346
+ # self.load_shaders()
347
+ if (variable.type == "sampler2D"):
348
+ self.set_uniform(variable.name, index)
349
+ variable.value.use(index)
350
+ continue
351
+ self.set_uniform(variable.name, variable.value)
352
+
353
+ def render(self) -> None:
354
+
355
+ # Optimization: Final shader doesn't need the full pipeline
356
+ if self.texture.final:
357
+ self.use_pipeline(self.scene.shader.texture.pipeline())
358
+ self.set_uniform("iFlip", self.scene.rendering)
359
+ self.render_fbo(self.texture.fbo(), clear=False)
360
+ return
361
+
362
+ self.use_pipeline(self._full_pipeline())
363
+
364
+ # Optimization: Only the iLayer uniform changes
365
+ for layer, box in enumerate(self.texture.row(0)):
366
+ self.set_uniform("iLayer", layer)
367
+ self.render_fbo(fbo=box.fbo, clear=box.clear)
368
+
369
+ self.texture.roll()
370
+
371
+ def handle(self, message: ShaderMessage) -> None:
372
+ if isinstance(message, ShaderMessage.Shader.Compile):
373
+ self.compile()
374
+
375
+ elif isinstance(message, ShaderMessage.Shader.Render):
376
+ self.render()
377
+
378
+ # Fixme: Should this be on a proper User Interface class?
379
+ if self.texture.final:
380
+ self.scene._render_ui()
381
+
382
+ def __ui__(self) -> None:
383
+ if imgui.button("Reload"):
384
+ self.compile()
385
+ imgui.same_line()
386
+ if imgui.button("Dump"):
387
+ self.dump_shaders()
388
+ if imgui.tree_node("Pipeline"):
389
+ for variable in self._full_pipeline():
390
+ imgui.text(f"{variable.name.ljust(16)}: {variable.value}")
391
+ imgui.tree_pop()