246 Commits

Author SHA1 Message Date
Jarcode
3cc5e226aa fix spelling error 2019-09-11 23:22:21 -07:00
Jarcode
4b84e7a928 Add GLFFT compute shaders 2019-09-11 23:07:20 -07:00
Jarcode
b26f11ae20 Add GLFFT 2019-09-11 23:06:03 -07:00
Jarcode
580e296e86 Add GLFFT sources 2019-09-11 23:04:22 -07:00
Jarcode
6f1680b335 Improve average windowing, remove smooth overrides 2019-09-11 16:20:04 -07:00
Jarcode
0d48dd059c Add generic 1D texture map pass 2019-09-11 15:25:32 -07:00
Jarcode
c4b348cf8b Added support for accelerated transformations & #expand directive 2019-09-11 15:24:07 -07:00
Jarcode
c591b87c7b Only mention default environment settings with verbose output 2019-09-09 21:53:28 -07:00
Jarcode
9abaafda5a add spectrwm environment settings 2019-09-09 21:44:28 -07:00
Jarcode
7bfa551247 Add spectrwm defaults, closes #131 2019-09-09 21:43:14 -07:00
Jarcode
7dc8a985fc make clang block storage local 2019-09-09 21:23:48 -07:00
Jarcode
a17151dd5d fixed glava-config LuaJIT builds, stripping 2019-09-09 20:54:57 -07:00
Jarcode
05067c74ed Update readme 2019-09-09 14:26:28 -07:00
Jarcode
112e371a9b Handle selecting lua implementation in meson.build 2019-09-09 14:23:52 -07:00
Jarcode
aa875b2a81 Temporarily disable glava-config by default 2019-09-09 14:05:26 -07:00
Jarcode
5ff291fe51 Document build changes 2019-09-08 18:27:27 -07:00
Jarcode
563792599a Added Clang build support 2019-09-08 18:25:04 -07:00
Jarcode
d821eeaf0a improve header & macro consistency 2019-09-08 17:08:41 -07:00
Jarcode
0592a422a1 manually fix warning-prone code in glad output 2019-09-08 16:43:04 -07:00
Jarcode
04ca255cd2 Remove overflow warning surpression 2019-09-08 16:38:46 -07:00
Jarcode
859bfb0b31 Correct GLX function pointer loading links 2019-09-08 14:55:19 -07:00
Levi Webb
25942ec6a3 remove github link 2019-09-08 13:07:26 -07:00
Levi Webb
84cf77890f sponsor information
(I just wanted the fancy heart button)
2019-09-08 13:05:58 -07:00
Jarcode
20debf2cce Fix Gtk3 deprecation warnings in glava-config, add lua contributing guidelines 2019-09-08 04:33:19 -07:00
Jarcode
0695c9cec0 glava-config entry progress (incomplete), handle account rename links in documentation 2019-09-07 22:44:36 -07:00
Jarcode
e46967cc02 fix glava-config gtk feature detection 2019-09-07 02:45:30 -07:00
Jarcode
90d275f2cd glava-config widget generation (incomplete) 2019-09-07 02:32:21 -07:00
Jarcode
4bfbc859f8 add icon support for glava window 2019-09-04 15:36:25 -07:00
Jarcode
7d6d7e2987 glava-config build process and initial widget layout 2019-09-04 10:46:56 -07:00
Jarcode
a38f79e279 Prefer system defaults for desktop window settings 2019-09-01 21:46:50 -07:00
Jarcode
1974869b5f workaround old meson build issues, see #131 2019-09-01 19:37:37 -07:00
Jarcode
69f25eb44b Fix include issue with older Meson versions, see #131 2019-09-01 15:30:52 -07:00
Jarcode
129d4d8c91 Fix file descriptor leaks, obs plugin settings 2019-09-01 06:25:07 -07:00
Jarcode
6d74062cb3 formatting and comment removal 2019-08-28 20:32:16 -07:00
Jarcode
c9c134a3e2 glava-obs functionality, see #129 2019-08-28 20:18:47 -07:00
Jarcode
5de224c3e1 Made renderer structure private, update Makefile, closes #130 2019-08-28 15:20:51 -07:00
Jarcode
0a4d0cb099 Update description 2019-08-28 01:14:48 -07:00
Jarcode
cc1d673e55 Update readme 2019-08-28 00:57:29 -07:00
Jarcode
99f0274e07 Makefile compatibility fix for install prefix 2019-08-28 00:54:36 -07:00
Jarcode
69ebfe9714 Refactor renderer -> glava_renderer, renderer_handle -> glava_handle 2019-08-28 00:49:56 -07:00
Jarcode
f562bc690d Shared library rework & external async API support 2019-08-28 00:24:59 -07:00
Jarcode
5630e2314b Update readme 2019-08-27 00:43:03 -07:00
Jarcode
617424c3fa Update readme 2019-08-27 00:41:40 -07:00
Jarcode
14029d784c Use proper channel count bultin 2019-08-27 00:21:26 -07:00
Jarcode
7fcf288a5b unflip bars by default 2019-08-27 00:18:29 -07:00
Jarcode
9d4674d680 Added support for mono bars rendering, closes #123 2019-08-26 23:03:22 -07:00
Jarcode
e8e8b1f701 Added time uniform requests, closes #121 2019-08-26 21:40:43 -07:00
Jarcode
3207db6d27 Explicitly free offscreen pixmap 2019-08-26 20:59:12 -07:00
Jarcode
6d48f95995 Update readme with Make instructions 2019-08-26 20:39:08 -07:00
Jarcode
ce77587e3a Warning fixes and supression, glad header update 2019-08-26 20:27:11 -07:00
Jarcode
0358143bb7 Added signal reloading and legacy makefile support 2019-08-26 20:11:13 -07:00
Jarcode
b4cdcb3120 Added missing premultiply steps, closes #127 2019-08-22 17:55:49 -07:00
Robin B
f1284a6835 Mention old meson behavior in README instructions (#119) 2019-06-12 23:38:51 +02:00
Levi Webb
1a7a6deb49 Merge pull request #113 from LiteracyFanatic/LiteracyFanatic-fix-parameter-typo
Rename parameter with typo
2019-04-24 03:40:03 -07:00
Jordan Cannon
8259d8bb74 Rename parameter with typo
While reading through render.c I noticed a small typo in one of the parameter names. I assume that udaa was intended to be udata. Since the parameter is not actually used in transform_smooth at present, I have renamed it to _ as done in some of the other transform functions.
2019-04-22 23:43:04 -05:00
Robin Broda
26d36170de glava-config: Use X11 as rendering backend 2019-04-04 01:50:22 +02:00
Robin Broda
dc2f08a21a Add glava-config configuration program 2019-04-04 01:36:05 +02:00
Jarcode
6d68a1923f Merge branch 'master' of https://github.com/wacossusca34/glava 2019-04-03 16:07:53 -07:00
Jarcode
eca50058a2 Fixed standalone shader path for meson 2019-04-03 16:07:41 -07:00
Robin Broda
b6edf9700a Optimize compatibility makefile 2019-04-04 00:56:08 +02:00
Robin Broda
b2b3d1017a Fix shaderdir passed to glava 2019-04-04 00:54:10 +02:00
Robin Broda
6e60bcc698 Change build system to meson 2019-04-04 00:48:06 +02:00
Jarcode
c766c574a6 Added fg/bg pipe defaults, fixed parsing bugs, fixed broken smoothing defaults 2019-04-03 01:20:45 -07:00
Jarcode
d72deb8fae Merge branch 'master' of https://github.com/wacossusca34/glava 2019-04-02 18:47:32 -07:00
Jarcode
bf2c5cfcbd Fixed invert default behaviour for radial, closes #111 2019-04-02 18:47:04 -07:00
Levi Webb
d374192223 Update README.md 2019-04-02 18:38:39 -07:00
Levi Webb
67305d419e Update README.md 2019-04-02 18:07:14 -07:00
Jarcode
a15292be64 Add prefix guidelines 2019-04-02 17:18:41 -07:00
Jarcode
8cb183fb9a Fixed emission bug with @n:(...) syntax 2019-04-02 16:20:21 -07:00
Jarcode
d1028c24da Fixed recursive pipe parsing, use proper pipe uniform prefix 2019-04-02 16:01:54 -07:00
Jarcode
452deea23d Handle preceding operators for pipe defaults 2019-04-02 15:45:20 -07:00
Jarcode
13e9f051ce Emit #line directives, refactor prefixes '__' -> '_', fix 'bars' outline 2019-04-02 03:22:46 -07:00
Jarcode
4b71de19c4 Fixed buggy outlines for 'bars' 2019-03-30 15:47:28 -07:00
Jarcode
ad66445c28 deprecate --stdin 2019-03-15 00:42:43 -07:00
Jarcode
706ec0fcd4 properly handle default pipe names 2019-03-15 00:32:33 -07:00
Jarcode
315063eb9c sanitize --pipe input 2019-03-14 22:07:09 -07:00
Jarcode
4cdf1f13f9 Allow underscores in @ syntax 2019-03-14 22:00:42 -07:00
Jarcode
06644549e2 remove unnessecary strdup 2019-03-14 21:57:26 -07:00
Jarcode
0acf7d4188 Migrate builtin macros to '__' prefix 2019-03-14 21:51:58 -07:00
Jarcode
aa56b89ddf Add --pipe flag 2019-03-14 21:39:50 -07:00
Jarcode
04a4da4993 Properly print formatting errors 2019-03-12 20:26:23 -07:00
Jarcode
0dc321900d Handle invalid color input from stdin 2019-03-12 20:25:07 -07:00
Levi Webb
299605ad1a removing trailing character for setting in test case 2019-03-12 18:24:41 -07:00
Levi Webb
c15535cf3f Update README.md 2019-03-12 17:41:27 -07:00
Levi Webb
dbadfffd17 Update README.md 2019-03-12 17:41:09 -07:00
Jarcode
5c9d974252 Fixed extglob in Makefile 2019-03-12 16:30:03 -07:00
Jarcode
d640ac5d3c Added basic test mode for debug builds 2019-03-12 16:18:46 -07:00
Levi Webb
094dec9b00 Fix spelling error breaking native transparency 2019-03-12 13:21:21 -07:00
Jarcode
c485d80013 Deprecate 'nativeonly', fix passing garbage data for vec4 stdin 2019-03-12 12:35:46 -07:00
Levi Webb
b37ab5ba09 Merge pull request #106 from wacossusca34/circle-fix-undefined-fragcolor
Prevent `fragment` from being potentially undefined (Fixes #105)
2019-03-12 11:34:33 -07:00
Robin B
1a595c2f19 Prevent fragment from being potentially undefined leading to driver-specific noise
This fixes noise in the center of the circle shader, where `d >= -(float(C_LINE) / 2.0F)` is false
2019-03-12 15:54:37 +01:00
Jarcode
c9dac68bb4 Allow '--stdin' to work properly with pipes, see #77 2019-03-10 16:36:13 -07:00
Jarcode
c14c2a835a Added support for reading uniforms from stdin, see #77 2019-03-10 16:13:34 -07:00
Jarcode
6f688e8b4d Restore new smoothing parameters 2019-03-09 21:14:08 -08:00
Jarcode
bfea8e167a Load system config for 'smooth_parameters.glsl' 2019-03-09 21:13:27 -08:00
Jarcode
d07b93d54e Fix clickthrough for reparented windows, closes #80 2019-03-09 16:55:03 -08:00
Jarcode
f7170afb75 Fixed attached base frequencies in 'bars' 2019-03-09 15:27:35 -08:00
Jarcode
8ccbf22732 Merge branch 'master' of https://github.com/wacossusca34/glava 2019-03-09 14:52:58 -08:00
Jarcode
5fdf0545a8 Added GLSL sampling modes 2019-03-09 14:52:38 -08:00
Levi Webb
b77d1a7b3a Update build requirements, closes #102 2019-03-02 11:26:03 -08:00
Jarcode
f87c70af2b Disable second 'graph' pass if nessecary (& other formatting), see #97 2019-02-05 17:45:17 -08:00
Jarcode
7c30c4a78a Added support for disabling shader passes, see #97 2019-02-04 22:07:26 -08:00
Levi Webb
bc2db4a415 Merge pull request #97 from arch1t3cht30/graph_tweaks
Anti-aliasing and better joining for the graph module
2019-02-02 14:58:33 -08:00
Theo Müller
3ff1097941 Fix inverting in antialias pass 2019-02-01 22:40:46 +01:00
Theo Müller
041bfdfd55 Make anti-aliasing work with invert 2019-02-01 20:19:15 +01:00
Theo Müller
8cbf7bc579 Cubic interpolation for joining graph channels 2019-01-31 12:05:05 +01:00
Theo Müller
91530dfc49 Make joining optional 2019-01-30 20:26:35 +01:00
Theo Müller
a39a1324e1 Add anti-aliasing option for graph 2019-01-30 19:59:51 +01:00
Theo Müller
140d98b404 Join middle borders instead of clamping them down 2019-01-30 19:59:51 +01:00
Levi Webb
f235362f44 Merge pull request #94 from dan-santana/musl-build
Fix musl build
2019-01-04 21:45:19 -08:00
Levi Webb
32853b73d8 Merge pull request #93 from dan-santana/master
Use SONAMEs when loading GLX libs dinamically
2019-01-04 21:43:29 -08:00
Daniel Santana
4705699bb6 Fix musl build 2019-01-04 22:23:52 -02:00
Daniel Santana
3ef23f0db8 Use SONAMEs when loading GLX libs dinamically 2019-01-04 21:57:52 -02:00
Levi Webb
4d51ccbd54 Update README.md 2019-01-01 14:00:11 -08:00
Levi Webb
6c02d15c80 Force bash in Makefile, see #91 2019-01-01 12:47:57 -08:00
Levi Webb
40dac829cc Merge pull request #92 from Aaahh/patch-7
Check SHADERDIR exist
2019-01-01 12:46:06 -08:00
Aaahh Ahh
bde3101c42 Check SHADERDIR exist
Unity has incorrectly set XDG_CONFIG_DIRS to a path that does not exist
2018-12-30 16:49:40 -05:00
Levi Webb
f2857e5f21 Merge pull request #83 from jubalh/readme
Add install instructions
2018-12-17 13:09:01 -08:00
Levi Webb
66a9b09b10 Merge pull request #82 from jubalh/giti
Add gitignore
2018-11-30 18:15:16 -08:00
Michael Vetter
31a39b6ecd Add install instructions
Some distros have packages for glava. Let's mention them.
2018-11-30 14:49:13 +01:00
Michael Vetter
dd0f5bf0f9 Add gitignore
Add files that we don't want to have in version control so devs have it
easier.
2018-11-30 14:42:58 +01:00
Jarcode
1337253257 Added audio backends, fifo support, see #78 2018-11-17 21:03:02 -08:00
Jarcode
76325cb126 Fixed uninitialized GLSL output variable, closes #70 2018-11-13 17:04:12 -08:00
Jarcode
426f70f579 Added --request parameter, fixed reading directives at end of file, limited most output to --verbose option 2018-11-02 16:27:54 -07:00
Jarcode
1d2b014da7 Fix leak with 'xroot' transparency 2018-11-02 13:09:48 -07:00
Jarcode
6670c54827 Cleaned up and optimized 'graph' module and its settings, fixes #76 2018-10-29 20:16:40 -07:00
Jarcode
f4ad41df32 Added '@' include specifier, #define workaround, closes #74, closes #75 2018-10-26 21:40:31 -07:00
Jarcode
93df114308 update wording 2018-10-26 18:42:01 -07:00
Jarcode
d21edcfb29 update contribution guidelines 2018-10-26 18:41:00 -07:00
Jarcode
e0b4f7d6c7 Merge branch 'master' of https://github.com/wacossusca34/glava 2018-10-26 18:35:44 -07:00
Jarcode
386d32076c Style changes and contribution guidelines 2018-10-26 18:35:29 -07:00
Robin B
52af6ac173 radial: configurable offsets (#73)
* Add: Offset radial visualizer (config)

* Add: Offset radial visualizer
2018-10-25 18:56:59 +02:00
Jarcode
3da1fb34dd stopped using centered coords for radial module, see #69 2018-10-21 12:13:56 -07:00
Jarcode
2c99124556 Fixed various static leaks 2018-10-21 10:28:06 -07:00
Jarcode
9d1c3a177c Fixed critical bug that caused GLava to stop rendering if it was obstructed 2018-10-09 17:43:15 -07:00
Jarcode
db625cf00c Add flags for i3 2018-10-08 17:52:04 -07:00
Jarcode
1845fad7fd update README.md 2018-10-08 17:51:18 -07:00
Jarcode
25c1701ce6 Update --desktop flags for i3 and awesome 2018-10-08 17:49:17 -07:00
Jarcode
b627219109 Updated makefile with various improvements 2018-10-08 17:36:17 -07:00
Jarcode
3dfbdb5127 Removed hard dependency on glad submodule 2018-10-08 17:32:44 -07:00
Jarcode
7bb7913bd3 Removed -march=native from Makefile 2018-10-08 12:43:18 -07:00
Jarcode
1a1cbc9cc8 Fixed changes to _XROOTPMAP_ID not updating background with "xroot" transparency 2018-10-08 11:53:58 -07:00
Jarcode
83a94e3eb4 Handle window map_state and VisibilityNotify events, closes #68 2018-10-08 11:18:45 -07:00
Jarcode
acdbb8f3b5 updated README.md 2018-10-07 12:04:00 -07:00
Jarcode
829c1be370 Merge branch 'master' of https://github.com/wacossusca34/glava 2018-10-07 08:20:33 -07:00
Jarcode
261ba8ded5 Added more meaningful error messages to the GLX backend 2018-10-07 08:20:19 -07:00
Levi Webb
fc9c74d256 Merge pull request #66 from coderobe/patch-readme
README: Add required X extensions
2018-10-06 15:21:11 -07:00
Levi Webb
a697af9a0a Merge pull request #67 from coderobe/patch-readme-archlinux
README: Mention Arch Linux binary release (community)
2018-10-06 15:20:40 -07:00
Robin B
437aa146a4 README: Mention Arch Linux binary release (community)
GLava is now part of the official Arch Linux [community] repository, maintained by me
2018-10-06 20:41:11 +02:00
Robin B
1c2f362219 README: Add required X extensions 2018-10-06 19:28:32 +02:00
Levi Webb
7dfb68fb1a Update README.md 2018-10-05 21:38:40 -07:00
Jarcode
353c3bd62f Added support for unmanaged windows, see #65 and #4 2018-10-04 20:14:37 -07:00
Jarcode
e4b16cdbb6 Added option to render bars on left/right sides, see #64 2018-10-03 17:45:48 -07:00
Jarcode
8102f99683 Added option to vertically flip bars output, see #64 2018-10-03 17:21:53 -07:00
Jarcode
14747b1e75 Use default PulseAudio buffer sizes when reading input from server, see #63 2018-09-30 14:50:10 -07:00
Levi Webb
217d5c9eea Fix spelling error 2018-09-26 19:04:44 -07:00
Jarcode
3be916f157 Fixed clickthrough issue on Openbox and XFCE, closes #61 2018-09-17 19:23:06 -07:00
Jarcode
bc955a5b3d Merge branch 'master' of https://github.com/wacossusca34/glava 2018-09-12 21:01:46 -07:00
Jarcode
c7ad0a5024 Added signal handler for SIGINT and SIGTERM 2018-09-12 21:01:12 -07:00
Levi Webb
83ad0d8f8a Merge pull request #60 from Smarthard/master
bars COLOR refactored & alpha channel support added
2018-09-11 21:31:25 -07:00
Smarthard
53f7352347 bars COLOR refactored & alpha channel support added 2018-09-11 19:01:24 +03:00
Jarcode
643b0cf3f5 Added detection for variable changes in Makefile, see #59 2018-09-09 19:26:32 -07:00
Jarcode
11a6137370 Added graceful renderer and audio thread destruction, fixes #47 2018-09-09 07:54:51 -07:00
Jarcode
d4b333e48c removed pointless message 2018-09-08 21:25:11 -07:00
Jarcode
d49be40fd3 Added --desktop flag with presets, added setclickthrough option, removed kde previous kde changes, updated readme 2018-09-08 21:06:18 -07:00
Jarcode
fb1d85dbf9 Added potential fixes for KDE 2018-09-08 14:05:40 -07:00
Levi Webb
31ebab0549 Merge pull request #56 from Aaahh/patch-5
Stray letter
2018-08-15 22:13:24 -07:00
Aaahh Ahh
3f621420a3 Stray letter 2018-08-15 23:14:54 -04:00
Levi Webb
9a9c6eaa37 Update README.md 2018-08-13 19:28:06 -07:00
Jarcode
3e06fa683a Updated readme with XFCE4 instructions 2018-08-06 09:12:26 -07:00
Jarcode
b971c1c8f9 Updated readme with XFCE4 instructions 2018-08-05 22:30:26 -07:00
Jarcode
45b614a692 Added configuration option for fullscreen check 2018-08-05 21:24:18 -07:00
Jarcode
fc80db664b fix startup resource leak 2018-08-05 19:47:12 -07:00
Jarcode
75a3affbc0 changed defaults to disable mirroring 2018-08-05 19:27:50 -07:00
Jarcode
198596eaee Wait for EWMH WM to add _NET_SUPPORTING_WM_CHECK property, addresses #54 2018-08-05 18:22:07 -07:00
Jarcode
e694261f4d Merge branch 'master' of https://github.com/wacossusca34/glava 2018-08-05 17:39:58 -07:00
Jarcode
8c3b9a5f21 Added audio buffer mirroring, closes #53 2018-08-05 17:38:59 -07:00
Levi Webb
ebade264ea Fix hyperlink text in license
GitHub displays license information at the head of the repostiory contents for certain licenses, however this breaks when license text is slightly altered. In this case, github only works with the GPLv3 when the license text contains `http:` instead of `https:`!
2018-06-02 09:42:07 -07:00
Jarcode
469b8ec7ad Merge branch 'master' of https://github.com/wacossusca34/glava 2018-06-02 09:29:51 -07:00
Jarcode
4cc8a5e3ba Added "pinned" option for setxwinstate, addresses #4 2018-06-02 09:28:13 -07:00
Levi Webb
309b1beeec Merge pull request #44 from coderobe/patch-1
Fix fatal make issue when using XDG_CONFIG_DIRS
2018-05-07 14:04:14 -07:00
Robin B
7b72a28f19 Fix fatal make issue when using XDG_CONFIG_DIRS 2018-05-07 11:48:17 +02:00
Levi Webb
b83c7cc59e Merge pull request #43 from Aaahh/patch-3
Update README.md
2018-05-02 06:33:29 -07:00
Aaahh Ahh
6318c57ff7 Update README.md 2018-04-27 17:30:07 -04:00
Jarcode
279437dcd1 Update README with accurate instructions for Ubuntu/Debian 2018-04-06 17:05:58 -07:00
Jarcode
869ebba6b4 Added dynamic symbol loading for GLX functions, closes #41 2018-04-06 17:02:43 -07:00
Jarcode
dfd16f9e22 Properly compare desktop window types, closes #37 2018-03-19 21:45:19 -07:00
Jarcode
b446ac99c9 Fixed resource leak associated with xlib usage, see #33 2018-03-18 22:01:03 -07:00
Jarcode
8024e308d8 fixed some code style issues 2018-03-18 17:27:36 -07:00
Jarcode
dd5586a76e cleaning up comment formatting & spelling 2018-03-18 17:23:45 -07:00
Jarcode
20e755fbcb Switched to using git tags for versioning 2018-03-18 17:00:14 -07:00
Jarcode
ccf3c7b169 updated version 2018-03-18 16:26:52 -07:00
Jarcode
4be89c3337 force GLX backend even on XWayland sessions 2018-03-18 16:22:23 -07:00
Jarcode
2220946a2f Updated readme with compatibility information 2018-03-18 16:19:25 -07:00
Jarcode
9fb80be00f Merge branch 'unstable' 2018-03-18 12:26:31 -07:00
Jarcode
283afaaaca Cleanup & implementation of #32 2018-03-18 12:20:20 -07:00
Jarcode
5ac2cc4a94 Removed changelog in favour of GitHub releases 2018-03-04 13:39:39 -08:00
Jarcode
273cca946e Merge branch 'master' of https://github.com/wacossusca34/glava 2018-03-04 12:49:51 -08:00
Jarcode
95ceadf0e1 Merge branch 'unstable' 2018-03-04 12:49:23 -08:00
Jarcode
f32f0ded0f Version increase 2018-03-04 12:41:51 -08:00
Jarcode
3a1dcac8c9 Fixed texture load order, closes #28 2018-03-04 12:40:17 -08:00
Levi Webb
8adc8fe459 Merge pull request #26 from coderobe/master
Fix LDFLAGS
2018-03-02 17:41:49 -08:00
Robin Broda
e2f32a14cf Fix LDFLAGS 2018-03-02 18:40:35 +01:00
Levi Webb
318b4b3395 Update README.md 2018-02-20 19:05:00 -08:00
Levi Webb
b89fcd2e44 Merge pull request #23 from Aaahh/patch-3
Update Package names
2018-02-17 17:18:15 -08:00
Aaahh Ahh
baed3086d0 Update Package names
libpulse and libxext6-dev don't exist, also add libglx0 as it's not installed by default
2018-02-17 15:25:36 -05:00
Jarcode
b4fe4b5c97 stopped using the word currently frequently 2018-02-12 17:00:07 -08:00
Jarcode
0849cb485e Merge branch 'unstable' 2018-02-12 16:50:29 -08:00
Jarcode
c086222fb2 updated changelog 2018-02-12 16:50:08 -08:00
Jarcode
639c6d7fe9 Merge branch 'unstable' 2018-02-12 16:41:18 -08:00
Jarcode
22bc7c446c Fixed window class not being set by glx backend, + other fixes 2018-02-12 16:36:27 -08:00
Jarcode
b86870e0fd Added passes for premultiplied alpha, fixed alpha blending for native transparency 2018-02-12 16:25:32 -08:00
Jarcode
f021457abd Added GLX window creation backend, addresses #18 2018-02-12 12:47:59 -08:00
Jarcode
4fd4ce4c3f added 'glfw_wcb.c' 2018-02-12 07:17:33 -08:00
Jarcode
e5f5671acc Added gl_wcb interface to abstract out glfw and glx usage 2018-02-11 20:43:52 -08:00
Jarcode
79d06b070e Fixed potetial segmentation fault for xwin_should_render, addresses #19 2018-02-11 12:02:26 -08:00
Jarcode
3df89da8bb fixed alpha blending for 'radial' inner circle 2018-02-11 11:42:26 -08:00
Jarcode
79b99b09be refactored X11 code in 'render.c' and 'xwin.c' 2018-02-11 09:38:23 -08:00
Jarcode
0f4eed5e20 Updated readme with list of compatibility issues 2018-02-08 17:25:57 -08:00
Jarcode
ae09561d11 added test case for mutter gaurd window, addresses #11 and #18 2018-02-08 17:13:38 -08:00
Jarcode
33aa5274ae Merge branch 'master' into unstable 2018-02-08 16:52:31 -08:00
Jarcode
caee6a739c added case for mutter gaurd window 2018-02-08 16:50:52 -08:00
Jarcode
c8cce220d8 fixed GLFW_MAXIMIZED macro for GLFW 3.1 2018-02-08 06:36:40 -08:00
Jarcode
be63f40dc2 Merge branch 'unstable' 2018-02-07 20:57:00 -08:00
Jarcode
f5a9f801d1 Backported for GLFW 3.1, fixes #13, *again* 2018-02-07 20:56:41 -08:00
Jarcode
53bd657226 Added debian(-based) compile instructions 2018-02-07 20:42:05 -08:00
Jarcode
e6f2507b53 Merge branch 'unstable' 2018-02-07 20:23:15 -08:00
Jarcode
2805528d43 fixed directive issue 2018-02-07 20:22:54 -08:00
Jarcode
1de3a5215b updated changelog 2018-02-07 20:18:05 -08:00
Jarcode
1db4ea3ded updated changelog 2018-02-07 20:17:43 -08:00
Jarcode
901cdb7d67 Merge branch 'unstable' 2018-02-07 20:06:09 -08:00
Jarcode
d82762a471 changed version, added -V (--version) flag 2018-02-07 20:02:24 -08:00
Jarcode
ec6d4d0c37 exempted glsl color syntax from string contents 2018-02-07 19:53:23 -08:00
Jarcode
aea30abf67 added RGBA hex color parsing for GLSL, fixed alpha blending bug 2018-02-07 19:29:56 -08:00
Jarcode
79d391938d moved hex color parsing to ext_parse_color, removed debug message 2018-02-07 17:46:34 -08:00
Jarcode
8c3a404f37 fixed include order in xwin.c, fixes #13 2018-02-07 17:17:58 -08:00
Jarcode
f539f18135 Always define GLFW_EXPOSE_NATIVE_GLX 2018-02-07 17:12:45 -08:00
Jarcode
ab55156826 Set window types and state ealier during initialization, fixed #17 2018-02-07 16:58:39 -08:00
Jarcode
5e813e25a9 Changed position of 'glfwShowWindow' call, related to #17 2018-02-07 16:41:12 -08:00
Jarcode
5ec584f35d Updated version 2018-02-06 19:06:30 -08:00
Jarcode
83f5583eeb Updated version 2018-02-06 19:04:25 -08:00
Jarcode
1c2e633bb6 added changelog 2018-02-06 19:00:12 -08:00
Jarcode
1e2aad05b5 Added support for old versions of 'glfw3native.h', fixes #13 2018-02-06 18:22:17 -08:00
Jarcode
8359ad9a98 Added configurable solid background color, closes #16 2018-02-06 18:13:23 -08:00
Jarcode
92f575adfd Removed unessecary 'XOpenDisplay' calls with 'glfwGetX11Display', addresses #15 2018-02-06 16:58:38 -08:00
Jarcode
916ead982e updated readme 2018-02-06 16:42:35 -08:00
106 changed files with 21636 additions and 2997 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
liberapay: Jarcode

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

55
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,55 @@
## Code Style
GLava uses a bastardized version of the [linux kernel style](https://www.kernel.org/doc/html/v4.10/process/coding-style.html), with the following modifications:
* Opening braces are _always_ on the same line as the token it is associated with (`if`, `while`, labels, functions). The only time this is not honoured is when a set of braces has no associated token (ie. scope usage).
* Indentation is 4 spaces, and tabs are forbidden
* The content of a `switch` statement, including `case` labels, are indented.
* Preprocessor directives should inherit the same intentation level as the code it resides in.
* Align tokens in repetitious lines by padding spacing between tokens.
The following rules of the linux style are **ignored**:
* Function size and control flow recommendations
* Comment formatting rules
* Any other rules regarding preprocessor directives
Naming rules and the usage of `typedef` is strictly honoured from the Linux style. Anything not mentioned here is probably subjective and won't hurt your chances of getting a PR accepted.
If you use GNU Emacs, the above style can be configured via the following elisp:
```emacs
(setq-default c-basic-offset 4)
(setq c-default-style "linux")
(setq tab-stop-list (number-sequence 4 200 4))
(c-set-offset (quote cpp-macro) 0 nil)
(c-set-offset 'case-label '+)
```
## Lua
If you are contributing to `glava-config`, we use a style close to standard Lua with some emphasis on compact table definitions and readability
* If an opening brace has no tokens preceding it on the same line, take the first entry in the table and place it on the same line following the brace
* If there are multiple closing braces, combine them onto the same line
* Always have exactly one space between braces and other tokens, but zero for brackets and parenthesis
* Use two-space indentation with no tabs
## Shaders
If you author and maintain your own shader module for GLava, you are free to use your preferred code style. Otherwise, shaders follow the same style as GLava's C sources.
The only exception to this is a hard rule for builtin prefixes. Variables should _never_ start with an underscore, as `__` are reserved by the GLSL compiler, and `_` are reserved for GLava builtins and namespaces.
## Pull Requests
You are free to make pull requests for any change, even if you are not sure if the proposed changes are appropriate. @jarcode-foss and/or @coderobe will be able to suggest changes or commentary on the PR if there is a reason it is not acceptable.
## Conduct
Engagement in the issue tracker and pull requests simply requires participants remain rational and on-topic.

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

101
Makefile
View File

@@ -1,82 +1,53 @@
src = $(wildcard *.c)
obj = $(src:.c=.o)
.PHONY: all install clean ninja
# Build type parameter
# In case these were specified explicitly as options instead of environment variables, export them to child processes
export DESTDIR
export CFLAGS
BUILD_DIR = build
MESON_CONF = $(BUILD_DIR) -Ddisable_obs=true -Ddisable_config=true --prefix /usr
# Support assigning standalone/debug builds as the old Makefile did, otherwise complain
ifeq ($(BUILD),debug)
CFLAGS_BUILD = -O1 -ggdb -Wall -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls
GLAD_GEN = c-debug
ASAN = -lasan
MESON_CONF += --buildtype=debug
else
CFLAGS_BUILD = -O2 -march=native
GLAD_GEN = c
endif
# Detect OS if not specified (OSX, Linux, BSD are supported)
ifndef INSTALL
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
INSTALL = osx
else
INSTALL = unix
ifdef BUILD
$(warning WARNING: ignoring build option '$(BUILD)' in compatibility Makefile)
endif
endif
# Install type parameter
ifeq ($(INSTALL),standalone)
CFLAGS_INSTALL = -DGLAVA_STANDALONE
endif
ifeq ($(INSTALL),unix)
CFLAGS_INSTALL = -DGLAVA_UNIX
ifdef XDG_CONFIG_DIRS
SHADER_DIR = $(firstword $(subst :, ,$XDG_CONFIG_DIR))/glava
else
SHADER_DIR = etc/xdg/glava
MESON_CONF += -Dstandalone=true
else
ifdef INSTALL
$(warning WARNING: ignoring install option '$(INSTALL)' in compatibility Makefile)
endif
endif
ifeq ($(INSTALL),osx)
CFLAGS_INSTALL = -DGLAVA_OSX
SHADER_DIR = Library/glava
endif
# Store relevant variables that may change depending on the environment or user input
STATE = $(BUILD),$(INSTALL),$(PYTHON),$(CC),$(CFLAGS),$(DESTDIR)
# Only update the file if the contents changed, `make` just looks at the timestamp
$(shell if [[ ! -e build_state ]]; then touch build_state; fi)
$(shell if [ '$(STATE)' != "`cat build_state`" ]; then echo '$(STATE)' > build_state; fi)
LDFLAGS = $(ASAN) -lpulse -lpulse-simple -pthread -lglfw -ldl -lm -lX11 -lXext
all: ninja
PYTHON = python
# Rebuild if the makefile state changes to maintain old behaviour and smooth rebuilds with altered parameters
build: build_state
$(warning !!PACKAGE MAINTAINER NOTICE!!)
$(warning Configuring build for compatibility with old makefile. Some new features may be missing.)
$(warning If you are a package maintainer consider using meson directly!)
@rm -rf $(BUILD_DIR)
meson $(BUILD_DIR)
meson configure $(MESON_CONF)
GLAD_INSTALL_DIR = glad
GLAD_SRCFILE = ./glad/src/glad.c
GLAD_ARGS = --generator=$(GLAD_GEN) --extensions=GL_EXT_framebuffer_multisample,GL_EXT_texture_filter_anisotropic
CFLAGS_COMMON = -I glad/include
CFLAGS_USE = $(CFLAGS_COMMON) $(CFLAGS_BUILD) $(CFLAGS_INSTALL) $(CFLAGS)
ninja: build
ninja -C $(BUILD_DIR)
all: glava
%.o: %.c glad.o
$(CC) $(CFLAGS_USE) -o $@ -c $(firstword $<)
glava: $(obj)
$(CC) -o glava $(obj) glad.o $(LDFLAGS)
glad.o:
cd $(GLAD_INSTALL_DIR) && $(PYTHON) -m glad $(GLAD_ARGS) --out-path=.
$(CC) $(CFLAGS_USE) -o glad.o $(GLAD_SRCFILE) -c
.PHONY: clean
clean:
rm -f $(obj) glava glad.o
.PHONY: install
install:
install -Dm755 glava "$(DESTDIR)/usr/bin/glava"
install -d "$(DESTDIR)/$(SHADER_DIR)"
cp -Rv shaders/* "$(DESTDIR)/$(SHADER_DIR)"
.PHONY: uninstall
uninstall:
rm /usr/bin/glava
rm -rf $(SHADER_DIR)/glava
ninja -C build install
clean:
rm -rf $(BUILD_DIR)

139
README.md
View File

@@ -1,44 +1,67 @@
## GLava
<img align="left" width="200" height="200" src="https://thumbs.gfycat.com/DefiantInformalIndianspinyloach-size_restricted.gif" />
GLava is an OpenGL audio spectrum visualizer. Its primary use case is for desktop widgets or backgrounds. Displayed to the left is the `radial` shader module running in GLava.
**GLava** is a general-purpose, highly configurable OpenGL audio spectrum visualizer for X11. Displayed to the left is the `radial` shader module, or for a more extensive demonstration [see this demo](https://streamable.com/dgpj8). Development is active, and reporting issues is encouranged.
**Compiling** (Or use the [`glava-git` AUR package](https://aur.archlinux.org/packages/glava-git/))**:**
**Compiling:**
```bash
$ git clone --recursive https://github.com/wacossusca34/glava
$ git clone https://github.com/jarcode-foss/glava
$ cd glava
$ make
$ sudo make install
$ glava
$ meson build --prefix /usr
$ ninja -C build
$ sudo ninja -C build install
```
You can pass `BUILD=debug` to the makefile for debug builds of both glad and glava, and you can manually specify install targets with `INSTALL=...`, possible arguments are `unix` for FHS compliant Linux and BSD distros, `osx` for Mac OSX, and `standalone` which allows you to run GLava in the build directory.
You can pass `-Dbuildtype=debug` to Meson for debug builds of glava, and `-Dstandalone=true` to run glava directly from the `build` directory.
Note that versions since `2.0` use Meson for the build system, although the `Makefile` will remain to work identically to earlier `1.xx` releases (with new features disabled). Package maintainers are encouraged to use Meson directly instead of the Make wrapper.
**Requirements:**
- X11
- X11 (Xext, Xcomposite, & Xrender)
- PulseAudio
- GLFW 3.2+
- Linux or BSD
- libBlocksRuntime if compiling with Clang
**Configuration tool requirements:**
- Lua (5.3 by default, change with `-Dlua_version=...`), and the following lua libraries:
- Lua GObject Introspection (LGI)
- LuaFilesystem (LFS)
- GTK+ 3
**Additional compile time requirements:**
- glad (included as a submodule)
- python (required to generate bindings with glad)
- GCC (this program uses GNU C features)
- Meson
- OBS (disable with `-Ddisable-obs=true`)
## [Configuration](https://github.com/wacossusca34/glava/wiki)
**Optional requirements:**
GLava will start by looking for an entry point in the user configuration folder (`~/.config/glava/rc.glsl`\*), and will fall back to loading from the shader installation folder (`/etc/xdg/glava`\*). The entry point will specify a module to load and should set global configuration variables. Configuration for specific modules can be done in their respective `.glsl` files, which the module itself will include.
- GLFW 3.1+ (optional, enable with `-Denable_glfw=true`)
**Ubuntu/Debian users:** the following command ensures you have all the needed packages and headers to compile GLava with the default feature set:
```bash
sudo apt-get install libgl1-mesa-dev libpulse0 libpulse-dev libxext6 libxext-dev libxrender-dev libxcomposite-dev liblua5.3-dev liblua5.3 lua-lgi lua-filesystem libobs0 libobs-dev meson build-essential gcc
```
Don't forget to run `sudo ldconfig` after installing.
## Installation
Some distributions have a package for `glava`. If your distribution is not listed please use the compilation instructions above.
- Arch Linux [`glava` package](https://www.archlinux.org/packages/community/x86_64/glava/), or [`glava-git` AUR package](https://aur.archlinux.org/packages/glava-git/)
- NixOS [package](https://github.com/NixOS/nixpkgs/blob/release-18.09/pkgs/applications/misc/glava/default.nix)
- openSUSE [package](https://build.opensuse.org/package/show/X11:Utilities/glava)
## [Configuration](https://github.com/jarcode-foss/glava/wiki)
GLava will start by looking for an entry point in the user configuration folder (`~/.config/glava/rc.glsl`), and will fall back to loading from the shader installation folder (`/etc/xdg/glava`). The entry point will specify a module to load and should set global configuration variables. Configuration for specific modules can be done in their respective `.glsl` files, which the module itself will include.
You should start by running `glava --copy-config`. This will copy over default configuration files and create symlinks to modules in your user config folder. GLava will either load system configuration files or the user provided ones, so it's not advised to copy these files selectively.
To embed GLava in your desktop (for EWMH compliant window managers), use `#request setxwintype "desktop"` and then position it accordingly with `#request setgeometry x y width height`. You may want to also use `#request setforcegeometry true` for some window managers.
To embed GLava in your desktop (for EWMH compliant window managers), run it with the `--desktop` flag and then position it accordingly with `#request setgeometry x y width height` in your `rc.glsl`.
\* On an XDG compliant Linux or BSD system. OSX will use `/Library/glava` and `~/Library/Preferences/glava` instead.
**Note for `#request setopacity`:** While most users will prefer the faster `xroot` transparency, GLFW 3.3 (unreleased) is needed in order to support the `native` transparency option (older versions still work). Arch users can install `glfw-x11-git` from the AUR and recompile GLava for this feature.
For more information, see the [main configuration page](https://github.com/jarcode-foss/glava/wiki).
## Desktop window compatibility
@@ -46,32 +69,60 @@ GLava aims to be compatible with _most_ EWMH compliant window managers. Below is
| WM | ! | Details
| :---: | --- | --- |
| GNOME (on X11) | ![-](https://placehold.it/15/118932/000000?text=+) | No notable issues
| Openbox (LXDE or standalone) | ![-](https://placehold.it/15/118932/000000?text=+) | Untested, but should work without issues
| Xfwm (XFCE) | ![-](https://placehold.it/15/118932/000000?text=+) | Untested, but should work without issues
| Fluxbox | ![-](https://placehold.it/15/118932/000000?text=+) | Untested, but should work without issues
| iceWM | ![-](https://placehold.it/15/118932/000000?text=+) | No notable issues
| kwin (KDE) | ![-](https://placehold.it/15/f03c15/000000?text=+) | [Issues with workspaces and stacking](https://github.com/wacossusca34/glava/issues/4), needs further testing
| i3 (and i3-gaps) | ![-](https://placehold.it/15/f03c15/000000?text=+) | [i3 does not respect the `"desktop"` window type](https://github.com/wacossusca34/glava/issues/6)
| Mutter (GNOME, Budgie) | ![-](https://placehold.it/15/118932/000000?text=+) | `"native"` (default) opacity should be used
| KWin (KDE) | ![-](https://placehold.it/15/118932/000000?text=+) | "Show Desktop" [temporarily hides GLava](https://github.com/jarcode-foss/glava/issues/4#issuecomment-419729184)
| Openbox (LXDE or standalone) | ![-](https://placehold.it/15/118932/000000?text=+) | No issues
| Xfwm (XFCE) | ![-](https://placehold.it/15/118932/000000?text=+) | No issues
| Fluxbox | ![-](https://placehold.it/15/118932/000000?text=+) | No issues
| IceWM | ![-](https://placehold.it/15/118932/000000?text=+) | No issues
| Bspwm | ![-](https://placehold.it/15/118932/000000?text=+) | No issues
| SpectrWM |
| Herbstluftwm | ![-](https://placehold.it/15/118932/000000?text=+) | `hc rule windowtype~'_NET_WM_WINDOW_TYPE_DESKTOP' manage=off` can be used to unmanage desktop windows
| Unity | ![-](https://placehold.it/15/118932/000000?text=+) | No issues
| AwesomeWM | ![-](https://placehold.it/15/118932/000000?text=+) | Defaults to unmanaged
| i3 (and i3-gaps) | ![-](https://placehold.it/15/118932/000000?text=+) | Defaults to unmanaged
| spectrwm | ![-](https://placehold.it/15/118932/000000?text=+) | Defaults to unmanaged
| EXWM | ![-](https://placehold.it/15/f03c15/000000?text=+) | EXWM does not have a desktop, and forces window decorations
| AwesomeWM | ![-](https://placehold.it/15/f03c15/000000?text=+) | Requires the WM to be restarted (`Super + Ctl + R`) in order for new desktop windows to behave correctly, can still be focused, may require other changes to config depending on layout
| Unity | ![-](https://placehold.it/15/1589F0/000000?text=+) | Needs testing
| Enlightenment | ![-](https://placehold.it/15/1589F0/000000?text=+) | Needs testing
| Bspwm | ![-](https://placehold.it/15/1589F0/000000?text=+) | Needs testing
| Herbstluftwm | ![-](https://placehold.it/15/1589F0/000000?text=+) | Needs testing
| xmonad | ![-](https://placehold.it/15/1589F0/000000?text=+) | Needs testing
| Xmonad | ![-](https://placehold.it/15/1589F0/000000?text=+) | Needs testing
| Any non EWMH-compliant WM | ![-](https://placehold.it/15/f03c15/000000?text=+) | Window types and hints will not work if the window manager does not support the EWMH standards.
Note that some WMs listed without issues have specific overrides when using the `--desktop` flag. See `shaders/env_*.glsl` files for details.
### [Demo](https://streamable.com/dgpj8)
## Reading from MPD's FIFO output
Above is the `graph` module, below is the `bars` module, `circle` to the left, and `radial` is to the right:
Add the following to your `~/.config/mpd.conf`:
<img src="https://thumbs.gfycat.com/LittleUniformGermanpinscher-size_restricted.gif">
```
audio_output {
type "fifo"
name "glava_fifo"
path "/tmp/mpd.fifo"
format "22050:16:2"
}
```
Note the `22050` sample rate -- this is the reccommended setting for GLava. Restart MPD (if nessecary) and start GLava with `glava --audio=fifo`.
## Using GLava with OBS
GLava installs a plugin for rendering directly to an OBS scene, if support was enabled at compile-time. This is enabled by default in Meson, but it is overridden to disabled in the `Makefile` for build compatibility.
To use the plugin, simply select `GLava Direct Source` from the source list in OBS and position the output accordingly. You can provide options to GLava in the source properties.
Note that this only works for the default GLX builds of both OBS and GLava. This feature will not work if OBS was compiled with EGL for context creation, or if GLava is using GLFW.
## Performance
GLava will have a notable performance impact by default due to reletively high update rates, interpolation, and smoothing. Because FFT computations are (at the moment) performed on the CPU, you may wish to _lower_ `setsamplesize` and `setbufsize` on old hardware.
However, there is functionality to prevent GLava from unessecarily eating resources. GLava will always halt completely when obscured, so a fullscreen application covering the visualizer should enounter no issues (ie. games). If you wish for GLava to halt rendering when _any_ fullscreen application is in focus regardless of visibility, you can set `setfullscreencheck` to `true` in `rc.glsl`.
Any serious performance and/or updating issues (low FPS/UPS) should be reported. At a minimum, modules should be expected to run smoothly on Intel HD graphics and software rasterizers like `llvmpipe`.
## Licensing
GLava is licensed under the terms of the GPLv3. GLava includes some (heavily modified) source code that originated from [cava](https://github.com/karlstav/cava), which was initially provided under the MIT license. The source files that originated from cava are the following:
GLava is licensed under the terms of the GPLv3, with the exemption of `khrplatform.h`, which is licensed under the terms in its header. GLava includes some (heavily modified) source code that originated from [cava](https://github.com/karlstav/cava), which was initially provided under the MIT license. The source files that originated from cava are the following:
- `[cava]/input/fifo.c -> [glava]/fifo.c`
- `[cava]/input/fifo.h -> [glava]/fifo.h`
@@ -82,12 +133,20 @@ The below copyright notice applies for the original versions of these files:
`Copyright (c) 2015 Karl Stavestrand <karl@stavestrand.no>`
The modified files are relicensed under the terms of the GPLv3. The MIT license is included for your convience and to satisfy the requirements of the original license, although it (no longer) applies to any code in this repository. You will find the original copyright notice and MIT license in the `LICENSE_ORIGINAL` file.
GLava also contains GLFFT, an excellent FFT implementation using Opengl 4.3 compute shaders. This was also initiallly provided under the MIT license, and applies to the following source files (where `*` refers to both `hpp` and `cpp`):
- `glfft/glfft.*`
- `glfft/glfft_common.hpp`
- `glfft/glfft_gl_interface.*`
- `glfft/glfft_interface.hpp`
- `glfft/glfft_wisdom.*`
The below copyright notice applies for the original versions of these files:
`Copyright (c) 2015 Hans-Kristian Arntzen <maister@archlinux.us>`
**The noted files above are all sublicensed under the terms of the GPLv3**. The MIT license is included for your convenience and to satisfy the requirements of the original license, although it no longer applies to any code in this repository. You will find the original copyright notice and MIT license in the `LICENSE_ORIGINAL` file for cava, or `glfft/LICENSE_ORIGINAL` for GLFFT.
The below copyright applies for the modifications to the files listed above, and the remaining sources in the repository:
`Copyright (c) 2017 Levi Webb`
## Porting
GLava was built with GLFW, making the graphics frontend mostly compatible if it were to be ported to Windows, and I have taken all the Xlib-specific code and placed it into `xwin.c` if anyone decides they wish to attempt at a port.

86
fifo.c
View File

@@ -1,86 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <time.h>
#include "fifo.h"
//input: FIFO
void* input_fifo(void* data)
{
struct audio_data *audio = (struct audio_data *)data;
int fd;
int n = 0;
signed char buf[1024];
int tempr, templ, lo;
int q, i;
int t = 0;
int size = 1024;
int bytes = 0;
int flags;
struct timespec req = { .tv_sec = 0, .tv_nsec = 10000000 };
fd = open(audio->source, O_RDONLY);
flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
while (1) {
bytes = read(fd, buf, sizeof(buf));
if (bytes == -1) { //if no bytes read sleep 10ms and zero shared buffer
nanosleep (&req, NULL);
t++;
if (t > 10) {
for (i = 0; i < 2048; i++)audio->audio_out_l[i] = 0;
for (i = 0; i < 2048; i++)audio->audio_out_r[i] = 0;
t = 0;
}
} else { //if bytes read go ahead
t = 0;
for (q = 0; q < (size / 4); q++) {
tempr = ( buf[ 4 * q + 3] << 2);
lo = ( buf[4 * q + 2] >> 6);
if (lo < 0)lo = abs(lo) + 1;
if (tempr >= 0)tempr = tempr + lo;
else tempr = tempr - lo;
templ = ( buf[ 4 * q + 1] << 2);
lo = ( buf[ 4 * q] >> 6);
if (lo < 0)lo = abs(lo) + 1;
if (templ >= 0)templ = templ + lo;
else templ = templ - lo;
if (audio->channels == 1) audio->audio_out_l[n] = (tempr +
templ) /
2;
//stereo storing channels in buffer
if (audio->channels == 2) {
audio->audio_out_l[n] = templ;
audio->audio_out_r[n] = tempr;
}
n++;
if (n == 2048 - 1)n = 0;
}
}
if (audio->terminate == 1) {
close(fd);
break;
}
}
return 0;
}

17
fifo.h
View File

@@ -1,17 +0,0 @@
#include <pthread.h>
struct audio_data {
volatile float* audio_out_r;
volatile float* audio_out_l;
bool modified;
size_t audio_buf_sz, sample_sz;
int format;
unsigned int rate;
char *source; // pulse source
int channels;
int terminate; // shared variable used to terminate audio thread
pthread_mutex_t mutex;
};
void* input_fifo(void* data);

2
glad

Submodule glad updated: 0a146b6723...c33992f23c

10
glad_generate.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
GLAD_GEN="${1:-c}"
pushd glad
python -m glad --generator=${GLAD_GEN} --extensions=GL_EXT_framebuffer_multisample,GL_EXT_texture_filter_anisotropic,GL_NV_texture_barrier --local-files --out-path=.
popd
cp glad/*.h glava/
cp glad/glad.c glava/

26
glava-cli/cli.c Normal file
View File

@@ -0,0 +1,26 @@
#include <glava.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
static glava_handle handle;
static void handle_term (int _) {
printf("Interrupt recieved, closing...\n");
glava_terminate(&handle);
}
static void handle_reload(int _) {
printf("User signal recieved, reloading...\n");
glava_reload(&handle);
}
int main(int argc, char** argv) {
const struct sigaction term_action = { .sa_handler = handle_term };
const struct sigaction reload_action = { .sa_handler = handle_reload };
sigaction(SIGTERM, &term_action, NULL);
sigaction(SIGINT, &term_action, NULL);
sigaction(SIGUSR1, &reload_action, NULL);
glava_entry(argc, argv, &handle);
return EXIT_SUCCESS;
}

320
glava-config/config.lua Normal file
View File

@@ -0,0 +1,320 @@
local lfs = require "lfs"
local mappings = require "glava-config.mappings"
local config = {
Profile = { mt = {} },
PROFILES_DIR = "profiles"
}
config.Profile.__index = config.Profile
setmetatable(config.Profile, config.Profile.mt)
-- Split path into entries, such that `table.concat` can be used to
-- reconstruct the path. Prepends the result with an empty string so
-- root (absolute) paths are preserved
local function path_split(str, sep)
local sep, fields = sep or ":", (str:sub(1, sep:len()) == sep and {""} or {})
local pattern = string.format("([^%s]+)", sep)
str:gsub(pattern, function(c) fields[#fields + 1] = c end)
return fields
end
-- Concatenates paths such that duplicate path separators are removed.
-- Can be used on non-split arguments, and resolves `..` syntax
local function path_concat(...)
local ret = {}
for _, v in ipairs({...}) do
for _, e in ipairs(path_split(v, "/")) do
if e ~= "" or #ret == 0 then
if e == ".." and #ret >= 1 then
ret[#ret] = nil
else
ret[#ret + 1] = e
end
end
end
end
return table.concat(ret, "/")
end
-- Wrap table such that it can be called to index and call its members,
-- useful for switch-style syntax
local function switch(tbl)
local mt = { __call = function(self, i) return rawget(self, i)() end }
return setmetatable(tbl, mt)
end
-- To parse data from GLSL configs we use some complex pattern matching.
--
-- Because Lua's patterns operate on a per-character basis and do not offer
-- any read-ahead functionality, we use a pattern 'replacement' functionality
-- such that the match of an input pattern is passed to a function to produce
-- an output pattern.
--
-- This effectively means we have some fairly powerful parsing which allows us
-- to handle things like quoted strings with escaped characters.
local function unquote(match)
local ret = {}
local escaped = false
for c in match:gmatch(".") do
if c == "\"" then
if escaped then ret[#ret + 1] = c end
elseif c ~= "\\" then ret[#ret + 1] = c end
if c == "\\" then
if escaped then ret[#ret + 1] = c end
escaped = not escaped
else escaped = false end
end
return table.concat(ret, "")
end
local function none(...) return ... end
local MATCH_ENTRY_PATTERN = "^%s*%#(%a+)%s+(%a+)"
local MATCH_DATA_PREFIX = "^%s*%#%a+%s+%a+"
local MATCH_TYPES = {
["float"] = { pattern = "(%d+.?%d*)" },
["int"] = { pattern = "(%d+)" },
["color-expr"] = { pattern = "(.+)" },
["expr"] = { pattern = "(.+)" },
["ident"] = { pattern = "(%a%w*)" },
["string"] = {
pattern = "(.+)",
cast = unquote,
-- Read-ahead function to generate a fixed-width pattern
-- to match the next (possibly quoted) string
transform = function(match)
local quoted = false
local start = true
local escaped = false
local count = 0
local skip = 0
for c in match:gmatch(".") do
count = count + 1
if c == "\"" then
if start then
start = false
quoted = true
elseif not escaped then
if quoted then
-- End-quote; end of string
break
else
-- Formatting error: non-escaped quote after string start: `foo"bar`
-- We attempt to resolve this by halting parsing and skipping the
-- out-of-context quotation
count = count - 1
skip = skip + 1
break
end
end
elseif c == " " then
if not start and not quoted then
-- Un-escaped space; end of string
-- skip the space itself
count = count - 1
break
end
else start = false end
if c == "\\" then
escaped = not escaped
else escaped = false end
end
-- Strings without an ending quote will simply take up the remainder of
-- the request, causing the following arguments to be overwritten. This
-- is intended to ensure we can save valid options after stripping out
-- the errornous quotes and using defaults for the subsequent arguments.
local ret = { "(" }
for t = 1, count do
ret[1 + t] = "."
end
ret[2 + count] = ")"
for t = 1, skip do
ret[2 + count + t] = "."
end
return table.concat(ret, "")
end,
serialize = function(x)
return string.format("\"%s\"", x)
end
}
}
config.path_concat = path_concat
config.path_split = path_split
local function create_pf(arr, mode, silent)
local parts = {}
local function errfmt(err)
return string.format("Failed to create '%s' in '%s': %s",
path_concat(parts, "/"), path_concat(arr, "/"), err)
end
for i, v in ipairs(arr) do
parts[#parts + 1] = v
local failret = false
if silent then failret = #parts == #arr end
local path = path_concat(parts, "/")
local m = (i == #arr and mode or "directory")
local attr, err = lfs.attributes(path, "mode")
if attr == nil then
local ret, err = switch {
file = function()
local ret, err = lfs.touch(path)
if not ret then return false, errfmt(err) end
end,
directory = function()
local ret, err = lfs.mkdir(path)
if not ret then return false, errfmt(err) end
end,
}(m)
if ret == false then return ret, err end
elseif attr ~= m then
if not (silent and #parts == #arr) then
return false, string.format("'%s' is not a %s", path, m)
else
return true
end
end
end
return true
end
local function create_p(path, ...) create_pf(path_split(path, "/"), ...) end
local function unwrap(ret, err)
if ret == nil or ret == false then
glava.fail(err)
else return ret end
end
function config.Profile:__call(args)
local self = { name = args.name or ".." }
self:rebuild()
return setmetatable(self, config.Profile)
end
function config.Profile:rename(new)
error("not implemented")
end
function config.Profile:get_path()
return path_concat(glava.config_path, config.PROFILES_DIR, self.name)
end
function config.Profile:rebuild()
self.store = {}
self.path = path_concat(glava.config_path, config.PROFILES_DIR, self.name)
unwrap(create_p(self.path, "directory", true))
local unbuilt = {}
for k, _ in pairs(mappings) do
unbuilt[k] = true
end
for file in lfs.dir(self.path) do
if file ~= "." and file ~= ".." and mappings[file] ~= nil then
self:rebuild_file(file, path_concat(path, file))
unbuilt[file] = nil
end
end
for file, _ in pairs(unbuilt) do
self:rebuild_file(file, path_concat(path, file), true)
end
end
function config.Profile:rebuild_file(file, path, phony)
local fstore = {}
local fmap = mappings[file]
self.store[file] = fstore
for k, _ in pairs(fmap) do
if type(k) == "string" and k ~= "name" then
unbuilt[k] = true
end
end
function parse_line(line, idx, key, default)
local map = fmap[key]
if map == nil then return end
local tt = type(map.field_type) == "table" and map.field_type or { map.field_type }
local _,e = string.find(line, MATCH_DATA_PREFIX)
local at = string.sub(line, 1, e)
if default == nil or fstore[key] == nil then
fstore[key] = {}
end
if default == nil then fstore[key].line = idx end
for t, v in ipairs(tt) do
local r, i, match = string.find(at, "%s*" .. MATCH_TYPES[v].pattern)
if r ~= nil then
-- Handle read-ahead pattern transforms
if MATCH_TYPES[v].transform ~= nil then
_, i, match = string.find(at, "%s*" .. MATCH_TYPES[v].transform(match))
end
if default == nil or fstore[key][t] == nil then
fstore[key][t] = MATCH_TYPES[v].cast and MATCH_TYPES[v].cast(match) or match
end
at = string.sub(at, 1, i)
else break end
end
end
local idx = 1
if phony ~= true then
for line in io.lines(path) do
local mtype, arg = string.match(line, MATCH_ENTRY_PATTERN)
if mtype ~= nil then
parse_line(line, idx, string.format("%s:%s", mtype, arg))
end
idx = idx + 1
end
end
idx = 1
for line in io.lines(path_concat(glava.system_shader_path, file)) do
local mtype, arg = string.match(line, MATCH_ENTRY_PATTERN)
if mtype ~= nil then
parse_line(line, idx, string.format("%s:%s", mtype, arg), true)
end
idx = idx + 1
end
end
-- Sync all
function config.Profile:sync()
for k, v in pairs(self.store) do self:sync_file(k) end
end
-- Sync filename relative to profile root
function config.Profile:sync_file(fname)
local fstore = self.store[fname]
local fmap = mappings[file]
local fpath = path_concat(self.path, fname)
local buf = {}
local extra = {}
local idx = 1
for k, v in fstore do
local parts = { string.format("#%s", string.gsub(k, ":", " ")) }
local field = fmap[k].field_type
for i, e in ipairs(type(field) == "table" and field or { field }) do
parts[#parts + 1] = MATCH_TYPES[e].serialize and MATCH_TYPES[e].serialize(v[i]) or v[i]
end
local serialized = table.concat(parts, " ")
if v.line then buf[line] = serialized
else extra[#extra + 1] = serialized end
end
if lfs.attributes(fpath, "mode") == "file" then
for line in io.lines(path) do
if not buf[idx] then
buf[idx] = line
end
idx = idx + 1
end
for _, v in ipairs(extra) do
buf[#buf + 1] = v
end
end
local handle, err = io.open(fpath, "w+")
if handle then
handle:write(table.concat(buf, "\n"))
handle:close()
else
glava.fail(string.format("Could not open file handle to \"%s\": %s", handle, err))
end
end
return config

109
glava-config/entry.c Normal file
View File

@@ -0,0 +1,109 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#define GLAVA_LUA_ENTRY "glava-config.main"
#define GLAVA_LUA_ENTRY_FUNC "entry"
#ifndef LUA_OK
#define LUA_OK 0
#endif
/* Should be already defined by Meson */
#ifndef GLAVA_RESOURCE_PATH
#define GLAVA_RESOURCE_PATH "../resources"
#endif
#ifndef SHADER_INSTALL_PATH
#ifndef GLAVA_STANDALONE
#define SHADER_INSTALL_PATH "/etc/xdg/glava"
#else
#define SHADER_INSTALL_PATH "../shaders/glava"
#endif
#endif
static int traceback(lua_State *L) {
if (!lua_isstring(L, 1))
return 1;
lua_getglobal(L, "debug");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return 1;
}
lua_getfield(L, -1, "traceback");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 2);
return 1;
}
lua_pushvalue(L, 1);
lua_pushinteger(L, 2);
lua_call(L, 2, 1);
return 1;
}
int main(int argc, char** argv) {
puts("WARNING: `glava-config` is in an incomplete state. Do not use this tool outside of development purposes.");
fflush(stdout);
lua_State* L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, traceback);
#ifdef GLAVA_STANDALONE
/* Local path environment for standalone execution */
lua_getglobal(L, "package");
lua_pushstring(L, "path");
lua_gettable(L, -2);
lua_pushstring(L, "./glava-env/?.lua;./glava-env/?/init.lua;");
lua_insert(L, -2);
lua_concat(L, 2);
lua_pushstring(L, "path");
lua_insert(L, -2);
lua_settable(L, -3);
lua_pop(L, 1);
#endif
/* GLava compilation settings */
lua_newtable(L);
lua_pushstring(L, "resource_path");
lua_pushstring(L, GLAVA_RESOURCE_PATH);
lua_rawset(L, -3);
lua_pushstring(L, "system_shader_path");
lua_pushstring(L, SHADER_INSTALL_PATH);
lua_rawset(L, -3);
lua_setglobal(L, "glava");
lua_getglobal(L, "require");
lua_pushstring(L, GLAVA_LUA_ENTRY);
lua_call(L, 1, 1);
lua_pushstring(L, GLAVA_LUA_ENTRY_FUNC);
lua_gettable(L, -2);
if (!lua_isfunction(L, -1)) {
fprintf(stderr, "FATAL: no `" GLAVA_LUA_ENTRY_FUNC "` function in entry module\n");
exit(EXIT_FAILURE);
}
for (int t = 0; t < argc; ++t)
lua_pushstring(L, argv[t]);
int result = EXIT_FAILURE;
switch (lua_pcall(L, argc, 1, 1)) {
case LUA_OK:
if (lua_isnumber(L, -1))
result = lua_tonumber(L, -1);
break;
case LUA_ERRRUN:
fprintf(stderr, "FATAL: error in `" GLAVA_LUA_ENTRY
"." GLAVA_LUA_ENTRY_FUNC "`: %s\n", lua_tostring(L, -1));
break;
default:
fprintf(stderr, "FATAL: unhandled error from lua_pcall\n");
break;
}
lua_close(L);
return result;
}

79
glava-config/main.lua Normal file
View File

@@ -0,0 +1,79 @@
local function dependency(name)
if package.loaded[name] then
return
else
for _, searcher in ipairs(package.searchers or package.loaders) do
local loader = searcher(name)
if type(loader) == 'function' then
package.preload[name] = loader
return
end
end
print("Dependency \"" .. name .. "\" is not installed.")
print("Please install it through your package manager or Lua distribution.")
os.exit(1)
end
end
function glava.fail(message)
print(string.format("!!FATAL!!: %s", message))
os.exit(1)
end
local main = {}
-- Format string, but silently return nil if varargs contains any nil entries
local function format_silent(fmt, ...)
for _, v in ipairs({...}) do
if v == nil then return nil end
end
return string.format(fmt, ...)
end
function main.entry(prog, ...)
dependency("lgi")
dependency("lfs")
if glava.resource_path:sub(glava.resource_path:len()) ~= "/" then
glava.resource_path = glava.resource_path .. "/"
end
glava.config_path = format_silent("%s/glava", os.getenv("XDG_CONFIG_HOME"))
or format_silent("%s/.config/glava", os.getenv("HOME"))
or "/home/.config/glava"
local lfs = require "lfs"
local window = require "glava-config.window"
glava.module_list = {}
for m in lfs.dir(glava.system_shader_path) do
if m ~= "." and m ~= ".."
and lfs.attributes(glava.system_shader_path .. "/" .. m, "mode") == "directory"
and m ~= "util" then
glava.module_list[#glava.module_list + 1] = m
end
end
local mappings = require "glava-config.mappings"
-- Associate `map_name = tbl` from mapping list for future lookups, etc.
for k, v in pairs(mappings) do
local i = 1
local adv = false
while v[i] ~= nil do
if type(v[i]) == "table" then
v[v[i][1]] = v[i]
v[i].advanced = adv
i = i + 1
elseif type(v[i]) == "string" and v[i] == "advanced" then
adv = true
table.remove(v, i)
else
glava.fail(string.format("Unknown mappings entry type for file: \"%s\"", type(v)))
end
end
end
-- Enter into Gtk window
window()
end
return main

38
glava-config/mappings.lua Normal file
View File

@@ -0,0 +1,38 @@
return {
["rc.glsl"] = {
name = "Global Options",
{ "request:mod",
field_type = "string",
field_attrs = { entries = glava.module_list },
description = "Visualizer module"
},
{ "request:fakeident",
field_type = "ident",
description = "Some identifier"
},
{ "request:fakefloat",
field_type = "float",
description = "Some Float"
},
{ "request:fakecolorexpr",
field_type = "color-expr",
field_attrs = { alpha = true },
description = "Color Expression"
},
{ "request:setbg",
field_type = "color",
field_attrs = { alpha = true },
description = "Window background color"
},
"advanced",
{ "request:setversion",
field_type = { "int", "int" },
field_attrs = {
frame_label = "Version",
{ lower = 0, upper = 10, width = 2 },
{ lower = 0, upper = 10, width = 2 }
},
description = "OpenGL context version request"
}
}
}

68
glava-config/utils.lua Normal file
View File

@@ -0,0 +1,68 @@
local lgi = require "lgi"
local Gdk = lgi.Gdk
local utils = {}
function utils.infer_color_bits(x)
if x:sub(1, 1) ~= "#" then
x = "#" .. x
end
for i = 1, 9 - x:len() do
x = x .. (x:len() >= 7 and "F" or "0")
end
return x
end
function utils.sanitize_color(x)
return utils.infer_color_bits(x):sub(1, 9):gsub("[^#0-9a-fA-F]", "0")
end
function utils.parse_color_rgba(x)
local x = utils.infer_color_bits(x)
return Gdk.RGBA.parse(
string.format(
"rgba(%d,%d,%d,%f)",
tonumber(x:sub(2, 3), 16),
tonumber(x:sub(4, 5), 16),
tonumber(x:sub(6, 7), 16),
tonumber(x:sub(8, 9), 16) / 255
)
)
end
function utils.rgba_to_gdk_color(x)
return Gdk.Color(
math.floor(x.red * 255 + 0.5),
math.floor(x.green * 255 + 0.5),
math.floor(x.blue * 255 + 0.5)
)
end
function utils.rgba_to_integral(x)
return {
red = math.floor(x.red * 255 + 0.5),
green = math.floor(x.green * 255 + 0.5),
blue = math.floor(x.blue * 255 + 0.5)
}
end
function utils.format_color_rgba(x)
return string.format(
"#%02X%02X%02X%02X",
math.floor(x.red * 255 + 0.5),
math.floor(x.green * 255 + 0.5),
math.floor(x.blue * 255 + 0.5),
math.floor(x.alpha * 255 + 0.5)
)
end
function utils.format_color_rgb(x)
return string.format(
"#%02X%02X%02X",
math.floor(x.red * 255 + 0.5),
math.floor(x.green * 255 + 0.5),
math.floor(x.blue * 255 + 0.5)
)
end
return utils

984
glava-config/window.lua Normal file
View File

@@ -0,0 +1,984 @@
--[[
MAINTAINER NOTICE:
This application aims to be both Gtk+ 3 and 4 compatible for future-proofing. This means
avoiding *every* deprecated widget in Gtk+ 3, and watching out for some old functionality:
* Gdk.Color usage, use Gdk.RGBA instead
* Pango styles and style overrides
* Check convenience wrappers for deprecation, ie. GtkColorButton
* Avoid seldom used containers, as they may have been removed in 4.x (ie. GtkButtonBox)
In some cases we use deprecated widgets or 3.x restricted functionality, but only when we
query that the types are available from LGI (and otherwise use 4.x compatible code).
]]
return function()
local lgi = require 'lgi'
local utils = require 'glava-config.utils'
local mappings = require 'glava-config.mappings'
local GObject = lgi.GObject
local Gtk = lgi.Gtk
local Pango = lgi.Pango
local Gdk = lgi.Gdk
local GdkPixbuf = lgi.GdkPixbuf
local cairo = lgi.cairo
-- Both `GtkColorChooserDialog` and `GtkColorSelectionDialog` are
-- supported by this tool, but the latter is deprecated and does
-- not exist in 4.x releases.
--
-- The old chooser, however, is objectively better so let's try
-- to use it if it exists.
local use_old_chooser = true
if Gtk.get_major_version() >= 4 then
use_old_chooser = false
end
local window
local repeat_pattern = cairo.SurfacePattern(
cairo.ImageSurface.create_from_png(glava.resource_path .. "transparent.png")
)
repeat_pattern:set_extend("REPEAT")
-- We need to define a CSS class to use an alternative font for
-- color and identity entries; used to indicate to the user that
-- the field has formatting requirements
local cssp = Gtk.CssProvider {}
cssp:load_from_data(".fixed-width-font-entry { font-family: \"Monospace\"; }")
local ItemColumn = {
PROFILE = 1,
ENABLED = 2,
ACTIVABLE = 3,
WEIGHT = 4,
VISIBLE = 5
}
-- Fill store with initial items.
local item_store = Gtk.ListStore.new {
[ItemColumn.PROFILE] = GObject.Type.STRING,
[ItemColumn.ENABLED] = GObject.Type.BOOLEAN,
[ItemColumn.ACTIVABLE] = GObject.Type.BOOLEAN,
[ItemColumn.VISIBLE] = GObject.Type.BOOLEAN,
[ItemColumn.WEIGHT] = GObject.Type.INT
}
local default_entry = {
[ItemColumn.PROFILE] = "Default",
[ItemColumn.ENABLED] = false,
[ItemColumn.VISIBLE] = false,
[ItemColumn.ACTIVABLE] = false,
[ItemColumn.WEIGHT] = 600
}
-- Apply `t[k] = v` to all table argument at array indexes,
-- and return the unpacked list of tables. Used for nesting
-- widget construction.
local function apply(tbl)
local ret = {}
for k, v in ipairs(tbl) do
ret[k] = v
tbl[k] = nil
end
for k, v in pairs(tbl) do
for _, r in ipairs(ret) do
r[k] = v
end
end
return unpack(ret)
end
-- Apply `binds[k] = v` while returning unpacked values
local binds = {}
local function bind(tbl)
local ret = {}
for k, v in pairs(tbl) do
binds[k] = v
ret[#ret + 1] = v
end
return unpack(ret)
end
local function link(tbl)
for _, v in ipairs(tbl) do
v:get_style_context():add_class("linked")
end
return unpack(tbl)
end
local function ComboBoxFixed(tbl)
local inst = Gtk.ComboBoxText { id = tbl.id }
for _, v in pairs(tbl) do
inst:append_text(v)
end
inst:set_active(tbl.default or 0)
return inst
end
local SpoilerView = function(tbl)
local stack = Gtk.Stack {
expand = true,
transition_type = Gtk.StackTransitionType.CROSSFADE
}
local btn = Gtk.CheckButton {
active = tbl.active or false
}
if tbl.active ~= true then
stack:add_named(Gtk.Box {}, "none")
end
stack:add_named(tbl[1], "view")
if tbl.active == true then
stack:add_named(Gtk.Box {}, "none")
end
function btn:on_toggled(path)
stack:set_visible_child_name(btn.active and "view" or "none")
end
return Gtk.Box {
expand = false,
orientation = "VERTICAL",
spacing = 4,
Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
btn,
Gtk.Label { label = tbl.label or "Spoiler" }
},
Gtk.Separator(),
stack
}
end
local ConfigView = function(tbl)
local grid = {
row_spacing = 2,
column_spacing = 12,
column_homogeneous = false,
row_homogeneous = false
}
local list = {}
local idx = 0
local function cbuild(list, entry)
list[#list + 1] = {
Gtk.Label { label = entry[1], halign = "START", valign = "START" },
left_attach = 0, top_attach = idx
}
list[#list + 1] = {
Gtk.Box { hexpand = true },
left_attach = 1, top_attach = idx
}
list[#list + 1] = {
apply { halign = "END", entry[3] or Gtk.Box {} },
left_attach = 2, top_attach = idx
}
list[#list + 1] = {
apply { halign = "FILL", hexpand = false, entry[2] },
left_attach = 3, top_attach = idx
}
list[#list + 1] = {
Gtk.Separator {
vexpand = false
}, left_attach = 0, top_attach = idx + 1, width = 3
}
idx = idx + 2
end
for _, entry in ipairs(tbl) do
cbuild(list, entry)
end
local adv = {}
if tbl.advanced then
idx = 0
for _, entry in ipairs(tbl.advanced) do
cbuild(adv, entry)
end
end
for k, v in pairs(grid) do
list[k] = v
adv[k] = v
end
return Gtk.ScrolledWindow {
expand = true,
Gtk.Box {
margin_top = 12,
margin_start = 16,
margin_end = 16,
hexpand = true,
vexpand = true,
halign = "FILL",
orientation = "VERTICAL",
spacing = 6,
Gtk.Grid(list),
#adv > 0 and SpoilerView
{ label = "Show Advanced",
Gtk.Grid(adv)
} or Gtk.Box {}
} }
end
local function wrap_label(widget, label)
if label then
widget = Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
Gtk.Label {
label = label
}, widget
}
end
return widget
end
-- Generators for producing widgets (and their layouts) that bind to configuration values
-- note: `get_data` returns stringified data
local widget_generators
widget_generators = {
-- A switch to represent a true/false value
["boolean"] = function(attrs)
local widget = Gtk.Switch { hexpand = false }
return {
widget = Gtk.Box { Gtk.Box { hexpand = true }, wrap_label(widget, attrs.label) },
set_data = function(x)
widget.active = x
return true
end,
get_data = function() return widget.active end,
connect = function(f) widget.on_state_set = f end
}
end,
-- Entry for a generic string, may have predefined selections
["string"] = function(attrs)
local widget = apply {
attrs.entries ~= nil
and apply { ComboBoxFixed(attrs.entries) }
or Gtk.Entry { width_chars = 12 },
hexpand = true
}
return {
widget = wrap_label(widget, attrs.label),
internal = widget,
set_data = function(x)
if not attrs.entries then
widget:set_text(x)
else
for k, v in ipairs(attrs.entries) do
if v == x then
widget:set_active(v - 1)
return true
end
end
return false
end
return true
end,
get_data = function()
local text = (not attrs.entries) and widget:get_text() or widget:get_active_text()
if attrs.translate then
text = attrs.translate[text]
end
return text
end,
connect = function(f)
-- Note: the underlying widget can be `GtkComboBoxText` or `GtkEntry`;
-- they simply just use the same signal for user input
widget.on_changed = f
end
}
end,
-- Entry for a valid C/GLSL identity, may have predefined selections
["ident"] = function(attrs)
local s = widget_generators.string(attrs)
-- Set fixed-width font if the users enter/select identifiers by their name,
-- rather than a description to indicate it's a GLSL identity
if not attrs.translate then
s.internal:get_style_context():add_provider(cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
s.internal:get_style_context():add_class("fixed-width-font-entry")
end
if not attrs.entries and not attrs._ignore_restrict then
-- Handle idenifier formatting for entries without a preset list
local handlers = {}
local function run_handlers()
for _, f in ipairs(handlers) do f() end
end
function s.internal:on_changed()
local i = s.internal.text
if i:match("[^%w]") ~= nil or i:sub(1, 1):match("[^%a]") ~= nil then
s.internal.text = i:gsub("[^%w]", ""):gsub("^[^%a]+", "")
else
run_handlers()
end
end
s.connect = function(f)
handlers[#handlers + 1] = f
end
end
return s
end,
-- A full GLSL expression
["expr"] = function(attrs)
-- Expressions can be implemented by using the identity field and disabling
-- input format restrictions.
attrs._ignore_restrict = true
return widget_generators.ident(attrs)
end,
-- Adjustable and bound floating-point value
["float"] = function(attrs)
local widget = Gtk.SpinButton {
hexpand = true,
adjustment = Gtk.Adjustment {
lower = attrs.lower or 0,
upper = attrs.upper or 100,
page_size = 1,
step_increment = attrs.increment or 1,
page_increment = attrs.increment or 1
},
width_chars = attrs.width or 6,
numeric = true,
digits = attrs.digits or 2,
climb_rate = attrs.increment or 1
}
return {
widget = wrap_label(widget, attrs.label),
set_data = function(x)
widget:set_text(x)
return true
end,
get_data = function() return widget:get_text() end,
connect = function(f) widget.on_value_changed = f end
}
end,
-- Adjustable and bound integral value
["int"] = function(attrs)
local widget = Gtk.SpinButton {
hexpand = true,
adjustment = Gtk.Adjustment {
lower = attrs.lower or 0,
upper = attrs.upper or 100,
page_size = 1,
step_increment = attrs.increment or 1,
page_increment = attrs.increment or 1
},
width_chars = attrs.width or 6,
numeric = true,
digits = 0,
climb_rate = attrs.increment or 1
}
return {
widget = wrap_label(apply { vexpand = false, widget }, attrs.label),
set_data = function(x)
widget:set_text(x)
return true
end,
get_data = function() return widget:get_text() end,
connect = function(f) widget.on_value_changed = f end
}
end,
-- The color type is the hardest to implement; as Gtk deprecated
-- the old color chooser button, so we have to implement our own.
-- The benefits of doing this mean we get to use the "nice" Gtk3
-- chooser, and the button rendering itself is much better.
["color"] = function(attrs)
local dialog_open = false
local handlers = {}
local function run_handlers()
for _, f in ipairs(handlers) do f() end
end
local c = Gdk.RGBA {
red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0
}
local area = Gtk.DrawingArea()
area:set_size_request(16, 16)
local draw = function(widget, cr)
local context = widget:get_style_context()
local width = widget:get_allocated_width()
local height = widget:get_allocated_height()
local aargc = { width / 2, height / 2, math.min(width, height) / 2, 0, 2 * math.pi }
Gtk.render_background(context, cr, 0, 0, width, height)
cr:set_source(repeat_pattern)
cr:arc(unpack(aargc))
cr:fill()
cr:set_source_rgba(c.red, c.green, c.blue, c.alpha)
cr:arc(unpack(aargc))
cr:fill()
end
if Gtk.get_major_version() >= 4 then
area:set_draw_func(draw)
else
area.on_draw = draw
end
local btn = Gtk.Button {
apply {
margin_top = 1,
margin_bottom = 1,
area
} }
local entry = Gtk.Entry {
hexpand = true,
width_chars = 9,
max_length = 9,
text = attrs.alpha and "#FFFFFFFF" or "#FFFFFF"
}
entry:get_style_context():add_provider(cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
entry:get_style_context():add_class("fixed-width-font-entry")
local widget = Gtk.Box {
orientation = "HORIZONTAL",
spacing = 0,
entry, btn
}
link { widget }
widget = wrap_label(widget, attrs.label)
function btn:on_clicked()
local c_change_staged = false
local dialog = (use_old_chooser and Gtk.ColorSelectionDialog or Gtk.ColorChooserDialog)
{ title = "Select Color",
transient_for = window,
modal = true,
destroy_with_parent = true
}
if use_old_chooser then
dialog.cancel_button:set_visible(false)
dialog.ok_button.label = "Close"
dialog.color_selection.current_rgba = c
if attrs.alpha then
dialog.color_selection.has_opacity_control = true
end
function dialog.color_selection:on_color_changed()
c_change_staged = true
c = dialog.color_selection.current_rgba
entry:set_text(attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c))
area:queue_draw()
end
else
dialog.rgba = c
if attrs.alpha then
dialog.use_alpha = true
end
end
dialog_open = true
local ret = dialog:run()
dialog_open = false
dialog:set_visible(false)
if not use_old_chooser and ret == Gtk.ResponseType.OK then
c = dialog.rgba
entry:set_text(attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c))
area:queue_draw()
run_handlers()
elseif use_old_chooser and c_change_staged then
run_handlers()
end
end
function entry:on_changed()
local s = utils.sanitize_color(entry.text)
c = utils.parse_color_rgba(s)
area:queue_draw()
if not dialog_open then run_handlers() end
end
return {
widget = widget,
set_data = function(x)
local s = utils.sanitize_color(x)
c = utils.parse_color_rgba(s)
area:queue_draw()
entry:set_text(s)
return true
end,
get_data = function(x)
return attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c)
end,
connect = function(f)
handlers[#handlers + 1] = f
end
}
end,
-- A field capable of producing a GLSL color expression.
["color-expr"] = function(attrs, header)
-- Define color control variables for use in color expressions
local controls = {
{ "Baseline", "d" },
{ "X axis", "gl_FragCoord.x" },
{ "Y axis", "gl_FragCoord.y" }
}
local control_list = {}
for i, v in ipairs(controls) do
control_list[i] = v[1]
controls[v[1]] = v[2]
end
-- Define color expression types. Field data is assigned according
-- to the associated pattern, and entries are ordered in terms of
-- match priority
local cetypes = {
{ "Gradient",
fields = {
{ "color" },
{ "color" },
{ "ident",
entries = control_list,
translate = controls,
header = "Axis:"
},
{ "float",
upper = 1000,
lower = -1000,
header = "Scale:"
} },
-- match against GLSL mix expression, ie.
-- `mix(#3366b2, #a0a0b2, clamp(d / GRADIENT, 0, 1))`
match = "mix%s*%(" ..
"%s*(#[%dA-Fa-f]*)%s*," ..
"%s*(#[%dA-Fa-f]*)%s*," ..
"%s*clamp%s*%(%s*(%w+)%s*/%s*(%w+)%s*,%s*0%s*,%s*1%s*%)%s*%)",
output = "mix(%s, %s, clamp(%s / %s, 0, 1))"
},
{ "Solid",
fields = { { "color" } },
match = "#[%dA-Fa-f]*",
output = "%s",
default = true
} }
local stack = Gtk.Stack { vhomogeneous = false }
local hstack = Gtk.Stack { vhomogeneous = false }
local cekeys = {}
local default = nil
for i, v in ipairs(cetypes) do
if not v.default then
cekeys[#cekeys + 1] = v[1]
else
table.insert(cekeys, 1, v[1])
end
cetypes[v[1]] = v
local wfields = {}
local hfields = {
Gtk.Label {
halign = "END",
valign = "START",
label = header
} }
local gen = {}
for k, e in ipairs(v.fields) do
v.alpha = attrs.alpha
local g = widget_generators[e[1]](e)
gen[#gen + 1] = g
wfields[k] = g.widget
hfields[#hfields + 1] = Gtk.Label {
halign = "END",
label = e.header
}
end
v.gen = gen
v.widget = Gtk.Box(
apply {
homogeneous = true,
orientation = "VERTICAL",
spacing = 1,
wfields
} )
v.hwidget = Gtk.Box(
apply {
homogeneous = true,
orientation = "VERTICAL",
spacing = 1,
hfields
} )
hstack:add_named(v.hwidget, v[1])
stack:add_named(v.widget, v[1])
if v.default then
default = v[1]
end
v.set_data = function(x)
for i, m in ipairs { string.match(x, v.match) } do
gen[i].set_data(m)
end
end
v.get_data = function()
local fields = {}
for i = 1, #v.fields do
fields[i] = gen[i]:get_data()
end
return string.format(v.output, unpack(fields))
end
v.connect = function(f)
for _, g in ipairs(gen) do
g.connect(f)
end
end
end
local cbox = apply {
hexpand = true,
ComboBoxFixed(cekeys)
}
stack:set_visible_child(cetypes[default].widget)
hstack:set_visible_child(cetypes[default].hwidget)
cetypes[default].widget:show()
cetypes[default].hwidget:show()
function cbox:on_changed()
local t = cbox:get_active_text()
stack:set_visible_child_name(t)
hstack:set_visible_child_name(t)
end
local widget = Gtk.Box {
orientation = "VERTICAL",
spacing = 1,
wrap_label(cbox, attrs.label), stack
}
return {
widget = widget,
header_widget = hstack,
set_data = function(x)
for i, v in ipairs(cetypes) do
if string.match(x, v.match) ~= nil then
v.set_data(x)
return true
end
end
return false
end,
get_data = function()
return cetypes[cbox:get_active_text()].get_data()
end,
connect = function(f)
for i, v in ipairs(cetypes) do
v.connect(f)
end
end
}
end
}
-- Extra widget for special service/autostart functionality
local ServiceView = function(self)
local switch = Gtk.Switch {
sensitive = false,
hexpand = false
}
local method = ComboBoxFixed {
"None",
"SystemD User Service",
"InitD Entry",
"Desktop Entry"
}
method.on_changed = function(box)
local opt = box:get_active_text()
switch.sensitive = opt ~= "None"
if switch.active == true and opt == "None" then
switch:activate()
end
for _, entry in item_store:pairs() do
if entry[ItemColumn.PROFILE] == self.name then
entry[ItemColumn.ACTIVABLE] = opt ~= "None"
if opt == "None" then
entry[ItemColumn.ENABLED] = false
end
end
end
end
switch.on_notify["active"] = function(inst, pspec)
for _, entry in item_store:pairs() do
if entry[ItemColumn.PROFILE] == self.name then
entry[ItemColumn.ENABLED] = switch.active
end
end
-- TODO handle enable here
end
return ConfigView {
{ "Enabled", Gtk.Box { Gtk.Box { hexpand = true }, switch } },
{ "Autostart Method", method }
}, switch
end
-- Produce a widget containing a scroll area full of widgets bound to
-- requests/defines in the specified profile.
local function ProfileView(name)
local self = { name = name }
local args = {}
for k, v in pairs(mappings) do
local layout = {}
for _, e in ipairs(v) do
if type(e) == "table" then
local header = nil
local fields = {}
local ftypes = type(e.field_type) == "table" and e.field_type or { e.field_type }
local fattrs = type(e.field_type) == "table" and e.field_attrs or { e.field_attrs }
if not fattrs then fattrs = {} end
for i, f in ipairs(ftypes) do
local entry = widget_generators[f](fattrs[i] or {}, e.header)
if not header then
header = entry.header_widget
end
fields[#fields + 1] = entry.widget
-- todo: finish linking config
entry.connect(function()
print(string.format("assign %s->%s->%s[%d] = %s", k, e[1], f, i, tostring(entry.get_data())))
end)
end
-- disable header display widget if there are multiple fields
if #fields > 1 then header = nil end
fields.orientation = "VERTICAL"
fields.spacing = 2
local fwidget = {
e.description,
#fields > 1 and
Gtk.Frame {
label = fattrs.frame_label,
apply {
margin_start = 4,
margin_end = 4,
margin_top = 4,
margin_bottom = 4,
Gtk.Box(fields)
} } or fields[1],
header or (e.header and Gtk.Label { valign = "START", label = e.header } or Gtk.Box {})
}
if not e.advanced then
layout[#layout + 1] = fwidget
else
if not layout.advanced then layout.advanced = {} end
layout.advanced[#layout.advanced + 1] = fwidget
end
end
end
args[#args + 1] = { tab_label = v.name, ConfigView(layout) }
end
local service, chk = ServiceView(self)
args[#args + 1] = {
tab_label = "Autostart",
name ~= "Default" and service or
Gtk.Box {
valign = "CENTER",
orientation = "VERTICAL",
spacing = 8,
Gtk.Label {
label = "Autostart options are not available for the default user profile."
},
Gtk.Button {
hexpand = false,
halign = "CENTER",
label = "Show Profiles"
} } }
args.expand = true
notebook = Gtk.Notebook(args)
notebook:show_all()
self.widget = notebook
self.autostart_enabled = chk
function self:rename(new)
self.name = new
end
function self:delete()
end
return self;
end
local view_registry = {}
view_registry[default_entry[ItemColumn.PROFILE]] = ProfileView(default_entry[ItemColumn.PROFILE])
item_store:append(default_entry)
window = Gtk.Window {
title = "GLava Config",
default_width = 320,
default_height = 200,
border_width = 5,
Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
homogeneous = false,
Gtk.Box {
hexpand = false,
orientation = "VERTICAL",
spacing = 5,
Gtk.ScrolledWindow {
shadow_type = "ETCHED_IN",
vexpand = true,
width_request = 200,
bind {
view = Gtk.TreeView {
model = item_store,
activate_on_single_click = true,
Gtk.TreeViewColumn {
title = "Profile",
expand = true,
{ bind { profile_renderer = Gtk.CellRendererText {} },
{ text = ItemColumn.PROFILE,
editable = ItemColumn.VISIBLE,
weight = ItemColumn.WEIGHT
} } },
Gtk.TreeViewColumn {
title = "Enabled",
alignment = 0.5,
-- Note `xalign` usage here comes from GtkCellRenderer, which unlike the
-- legacy alignment widget is not deprecated
{ bind { toggle_renderer = Gtk.CellRendererToggle { xalign = 0.5 } },
{ active = ItemColumn.ENABLED,
activatable = ItemColumn.ACTIVABLE,
visible = ItemColumn.VISIBLE
} } } } } },
link {
Gtk.Box {
hexpand = true,
bind {
reload = Gtk.Button {
Gtk.Image {
icon_name = "view-refresh-symbolic"
} },
},
bind {
add = Gtk.Button {
halign = "FILL",
hexpand = true,
label = "Create Profile",
} },
bind {
remove = Gtk.Button {
halign = "END",
sensitive = false,
Gtk.Image {
icon_name = "user-trash-symbolic"
} } } } } },
Gtk.Box {
orientation = "VERTICAL",
spacing = 6,
link {
Gtk.Box {
Gtk.ToggleButton {
Gtk.Image {
icon_name = "view-paged-symbolic"
},
on_clicked = function()
--
end
},
bind {
display_path = Gtk.Entry {
-- todo: bind to config
text = "~/.config/glava/rc.glsl",
editable = false,
hexpand = true
} } } },
bind {
stack_view = Gtk.Stack {
expand = true,
transition_type = Gtk.StackTransitionType.CROSSFADE
} } } } }
local selection = binds.view:get_selection()
selection.mode = 'SINGLE'
binds.stack_view:add_named(view_registry[default_entry[ItemColumn.PROFILE]].widget,
default_entry[ItemColumn.PROFILE])
function unique_profile(profile_name_proto)
local profile_idx = 0
local profile_name = profile_name_proto
while true do
local used = false
for i, entry in item_store:pairs() do
if entry[ItemColumn.PROFILE] == profile_name then
used = true
end
end
if not used then break else
profile_idx = profile_idx + 1
profile_name = profile_name_proto .. " (" .. tostring(profile_idx) .. ")"
end
end
return profile_name
end
function binds.view:on_row_activated(path, column)
local name = item_store[path][ItemColumn.PROFILE]
binds.stack_view:set_visible_child_name(name)
binds.remove.sensitive = (name ~= "Default")
end
function binds.profile_renderer:on_edited(path_string, new_profile)
local path = Gtk.TreePath.new_from_string(path_string)
local old = item_store[path][ItemColumn.PROFILE]
local store = binds.stack_view:get_child_by_name(old)
new_profile = string.match(new_profile, "^%s*(.-)%s*$")
if old == new_profile or new_profile == "Default" then return end
new_profile = unique_profile(new_profile)
print("Renamining profile \"" .. old .. "\" -> \"" .. new_profile .. "\"")
binds.stack_view:remove(store)
binds.stack_view:add_named(store, new_profile)
local vstore = view_registry[old]
view_registry[old] = nil
view_registry[new_profile] = vstore
vstore:rename(new_profile)
item_store[path][ItemColumn.PROFILE] = new_profile
end
function binds.toggle_renderer:on_toggled(path_string)
local path = Gtk.TreePath.new_from_string(path_string)
if view_registry[item_store[path][ItemColumn.PROFILE]].autostart_enabled.active
~= not item_store[path][ItemColumn.ENABLED] then
view_registry[item_store[path][ItemColumn.PROFILE]].autostart_enabled:activate()
end
item_store[path][ItemColumn.ENABLED] =
view_registry[item_store[path][ItemColumn.PROFILE]].autostart_enabled.active
end
function binds.add:on_clicked()
local profile_name = unique_profile("New Profile")
local entry = {
[ItemColumn.PROFILE] = profile_name,
[ItemColumn.ENABLED] = false,
[ItemColumn.ACTIVABLE] = false,
[ItemColumn.VISIBLE] = true,
[ItemColumn.WEIGHT] = 400
}
local view = ProfileView(profile_name)
item_store:append(entry)
view_registry[profile_name] = view
binds.stack_view:add_named(view.widget, profile_name);
end
function binds.remove:on_clicked()
local dialog = Gtk.Dialog {
title = "Confirmation",
transient_for = window,
modal = true,
destroy_with_parent = true
}
local byes = dialog:add_button("Yes", Gtk.ResponseType.YES)
local bcancel = dialog:add_button("Cancel", Gtk.ResponseType.CANCEL)
dialog:get_action_area().halign = Gtk.Align.CENTER
local box = Gtk.Box {
orientation = 'HORIZONTAL',
spacing = 8,
border_width = 8,
Gtk.Image {
icon_name = "dialog-warning-symbolic",
icon_size = Gtk.IconSize.DIALOG,
},
Gtk.Label {
label = "Are you sure you want to delete the selected profile?"
} }
dialog:get_content_area():add(box)
box:show_all()
local ret = dialog:run()
dialog:set_visible(false)
if ret ~= Gtk.ResponseType.YES then return end
local model, iter = selection:get_selected()
if model and iter then
for iter, entry in item_store:pairs() do
if selection:iter_is_selected(iter) then
binds.stack_view:remove(
binds.stack_view:get_child_by_name(
entry[ItemColumn.PROFILE]))
view_registry[entry[ItemColumn.PROFILE]]:delete()
view_registry[entry[ItemColumn.PROFILE]] = nil
end
end
model:remove(iter)
end
end
function window:on_destroy() os.exit(0) end
window:show_all()
window:set_icon_from_file(glava.resource_path .. "glava.bmp")
Gtk.main()
end

263
glava-obs/entry.c Normal file
View File

@@ -0,0 +1,263 @@
#include <stdlib.h>
#include <obs/obs-module.h>
#include <obs/obs.h>
#include <obs/util/threading.h>
#include <obs/util/platform.h>
#include "../glava/glava.h"
#include <X11/Xlib.h>
#pragma GCC visibility push(default)
OBS_DECLARE_MODULE();
#pragma GCC visibility pop
static glava_handle handle;
/* To access OBS's GL context and internal texture handles we need to define internal
OBS structures such that we can access these members. This is not API-stable, but
these structure layouts rarely change. */
/* OBS INTERNAL DEFS */
typedef struct __GLXcontextRec* GLXContext;
typedef XID GLXPixmap;
typedef XID GLXDrawable;
typedef XID GLXPbuffer;
struct gl_platform_internal {
Display *display;
GLXContext context;
GLXPbuffer pbuffer;
};
struct gs_device_internal {
struct gl_platform_internal* plat;
/* trailing members present */
};
struct gs_subsystem_internal {
void* module;
struct gs_device_internal* device;
/* trailing members present */
};
struct gs_texture {
struct gs_device_internal* device;
int type;
int format;
int gl_format;
int gl_target;
int gl_internal_format;
int gl_type;
unsigned int texture;
uint32_t levels;
bool is_dynamic;
bool is_render_target;
bool is_dummy;
bool gen_mipmaps;
void* cur_sampler;
void* fbo;
};
struct gs_texture_2d_internal {
struct gs_texture base;
uint32_t width;
uint32_t height;
/* trailing members present */
};
/* END OBS INTERNAL DEFS */
struct mod_state {
obs_source_t* source;
pthread_t thread;
bool initialized;
gs_texture_t* gs_tex;
unsigned int old_tex;
struct {
char* opts;
int w, h;
} cfg;
};
static const char* get_name(void* _) {
UNUSED_PARAMETER(_);
return "GLava Direct Source";
}
static obs_properties_t* get_properties(void* _) {
UNUSED_PARAMETER(_);
obs_properties_t* props = obs_properties_create();
// (obs_properties_t *props, const char *name, const char *description, int min, int max, int step)
obs_properties_add_int (props, "width", "Output width", 0, 65535, 1);
obs_properties_add_int (props, "height", "Output height", 0, 65535, 1);
obs_properties_add_text(props, "options", "GLava options", OBS_TEXT_DEFAULT);
return props;
}
static uint32_t get_width(void* data) {
struct mod_state* s = (struct mod_state*) data;
return (uint32_t) s->cfg.w;
}
static uint32_t get_height(void* data) {
struct mod_state* s = (struct mod_state*) data;
return (uint32_t) s->cfg.h;
}
static void* work_thread(void* _) {
UNUSED_PARAMETER(_);
glava_entry(1, (char**) &"glava", &handle);
return NULL;
}
static void glava_join(void* data) {
struct mod_state* s = (struct mod_state*) data;
glava_terminate(&handle);
if (s->initialized) {
if (pthread_join(s->thread, NULL)) {
blog(LOG_ERROR, "Failed to join GLava thread");
return;
}
}
s->initialized = false;
if (s->gs_tex != NULL) {
obs_enter_graphics();
/* restore old GL texture */
((struct gs_texture_2d_internal*) s->gs_tex)->base.texture = s->old_tex;
gs_texture_destroy(s->gs_tex);
obs_leave_graphics();
s->gs_tex = NULL;
}
}
static void glava_start(void* data) {
struct mod_state* s = (struct mod_state*) data;
if (s->initialized) {
blog(LOG_ERROR, "Already initialized GLava thread");
return;
}
if (pthread_create(&s->thread, NULL, work_thread, s) != 0) {
blog(LOG_ERROR, "Failed to create GLava thread");
return;
}
s->initialized = true;
/* Obtain GLava's texture handle */
blog(LOG_INFO, "Waiting for GLava GL texture...");
glava_wait(&handle);
unsigned int g_tex = glava_tex(handle);
glava_sizereq(handle, 0, 0, s->cfg.w, s->cfg.h);
obs_enter_graphics();
/* Create a new high-level texture object */
s->gs_tex = gs_texture_create(s->cfg.w, s->cfg.h, GS_RGBA, 1, NULL, GS_DYNAMIC);
/* Re-assign the internal GL texture for the object */
s->old_tex = ((struct gs_texture_2d_internal*) s->gs_tex)->base.texture;
((struct gs_texture_2d_internal*) s->gs_tex)->base.texture = g_tex;
obs_leave_graphics();
blog(LOG_INFO, "GLava texture assigned");
}
static void destroy(void* data) {
struct mod_state* s = (struct mod_state*) data;
if (s) {
glava_join(s);
bfree(s);
}
}
static void update(void* data, obs_data_t* settings) {
struct mod_state* s = (struct mod_state*) data;
s->cfg.w = (int) obs_data_get_int(settings, "width");
s->cfg.h = (int) obs_data_get_int(settings, "height");
const char* opts = obs_data_get_string(settings, "options");
printf("debug: input str '%s', set '%s'\n", opts, s->cfg.opts);
bool opts_changed = s->cfg.opts == NULL || strcmp(opts, s->cfg.opts);
if (s->cfg.opts != NULL) {
free(s->cfg.opts);
}
s->cfg.opts = strdup(opts);
if (opts_changed) {
blog(LOG_INFO, "Updating GLava state");
glava_join(s);
glava_start(s);
} else {
glava_sizereq(handle, 0, 0, s->cfg.w, s->cfg.h);
((struct gs_texture_2d_internal*) s->gs_tex)->width = s->cfg.w;
((struct gs_texture_2d_internal*) s->gs_tex)->height = s->cfg.h;
}
}
static void video_render(void* data, gs_effect_t* effect) {
struct mod_state* s = (struct mod_state*) data;
if (s->gs_tex == NULL)
return;
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
gs_eparam_t* img = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(img, s->gs_tex);
while (gs_effect_loop(effect, "Draw"))
obs_source_draw(s->gs_tex, 0, 0, 0, 0, true);
}
static void* create(obs_data_t* settings, obs_source_t* source) {
blog(LOG_INFO, "Initializing GLava OBS Plugin...");
struct mod_state* s = bzalloc(sizeof(struct mod_state));
s->source = source;
s->cfg = (typeof(s->cfg)) { .w = 512, .h = 256, .opts = NULL };
s->gs_tex = NULL;
s->initialized = false;
struct obs_video_info ovi;
if (!obs_get_video_info(&ovi)) {
blog(LOG_ERROR, "Failed to obtain `obs_video_info`");
return NULL;
}
if (strncmp(ovi.graphics_module, "libobs-opengl", 13) != 0) {
blog(LOG_ERROR, "No GLX rendering context present");
return NULL;
}
obs_enter_graphics();
struct gs_subsystem_internal* sub = (struct gs_subsystem_internal*) gs_get_context();
glava_assign_external_ctx(sub->device->plat->context);
obs_leave_graphics();
update(s, settings);
return s;
}
static struct obs_source_info glava_src = {
.id = "glava",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_DO_NOT_DUPLICATE,
.get_name = get_name,
.create = create,
.destroy = destroy,
.update = update,
.video_render = video_render,
.get_width = get_width,
.get_height = get_height,
.get_properties = get_properties
};
__attribute__((visibility("default"))) bool obs_module_load(void) {
obs_register_source(&glava_src);
return true;
}

280
glava.c
View File

@@ -1,280 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "fifo.h"
#include "pulse_input.h"
#include "render.h"
#include "xwin.h"
#define GLAVA_VERSION "1.1"
#ifdef GLAD_DEBUG
#define GLAVA_RELEASE_TYPE_PREFIX "debug, "
#else
#define GLAVA_RELEASE_TYPE_PREFIX "stable, "
#endif
#ifdef GLAVA_STANDALONE
#define GLAVA_RELEASE_TYPE_BUILD "standalone"
#elif GLAVA_UNIX
#define GLAVA_RELEASE_TYPE_BUILD "unix/fhs"
#elif GLAVA_OSX
#define GLAVA_RELEASE_TYPE_BUILD "osx"
#else
#define GLAVA_RELEASE_TYPE_BUILD "?"
#endif
#define GLAVA_RELEASE_TYPE GLAVA_RELEASE_TYPE_PREFIX GLAVA_RELEASE_TYPE_BUILD
#define FORMAT(...) \
({ \
char* buf = malloc(PATH_MAX); \
snprintf(buf, PATH_MAX, __VA_ARGS__); \
buf; \
})
#define ENV(e, ...) \
({ \
const char* _e = getenv(e); \
if (!_e) \
_e = FORMAT(__VA_ARGS__); \
_e; \
})
#ifdef GLAVA_STANDALONE
#define SHADER_INSTALL_PATH "shaders"
#define SHADER_USER_PATH "userconf"
/* FHS compliant systems */
#elif defined(__unix__) || defined(GLAVA_UNIX)
#define SHADER_INSTALL_PATH "/etc/xdg/glava"
#define SHADER_USER_PATH FORMAT("%s/glava", ENV("XDG_CONFIG_HOME", "%s/.config", ENV("HOME", "/home")))
/* OSX */
#elif (defined(__APPLE__) && defined(__MACH__)) || defined(GLAVA_OSX)
#define SHADER_INSTALL_PATH "/Library/glava"
#define SHADER_USER_PATH FORMAT("%s/Library/Preferences/glava", ENV("HOME", "/"))
#else
#error "Unsupported target system"
#endif
/* Copy installed shaders/configuration from the installed location
(usually /etc/xdg). Modules (folders) will be linked instead of
copied. */
static void copy_cfg(const char* path, const char* dest, bool verbose) {
size_t
sl = strlen(path),
tl = strlen(dest),
pgsz = (size_t) getpagesize(); /* optimal buffer size */
DIR* dir = opendir(path);
if (!dir) {
fprintf(stderr, "'%s' does not exist\n", path);
exit(EXIT_FAILURE);
}
umask(~(S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH));
if (mkdir(dest, ACCESSPERMS) && errno != EEXIST) {
fprintf(stderr, "could not create directory '%s': %s\n", dest, strerror(errno));
exit(EXIT_FAILURE);
}
struct dirent* d;
while ((d = readdir(dir)) != NULL) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
int type = 0;
size_t
dl = strlen(d->d_name),
pl = sl + dl + 2,
fl = tl + dl + 2;
char p[pl], f[fl];
snprintf(p, pl, "%s/%s", path, d->d_name);
snprintf(f, fl, "%s/%s", dest, d->d_name);
if (d->d_type != DT_UNKNOWN) /* don't bother with stat if we already have the type */
type = d->d_type == DT_REG ? 1 : (d->d_type == DT_DIR ? 2 : 0);
else {
struct stat st;
if (lstat(p, &st)) {
fprintf(stderr, "failed to stat '%s': %s\n", p, strerror(errno));
} else
type = S_ISREG(st.st_mode) ? 1 : (S_ISDIR(st.st_mode) ? 2 : 0);
}
switch (type) {
case 1:
{
int source = -1, dest = -1;
uint8_t buf[pgsz];
ssize_t r, t, w, a;
if ((source = open(p, O_RDONLY)) < 0) {
fprintf(stderr, "failed to open (source) '%s': %s\n", p, strerror(errno));
goto cleanup;
}
if ((dest = open(f, O_TRUNC | O_WRONLY | O_CREAT, ACCESSPERMS)) < 0) {
fprintf(stderr, "failed to open (destination) '%s': %s\n", f, strerror(errno));
goto cleanup;
}
for (t = 0; (r = read(source, buf, pgsz)) > 0; t += r) {
for (a = 0; a < r && (w = write(dest, buf + a, r - a)) > 0; a += w);
if (w < 0) {
fprintf(stderr, "error while writing '%s': %s\n", f, strerror(errno));
goto cleanup;
}
}
if (r < 0) {
fprintf(stderr, "error while reading '%s': %s\n", p, strerror(errno));
goto cleanup;
}
if (verbose)
printf("copy '%s' -> '%s'\n", p, f);
cleanup:
if (source > 0) close(source);
if (dest > 0) close(dest);
}
break;
case 2:
if (symlink(p, f) && errno != EEXIST)
fprintf(stderr, "failed to symlink '%s' -> '%s': %s\n", p, f, strerror(errno));
else if (verbose)
printf("symlink '%s' -> '%s'\n", p, f);
break;
}
}
closedir(dir);
}
static const char* help_str =
"Usage: %s [OPTIONS]...\n"
"Opens a window with an OpenGL context to draw an audio visualizer.\n"
"\n"
"Available arguments:\n"
"-h, --help show this help and exit\n"
"-v, --verbose enables printing of detailed information about execution\n"
"-m, --force-mod=NAME forces the specified module to load instead, ignoring any\n"
" `#request mod` instances in the entry point.\n"
"-e, --entry=NAME specifies the name of the file to look for when loading shaders,\n"
" by default this is \"rc.glsl\".\n"
"-C, --copy-config creates copies and symbolic links in the user configuration\n"
" directory for glava, copying any files in the root directory\n"
" of the installed shader directory, and linking any modules.\n"
"\n"
"GLava (glava) " GLAVA_VERSION " (" GLAVA_RELEASE_TYPE ")\n"
" -- Copyright (C) 2017 Levi Webb\n";
static const char* opt_str = "hve:Cm:";
static struct option p_opts[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"entry", required_argument, 0, 'e'},
{"force-mod", required_argument, 0, 'm'},
{"copy-config", no_argument, 0, 'C'},
{0, 0, 0, 0 }
};
int main(int argc, char** argv) {
/* Evaluate these macros only once, since they allocate */
const char* install_path = SHADER_INSTALL_PATH;
const char* user_path = SHADER_USER_PATH;
const char* entry = "rc.glsl";
const char* force = NULL;
const char* system_shader_paths[] = { user_path, install_path, NULL };
bool verbose = false;
bool copy_mode = false;
int c, idx, n = 0;
while ((c = getopt_long(argc, argv, opt_str, p_opts, &idx)) != -1) {
switch (c) {
case 'v': verbose = true; break;
case 'C': copy_mode = true; break;
case 'e': entry = optarg; break;
case 'm': force = optarg; break;
case '?': exit(EXIT_FAILURE); break;
default:
case 'h':
printf(help_str, argc > 0 ? argv[0] : "glava");
exit(EXIT_SUCCESS);
}
}
if (copy_mode) {
copy_cfg(install_path, user_path, verbose);
exit(EXIT_SUCCESS);
}
renderer* r = rd_new(system_shader_paths, entry, force);
float b0[r->bufsize_request], b1[r->bufsize_request];
size_t t;
for (t = 0; t < r->bufsize_request; ++t) {
b0[t] = 0.0F;
b1[t] = 0.0F;
}
struct audio_data audio = {
.source = ({
char* src = NULL;
if (r->audio_source_request && strcmp(r->audio_source_request, "auto") != 0) {
src = strdup(r->audio_source_request);
}
src;
}),
.rate = (unsigned int) r->rate_request,
.format = -1,
.terminate = 0,
.channels = 2,
.audio_out_r = b0,
.audio_out_l = b1,
.mutex = PTHREAD_MUTEX_INITIALIZER,
.audio_buf_sz = r->bufsize_request,
.sample_sz = r->samplesize_request,
.modified = false
};
if (!audio.source) {
get_pulse_default_sink(&audio);
printf("Using default PulseAudio sink: %s\n", audio.source);
}
pthread_t thread;
int thread_id = pthread_create(&thread, NULL, input_pulse, (void*) &audio);
float lb[r->bufsize_request], rb[r->bufsize_request];
while (r->alive) {
rd_time(r); /* update timer for this frame */
bool modified; /* if the audio buffer has been updated by the streaming thread */
/* lock the audio mutex and read our data */
pthread_mutex_lock(&audio.mutex);
modified = audio.modified;
if (modified) {
/* create our own copies of the audio buffers, so the streaming thread can continue to append to it */
memcpy(lb, (void*) audio.audio_out_l, r->bufsize_request * sizeof(float));
memcpy(rb, (void*) audio.audio_out_r, r->bufsize_request * sizeof(float));
audio.modified = false; /* set this flag to false until the next time we read */
}
pthread_mutex_unlock(&audio.mutex);
/* Only render if needed (ie. stop rendering when fullscreen windows are focused) */
if (xwin_should_render()) {
rd_update(r, lb, rb, r->bufsize_request, modified);
} else {
/* Sleep for 50ms and then attempt to render again */
struct timespec tv = {
.tv_sec = 0, .tv_nsec = 50 * 1000000
};
nanosleep(&tv, NULL);
}
}
rd_destroy(r);
}

129
glava/fifo.c Normal file
View File

@@ -0,0 +1,129 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include "fifo.h"
/* Implementation struct storage */
typeof(*audio_impls) audio_impls[sizeof(audio_impls) / sizeof(struct audio_impl*)] = {};
size_t audio_impls_idx = 0;
/* FIFO backend */
static void init(struct audio_data* audio) {
if (!audio->source) {
audio->source = strdup("/tmp/mpd.fifo");
}
}
static void* entry(void* data) {
struct audio_data* audio = (struct audio_data *) data;
float* bl = (float*) audio->audio_out_l;
float* br = (float*) audio->audio_out_r;
size_t fsz = audio->audio_buf_sz;
size_t ssz = audio->sample_sz;
int fd;
int16_t buf[ssz / 2];
size_t q;
int timeout = 50;
struct timespec tv_last = {}, tv;
bool measured = false;
if ((fd = open(audio->source, O_RDONLY)) == -1) {
fprintf(stderr, "failed to open FIFO audio source \"%s\": %s\n", audio->source, strerror(errno));
exit(EXIT_FAILURE);
}
struct pollfd pfd = {
.fd = fd,
.events = POLLIN
};
size_t buffer_offset = (fsz - (ssz / 4));
while (true) {
/* The poll timeout is set to accommodate an approximate UPS, but has little purpose except
for effectively setting the rate of empty samples in the event of the FIFO descriptor
blocking for long periods of time. */
switch (poll(&pfd, 1, timeout)) {
case -1:
fprintf(stderr, "FIFO backend: poll() failed (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
case 0:
pthread_mutex_lock(&audio->mutex);
memmove(bl, &bl[ssz / 4], buffer_offset * sizeof(float));
memmove(br, &br[ssz / 4], buffer_offset * sizeof(float));
for (q = 0; q < (ssz / 4); ++q) bl[buffer_offset + q] = 0;
for (q = 0; q < (ssz / 4); ++q) br[buffer_offset + q] = 0;
audio->modified = true;
pthread_mutex_unlock(&audio->mutex);
break;
default: {
read(fd, buf, sizeof(buf));
clock_gettime(CLOCK_REALTIME, measured ? &tv : &tv_last);
if (measured) {
/* Set the timeout slightly higher than the delay between samples to prevent empty writes */
timeout = (((tv.tv_sec - tv_last.tv_sec) * 1000) + ((tv.tv_nsec - tv_last.tv_nsec) / 1000000)) + 1;
tv_last = tv;
} else measured = true;
pthread_mutex_lock(&audio->mutex);
memmove(bl, &bl[ssz / 4], buffer_offset * sizeof(float));
memmove(br, &br[ssz / 4], buffer_offset * sizeof(float));
for (size_t n = 0, q = 0; q < (ssz / 2); q += 2) {
size_t idx = (fsz - (ssz / 4)) + n;
if (audio->channels == 1) {
float sample = ((buf[q] + buf[q + 1]) / 2) / (float) 65535;
bl[idx] = sample;
br[idx] = sample;
}
if (audio->channels == 2) {
bl[idx] = buf[q] / (float) 65535;
br[idx] = buf[q + 1] / (float) 65535;
}
n++;
}
audio->modified = true;
pthread_mutex_unlock(&audio->mutex);
break;
}
}
if (audio->terminate == 1) {
close(fd);
break;
}
}
return 0;
}
AUDIO_ATTACH(fifo);

46
glava/fifo.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef FIFO_H
#define FIFO_H
#include <pthread.h>
#include <stdlib.h>
#include <stdbool.h>
struct audio_data {
volatile float* audio_out_r;
volatile float* audio_out_l;
bool modified;
size_t audio_buf_sz, sample_sz;
int format;
unsigned int rate;
char *source; // pulse source
int channels;
int terminate; // shared variable used to terminate audio thread
pthread_mutex_t mutex;
};
struct audio_impl {
const char* name;
void (*init)(struct audio_data* data);
void* (*entry)(void* data);
};
#define AUDIO_FUNC(F) \
.F = (typeof(((struct audio_impl*) NULL)->F)) &F
extern struct audio_impl* audio_impls[4];
extern size_t audio_impls_idx;
static inline void register_audio_impl(struct audio_impl* impl) { audio_impls[audio_impls_idx++] = impl; }
#define AUDIO_ATTACH(N) \
static struct audio_impl N##_var = { \
.name = #N, \
AUDIO_FUNC(init), \
AUDIO_FUNC(entry), \
}; \
void __attribute__((constructor)) _##N##_construct(void) { \
register_audio_impl(&N##_var); \
}
#endif

2550
glava/glad.c Normal file

File diff suppressed because it is too large Load Diff

5183
glava/glad.h Normal file

File diff suppressed because it is too large Load Diff

577
glava/glava.c Normal file
View File

@@ -0,0 +1,577 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "fifo.h"
#include "pulse_input.h"
#include "render.h"
#include "xwin.h"
#ifdef GLAD_DEBUG
#define GLAVA_RELEASE_TYPE_PREFIX "debug, "
#else
#define GLAVA_RELEASE_TYPE_PREFIX "stable, "
#endif
#ifdef GLAVA_STANDALONE
#define GLAVA_RELEASE_TYPE_BUILD "standalone"
#elif GLAVA_UNIX
#define GLAVA_RELEASE_TYPE_BUILD "unix/fhs"
#elif GLAVA_OSX
#define GLAVA_RELEASE_TYPE_BUILD "osx"
#else
#define GLAVA_RELEASE_TYPE_BUILD "?"
#endif
#define GLAVA_RELEASE_TYPE GLAVA_RELEASE_TYPE_PREFIX GLAVA_RELEASE_TYPE_BUILD
#define FORMAT(...) \
({ \
char* buf = malloc(PATH_MAX); \
snprintf(buf, PATH_MAX, __VA_ARGS__); \
buf; \
})
#define ENV(e, ...) \
({ \
const char* _e = getenv(e); \
if (!_e) \
_e = FORMAT(__VA_ARGS__); \
_e; \
})
#ifdef GLAVA_STANDALONE
#define SHADER_INSTALL_PATH "../shaders/glava"
#define SHADER_USER_PATH "userconf"
/* FHS compliant systems */
#elif defined(__unix__) || defined(GLAVA_UNIX)
#ifndef SHADER_INSTALL_PATH
#define SHADER_INSTALL_PATH "/etc/xdg/glava"
#endif
#define SHADER_USER_PATH FORMAT("%s/glava", ENV("XDG_CONFIG_HOME", "%s/.config", ENV("HOME", "/home")))
/* OSX */
#elif (defined(__APPLE__) && defined(__MACH__)) || defined(GLAVA_OSX)
#ifndef SHADER_INSTALL_PATH
#define SHADER_INSTALL_PATH "/Library/glava"
#endif
#define SHADER_USER_PATH FORMAT("%s/Library/Preferences/glava", ENV("HOME", "/"))
#else
#error "Unsupported target system"
#endif
#ifndef ACCESSPERMS
#define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO) /* 0777 */
#endif
static volatile bool reload = false;
__attribute__((noreturn, visibility("default"))) void glava_return_builtin(void) { exit(EXIT_SUCCESS); }
__attribute__((noreturn, visibility("default"))) void glava_abort_builtin (void) { exit(EXIT_FAILURE); }
__attribute__((noreturn, visibility("default"))) void (*glava_return) (void) = glava_return_builtin;
__attribute__((noreturn, visibility("default"))) void (*glava_abort) (void) = glava_abort_builtin;
/* Copy installed shaders/configuration from the installed location
(usually /etc/xdg). Modules (folders) will be linked instead of
copied. */
static void copy_cfg(const char* path, const char* dest, bool verbose) {
size_t
sl = strlen(path),
tl = strlen(dest),
pgsz = (size_t) getpagesize(); /* optimal buffer size */
DIR* dir = opendir(path);
if (!dir) {
fprintf(stderr, "'%s' does not exist\n", path);
glava_abort();
}
umask(~(S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH));
if (mkdir(dest, ACCESSPERMS) && errno != EEXIST) {
fprintf(stderr, "could not create directory '%s': %s\n", dest, strerror(errno));
glava_abort();
}
struct dirent* d;
while ((d = readdir(dir)) != NULL) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
int type = 0;
size_t
dl = strlen(d->d_name),
pl = sl + dl + 2,
fl = tl + dl + 2;
char p[pl], f[fl];
snprintf(p, pl, "%s/%s", path, d->d_name);
snprintf(f, fl, "%s/%s", dest, d->d_name);
if (d->d_type != DT_UNKNOWN) /* don't bother with stat if we already have the type */
type = d->d_type == DT_REG ? 1 : (d->d_type == DT_DIR ? 2 : 0);
else {
struct stat st;
if (lstat(p, &st)) {
fprintf(stderr, "failed to stat '%s': %s\n", p, strerror(errno));
} else
type = S_ISREG(st.st_mode) ? 1 : (S_ISDIR(st.st_mode) ? 2 : 0);
}
switch (type) {
case 1: {
int source = -1, dest = -1;
uint8_t buf[pgsz];
ssize_t r, t, w, a;
if (!strncmp(p, "env_", 4))
break;
if ((source = open(p, O_RDONLY)) < 0) {
fprintf(stderr, "failed to open (source) '%s': %s\n", p, strerror(errno));
goto cleanup;
}
if ((dest = open(f, O_TRUNC | O_WRONLY | O_CREAT, ACCESSPERMS)) < 0) {
fprintf(stderr, "failed to open (destination) '%s': %s\n", f, strerror(errno));
goto cleanup;
}
for (t = 0; (r = read(source, buf, pgsz)) > 0; t += r) {
for (a = 0; a < r && (w = write(dest, buf + a, r - a)) > 0; a += w);
if (w < 0) {
fprintf(stderr, "error while writing '%s': %s\n", f, strerror(errno));
goto cleanup;
}
}
if (r < 0) {
fprintf(stderr, "error while reading '%s': %s\n", p, strerror(errno));
goto cleanup;
}
if (verbose)
printf("copy '%s' -> '%s'\n", p, f);
cleanup:
if (source > 0) close(source);
if (dest > 0) close(dest);
}
break;
case 2:
if (symlink(p, f) && errno != EEXIST)
fprintf(stderr, "failed to symlink '%s' -> '%s': %s\n", p, f, strerror(errno));
else if (verbose)
printf("symlink '%s' -> '%s'\n", p, f);
break;
}
}
closedir(dir);
}
#define GLAVA_VERSION_STRING "GLava (glava) " GLAVA_VERSION " (" GLAVA_RELEASE_TYPE ")"
static const char* help_str =
"Usage: %s [OPTIONS]...\n"
"Opens a window with an OpenGL context to draw an audio visualizer.\n"
"\n"
"Available arguments:\n"
"-h, --help show this help and exit\n"
"-v, --verbose enables printing of detailed information about execution\n"
"-d, --desktop enables running glava as a desktop window by detecting the\n"
" desktop environment and setting the appropriate properties\n"
" automatically. Can override properties in \"rc.glsl\".\n"
"-r, --request=REQUEST evaluates the specified request after loading \"rc.glsl\".\n"
"-m, --force-mod=NAME forces the specified module to load instead, ignoring any\n"
" `#request mod` instances in the entry point.\n"
"-e, --entry=FILE specifies the name of the file to look for when loading shaders,\n"
" by default this is \"rc.glsl\".\n"
"-C, --copy-config creates copies and symbolic links in the user configuration\n"
" directory for glava, copying any files in the root directory\n"
" of the installed shader directory, and linking any modules.\n"
"-b, --backend specifies a window creation backend to use. By default, the most\n"
" appropriate backend will be used for the underlying windowing\n"
" system.\n"
"-a, --audio=BACKEND specifies an audio input backend to use.\n"
"-p, --pipe[=BIND[:TYPE]] binds value(s) to be read from stdin. The input my be read using\n"
" `@name` or `@name:default` syntax within shader sources.\n"
" A stream of inputs (each overriding the previous) must be\n"
" assigned with the `name = value` syntax and separated by\n"
" newline (\'\\n\') characters.\n"
"-V, --version print application version and exit\n"
"\n"
"The REQUEST argument is evaluated identically to the \'#request\' preprocessor directive\n"
"in GLSL files.\n"
"\n"
"The FILE argument may be any file path. All specified file paths are relative to the\n"
"active configuration root (usually ~/.config/glava if present).\n"
"\n"
"The BACKEND argument may be any of the following strings (for this particular build):\n"
"%s"
"\n"
"The BIND argument must a valid GLSL identifier."
"\n"
"The TYPE argument must be a valid GLSL type. If `--pipe` is used without a \n"
"type argument, the default type is `vec4` (type used for RGBA colors).\n"
"\n"
GLAVA_VERSION_STRING "\n";
static const char* opt_str = "dhvVe:Cm:b:r:a:i::p::";
static struct option p_opts[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"desktop", no_argument, 0, 'd'},
{"audio", required_argument, 0, 'a'},
{"request", required_argument, 0, 'r'},
{"entry", required_argument, 0, 'e'},
{"force-mod", required_argument, 0, 'm'},
{"copy-config", no_argument, 0, 'C'},
{"backend", required_argument, 0, 'b'},
{"pipe", optional_argument, 0, 'p'},
{"stdin", optional_argument, 0, 'i'},
{"version", no_argument, 0, 'V'},
#ifdef GLAVA_DEBUG
{"run-tests", no_argument, 0, 'T'},
#endif
{0, 0, 0, 0 }
};
#define append_buf(buf, sz_store, ...) \
({ \
buf = realloc(buf, ++(*sz_store) * sizeof(*buf)); \
buf[*sz_store - 1] = __VA_ARGS__; \
})
/* Wait for glava_renderer target texture to be initialized and valid */
__attribute__((visibility("default")))
void glava_wait(glava_handle* ref) {
while(__atomic_load_n(ref, __ATOMIC_SEQ_CST) == NULL) {
/* Edge case: handle has not been assigned */
struct timespec tv = {
.tv_sec = 0, .tv_nsec = 10 * 1000000
};
nanosleep(&tv, NULL);
}
pthread_mutex_lock(&(*ref)->lock);
while ((*ref)->flag == false)
pthread_cond_wait(&(*ref)->cond, &(*ref)->lock);
pthread_mutex_unlock(&(*ref)->lock);
}
__attribute__((visibility("default")))
unsigned int glava_tex(glava_handle r) {
return r->off_tex;
}
/* Atomic size request */
__attribute__((visibility("default")))
void glava_sizereq(glava_handle r, int x, int y, int w, int h) {
r->sizereq = (typeof(r->sizereq)) { .x = x, .y = y, .w = w, .h = h };
__atomic_store_n(&r->sizereq_flag, GLAVA_REQ_RESIZE, __ATOMIC_SEQ_CST);
}
/* Atomic terminate request */
__attribute__((visibility("default")))
void glava_terminate(glava_handle* ref) {
glava_handle store = __atomic_exchange_n(ref, NULL, __ATOMIC_SEQ_CST);
if (store)
__atomic_store_n(&store->alive, false, __ATOMIC_SEQ_CST);
}
/* Atomic reload request */
__attribute__((visibility("default")))
void glava_reload(glava_handle* ref) {
glava_handle store = __atomic_exchange_n(ref, NULL, __ATOMIC_SEQ_CST);
if (store) {
__atomic_store_n(&reload, true, __ATOMIC_SEQ_CST);
__atomic_store_n(&store->alive, false, __ATOMIC_SEQ_CST);
}
}
/* Main entry */
__attribute__((visibility("default")))
void glava_entry(int argc, char** argv, glava_handle* ret) {
/* Evaluate these macros only once, since they allocate */
const char
* install_path = SHADER_INSTALL_PATH,
* user_path = SHADER_USER_PATH,
* entry = "rc.glsl",
* force = NULL,
* backend = NULL,
* audio_impl_name = "pulseaudio";
const char* system_shader_paths[] = { user_path, install_path, NULL };
int stdin_type = STDIN_TYPE_NONE;
char** requests = malloc(1);
size_t requests_sz = 0;
struct rd_bind* binds = malloc(1);
size_t binds_sz = 0;
bool verbose = false, copy_mode = false, desktop = false, test = false;
int c, idx;
while ((c = getopt_long(argc, argv, opt_str, p_opts, &idx)) != -1) {
switch (c) {
case 'v': verbose = true; break;
case 'C': copy_mode = true; break;
case 'd': desktop = true; break;
case 'r': append_buf(requests, &requests_sz, optarg); break;
case 'e': entry = optarg; break;
case 'm': force = optarg; break;
case 'b': backend = optarg; break;
case 'a': audio_impl_name = optarg; break;
case '?': glava_abort(); break;
case 'V':
puts(GLAVA_VERSION_STRING);
glava_return();
break;
default:
case 'h': {
char buf[2048];
size_t bsz = 0;
for (size_t t = 0; t < audio_impls_idx; ++t)
bsz += snprintf(buf + bsz, sizeof(buf) - bsz, "\t\"%s\"%s\n", audio_impls[t]->name,
!strcmp(audio_impls[t]->name, audio_impl_name) ? " (default)" : "");
printf(help_str, argc > 0 ? argv[0] : "glava", buf);
glava_return();
break;
}
case 'p': {
if (stdin_type != STDIN_TYPE_NONE) goto conflict_error;
char* parsed_name = NULL;
const char* parsed_type = NULL;
if (optarg) {
size_t in_sz = strlen(optarg);
int sep = -1;
for (size_t t = 0; t < in_sz; ++t) {
switch (optarg[t]) {
case ' ': optarg[t] = '\0'; goto after;
case ':': sep = (int) t; break;
}
}
after:
if (sep >= 0) {
parsed_type = optarg + sep + 1;
optarg[sep] = '\0';
}
parsed_name = optarg;
} else parsed_name = PIPE_DEFAULT;
if (*parsed_name == '\0') {
fprintf(stderr, "Error: invalid pipe binding name: \"%s\"\n"
"Zero length names are not permitted.\n", parsed_name);
glava_abort();
}
for (char* c = parsed_name; *c != '\0'; ++c) {
switch (*c) {
case '0' ... '9':
if (c == parsed_name) {
fprintf(stderr, "Error: invalid pipe binding name: \"%s\" ('%c')\n"
"Valid names may not start with a number.\n", parsed_name, *c);
glava_abort();
}
case 'a' ... 'z':
case 'A' ... 'Z':
case '_': continue;
default:
fprintf(stderr, "Error: invalid pipe binding name: \"%s\" ('%c')\n"
"Valid names may only contain [a..z], [A..Z], [0..9] "
"and '_' characters.\n", parsed_name, *c);
glava_abort();
}
}
for (size_t t = 0; t < binds_sz; ++t) {
if (!strcmp(binds[t].name, parsed_name)) {
fprintf(stderr, "Error: attempted to re-bind pipe argument: \"%s\"\n", parsed_name);
glava_abort();
}
}
int type = -1;
if (parsed_type == NULL || strlen(parsed_type) == 0) {
type = STDIN_TYPE_VEC4;
parsed_type = bind_types[STDIN_TYPE_VEC4].n;
} else {
for (size_t t = 0 ; bind_types[t].n != NULL; ++t) {
if (!strcmp(bind_types[t].n, parsed_type)) {
type = bind_types[t].i;
parsed_type = bind_types[t].n;
break;
}
}
}
if (type == -1) {
fprintf(stderr, "Error: Unsupported `--pipe` GLSL type: \"%s\"\n", parsed_type);
glava_abort();
}
struct rd_bind bd = {
.name = parsed_name,
.type = type,
.stype = parsed_type
};
append_buf(binds, &binds_sz, bd);
break;
}
case 'i': {
if (binds_sz > 0) goto conflict_error;
fprintf(stderr, "Warning: `--stdin` is deprecated and will be "
"removed in a future release, use `--pipe` instead. \n");
stdin_type = -1;
if (optarg == NULL) {
stdin_type = STDIN_TYPE_VEC4;
} else {
for (size_t t = 0 ; bind_types[t].n != NULL; ++t) {
if (!strcmp(bind_types[t].n, optarg)) {
stdin_type = bind_types[t].i;
break;
}
}
}
if (stdin_type == -1) {
fprintf(stderr, "Error: Unsupported `--stdin` GLSL type: \"%s\"\n", optarg);
glava_abort();
}
break;
}
conflict_error:
fprintf(stderr, "Error: cannot use `--pipe` and `--stdin` together\n");
glava_abort();
#ifdef GLAVA_DEBUG
case 'T': {
entry = "test_rc.glsl";
test = true;
}
#endif
}
}
if (copy_mode) {
copy_cfg(install_path, user_path, verbose);
glava_return();
}
/* Handle `--force` argument as a request override */
if (force) {
const size_t bsz = 5 + strlen(force);
char* force_req_buf = malloc(bsz);
snprintf(force_req_buf, bsz, "mod %s", force);
append_buf(requests, &requests_sz, force_req_buf);
}
/* Null terminate array arguments */
append_buf(requests, &requests_sz, NULL);
append_buf(binds, &binds_sz, (struct rd_bind) { .name = NULL });
float* b0, * b1, * lb, * rb;
size_t t;
struct audio_data audio;
struct audio_impl* impl = NULL;
pthread_t thread;
int return_status;
for (t = 0; t < audio_impls_idx; ++t) {
if (!strcmp(audio_impls[t]->name, audio_impl_name)) {
impl = audio_impls[t];
break;
}
}
if (!impl) {
fprintf(stderr, "The specified audio backend (\"%s\") is not available.\n", audio_impl_name);
glava_abort();
}
instantiate: {}
glava_renderer* rd = rd_new(system_shader_paths, entry, (const char**) requests,
backend, binds, stdin_type, desktop, verbose, test);
if (ret)
__atomic_store_n(ret, rd, __ATOMIC_SEQ_CST);
b0 = malloc(rd->bufsize_request * sizeof(float));
b1 = malloc(rd->bufsize_request * sizeof(float));
lb = malloc(rd->bufsize_request * sizeof(float));
rb = malloc(rd->bufsize_request * sizeof(float));
for (t = 0; t < rd->bufsize_request; ++t) {
b0[t] = 0.0F;
b1[t] = 0.0F;
}
audio = (struct audio_data) {
.source = ({
char* src = NULL;
if (rd->audio_source_request && strcmp(rd->audio_source_request, "auto") != 0) {
src = strdup(rd->audio_source_request);
}
src;
}),
.rate = (unsigned int) rd->rate_request,
.format = -1,
.terminate = 0,
.channels = rd->mirror_input ? 1 : 2,
.audio_out_r = b0,
.audio_out_l = b1,
.mutex = PTHREAD_MUTEX_INITIALIZER,
.audio_buf_sz = rd->bufsize_request,
.sample_sz = rd->samplesize_request,
.modified = false
};
impl->init(&audio);
if (verbose) printf("Using audio source: %s\n", audio.source);
pthread_create(&thread, NULL, impl->entry, (void*) &audio);
while (__atomic_load_n(&rd->alive, __ATOMIC_SEQ_CST)) {
rd_time(rd); /* update timer for this frame */
bool modified; /* if the audio buffer has been updated by the streaming thread */
/* lock the audio mutex and read our data */
pthread_mutex_lock(&audio.mutex);
modified = audio.modified;
if (modified) {
/* create our own copies of the audio buffers, so the streaming
thread can continue to append to it */
memcpy(lb, (void*) audio.audio_out_l, rd->bufsize_request * sizeof(float));
memcpy(rb, (void*) audio.audio_out_r, rd->bufsize_request * sizeof(float));
audio.modified = false; /* set this flag to false until the next time we read */
}
pthread_mutex_unlock(&audio.mutex);
bool ret = rd_update(rd, lb, rb, rd->bufsize_request, modified);
if (!ret) {
/* Sleep for 50ms and then attempt to render again */
struct timespec tv = {
.tv_sec = 0, .tv_nsec = 50 * 1000000
};
nanosleep(&tv, NULL);
}
#ifdef GLAVA_DEBUG
if (ret && rd_get_test_mode(rd))
break;
#endif
}
#ifdef GLAVA_DEBUG
if (rd_get_test_mode(rd)) {
if (rd_test_evaluate(rd)) {
fprintf(stderr, "Test results did not match expected output\n");
fflush(stderr);
glava_abort();
}
}
#endif
audio.terminate = 1;
if ((return_status = pthread_join(thread, NULL))) {
fprintf(stderr, "Failed to join with audio thread: %s\n", strerror(return_status));
}
free(audio.source);
free(b0);
free(b1);
free(lb);
free(rb);
rd_destroy(rd);
if (__atomic_exchange_n(&reload, false, __ATOMIC_SEQ_CST))
goto instantiate;
}

27
glava/glava.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef _GLAVA_H
#define _GLAVA_H
#include <stdbool.h>
#include <stdint.h>
#include <pthread.h>
#define GLAVA_REQ_NONE 0
#define GLAVA_REQ_RESIZE 1
struct gl_data;
struct glava_renderer;
/* External API */
typedef struct glava_renderer* volatile glava_handle;
__attribute__((noreturn, visibility("default"))) void (*glava_abort) (void);
__attribute__((noreturn, visibility("default"))) void (*glava_return) (void);
__attribute__((visibility("default"))) void glava_assign_external_ctx (void* ctx);
__attribute__((visibility("default"))) void glava_entry (int argc, char** argv, glava_handle* ret);
__attribute__((visibility("default"))) void glava_terminate (glava_handle* ref);
__attribute__((visibility("default"))) void glava_reload (glava_handle* ref);
__attribute__((visibility("default"))) void glava_sizereq (glava_handle r, int x, int y, int w, int h);
__attribute__((visibility("default"))) void glava_wait (glava_handle* ref);
__attribute__((visibility("default"))) unsigned int glava_tex (glava_handle r);
#endif /* _GLAVA_H */

151
glava/glfw_wcb.c Normal file
View File

@@ -0,0 +1,151 @@
/* GLFW window and OpenGL context creation. */
#ifdef GLAVA_GLFW
#define GLAVA_RDX11
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <X11/Xlib.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "render.h"
#include "xwin.h"
#define GLFW_EXPOSE_NATIVE_X11
/* Hack to make GLFW 3.1 headers work with GLava. We don't use the context APIs from GLFW, but
the old headers require one of them to be selected for exposure in glfw3native.h. */
#if GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR <= 1
#define GLFW_EXPOSE_NATIVE_GLX
#endif
#include <GLFW/glfw3native.h>
/* Fixes for old GLFW versions */
#ifndef GLFW_TRUE
#define GLFW_TRUE GL_TRUE
#endif
#ifndef GLFW_FALSE
#define GLFW_FALSE GL_FALSE
#endif
#define DECL_WINDOW_HINT(F, H) \
static void F(bool var) { glfwWindowHint(H, var); }
#define DECL_WINDOW_HINT_STUB(F) \
static void F(bool _) { fprintf(stderr, "Warning: " #F " not implemented for GLFW backend\n"); }
static void init(void) {
if (!glfwInit()) {
fprintf(stderr, "glfwInit(): failed\n");
abort();
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_FLOATING, GLFW_FALSE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
}
DECL_WINDOW_HINT(set_floating, GLFW_FLOATING);
DECL_WINDOW_HINT(set_decorated, GLFW_DECORATED);
DECL_WINDOW_HINT(set_focused, GLFW_FOCUSED);
#ifdef GLFW_MAXIMIZED
DECL_WINDOW_HINT(set_maximized, GLFW_MAXIMIZED);
#else
DECL_WINDOW_HINT_STUB(set_maximized);
#endif
extern struct gl_wcb wcb_glfw;
static bool offscreen(void) { return false; }
static void* create_and_bind(const char* name, const char* class,
const char* type, const char** states,
size_t states_sz,
int d, int h,
int x, int y,
int version_major, int version_minor,
bool clickthrough, bool offscreen) {
GLFWwindow* w;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, version_major);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, version_minor);
if (!(w = glfwCreateWindow(d, h, class, NULL, NULL))) {
fprintf(stderr, "glfwCreateWindow(): failed\n");
glfwTerminate();
return NULL;
}
if (type)
xwin_settype(&wcb_glfw, w, type);
for (size_t t = 0; t < states_sz; ++t)
xwin_addstate(&wcb_glfw, w, states[t]);
glfwSetWindowPos(w, x, y);
glfwMakeContextCurrent(w);
if (!glad_instantiated) {
gladLoadGL();
glad_instantiated = true;
}
return w;
}
static void set_transparent(bool transparent) {
#ifdef GLFW_TRANSPARENT_FRAMEBUFFER
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, transparent ? GLFW_TRUE : GLFW_FALSE);
#elif GLFW_TRANSPARENT
glfwWindowHint(GLFW_TRANSPARENT, transparent ? GLFW_TRUE : GLFW_FALSE);
#else
if (transparent)
fprintf(stderr, "Warning: the linked version of GLFW3 does not have transparency support"
" (GLFW_TRANSPARENT[_FRAMEBUFFER])!\n");
#endif
}
static void set_geometry(GLFWwindow* w, int x, int y, int d, int h) {
glfwSetWindowPos(w, x, y);
glfwSetWindowSize(w, d, h);
}
static void set_visible(GLFWwindow* w, bool visible) {
if (visible) glfwShowWindow(w);
else glfwHideWindow(w);
}
static void swap_buffers(GLFWwindow* w) {
glfwSwapBuffers(w);
glfwPollEvents();
}
static Display* get_x11_display(void) { return glfwGetX11Display(); }
static Window get_x11_window (GLFWwindow* w) { return glfwGetX11Window(w); }
static bool should_close (GLFWwindow* w) { return glfwWindowShouldClose(w); }
static bool should_render (GLFWwindow* w) { return true; }
static bool bg_changed (GLFWwindow* w) { return false; }
static void get_fbsize (GLFWwindow* w, int* d, int* h) { glfwGetFramebufferSize(w, d, h); }
static void get_pos (GLFWwindow* w, int* x, int* y) { glfwGetWindowPos(w, x, y); }
static double get_time (GLFWwindow* w) { return glfwGetTime(); }
static void set_time (GLFWwindow* w, double time) { glfwSetTime(time); }
static void set_swap (int i) { glfwSwapInterval(i); }
static void raise (GLFWwindow* w) { glfwShowWindow(w); }
static void destroy (GLFWwindow* w) { glfwDestroyWindow(w); }
static void terminate (void) { glfwTerminate(); }
static const char* get_environment(void) { return xwin_detect_wm(&wcb_glfw); }
WCB_ATTACH("glfw", wcb_glfw);
#endif /* GLAVA_GLFW */

738
glava/glsl_ext.c Normal file
View File

@@ -0,0 +1,738 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "glava.h"
#include "render.h"
#include "glsl_ext.h"
#define LINE_START 0
#define GLSL 1
#define MACRO 2
#define REQUEST 3
#define INCLUDE 4
#define COLOR 5
#define DEFINE 6
#define BIND 7
#define EXPAND 8
struct sbuf {
char* buf;
size_t at; /* index of final null character */
size_t bsize; /* actual buffer size */
};
#define append(sbuf, str) n_append(sbuf, strlen(str), str)
static inline void expand_for(struct sbuf* sbuf, size_t len) {
bool resize = false;
while (len + 1 > sbuf->bsize - sbuf->at) {
sbuf->bsize *= 2;
resize = true;
}
if (resize)
sbuf->buf = realloc(sbuf->buf, sbuf->bsize);
}
/* append 'n' bytes from 'str' to the resizable buffer */
static void n_append(struct sbuf* sbuf, size_t len, const char* str) {
expand_for(sbuf, len);
memcpy(sbuf->buf + sbuf->at, str, len);
sbuf->at += len;
sbuf->buf[sbuf->at] = '\0';
}
#define s_append(sbuf, fmt, ...) se_append(sbuf, 64, fmt, __VA_ARGS__)
/* append the formatted string to the resizable buffer, where elen is extra space for formatted chars */
static void se_append(struct sbuf* sbuf, size_t elen, const char* fmt, ...) {
size_t space = strlen(fmt) + elen;
expand_for(sbuf, space);
va_list args;
va_start(args, fmt);
int written;
if ((written = vsnprintf(sbuf->buf + sbuf->at, space, fmt, args)) < 0)
glava_abort();
sbuf->at += written;
va_end(args);
}
#define parse_error(line, f, fmt, ...) \
do { \
fprintf(stderr, "[%s:%d] " fmt "\n", f, (int) line, __VA_ARGS__); \
glava_abort(); \
} while (0)
#define parse_error_s(line, f, s) \
do { \
fprintf(stderr, "[%s:%d] " s "\n", f, (int) line); \
glava_abort(); \
} while (0)
struct schar {
char* buf;
size_t sz;
};
bool ext_parse_color(const char* str, size_t elem_sz, float** results) {
size_t t, len = strlen(str), i = 0, s = 0;
uint8_t elem_bytes[elem_sz];
/* Ignore '0x' prefix, if present */
if (len >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
len -= 2;
str += 2;
}
for (t = 0; t < len && t < 8; ++t) {
char c = str[t];
uint8_t b;
/* obtain value from character */
switch (c) {
case 'a' ... 'f': b = (c - 'a') + 10; break;
case 'A' ... 'F': b = (c - 'A') + 10; break;
case '0' ... '9': b = c - '0'; break;
default: return false;
}
elem_bytes[s] = b;
if (s >= elem_sz - 1) { /* advance to next element */
uint32_t e = 0; /* component storage */
/* mask storage with input data */
for (size_t v = 0; v < elem_sz; ++v) {
e |= (uint32_t) elem_bytes[v] << (((elem_sz - 1) - v) * 4);
}
/* convert to [0, 1] as floating point value */
*results[i] = (float) e / (float) ((1 << (elem_sz * 4)) - 1);
s = 0;
++i;
} else { /* advance character */
++s;
}
}
return true;
}
static void free_after(struct glsl_ext* ext, void* ptr) {
++ext->destruct_sz;
ext->destruct = realloc(ext->destruct, sizeof(void*) * ext->destruct_sz);
ext->destruct[ext->destruct_sz - 1] = ptr;
}
static void inherit(struct glsl_ext* parent, struct glsl_ext* child) {
free_after(parent, child->processed);
parent->destruct = realloc(parent->destruct, sizeof(void*) * (parent->destruct_sz + child->destruct_sz));
memcpy(parent->destruct + parent->destruct_sz, child->destruct, sizeof(void*) * child->destruct_sz);
parent->destruct_sz += child->destruct_sz;
free(child->destruct);
}
/* handle raw arguments for #include and #request directives */
static struct schar directive(struct glsl_ext* ext, char** args,
size_t args_sz, int state,
size_t line, const char* f) {
switch (state) {
case DEFINE: {
/* Workaround for re-defining macros in GLSL. By default this is generally an error in most
compilers/drivers, but we would prefer to override (non-function) definitions instead.
Due to how this directive is parsed, the macro itself is still emitted afterwards. */
if (args_sz == 0) {
parse_error_s(line, f, "No arguments provided to #define directive!");
}
size_t bsz = (strlen(args[0]) * 3) + 64;
struct schar ret = { .buf = malloc(bsz) };
int r = snprintf(ret.buf, bsz, "#ifdef %1$s\n#undef %1$s\n#endif\n", args[0]);
if (r < 0)
glava_abort();
ret.sz = r;
free_after(ext, ret.buf);
return ret;
}
case INCLUDE: {
if (args_sz == 0) {
parse_error_s(line, f, "No arguments provided to #include directive!");
}
char* target = args[0];
/* Handle `:` config specifier */
size_t tsz = strlen(target);
if (tsz && target[0] == ':' && ext->cfd) {
target = &target[1];
ext->cd = ext->cfd;
}
/* Handle `@` default specifier */
if (tsz && target[0] == '@') {
if (!ext->dd) {
parse_error_s(line, f, "encountered '@' path specifier while no default "
"directory is available in the current context");
}
target = &target[1];
ext->cd = ext->dd;
}
char path[strlen(ext->cd) + tsz + 2];
snprintf(path, sizeof(path) / sizeof(char), "%s/%s", ext->cd, target);
int fd = open(path, O_RDONLY);
if (fd == -1)
parse_error(line, f, "failed to load GLSL shader source "
"specified by #include directive '%s': %s\n",
path, strerror(errno));
struct stat st;
fstat(fd, &st);
char* map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (!map)
parse_error(line, f, "failed to map GLSL shader source "
"specified by #include directive '%s': %s\n",
path, strerror(errno));
struct glsl_ext next = {
.source = map,
.source_len = st.st_size,
.cd = ext->cd,
.cfd = ext->cfd,
.dd = ext->dd,
.handlers = ext->handlers,
.binds = ext->binds,
.ss_lookup = ext->ss_lookup,
.ss_len = ext->ss_len,
.efuncs = ext->efuncs
};
/* recursively process */
ext_process(&next, target);
inherit(ext, &next);
munmap(map, st.st_size);
close(fd);
ext->ss_lookup = next.ss_lookup;
struct schar ret = {
.buf = next.processed,
.sz = next.p_len
};
return ret;
}
case REQUEST: {
if (args_sz > 0) {
char* request = args[0];
struct request_handler* handler;
bool found = false;
size_t t;
for (t = 0; (handler = &ext->handlers[t])->name != NULL; ++t) {
if(!strcmp(handler->name, request)) {
found = true;
void** processed_args = malloc(strlen(handler->fmt) * sizeof(void*));
char c;
size_t i;
for (i = 0; (c = handler->fmt[i]) != '\0'; ++i) {
if (args_sz <= 1 + i)
parse_error(line, f,
"failed to execute request '%s': expected format '%s'\n",
request, handler->fmt);
char* raw = args[1 + i];
switch (c) {
case 'i': {
int v = (int) strtol(raw, NULL, 0);
processed_args[i] = malloc(sizeof(int));
*(int*) processed_args[i] = v;
break;
}
case 'f': {
float f = strtof(raw, NULL);
processed_args[i] = malloc(sizeof(float));
*(float*) processed_args[i] = f;
break;
}
case 's': { *(char**) &processed_args[i] = raw; break; }
case 'b': {
bool v;
if (!strcmp(raw, "true")) {
v = true;
} else if (!strcmp(raw, "false")) {
v = false;
} else if (strlen(raw) == 1) {
switch (raw[0]) {
case 't': { v = true; break; }
case 'f': { v = false; break; }
case '1': { v = true; break; }
case '0': { v = false; break; }
default:
parse_error_s(line, f, "tried to parse invalid "
"raw string into a boolean");
}
} else
parse_error_s(line, f, "tried to parse invalid "
"raw string into a boolean");
processed_args[i] = malloc(sizeof(bool));
*(bool*) processed_args[i] = v;
break;
}
}
}
handler->handler(request, processed_args);
for (i = 0; (c = handler->fmt[i]) != '\0'; ++i)
if (c != 's')
free(processed_args[i]);
free(processed_args);
}
}
if (!found)
parse_error(line, f, "unknown request type '%s'", request);
}
goto return_empty;
}
case EXPAND: {
if (args_sz >= 2) {
char* fmacro = args[0];
size_t fmacro_sz = strlen(fmacro);
char* arg = args[1];
size_t expand_n = 0;
bool match = false;
if (ext->efuncs) {
for (size_t t = 0; ext->efuncs[t].name != NULL; ++t) {
if (!strcmp(arg, ext->efuncs[t].name)) {
expand_n = ext->efuncs[t].call();
match = true;
break;
}
}
}
if (!match)
parse_error(line, f, "#expand directive specified invalid input \"%s\"", arg);
/* (2 {paren} + 1 {semicolon} + 1 {newline} + 4 {input buf} + macro) * expand + 1 */
size_t bsz = ((8 + fmacro_sz) * expand_n) + 1;
struct schar ret = { .buf = malloc(bsz) };
int r = 0;
for (size_t t = 0; t < expand_n; ++t) {
int sr = snprintf(ret.buf + r, bsz - r, "%s(%d);\n", args[0], (int) t);
if (sr >= 0)
r += sr;
else
parse_error(line, f, "internal formatting error (snprintf returned %d)", sr);
}
ret.sz = r;
free_after(ext, ret.buf);
return ret;
} else
parse_error(line, f, "#expand directive missing arguments, "
"requires 2 identifiers (got %d)\n", (int) args_sz);
goto return_empty;
}
return_empty:
default: return (struct schar) { .buf = NULL, .sz = 0 };
}
}
/* state machine parser */
void ext_process(struct glsl_ext* ext, const char* f) {
ext->destruct = malloc(1);
ext->destruct_sz = 0;
if (!ext->ss_lookup) {
ext->ss_lookup = malloc(sizeof(ext->ss_lookup[0]));
ext->ss_len_s = 0;
ext->ss_len = &ext->ss_len_s;
ext->ss_own = true;
} else ext->ss_own = false;
ext->ss_lookup = realloc(ext->ss_lookup, sizeof(ext->ss_lookup[0]) * ++(*ext->ss_len));
int ss_cur = *ext->ss_len - 1;
ext->ss_lookup[ss_cur] = strdup(f);
struct sbuf sbuf = {
.buf = malloc(256),
.at = 0,
.bsize = 256
};
size_t source_len = ext->source_len;
size_t t;
char at;
int state = LINE_START;
size_t macro_start_idx = 0, arg_start_idx = 0, cbuf_idx, bbuf_idx, b_restart;
size_t line = 1;
bool quoted = false, arg_start = false, b_sep = false, b_spc = false, b_pre = true;
int b_br = 0;
char cbuf[9];
char bbuf[256];
char** args = malloc(sizeof(char*));
size_t args_sz = 0;
bool prev_slash = false, comment = false, comment_line = false, prev_asterix = false,
prev_escape = false, string = false, skip_color_start = false;
se_append(&sbuf, 32, "#line 1 %d\n", ss_cur);
for (t = 0; t <= source_len; ++t) {
at = source_len == t ? '\0' : ext->source[t];
if (at == '\n')
++line;
switch (state) {
case LINE_START: { /* processing start of line */
switch (at) {
case '#': {
macro_start_idx = t;
state = MACRO;
continue;
}
case '\n':
if (comment && comment_line) {
comment = false;
comment_line = false;
}
case '\t':
case ' ':
goto copy;
default: state = GLSL;
/* let execution continue into next state */
}
}
case GLSL: { /* copying GLSL source or unrelated preprocessor syntax */
switch (at) {
case '"':
if (!comment && !prev_escape)
string = !string;
goto normal_char;
case '\\':
if (!comment) {
prev_escape = !prev_escape;
prev_asterix = false;
prev_slash = false;
goto copy;
} else goto normal_char;
case '/':
if (!comment) {
if (prev_slash) {
comment = true;
comment_line = true;
prev_slash = false;
} else prev_slash = true;
} else if (!comment_line) {
if (prev_asterix) {
comment = false;
prev_asterix = false;
}
}
prev_escape = false;
goto copy;
case '*':
if (!comment) {
if (prev_slash) {
comment = true;
prev_slash = false;
}
} else prev_asterix = true;
prev_escape = false;
goto copy;
case '#': {
/* handle hex color syntax */
if (!comment && !string && !skip_color_start) {
if (ext->source[t + 1] == '#') {
skip_color_start = true;
goto normal_char;
}
state = COLOR;
cbuf_idx = 0;
continue;
} else {
skip_color_start = false;
goto normal_char;
}
}
case '@': {
/* handle bind syntax */
if (!comment && !string && ext->binds != NULL) {
state = BIND;
b_sep = false;
b_spc = false;
b_pre = true;
b_br = 0;
b_restart = 0;
bbuf_idx = 0;
continue;
} else goto normal_char;
}
case '\n':
if (comment && comment_line) {
comment = false;
comment_line = false;
}
state = LINE_START;
normal_char:
default:
prev_asterix = false;
prev_slash = false;
prev_escape = false;
goto copy;
}
}
case COLOR: { /* parse hex color syntax (#ffffffff -> vec4(1.0, 1.0, 1.0, 1.0)) */
switch (at) {
case 'a' ... 'z':
case 'A' ... 'Z':
case '0' ... '9': {
cbuf[cbuf_idx] = at;
++cbuf_idx;
if (cbuf_idx >= 8)
goto emit_color;
else continue;
}
emit_color:
default:
cbuf[cbuf_idx] = '\0'; /* null terminate */
float r = 0.0F, g = 0.0F, b = 0.0F, a = 1.0F;
if (ext_parse_color(cbuf, 2, (float*[]) { &r, &g, &b, &a })) {
se_append(&sbuf, 64, " vec4(%.6f, %.6f, %.6f, %.6f) ", r, g, b, a);
} else {
parse_error(line, f, "Invalid color format '#%s' while "
"parsing GLSL color syntax extension", cbuf);
}
state = at == '\n' ? LINE_START : GLSL;
if (cbuf_idx >= 8)
continue;
else goto copy; /* copy character if it ended the sequence */
}
}
case BIND: { /* parse bind syntax (@name:default -> __IN_name | default)*/
switch (at) {
default:
if (b_br > 0) goto handle_bind; /* store characters in braces */
else goto emit_bind; /* emit on unexpected char outside braces */
case '(':
if (b_sep && !b_spc) {
++b_br; goto handle_bind; /* inc. brace level */
} else goto emit_bind; /* emit if wrong context: `@sym(`, `@(` (no ':') */
case ')':
/* start emitting on unexpected ')': `@sym:v)`, `@s)` */
if (b_br <= 0 || !b_sep) goto emit_bind;
else {
--b_br;
if (b_br <= 0) b_spc = true;
goto handle_bind; /* dec. brace level */
}
case ' ': if (b_br <= 0) b_spc = true; /* flag a non-braced space */
case '#': case '+': case '-':
case '!': case '~': case '&':
if (b_sep && (b_br > 0 || b_pre))
goto handle_bind; /* handle precede syntax only for defaults */
else goto emit_bind; /* if encountered, skip to emit */
case ':':
if (!b_sep) b_restart = t;
b_sep = true;
handle_bind: /* use character for binding syntax */
case 'a' ... 'z':
case 'A' ... 'Z':
case '0' ... '9':
case '_': {
if (b_spc && at != ')')
goto emit_bind; /* skip non-braced characters after space: `@sym:vec4 c` */
if (b_sep && at != ':')
b_pre = false;
bbuf[bbuf_idx] = at;
++bbuf_idx;
if (bbuf_idx >= sizeof(bbuf) - 1)
goto emit_bind; /* start emitting if buffer was filled */
else continue;
}
emit_bind: /* end binding syntax with current char */
case '\n':
case '\0': {
const char* parsed_name = NULL;
const char* parsed_default = NULL;
bbuf[bbuf_idx] = '\0'; /* null terminate */
int sep = -1;
for (size_t p = 0; p < bbuf_idx; ++p)
if (bbuf[p] == ':') sep = p;
if (sep >= 0) {
parsed_default = bbuf + sep + 1;
bbuf[sep] = '\0';
}
parsed_name = bbuf;
bool m = false;
for (struct rd_bind* bd = ext->binds; bd->name != NULL; ++bd) {
if (!strcmp(parsed_name, bd->name)) {
se_append(&sbuf, 128, " _IN_%s ", parsed_name);
m = true;
break;
}
}
if (!m) {
if (parsed_default && b_restart > 0) {
/* To emit the default, we push back the cursor to where it starts
and simply resume parsing from a normal context. */
t = b_restart;
} else parse_error(line, f,
"Unexpected `--pipe` binding name '@%s' while parsing GLSL."
" Try assigning a default or binding the value.", parsed_name);
}
state = GLSL;
continue;
}
}
}
/* emit contents from start of macro to current index and resume regular parsing*/
#define skip_macro() \
do { \
n_append(&sbuf, t - macro_start_idx, &ext->source[macro_start_idx]); \
state = at == '\n' ? LINE_START : GLSL; \
goto copy; \
} while (0)
case MACRO: { /* processing start of macro */
switch (at) {
case '\n':
case ' ':
case '\t':
case '\0': { /* end parsing directive */
#define DIRECTIVE_CMP(lower, upper) \
(!strncmp("#" lower, &ext->source[macro_start_idx], t - macro_start_idx) \
|| !strncmp("#" upper, &ext->source[macro_start_idx], t - macro_start_idx))
#define DIRECTIVE_CASE(lower, upper) \
({ if (state == MACRO && DIRECTIVE_CMP(#lower, #upper)) \
{ state = upper; goto prepare_arg_parse; } })
DIRECTIVE_CASE(request, REQUEST);
DIRECTIVE_CASE(include, INCLUDE);
DIRECTIVE_CASE(define, DEFINE);
DIRECTIVE_CASE(expand, EXPAND);
/* no match */
if (state == MACRO) skip_macro();
#undef DIRECTIVE_CMP
#undef DIRECTIVE_CASE
prepare_arg_parse:
{
arg_start_idx = t + 1;
arg_start = true;
args_sz = 0;
*args = NULL;
}
}
case '0' ... '9':
/* digits at the start of an identifier are not legal */
if (macro_start_idx == t - 1)
goto macro_parse_error;
case 'a' ... 'z':
case 'A' ... 'Z':
continue;
default:
macro_parse_error:
/* invalid char, malformed! */
parse_error(line, f, "Unexpected character '%c' while parsing GLSL directive", at);
}
}
/* scope-violating macro to copy the result of the currently parsed argument */
#define copy_arg(end) \
do { if (end - arg_start_idx > 0) { \
++args_sz; \
args = realloc(args, sizeof(char*) * args_sz); \
args[args_sz - 1] = malloc((end - arg_start_idx) + 1); \
memcpy(args[args_sz - 1], &ext->source[arg_start_idx], end - arg_start_idx); \
args[args_sz - 1][end - arg_start_idx] = '\0'; \
} } while (0)
case REQUEST:
case INCLUDE:
case DEFINE:
case EXPAND: {
switch (at) {
case ' ':
case '\t':
case '\n':
case '\0':
if (!quoted) {
/* end arg */
copy_arg(t);
arg_start = true;
arg_start_idx = t + 1;
} else arg_start = false;
if (at == '\n' || at == '\0' || state == DEFINE) {
/* end directive */
size_t a;
struct schar r = directive(ext, args, args_sz, state, line, f);
for (a = 0; a < args_sz; ++a) {
free(args[a]);
}
args_sz = 0;
/* if something was returned (ie. included file), paste the results */
if (r.buf) {
n_append(&sbuf, r.sz, r.buf);
append(&sbuf, "\n");
se_append(&sbuf, 48, "#line %d %d\n", line, ss_cur);
}
if (state == DEFINE) skip_macro();
else state = LINE_START;
}
break;
case '(':
if (state != DEFINE || args_sz != 0) goto arg; /* only handle first arg of #define */
skip_macro(); /* ignore macro functions */
case '"':
if (state == DEFINE) goto arg; /* do not handle quoting for #define */
if (quoted) {
/* end arg */
copy_arg(t);
quoted = false;
arg_start = true;
arg_start_idx = t + 1;
} else if (arg_start) {
++arg_start_idx;
quoted = true;
} else arg_start = false;
break;
default: {
arg: arg_start = false;
}
}
continue;
}
#undef copy_arg
}
copy:
if (at != '\0')
n_append(&sbuf, 1, &at);
}
ext->processed = sbuf.buf;
ext->p_len = sbuf.at;
if (args) {
for (t = 0; t < args_sz; ++t) {
free(args[t]);
}
free(args);
}
}
void ext_free(struct glsl_ext* ext) {
size_t t;
free(ext->processed);
if (ext->ss_own) {
for (t = 0; t < ext->ss_len_s; ++t)
free(ext->ss_lookup[t]);
free(ext->ss_lookup);
}
for (t = 0; t < ext->destruct_sz; ++t)
free(ext->destruct[t]);
free(ext->destruct);
}

68
glava/glsl_ext.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef GLSL_EXT_H
#define GLSL_EXT_H
#include <stdlib.h>
#include <stdbool.h>
struct request_handler {
const char* name;
/*
handler format:
'i' - signed integer (void* -> int*)
'f' - float (void* -> float*)
's' - string (void* -> const char*)
'b' - bool (void* -> bool*)
example:
.fmt = "sii" // takes a string, and then two integers
.fmt = "ffb" // takes two floats, then a boolean
*/
const char* fmt;
#if defined(__clang__)
void (^handler)(const char* name, void** args);
#elif defined(__GNUC__) || defined(__GNUG__)
void (*handler)(const char* name, void** args);
#else
#error "no nested function/block syntax available"
#endif
};
struct glsl_ext_efunc {
char* name;
#if defined(__clang__)
size_t (^call)(void);
#elif defined(__GNUC__) || defined(__GNUG__)
size_t (*call)(void);
#else
#error "no nested function/block syntax available"
#endif
};
struct glsl_ext {
char* processed; /* OUT: null terminated processed source */
size_t p_len; /* OUT: length of processed buffer, excluding null char */
const char* source; /* IN: raw data passed via ext_process */
size_t source_len; /* IN: raw source len */
const char* cd; /* IN: current directory */
const char* cfd; /* IN: config directory, if NULL it is assumed to cd */
const char* dd; /* IN: default directory */
struct rd_bind* binds; /* OPT IN: --pipe binds */
struct glsl_ext_efunc* efuncs; /* OPT IN: `#expand` binds */
void** destruct; /* internal */
size_t destruct_sz; /* internal */
char** ss_lookup; /* source-string lookup table */
size_t* ss_len;
size_t ss_len_s;
bool ss_own;
/* IN: NULL (where the last element's 'name' member is NULL) terminated
array of request handlers */
struct request_handler* handlers;
};
void ext_process(struct glsl_ext* ext, const char* f);
void ext_free (struct glsl_ext* ext);
bool ext_parse_color(const char* hex, size_t elem_sz, float** results);
#endif

665
glava/glx_wcb.c Normal file
View File

@@ -0,0 +1,665 @@
/* Xlib window creation and GLX context creation backend */
#ifdef GLAVA_GLX
#define GLAVA_RDX11
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <dlfcn.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include "glad.h"
#include "render.h"
#include "xwin.h"
typedef struct __GLXcontextRec* GLXContext;
typedef XID GLXPixmap;
typedef XID GLXDrawable;
typedef void (*__GLXextFuncPtr)(void);
/* GLX 1.3 and later */
typedef struct __GLXFBConfigRec* GLXFBConfig;
typedef XID GLXFBConfigID;
typedef XID GLXContextID;
typedef XID GLXWindow;
typedef XID GLXPbuffer;
/*
* Tokens for glXChooseVisual and glXGetConfig:
*/
#define GLX_USE_GL 1
#define GLX_BUFFER_SIZE 2
#define GLX_LEVEL 3
#define GLX_RGBA 4
#define GLX_DOUBLEBUFFER 5
#define GLX_STEREO 6
#define GLX_AUX_BUFFERS 7
#define GLX_RED_SIZE 8
#define GLX_GREEN_SIZE 9
#define GLX_BLUE_SIZE 10
#define GLX_ALPHA_SIZE 11
#define GLX_DEPTH_SIZE 12
#define GLX_STENCIL_SIZE 13
#define GLX_ACCUM_RED_SIZE 14
#define GLX_ACCUM_GREEN_SIZE 15
#define GLX_ACCUM_BLUE_SIZE 16
#define GLX_ACCUM_ALPHA_SIZE 17
/*
* Error codes returned by glXGetConfig:
*/
#define GLX_BAD_SCREEN 1
#define GLX_BAD_ATTRIBUTE 2
#define GLX_NO_EXTENSION 3
#define GLX_BAD_VISUAL 4
#define GLX_BAD_CONTEXT 5
#define GLX_BAD_VALUE 6
#define GLX_BAD_ENUM 7
/*
* GLX 1.1 and later:
*/
#define GLX_VENDOR 1
#define GLX_VERSION 2
#define GLX_EXTENSIONS 3
/*
* GLX 1.3 and later:
*/
#define GLX_CONFIG_CAVEAT 0x20
#define GLX_DONT_CARE 0xFFFFFFFF
#define GLX_X_VISUAL_TYPE 0x22
#define GLX_TRANSPARENT_TYPE 0x23
#define GLX_TRANSPARENT_INDEX_VALUE 0x24
#define GLX_TRANSPARENT_RED_VALUE 0x25
#define GLX_TRANSPARENT_GREEN_VALUE 0x26
#define GLX_TRANSPARENT_BLUE_VALUE 0x27
#define GLX_TRANSPARENT_ALPHA_VALUE 0x28
#define GLX_WINDOW_BIT 0x00000001
#define GLX_PIXMAP_BIT 0x00000002
#define GLX_PBUFFER_BIT 0x00000004
#define GLX_AUX_BUFFERS_BIT 0x00000010
#define GLX_FRONT_LEFT_BUFFER_BIT 0x00000001
#define GLX_FRONT_RIGHT_BUFFER_BIT 0x00000002
#define GLX_BACK_LEFT_BUFFER_BIT 0x00000004
#define GLX_BACK_RIGHT_BUFFER_BIT 0x00000008
#define GLX_DEPTH_BUFFER_BIT 0x00000020
#define GLX_STENCIL_BUFFER_BIT 0x00000040
#define GLX_ACCUM_BUFFER_BIT 0x00000080
#define GLX_NONE 0x8000
#define GLX_SLOW_CONFIG 0x8001
#define GLX_TRUE_COLOR 0x8002
#define GLX_DIRECT_COLOR 0x8003
#define GLX_PSEUDO_COLOR 0x8004
#define GLX_STATIC_COLOR 0x8005
#define GLX_GRAY_SCALE 0x8006
#define GLX_STATIC_GRAY 0x8007
#define GLX_TRANSPARENT_RGB 0x8008
#define GLX_TRANSPARENT_INDEX 0x8009
#define GLX_VISUAL_ID 0x800B
#define GLX_SCREEN 0x800C
#define GLX_NON_CONFORMANT_CONFIG 0x800D
#define GLX_DRAWABLE_TYPE 0x8010
#define GLX_RENDER_TYPE 0x8011
#define GLX_X_RENDERABLE 0x8012
#define GLX_FBCONFIG_ID 0x8013
#define GLX_RGBA_TYPE 0x8014
#define GLX_COLOR_INDEX_TYPE 0x8015
#define GLX_MAX_PBUFFER_WIDTH 0x8016
#define GLX_MAX_PBUFFER_HEIGHT 0x8017
#define GLX_MAX_PBUFFER_PIXELS 0x8018
#define GLX_PRESERVED_CONTENTS 0x801B
#define GLX_LARGEST_PBUFFER 0x801C
#define GLX_WIDTH 0x801D
#define GLX_HEIGHT 0x801E
#define GLX_EVENT_MASK 0x801F
#define GLX_DAMAGED 0x8020
#define GLX_SAVED 0x8021
#define GLX_WINDOW 0x8022
#define GLX_PBUFFER 0x8023
#define GLX_PBUFFER_HEIGHT 0x8040
#define GLX_PBUFFER_WIDTH 0x8041
#define GLX_RGBA_BIT 0x00000001
#define GLX_COLOR_INDEX_BIT 0x00000002
#define GLX_PBUFFER_CLOBBER_MASK 0x08000000
/*
* GLX 1.4 and later:
*/
#define GLX_SAMPLE_BUFFERS 0x186a0 /*100000*/
#define GLX_SAMPLES 0x186a1 /*100001*/
/* glXCreateContextAttribsARB extension definitions */
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
typedef void (*glXSwapIntervalEXTProc) (Display*, GLXDrawable, int);
GLXFBConfig* (*glXChooseFBConfig) (Display* dpy, int screen, const int* attribList, int* nitems);
XVisualInfo* (*glXGetVisualFromFBConfig)(Display* dpy, GLXFBConfig config);
int (*glXGetFBConfigAttrib) (Display* dpy, GLXFBConfig config, int attribute, int *value );
Bool (*glXMakeCurrent) (Display* dpy, GLXDrawable drawable, GLXContext ctx);
GLXDrawable (*glXGetCurrentDrawable) (void);
__GLXextFuncPtr (*glXGetProcAddressARB) (const GLubyte *);
void (*glXSwapBuffers) (Display* dpy, GLXDrawable drawable);
void (*glXDestroyContext) (Display* dpy, GLXContext ctx);
Bool (*glXQueryVersion) (Display* dpy, int* major, int* minor);
GLXPixmap (*glXCreateGLXPixmap) (Display* dpy, XVisualInfo* vis, Pixmap pixmap);
extern struct gl_wcb wcb_glx;
static Display* display;
static int swap;
static bool floating, decorated, focused, maximized, transparent;
struct glxwin {
Window w;
GLXContext context;
double time;
bool should_close, should_render, bg_changed, clickthrough, offscreen;
char override_state;
Pixmap off_pixmap;
GLXPixmap off_glxpm;
};
static Atom ATOM__MOTIF_WM_HINTS, ATOM_WM_DELETE_WINDOW, ATOM_WM_PROTOCOLS, ATOM__NET_ACTIVE_WINDOW, ATOM__XROOTPMAP_ID;
static GLXContext sharelist_ctx;
static bool sharelist_assigned = false;
static bool offscreen(void) {
return sharelist_assigned;
}
/* Public function that can be called before GLava instantiation for offscreen rendering hooks */
/* This hook resides here since it relies on GLX functionality. */
__attribute__((visibility("default"))) void glava_assign_external_ctx(void* ctx) {
sharelist_ctx = (GLXContext) ctx;
sharelist_assigned = true;
}
static void* resolve_f(const char* symbol, void* gl) {
void* s = NULL;
if (gl) s = dlsym(gl, symbol);
if (!s) {
fprintf(stderr, "Failed to resolve GLX symbol: `%s`\n", symbol);
glava_abort();
}
return s;
}
static void init(void) {
/* XQuartz */
#ifdef __APPLE__
static const char *dl_names[] = {
"../Frameworks/OpenGL.framework/OpenGL",
"/Library/Frameworks/OpenGL.framework/OpenGL",
"/System/Library/Frameworks/OpenGL.framework/OpenGL",
"/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"
};
#else
static const char *dl_names[] = {"libGL.so.1", "libGL.so"};
#endif
display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "XOpenDisplay(): could not establish connection to X11 server\n");
abort();
}
floating = false;
decorated = true;
focused = false;
maximized = false;
transparent = false;
void* hgl = NULL;
for(size_t i = 0; i < (sizeof(dl_names) / sizeof(dl_names[0])) && hgl == NULL; ++i)
hgl = dlopen(dl_names[1], RTLD_LAZY);
if (!hgl) {
fprintf(stderr, "Failed to load GLX functions (libGL and libGLX do not exist!)\n");
glava_abort();
}
#define resolve(name) do { name = (typeof(name)) resolve_f(#name, hgl); } while (0)
#define intern(name, only_if_exists) \
do { ATOM_##name = XInternAtom(display, #name, only_if_exists); } while (0)
resolve(glXChooseFBConfig);
resolve(glXGetVisualFromFBConfig);
resolve(glXGetFBConfigAttrib);
resolve(glXMakeCurrent);
resolve(glXGetCurrentDrawable);
resolve(glXGetProcAddressARB);
resolve(glXSwapBuffers);
resolve(glXDestroyContext);
resolve(glXQueryVersion);
resolve(glXCreateGLXPixmap);
intern(_MOTIF_WM_HINTS, false);
intern(WM_DELETE_WINDOW, true);
intern(WM_PROTOCOLS, true);
intern(_NET_ACTIVE_WINDOW, false);
intern(_XROOTPMAP_ID, false);
#undef intern
#undef resolve
}
static void apply_decorations(Window w) {
if (!decorated) {
struct {
unsigned long flags, functions, decorations;
long input_mode;
unsigned long status;
} hints;
hints.flags = 2;
hints.decorations = 0;
XChangeProperty(display, w, ATOM__MOTIF_WM_HINTS, ATOM__MOTIF_WM_HINTS, 32, PropModeReplace,
(unsigned char*) &hints, sizeof(hints) / sizeof(long));
}
}
static bool find_parent(Window w, Window* parent) {
Window root, *children = NULL;
unsigned int num_children;
if(!XQueryTree(display, w, &root, parent, &children, &num_children))
return false;
if (children)
XFree(children);
return *parent != None;
}
static void apply_clickthrough(struct glxwin* w) {
if (w->clickthrough) {
int ignored;
if (XShapeQueryExtension(display, &ignored, &ignored)) {
Window root = DefaultRootWindow(display);
Window win = w->w;
while (win != None) {
Region region;
if ((region = XCreateRegion())) {
XShapeCombineRegion(display, w->w, ShapeInput, 0, 0, region, ShapeSet);
XDestroyRegion(region);
}
Window parent;
find_parent(win, &parent);
win = (parent == root ? None : parent);
}
} else {
fprintf(stderr, "Warning: XShape extension not available\n");
}
}
}
static void process_events(struct glxwin* w) {
while (XPending(display) > 0) {
XEvent ev;
XNextEvent(display, &ev);
switch (ev.type) {
case ClientMessage:
if (ev.xclient.message_type == ATOM_WM_PROTOCOLS
&& ev.xclient.data.l[0] == ATOM_WM_DELETE_WINDOW) {
w->should_close = true;
}
break;
case MapNotify:
apply_clickthrough(w);
XFlush(display);
break;
case VisibilityNotify:
switch (ev.xvisibility.state) {
case VisibilityFullyObscured:
w->should_render = false;
break;
case VisibilityUnobscured:
case VisibilityPartiallyObscured:
w->should_render = true;
break;
default:
fprintf(stderr, "Invalid VisibilityNotify event state (%d)\n", ev.xvisibility.state);
break;
}
break;
case PropertyNotify:
if (ev.xproperty.atom == ATOM__XROOTPMAP_ID) {
w->bg_changed = true;
}
break;
default: break;
}
}
}
static void* create_and_bind(const char* name, const char* class,
const char* type, const char** states,
size_t states_sz,
int d, int h,
int x, int y,
int version_major, int version_minor,
bool clickthrough, bool off) {
/* Assume offscreen rendering if hook has been used */
if (offscreen())
off = true;
struct glxwin* w = malloc(sizeof(struct glxwin));
*w = (struct glxwin) {
.override_state = '\0',
.time = 0.0,
.should_close = false,
.should_render = true,
.bg_changed = false,
.clickthrough = false,
.offscreen = off
};
XVisualInfo* vi;
XSetWindowAttributes attr = {};
GLXFBConfig* fbc;
int fb_sz, best = -1, samp = -1;
int glx_minor, glx_major;
glXQueryVersion(display, &glx_minor, &glx_major);
if (glx_major <= 1 && glx_minor < 4) {
fprintf(stderr,
"\nGLX extension version mismatch on the current display (1.4+ required, %d.%d available)\n"
"This is usually due to an outdated X server or graphics drivers.\n\n",
glx_minor, glx_major);
glava_abort();
}
static int gl_attrs[] = {
GLX_X_RENDERABLE, True,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_DOUBLEBUFFER, True,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
None
};
int context_attrs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, version_major,
GLX_CONTEXT_MINOR_VERSION_ARB, version_minor,
// GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
None
};
fbc = glXChooseFBConfig(display, DefaultScreen(display), gl_attrs, &fb_sz);
if (!fbc) {
fprintf(stderr,
"\nFailed to obtain a GLX frame buffer that supports OpenGL %d.%d.\n"
"This is usually due to running on very old hardware or not having appropriate drivers.\n\n"
"glXChooseFBConfig(): failed with attrs "
"(GLX_CONTEXT_MAJOR_VERSION_ARB, GLX_CONTEXT_MINOR_VERSION_ARB)\n\n",
version_major, version_minor);
glava_abort();
}
for (int t = 0; t < fb_sz; ++t) {
XVisualInfo* xvi = glXGetVisualFromFBConfig(display, fbc[t]);
if (xvi) {
int samp_buf, samples;
glXGetFBConfigAttrib(display, fbc[t], GLX_SAMPLE_BUFFERS, &samp_buf);
glXGetFBConfigAttrib(display, fbc[t], GLX_SAMPLES, &samples );
XRenderPictFormat* fmt = XRenderFindVisualFormat(display, xvi->visual);
if (!fmt || (transparent ? fmt->direct.alphaMask == 0 : fmt->direct.alphaMask != 0))
continue;
if (best < 0 || (samp_buf && samples > samp)) {
best = t;
samp = samples;
}
XFree(xvi);
}
}
if (best == -1) {
fprintf(stderr, "Could not find suitable format for FBConfig\n");
abort();
}
GLXFBConfig config = fbc[best];
XFree(fbc);
vi = glXGetVisualFromFBConfig(display, config);
attr.colormap = XCreateColormap(display, DefaultRootWindow(display), vi->visual, AllocNone);
attr.event_mask = ExposureMask | KeyPressMask | StructureNotifyMask;
attr.event_mask |= PropertyChangeMask | VisibilityChangeMask;
attr.background_pixmap = None;
attr.border_pixel = 0;
unsigned long vmask = CWColormap | CWEventMask | CWBackPixmap | CWBorderPixel;
if (type[0] == '!') {
vmask |= CWOverrideRedirect;
attr.override_redirect = true;
w->override_state = type[1];
}
if (!(w->w = XCreateWindow(display, DefaultRootWindow(display)/**xwin_get_desktop_layer(&wcb_glx)*/,
x, y, d, h, 0,
vi->depth, InputOutput, vi->visual,
vmask, &attr))) {
fprintf(stderr, "XCreateWindow(): failed\n");
abort();
}
bool desktop = false;
if (type)
desktop = xwin_settype(&wcb_glx, w, type);
for (size_t t = 0; t < states_sz; ++t)
xwin_addstate(&wcb_glx, w, states[t]);
if (floating) xwin_addstate(&wcb_glx, w, "above");
if (maximized) {
xwin_addstate(&wcb_glx, w, "maximized_horz");
xwin_addstate(&wcb_glx, w, "maximized_vert");
}
XSetClassHint(display, w->w, &((XClassHint) { .res_name = (char*) class, .res_class = (char*) class }));
apply_decorations(w->w);
XStoreName(display, w->w, name);
XSetWMProtocols(display, w->w, &ATOM_WM_DELETE_WINDOW, 1);
/* Eliminate the window's effective region */
w->clickthrough = desktop || clickthrough;
apply_clickthrough(w);
glXCreateContextAttribsARBProc glXCreateContextAttribsARB = NULL;
glXSwapIntervalEXTProc glXSwapIntervalEXT = NULL;
glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
glXGetProcAddressARB((const GLubyte*) "glXCreateContextAttribsARB");
glXSwapIntervalEXT = (glXSwapIntervalEXTProc)
glXGetProcAddressARB((const GLubyte*) "glXSwapIntervalEXT");
if (!glXCreateContextAttribsARB) {
fprintf(stderr, "glXGetProcAddressARB(\"glXCreateContextAttribsARB\"): failed\n");
abort();
}
if (!(w->context = glXCreateContextAttribsARB(display, config, sharelist_assigned ? sharelist_ctx : 0, True, context_attrs))) {
fprintf(stderr, "glXCreateContextAttribsARB(): failed\n");
abort();
}
XSync(display, False);
if (w->offscreen) {
w->off_pixmap = XCreatePixmap(display, w->w, d, h,
DefaultDepth(display, DefaultScreen(display)));
w->off_glxpm = glXCreateGLXPixmap(display, vi, w->off_pixmap);
glXMakeCurrent(display, w->off_glxpm, w->context);
} else
glXMakeCurrent(display, w->w, w->context);
if (!glad_instantiated) {
gladLoadGL();
glad_instantiated = true;
}
GLXDrawable drawable = glXGetCurrentDrawable();
if (glXSwapIntervalEXT) glXSwapIntervalEXT(display, drawable, swap);
if (!transparent)
XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask);
XFree(vi);
return w;
}
static void raise(struct glxwin* w) {
if (w->override_state == '\0') {
XClientMessageEvent ev = {
.type = ClientMessage,
.serial = 0,
.send_event = true,
.display = display,
.window = w->w,
.message_type = ATOM__NET_ACTIVE_WINDOW,
.format = 32,
.data = { .l = {
[0] = 1, /* source indication -- `1` when coming from an application */
[1] = 0, /* timestamp -- `0` to (attempt to) ignore */
[2] = w->w /* requestor's currently active window -- `0` for none */
}
}
};
/* Send the client message as defined by EWMH standards (usually works) */
XSendEvent(display, DefaultRootWindow(display), false, StructureNotifyMask, (XEvent*) &ev);
}
/* Raise the client in the X11 stacking order (sometimes works, can be blocked by the WM) */
XRaiseWindow(display, w->w);
XFlush(display);
}
static void set_swap (int _swap) { swap = _swap; }
static void set_floating (bool _floating) { floating = _floating; }
static void set_decorated (bool _decorated) { decorated = _decorated; }
static void set_focused (bool _focused) { focused = _focused; }
static void set_maximized (bool _maximized) { maximized = _maximized; }
static void set_transparent(bool _transparent) { transparent = _transparent; }
static void set_geometry(struct glxwin* w, int x, int y, int d, int h) {
XMoveResizeWindow(display, w->w, x, y, (unsigned int) d, (unsigned int) h);
}
static void set_visible(struct glxwin* w, bool visible) {
if (w->offscreen)
return;
if (visible) {
XMapWindow(display, w->w);
switch (w->override_state) {
case '+': XRaiseWindow(display, w->w); break;
case '-': XLowerWindow(display, w->w); break;
default: break;
}
XFlush(display);
}
else XUnmapWindow(display, w->w);
}
static bool should_close (struct glxwin* w) { return w->should_close; }
static bool bg_changed (struct glxwin* w) { return w->bg_changed; }
static bool should_render(struct glxwin* w) {
if (w->offscreen)
return true;
/* For nearly all window managers, windows are 'minimized' by unmapping parent windows.
VisibilityNotify events are not sent in these instances, so we have to read window
attributes to see if our window isn't viewable. */
XWindowAttributes attrs;
XGetWindowAttributes(display, w->w, &attrs);
process_events(w);
return w->should_render && attrs.map_state == IsViewable;
}
static void swap_buffers(struct glxwin* w) {
if (w->offscreen)
glXSwapBuffers(display, w->off_glxpm);
else
glXSwapBuffers(display, w->w);
process_events(w);
}
static void get_fbsize(struct glxwin* w, int* d, int* h) {
XWindowAttributes a;
XGetWindowAttributes(display, w->w, &a);
*d = a.width;
*h = a.height;
}
static void get_pos(struct glxwin* w, int* x, int* y) {
Window _ignored;
XTranslateCoordinates(display, w->w, DefaultRootWindow(display), 0, 0, x, y, &_ignored);
}
static double get_timert(void) {
struct timespec tv;
if (clock_gettime(CLOCK_REALTIME, &tv)) {
fprintf(stderr, "clock_gettime(CLOCK_REALTIME, ...): %s\n", strerror(errno));
}
return (double) tv.tv_sec + ((double) tv.tv_nsec / 1000000000.0);
}
static void destroy(struct glxwin* w) {
glXMakeCurrent(display, None, NULL); /* release context */
glXDestroyContext(display, w->context);
/* Some picking around indicates the GLX pixmap (for offscreen rendering) is
actually associated with the X pixmap and simply returns a handle, so we
do not have to free the GLX pixmap. */
if (w->offscreen)
XFreePixmap(display, w->off_pixmap);
XDestroyWindow(display, w->w);
free(w);
}
static void terminate(void) {
XCloseDisplay(display);
}
static double get_time (struct glxwin* w) { return get_timert() - w->time; }
static void set_time (struct glxwin* w, double time) { w->time = get_timert() - time; }
static Display* get_x11_display(struct glxwin* w) { return display; }
static Window get_x11_window (struct glxwin* w) { return w->w; }
static const char* get_environment(void) { return xwin_detect_wm(&wcb_glx); }
WCB_ATTACH("glx", wcb_glx);
#endif /* GLAVA_GLX */

282
glava/khrplatform.h Normal file
View File

@@ -0,0 +1,282 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(_WIN32) && !defined(__SCITECH_SNAP__)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef _WIN64
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

View File

@@ -9,22 +9,22 @@
#include "fifo.h"
static pa_mainloop *m_pulseaudio_mainloop;
static pa_mainloop* m_pulseaudio_mainloop;
static void cb(__attribute__((unused)) pa_context *pulseaudio_context,
const pa_server_info *i,
void *userdata) {
static void cb(__attribute__((unused)) pa_context* pulseaudio_context,
const pa_server_info* i,
void* userdata) {
//getting default sink name
struct audio_data *audio = (struct audio_data *)userdata;
/* Obtain default sink name */
struct audio_data* audio = (struct audio_data*) userdata;
audio->source = malloc(sizeof(char) * 1024);
strcpy(audio->source,i->default_sink_name);
//appending .monitor suffix
/* Append `.monitor` suffix */
audio->source = strcat(audio->source, ".monitor");
//quiting mainloop
/* Quiting mainloop */
pa_context_disconnect(pulseaudio_context);
pa_context_unref(pulseaudio_context);
pa_mainloop_quit(m_pulseaudio_mainloop, 0);
@@ -32,20 +32,14 @@ static void cb(__attribute__((unused)) pa_context *pulseaudio_context,
}
static void pulseaudio_context_state_callback(pa_context *pulseaudio_context,
void *userdata) {
static void pulseaudio_context_state_callback(pa_context* pulseaudio_context, void* userdata) {
// make sure loop is ready
switch (pa_context_get_state(pulseaudio_context))
{
case PA_CONTEXT_UNCONNECTED:
break;
case PA_CONTEXT_CONNECTING:
break;
case PA_CONTEXT_AUTHORIZING:
break;
case PA_CONTEXT_SETTING_NAME:
break;
/* Ensure loop is ready */
switch (pa_context_get_state(pulseaudio_context)) {
case PA_CONTEXT_UNCONNECTED: break;
case PA_CONTEXT_CONNECTING: break;
case PA_CONTEXT_AUTHORIZING: break;
case PA_CONTEXT_SETTING_NAME: break;
case PA_CONTEXT_READY: /* extract default sink name */
pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, userdata));
break;
@@ -54,38 +48,39 @@ static void pulseaudio_context_state_callback(pa_context *pulseaudio_context,
exit(EXIT_FAILURE);
break;
case PA_CONTEXT_TERMINATED:
// printf("PulseAudio context terminated!\n");
pa_mainloop_quit(m_pulseaudio_mainloop, 0);
break;
}
}
}
void get_pulse_default_sink(struct audio_data* audio) {
static void init(struct audio_data* audio) {
if (audio->source) return;
pa_mainloop_api *mainloop_api;
pa_context *pulseaudio_context;
pa_mainloop_api* mainloop_api;
pa_context* pulseaudio_context;
int ret;
// Create a mainloop API and connection to the default server
/* Create a mainloop API and connection to the default server */
m_pulseaudio_mainloop = pa_mainloop_new();
mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop);
pulseaudio_context = pa_context_new(mainloop_api, "glava device list");
// This function connects to the pulse server
/* Connect to the PA server */
pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOFLAGS,
NULL);
// This function defines a callback so the server will tell us its state.
/* Define a callback so the server will tell us its state */
pa_context_set_state_callback(pulseaudio_context,
pulseaudio_context_state_callback,
(void*)audio);
// starting a mainloop to get default sink
/* Start mainloop to get default sink */
// starting with one non blokng iteration in case pulseaudio is not able to run
/* Start with one non blocking iteration in case pulseaudio is not able to run */
if (!(ret = pa_mainloop_iterate(m_pulseaudio_mainloop, 0, &ret))){
printf("Could not open pulseaudio mainloop to "
"find default device name: %d\n"
@@ -94,10 +89,8 @@ void get_pulse_default_sink(struct audio_data* audio) {
exit(EXIT_FAILURE);
}
pa_mainloop_run(m_pulseaudio_mainloop, &ret);
}
/* Sample format for native 'float' type */
@@ -113,26 +106,25 @@ void get_pulse_default_sink(struct audio_data* audio) {
#error "Unsupported float format (requires 32 bit IEEE (little or big endian) floating point support)"
#endif
void* input_pulse(void* data) {
struct audio_data *audio = (struct audio_data *)data;
static void* entry(void* data) {
struct audio_data* audio = (struct audio_data*) data;
int i, n;
size_t ssz = audio->sample_sz;
float buf[ssz / 2];
/* The sample type to use */
const pa_sample_spec ss = {
.format = FSAMPLE_FORMAT,
.rate = audio->rate,
.channels = 2
};
const pa_buffer_attr pb = {
.maxlength = ssz * 2,
.maxlength = (uint32_t) -1,
.fragsize = ssz
};
pa_simple *s = NULL;
pa_simple* s = NULL;
int error;
if (!(s = pa_simple_new(NULL, "glava", PA_STREAM_RECORD,
audio->source, "audio for glava",
&ss, NULL, &pb, &error))) {
@@ -141,13 +133,13 @@ void* input_pulse(void* data) {
audio->source, pa_strerror(error));
exit(EXIT_FAILURE);
}
n = 0;
float* bl = (float*) audio->audio_out_l;
float* br = (float*) audio->audio_out_r;
size_t fsz = audio->audio_buf_sz;
while (1) {
/* Record some data ... */
@@ -163,17 +155,21 @@ void* input_pulse(void* data) {
memmove(bl, &bl[ssz / 4], (fsz - (ssz / 4)) * sizeof(float));
memmove(br, &br[ssz / 4], (fsz - (ssz / 4)) * sizeof(float));
// sorting out channelss
/* sorting out channels */
for (n = 0, i = 0; i < ssz / 2; i += 2) {
// size_t idx = (i / 2) + (at * (BUFSIZE / 2));
/* size_t idx = (i / 2) + (at * (BUFSIZE / 2)); */
int idx = (fsz - (ssz / 4)) + n;
if (audio->channels == 1) bl[idx] = (buf[i] + buf[i + 1]) / 2;
if (audio->channels == 1) {
float sample = (buf[i] + buf[i + 1]) / 2;
bl[idx] = sample;
br[idx] = sample;
}
// stereo storing channels in buffer
/* stereo storing channels in buffer */
if (audio->channels == 2) {
bl[idx] = buf[i];
br[idx] = buf[i + 1];
@@ -183,7 +179,7 @@ void* input_pulse(void* data) {
audio->modified = true;
pthread_mutex_unlock(&audio->mutex);
if (audio->terminate == 1) {
pa_simple_free(s);
break;
@@ -192,3 +188,5 @@ void* input_pulse(void* data) {
return 0;
}
AUDIO_ATTACH(pulseaudio);

View File

@@ -1,3 +1,10 @@
#ifndef PULSE_INPUT_H
#define PULSE_INPUT_H
#include "fifo.h"
void get_pulse_default_sink(struct audio_data* audio);
void* input_pulse(void* data);
#endif

2490
glava/render.c Normal file

File diff suppressed because it is too large Load Diff

139
glava/render.h Normal file
View File

@@ -0,0 +1,139 @@
#ifndef RENDER_H
#define RENDER_H
#include <stdbool.h>
#include <stdint.h>
#include <pthread.h>
#include "glava.h"
typedef struct glava_renderer {
volatile bool alive;
bool mirror_input;
size_t bufsize_request, rate_request, samplesize_request;
char* audio_source_request;
unsigned int off_tex; /* final GL texture for offscreen rendering */
pthread_mutex_t lock; /* lock for reading from offscreen texture */
pthread_cond_t cond; /* cond for reading from offscreen texture */
bool flag; /* vadility flag for reading from offscreen tecture */
volatile struct {
int x, y, w, h;
} sizereq;
volatile int sizereq_flag;
struct gl_data* gl;
} glava_renderer;
extern const struct {
const char* n;
int i;
} bind_types[];
extern bool glad_instantiated;
#define STDIN_TYPE_NONE 0
#define STDIN_TYPE_INT 1
#define STDIN_TYPE_FLOAT 2
#define STDIN_TYPE_BOOL 3
#define STDIN_TYPE_VEC2 4
#define STDIN_TYPE_VEC3 5
#define STDIN_TYPE_VEC4 6
#define PIPE_DEFAULT "_"
struct rd_bind {
const char* name;
const char* stype;
int type;
};
#ifdef GLAVA_DEBUG
bool rd_get_test_mode (struct glava_renderer*);
bool rd_test_evaluate (struct glava_renderer*);
#endif
struct glava_renderer* rd_new (const char** paths, const char* entry,
const char** requests, const char* force_backend,
struct rd_bind* bindings, int stdin_type,
bool auto_desktop, bool verbose,
bool test_mode);
bool rd_update (struct glava_renderer*, float* lb, float* rb,
size_t bsz, bool modified);
void rd_destroy (struct glava_renderer*);
void rd_time (struct glava_renderer*);
void* rd_get_impl_window(struct glava_renderer*);
struct gl_wcb* rd_get_wcb (struct glava_renderer*);
/* gl_wcb - OpenGL Window Creation Backend interface */
struct gl_wcb {
const char* name;
bool (*offscreen) (void);
void (*init) (void);
void* (*create_and_bind)(const char* name, const char* class,
const char* type, const char** states,
size_t states_sz,
int w, int h,
int x, int y,
int version_major, int version_minor,
bool clickthrough, bool offscreen);
bool (*should_close) (void* ptr);
bool (*should_render) (void* ptr);
bool (*bg_changed) (void* ptr);
void (*swap_buffers) (void* ptr);
void (*raise) (void* ptr);
void (*destroy) (void* ptr);
void (*terminate) (void);
void (*get_pos) (void* ptr, int* x, int* y);
void (*get_fbsize) (void* ptr, int* w, int* h);
void (*set_geometry) (void* ptr, int x, int y, int w, int h);
void (*set_swap) (int interval);
void (*set_floating) (bool floating);
void (*set_decorated) (bool decorated);
void (*set_focused) (bool focused);
void (*set_maximized) (bool maximized);
void (*set_transparent)(bool transparent);
double (*get_time) (void* ptr);
void (*set_time) (void* ptr, double time);
void (*set_visible) (void* ptr, bool visible);
const char* (*get_environment) (void);
#ifdef GLAVA_RDX11
Display* (*get_x11_display)(void);
Window (*get_x11_window) (void* ptr);
#else /* define placeholders to ensure equal struct size */
void* _X11_DISPLAY_PLACEHOLDER;
void* _X11_WINDOW_PLACEHOLDER;
#endif
};
#define WCB_FUNC(F) \
.F = (typeof(((struct gl_wcb*) NULL)->F)) &F
#define WCB_ATTACH(B, N) \
struct gl_wcb N = { \
.name = B, \
WCB_FUNC(offscreen), \
WCB_FUNC(init), \
WCB_FUNC(create_and_bind), \
WCB_FUNC(should_close), \
WCB_FUNC(should_render), \
WCB_FUNC(bg_changed), \
WCB_FUNC(swap_buffers), \
WCB_FUNC(raise), \
WCB_FUNC(destroy), \
WCB_FUNC(terminate), \
WCB_FUNC(set_swap), \
WCB_FUNC(get_pos), \
WCB_FUNC(get_fbsize), \
WCB_FUNC(set_geometry), \
WCB_FUNC(set_floating), \
WCB_FUNC(set_decorated), \
WCB_FUNC(set_focused), \
WCB_FUNC(set_maximized), \
WCB_FUNC(set_transparent), \
WCB_FUNC(set_time), \
WCB_FUNC(get_time), \
WCB_FUNC(set_visible), \
WCB_FUNC(get_environment), \
WCB_FUNC(get_x11_display), \
WCB_FUNC(get_x11_window) \
}
#endif /* RENDER_H */

472
glava/xwin.c Normal file
View File

@@ -0,0 +1,472 @@
/* X11 specific code and features */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/XShm.h>
#include "glad.h"
#define GLAVA_RDX11
#include "render.h"
#include "xwin.h"
/* BMP Image header */
struct __attribute__((packed)) bmp_header {
uint16_t header;
uint32_t size;
uint16_t reserved0, reserved1;
uint32_t offset;
/* BITMAPINFOHEADER */
uint32_t header_size, width, height;
uint16_t planes, bits_per_pixel;
uint32_t compression, image_size, hres, vres, colors, colors_used;
};
#define BMP_HEADER_MAGIC 0x4D42
#define BMP_BITFIELDS 3
void xwin_assign_icon_bmp(struct gl_wcb* wcb, void* impl, const char* path) {
int fd = open(path, O_RDONLY);
if (fd == -1) {
fprintf(stderr, "failed to load icon '%s': %s\n", path, strerror(errno));
return;
}
Display* d = wcb->get_x11_display();
Window w = wcb->get_x11_window(impl);
struct stat st;
fstat(fd, &st);
const struct bmp_header* header = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (header->header != BMP_HEADER_MAGIC) {
fprintf(stderr, "failed to load icon '%s': invalid BMP header.\n", path);
close(fd);
return;
}
if (header->bits_per_pixel != 32) {
fprintf(stderr, "failed to load icon '%s': wrong bit depth (%d).\n",
path, (int) header->bits_per_pixel);
close(fd);
return;
}
if (header->planes != 1 || header->compression != BMP_BITFIELDS) {
fprintf(stderr, "failed to load icon '%s': invalid BMP format, requires RGBA bitfields.\n", path);
close(fd);
return;
}
/* Obtain image data pointer from offset */
const char* data = (const char*) (((const uint8_t*) header) + header->offset);
/* Assign icon using the older WMHints. Most window managers don't actually use this. */
XWMHints hints = {};
hints.flags = IconPixmapHint;
hints.icon_pixmap = XCreateBitmapFromData(d, w, data, header->width, header->height);
XSetWMHints(d, w, &hints);
/* To assign the icon property we need to convert the image data to `unsigned long`, which
can be 64-bits and padded depending on the architecture. Additionally we need to flip the
Y-axis due to how BMP data is stored. */
size_t sz = header->width * header->height;
size_t asz = sz + 2;
unsigned long* off = malloc(asz * sizeof(unsigned long));
for (size_t x = 0; x < header->width; ++x) {
for (size_t y = 0; y < header->height; ++y) {
off[x + (((header->height - 1) - y) * header->height) + 2]
= ((const uint32_t*) data)[x + (y * header->height)];
}
}
/* The first two elements represent the icon dimensions */
off[0] = header->width;
off[1] = header->height;
XChangeProperty(d, w, XInternAtom(d, "_NET_WM_ICON", true),
XA_CARDINAL, 32, PropModeReplace, (const unsigned char*) off, asz);
free(off);
close(fd);
};
/* Note: currently unused */
Window* __attribute__ ((unused)) xwin_get_desktop_layer(struct gl_wcb* wcb) {
static Window desktop;
static bool searched = false;
if (!searched) {
Display* d = wcb->get_x11_display();
Atom class = XInternAtom(d, "WM_CLASS", false);
desktop = DefaultRootWindow(d);
Window _ignored, * children;
unsigned int nret;
XQueryTree(d, desktop, &_ignored, &_ignored, &children, &nret);
if (children) {
for (unsigned int t = 0; t < nret; ++t) {
char* name;
XFetchName(d, children[t], &name);
if (name) {
/* Mutter-based window managers */
if (!strcmp(name, "mutter guard window")) {
printf("Reparenting to mutter guard window instead of root window\n");
desktop = children[t];
t = nret; /* break after */
}
XFree(name);
}
unsigned long bytes;
XTextProperty text = {};
char** list;
int list_sz;
/* Get WM_CLASS property */
if (Success == XGetWindowProperty(d, children[t], class, 0, 512, false, AnyPropertyType,
&text.encoding, &text.format, &text.nitems, &bytes,
&text.value)) {
/* decode string array */
if (Success == XmbTextPropertyToTextList(d, &text, &list, &list_sz)) {
if (list_sz >= 1 && !strcmp(list[0], "plasmashell")) {
desktop = children[t];
t = nret;
}
XFreeStringList(list);
}
XFree(text.value);
}
}
XFree(children);
}
searched = true;
}
return &desktop;
}
void xwin_wait_for_wm(void) {
Display* d = XOpenDisplay(0);
Atom check = None;
bool exists = false;
struct timespec tv = { .tv_sec = 0, .tv_nsec = 50 * 1000000 };
do {
if (check == None) {
check = XInternAtom(d, "_NET_SUPPORTING_WM_CHECK", true);
}
if (check) {
int num_prop, idx;
Atom* props = XListProperties(d, DefaultRootWindow(d), &num_prop);
for (idx = 0; idx < num_prop; ++idx) {
if (props[idx] == check) {
exists = true;
break;
}
}
XFree(props);
}
if (!exists) nanosleep(&tv, NULL);
} while (!exists);
XCloseDisplay(d);
}
const char* xwin_detect_wm(struct gl_wcb* wcb) {
Display* d = wcb->get_x11_display();
Atom check = XInternAtom(d, "_NET_SUPPORTING_WM_CHECK", false);
Atom name = XInternAtom(d, "_NET_WM_NAME", false);
Atom type = XInternAtom(d, "UTF8_STRING", false);
union {
Atom a;
int i;
long unsigned int lui;
} ignored;
unsigned long nitems = 0;
unsigned char* wm_name = NULL;
Window* wm_check;
if (Success != XGetWindowProperty(d, DefaultRootWindow(d), check, 0, 1024, false, XA_WINDOW,
&ignored.a, &ignored.i, &nitems, &ignored.lui, (unsigned char**) &wm_check)) {
return NULL;
}
if (nitems > 0 && Success == XGetWindowProperty(d, *wm_check, name, 0, 1024, false, type,
&ignored.a, &ignored.i, &nitems, &ignored.lui, &wm_name)) {
if (nitems > 0) {
static const char* wm_name_store = NULL;
if (wm_name_store) XFree((unsigned char*) wm_name_store);
wm_name_store = (const char*) wm_name;
} else {
XFree(wm_name);
wm_name = NULL;
}
}
XFree(wm_check);
return (const char*) wm_name;
}
static int stub_handler(Display* d, XErrorEvent* e) { return 0; }
bool xwin_should_render(struct gl_wcb* wcb, void* impl) {
bool ret = true, should_close = false;
Display* d = wcb->get_x11_display();
if (!d) {
d = XOpenDisplay(0);
should_close = true;
}
Atom prop = XInternAtom(d, "_NET_ACTIVE_WINDOW", true);
Atom fullscreen = XInternAtom(d, "_NET_WM_STATE_FULLSCREEN", true);
Atom actual_type;
int actual_format, t;
unsigned long nitems, bytes_after;
unsigned char* data = NULL;
XSetErrorHandler(stub_handler); /* dummy error handler */
if (Success != XGetWindowProperty(d, DefaultRootWindow(d), prop, 0, 1, false, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after, &data)) {
goto close; /* if an error occurs here, the WM probably isn't EWMH compliant */
}
if (!nitems)
goto close;
Window active = ((Window*) data)[0];
prop = XInternAtom(d, "_NET_WM_STATE", true);
if (data) {
XFree(data);
data = NULL;
}
if (Success != XGetWindowProperty(d, active, prop, 0, LONG_MAX, false, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after, &data)) {
goto close; /* some WMs are a little slow on creating _NET_WM_STATE, so errors may occur here */
}
for (t = 0; t < nitems; ++t) {
if (fullscreen == ((Atom*) data)[t]) {
ret = false;
}
}
close:
if (data)
XFree(data);
if (should_close)
XCloseDisplay(d);
return ret;
}
/* Create string copy on stack with upcase chars */
#define S_UPPER(in, out) char out[strlen(in) + 1]; \
do { \
for (size_t t = 0; t < sizeof(out) / sizeof(char); ++t) { \
char c = in[t]; \
switch (c) { \
case 'a' ... 'z': c -= 'a' - 'A'; \
default: out[t] = c; \
} \
} \
} while (0)
static void xwin_changeatom(struct gl_wcb* wcb, void* impl, const char* type,
const char* atom, const char* fmt, int mode) {
Window w = wcb->get_x11_window(impl);
Display* d = wcb->get_x11_display();
Atom wtype = XInternAtom(d, atom, false);
char buf[256];
snprintf(buf, sizeof(buf), fmt, type);
Atom desk = XInternAtom(d, buf, false);
XChangeProperty(d, w, wtype, XA_ATOM, 32, mode, (unsigned char*) &desk, 1);
}
/* Set window types defined by the EWMH standard, possible values:
-> "desktop", "dock", "toolbar", "menu", "utility", "splash", "dialog", "normal" */
bool xwin_settype(struct gl_wcb* wcb, void* impl, const char* rtype) {
S_UPPER(rtype, type);
if (type[0] != '!') {
xwin_changeatom(wcb, impl, type, "_NET_WM_WINDOW_TYPE",
"_NET_WM_WINDOW_TYPE_%s", PropModeReplace);
}
return !strcmp(type, "DESKTOP");
}
void xwin_addstate(struct gl_wcb* wcb, void* impl, const char* rstate) {
S_UPPER(rstate, state);
if (strcmp(state, "PINNED"))
xwin_changeatom(wcb, impl, state, "_NET_WM_STATE", "_NET_WM_STATE_%s", PropModeAppend);
else
xwin_setdesktop(wcb, impl, XWIN_ALL_DESKTOPS);
}
void xwin_setdesktop(struct gl_wcb* wcb, void* impl, unsigned long desktop) {
Window w = wcb->get_x11_window(impl);
Display* d = wcb->get_x11_display();
Atom wtype = XInternAtom(d, "_NET_WM_DESKTOP", false);
XChangeProperty(d, w, wtype, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &desktop, 1);
}
static Drawable get_drawable(Display* d, Window w) {
Drawable p;
Atom act_type;
int act_format;
unsigned long nitems, bytes_after;
unsigned char *data = NULL;
Atom id;
id = XInternAtom(d, "_XROOTPMAP_ID", False);
if (XGetWindowProperty(d, w, id, 0, 1, False, XA_PIXMAP,
&act_type, &act_format, &nitems, &bytes_after,
&data) == Success && data) {
p = *((Pixmap *) data);
XFree(data);
} else {
p = w;
}
return p;
}
unsigned int xwin_copyglbg(struct glava_renderer* rd, unsigned int tex) {
GLuint texture = (GLuint) tex;
if (!texture)
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
int x, y, w, h;
rd_get_wcb(rd)->get_fbsize(rd_get_impl_window(rd), &w, &h);
rd_get_wcb(rd)->get_pos(rd_get_impl_window(rd), &x, &y);
XColor c;
Display* d = rd_get_wcb(rd)->get_x11_display();
Drawable src = get_drawable(d, DefaultRootWindow(d));
bool use_shm = XShmQueryExtension(d);
/* Obtain section of root pixmap */
XShmSegmentInfo shminfo;
Visual* visual = DefaultVisual(d, DefaultScreen(d));
XVisualInfo match = { .visualid = XVisualIDFromVisual(visual) };
int nret;
XVisualInfo* info = XGetVisualInfo(d, VisualIDMask, &match, &nret);
XImage* image;
if (use_shm) {
image = XShmCreateImage(d, visual, info->depth, ZPixmap, NULL,
&shminfo, (unsigned int) w, (unsigned int) h);
if ((shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height,
IPC_CREAT | 0777)) == -1) {
fprintf(stderr, "shmget() failed: %s\n", strerror(errno));
glava_abort();
}
shminfo.shmaddr = image->data = shmat(shminfo.shmid, 0, 0);
shminfo.readOnly = false;
XShmAttach(d, &shminfo);
XShmGetImage(d, src, image, x, y, AllPlanes);
} else {
image = XGetImage(d, src, x, y, (unsigned int) w, (unsigned int) h,
AllPlanes, ZPixmap);
}
/* Try to convert pixel bit depth to OpenGL storage format. The following formats\
will need intermediate conversion before OpenGL can accept the data:
- 8-bit pixel formats (retro displays, low-bandwidth virtual displays)
- 36-bit pixel formats (rare deep color displays) */
if (image) {
bool invalid = false, aligned = false;
GLenum type = 0;
switch (image->bits_per_pixel) {
case 16:
switch (image->depth) {
case 12: type = GL_UNSIGNED_SHORT_4_4_4_4; break; /* 12-bit (rare) */
case 15: type = GL_UNSIGNED_SHORT_5_5_5_1; break; /* 15-bit, hi-color */
case 16: /* 16-bit, hi-color */
type = GL_UNSIGNED_SHORT_5_6_5;
aligned = true;
break;
}
break;
case 32:
switch (image->depth) {
case 24: type = GL_UNSIGNED_BYTE; break; /* 24-bit, true color */
case 30: type = GL_UNSIGNED_INT_10_10_10_2; break; /* 30-bit, deep color */
}
break;
case 64:
if (image->depth == 48) /* 48-bit deep color */
type = GL_UNSIGNED_SHORT;
else goto invalid;
break;
/* >64-bit formats */
case 128:
if (image->depth == 96)
type = GL_UNSIGNED_INT;
else goto invalid;
break;
default:
invalid: invalid = true;
}
uint8_t* buf;
if (invalid) {
abort();
/* Manual reformat (slow) */
buf = malloc(4 * w * h);
int xi, yi;
Colormap map = DefaultColormap(d, DefaultScreen(d));
for (yi = 0; yi < h; ++yi) {
for (xi = 0; xi < w; ++xi) {
c.pixel = XGetPixel(image, xi, yi);
XQueryColor(d, map, &c);
size_t base = (xi + (yi * w)) * 4;
buf[base + 0] = c.red / 256;
buf[base + 1] = c.green / 256;
buf[base + 2] = c.blue / 256;
buf[base + 3] = 255;
}
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
free(buf);
} else {
/* Use image data directly. The alpha value is garbage/unassigned data, but
we need to read it because X11 keeps pixel data aligned */
buf = (uint8_t*) image->data;
/* Data could be 2, 4, or 8 byte aligned, the RGBA format and type (depth)
already ensures reads will be properly aligned across scanlines */
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLenum format = image->bitmap_bit_order == LSBFirst ?
(!aligned ? GL_BGRA : GL_BGR) :
(!aligned ? GL_RGBA : GL_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, format, type, buf);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); /* restore default */
}
}
if (use_shm) {
XShmDetach(d, &shminfo);
shmdt(shminfo.shmaddr);
shmctl(shminfo.shmid, IPC_RMID, NULL);
}
if (image) XDestroyImage(image);
XFree(info);
return texture;
}

22
glava/xwin.h Normal file
View File

@@ -0,0 +1,22 @@
#define XWIN_ALL_DESKTOPS 0xFFFFFFFF
#ifndef XWIN_H
#define XWIN_H
#include <stdbool.h>
#include "render.h"
typedef unsigned long int Window;
void xwin_assign_icon_bmp(struct gl_wcb* wcb, void* impl, const char* path);
bool xwin_should_render(struct gl_wcb* wcb, void* impl);
void xwin_wait_for_wm(void);
bool xwin_settype(struct gl_wcb* wcb, void* impl, const char* type);
void xwin_setdesktop(struct gl_wcb* wcb, void* impl, unsigned long desktop);
void xwin_addstate(struct gl_wcb* wcb, void* impl, const char* state);
unsigned int xwin_copyglbg(struct glava_renderer* rd, unsigned int texture);
Window* xwin_get_desktop_layer(struct gl_wcb* wcb);
const char* xwin_detect_wm(struct gl_wcb* wcb);
#endif

19
glfft/LICENSE_ORIGINAL Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1125
glfft/glfft.cpp Normal file

File diff suppressed because it is too large Load Diff

225
glfft/glfft.hpp Normal file
View File

@@ -0,0 +1,225 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef GLFFT_HPP__
#define GLFFT_HPP__
#include "glfft_interface.hpp"
#include "glfft_common.hpp"
#include "glfft_wisdom.hpp"
#include <vector>
#include <unordered_map>
#include <limits>
/// GLFFT doesn't try to preserve GL state in any way.
/// E.g. SHADER_STORAGE_BUFFER bindings, programs bound, texture bindings, etc.
/// Applications calling this library must expect that some GL state will be modified.
/// No rendering state associated with graphics will be modified.
namespace GLFFT
{
class FFT
{
public:
/// @brief Creates a full FFT.
///
/// All buffer allocation done by GLFFT will be done in constructor.
/// Will throw if invalid parameters are passed.
///
/// @param context The graphics context.
/// @param Nx Number of samples in horizontal dimension.
/// @param Ny Number of samples in vertical dimension.
/// @param type The transform type.
/// @param direction Forward, inverse or inverse with convolution.
/// For real-to-complex and complex-to-real transforms, the
/// transform type must match.
/// @param input_target GL object type of input target. For real-to-complex with texture as input, ImageReal is used.
/// @param output_target GL object type of output target. For complex-to-real with texture as output, ImageReal is used.
/// @param cache A program cache for caching the GLFFT programs created.
/// @param options FFT options such as performance related parameters and types.
/// @param wisdom GLFFT wisdom which can override performance related options
/// (options.performance is used as a fallback).
FFT(Context *context, unsigned Nx, unsigned Ny,
Type type, Direction direction, Target input_target, Target output_target,
std::shared_ptr<ProgramCache> cache, const FFTOptions &options,
const FFTWisdom &wisdom = FFTWisdom());
/// @brief Creates a single stage FFT. Used mostly internally for benchmarking partial FFTs.
///
/// All buffer allocation done by GLFFT will be done in constructor.
/// Will throw if invalid parameters are passed.
///
/// @param context The graphics context.
/// @param Nx Number of samples in horizontal dimension.
/// @param Ny Number of samples in vertical dimension.
/// @param radix FFT radix to test.
/// @param p Accumulated p factor. If 1, "first pass" mode is tested, otherwise, generic FFT stages.
/// @param mode The transform mode.
/// @param input_target GL object type of input target. For real-to-complex with texture as input, ImageReal is used.
/// @param output_target GL object type of output target. For complex-to-real with texture as output, ImageReal is used.
/// @param cache A program cache for caching the GLFFT programs created.
/// @param options FFT options such as performance related parameters and types.
FFT(Context *context, unsigned Nx, unsigned Ny, unsigned radix, unsigned p,
Mode mode, Target input_target, Target output_target,
std::shared_ptr<ProgramCache> cache, const FFTOptions &options);
/// @brief Process the FFT.
///
/// The type of object passed here must match what FFT was initialized with.
///
/// @param cmd Command buffer for issuing dispatch commands.
/// @param output Output buffer or image.
/// NOTE: For images, the texture must be using immutable storage, i.e. glTexStorage2D!
/// @param input Input buffer or texture.
/// @param input_aux If using convolution transform type,
/// the content of input and input_aux will be multiplied together.
void process(CommandBuffer *cmd, Resource *output, Resource *input, Resource *input_aux = nullptr);
/// @brief Run process() multiple times, timing the results.
///
/// Mostly used internally by GLFFT wisdom, glfft_cli's bench, and so on.
///
/// @param context The graphics context.
/// @param output Output buffer or image.
/// NOTE: For images, the texture must be using immutable storage, i.e. glTexStorage2D!
/// @param input Input buffer or texture.
/// @param warmup_iterations Number of iterations to run to "warm" up GL, ensures we don't hit
/// recompilations or similar when benching.
/// @param iterations Number of iterations to run the benchmark.
/// Each iteration will ensure timing with a glFinish() followed by timing.
/// @param dispatches_per_iteration Number of calls to process() we should do per iteration.
/// @param max_time The max time the benchmark should run. Will be checked after each iteration is complete.
///
/// @returns Average GPU time per process() call.
double bench(Context *context, Resource *output, Resource *input,
unsigned warmup_iterations, unsigned iterations, unsigned dispatches_per_iteration,
double max_time = std::numeric_limits<double>::max());
/// @brief Returns cost for a process() call. Only used for debugging.
double get_cost() const { return cost; }
/// @brief Returns number of passes (glDispatchCompute) in a process() call.
unsigned get_num_passes() const { return passes.size(); }
/// @brief Returns Nx.
unsigned get_dimension_x() const { return size_x; }
/// @brief Returns Ny.
unsigned get_dimension_y() const { return size_y; }
/// @brief Sets offset and scale parameters for normalized texel coordinates when sampling textures.
///
/// By default, these values are 0.5 / size (samples in the center of texel (0, 0)).
/// Scale is 1.0 / size, so it steps one texel for each coordinate in the FFT transform.
/// Setting this to something custom is useful to get downsampling with GL_LINEAR -> FFT transform
/// without having to downsample the texture first, then FFT.
void set_texture_offset_scale(float offset_x, float offset_y, float scale_x, float scale_y)
{
texture.offset_x = offset_x;
texture.offset_y = offset_y;
texture.scale_x = scale_x;
texture.scale_y = scale_y;
}
/// @brief Set binding range for input.
///
/// If input is an SSBO, set a custom binding range to be passed to glBindBufferRange.
/// By default, the entire buffer is bound.
void set_input_buffer_range(size_t offset, size_t size)
{
ssbo.input.offset = offset;
ssbo.input.size = size;
}
/// @brief Set binding range for input_aux.
///
/// If input_aux is an SSBO, set a custom binding range to be passed to glBindBufferRange.
/// By default, the entire buffer is bound.
void set_input_aux_buffer_range(size_t offset, size_t size)
{
ssbo.input_aux.offset = offset;
ssbo.input_aux.size = size;
}
/// @brief Set binding range for output.
///
/// If output buffer is an SSBO, set a custom binding range to be passed to glBindBufferRange.
/// By default, the entire buffer is bound.
void set_output_buffer_range(size_t offset, size_t size)
{
ssbo.output.offset = offset;
ssbo.output.size = size;
}
/// @brief Set samplers for input textures.
///
/// Set sampler objects to be used for input and input_aux if textures are used as input.
/// By default, sampler object 0 will be used (inheriting sampler parameters from the texture object itself).
void set_samplers(Sampler *sampler0, Sampler *sampler1 = nullptr)
{
texture.samplers[0] = sampler0;
texture.samplers[1] = sampler1;
}
private:
Context *context;
struct Pass
{
Parameters parameters;
unsigned workgroups_x;
unsigned workgroups_y;
unsigned uv_scale_x;
unsigned stride;
Program *program;
};
double cost = 0.0;
std::unique_ptr<Buffer> temp_buffer;
std::unique_ptr<Buffer> temp_buffer_image;
std::vector<Pass> passes;
std::shared_ptr<ProgramCache> cache;
std::unique_ptr<Program> build_program(const Parameters &params);
static std::string load_shader_string(const char *path);
static void store_shader_string(const char *path, const std::string &source);
Program* get_program(const Parameters &params);
struct
{
float offset_x = 0.0f, offset_y = 0.0f, scale_x = 1.0f, scale_y = 1.0f;
Sampler *samplers[2] = { nullptr, nullptr };
} texture;
struct
{
struct
{
size_t offset = 0;
size_t size = 0;
} input, input_aux, output;
} ssbo;
unsigned size_x, size_y;
};
}
#endif

178
glfft/glfft_common.hpp Normal file
View File

@@ -0,0 +1,178 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// For the most part used by the implementation.
#ifndef GLFFT_COMMON_HPP__
#define GLFFT_COMMON_HPP__
#include "glfft_interface.hpp"
#include <functional>
#include <cstddef>
#include <cstdlib>
#include <string>
#include <cstring>
#include <memory>
#include <unordered_map>
namespace GLFFT
{
enum Direction
{
/// Forward FFT transform.
Forward = -1,
/// Inverse FFT transform, but with two inputs (in frequency domain) which are multiplied together
/// for convolution.
InverseConvolve = 0,
/// Inverse FFT transform.
Inverse = 1
};
enum Mode
{
Horizontal,
HorizontalDual,
Vertical,
VerticalDual,
ResolveRealToComplex,
ResolveComplexToReal,
};
enum Type
{
/// Regular complex-to-complex transform.
ComplexToComplex,
/// Complex-to-complex dual transform where the complex value is four-dimensional,
/// i.e. a vector of two complex values. Typically used to transform RGBA data.
ComplexToComplexDual,
/// Complex-to-real transform. N / 2 + 1 complex values are used per row with a stride of N complex samples.
ComplexToReal,
/// Real-to-complex transform. N / 2 + 1 complex output samples are created per row with a stride of N complex samples.
RealToComplex
};
enum Target
{
/// GL_SHADER_STORAGE_BUFFER
SSBO,
/// Textures, when used as output, type is determined by transform type.
/// ComplexToComplex / RealToComplex -> GL_RG16F
/// ComplexToComplexDual -> GL_RGBA16F
Image,
/// Real-valued (single component) textures, when used as output, type is determined by transform type.
/// ComplexToReal -> GL_R32F (because GLES 3.1 doesn't have GL_R16F image type).
ImageReal
};
struct Parameters
{
unsigned workgroup_size_x;
unsigned workgroup_size_y;
unsigned workgroup_size_z;
unsigned radix;
unsigned vector_size;
Direction direction;
Mode mode;
Target input_target;
Target output_target;
bool p1;
bool shared_banked;
bool fft_fp16, input_fp16, output_fp16;
bool fft_normalize;
bool operator==(const Parameters &other) const
{
return std::memcmp(this, &other, sizeof(Parameters)) == 0;
}
};
/// @brief Options for FFT implementation.
/// Defaults for performance as conservative.
struct FFTOptions
{
struct Performance
{
/// Workgroup size used in layout(local_size_x).
/// Only affects performance, however, large values may make implementations of smaller sized FFTs impossible.
/// FFT constructor will throw in this case.
unsigned workgroup_size_x = 4;
/// Workgroup size used in layout(local_size_x).
/// Only affects performance, however, large values may make implementations of smaller sized FFTs impossible.
/// FFT constructor will throw in this case.
unsigned workgroup_size_y = 1;
/// Vector size. Very GPU dependent. "Scalar" GPUs prefer 2 here, vector GPUs prefer 4 (and maybe 8).
unsigned vector_size = 2;
/// Whether to use banked shared memory or not.
/// Desktop GPUs prefer true here, false for mobile in general.
bool shared_banked = false;
} performance;
struct Type
{
/// Whether internal shader should be mediump float.
bool fp16 = false;
/// Whether input SSBO is a packed 2xfp16 format. Otherwise, regular FP32.
bool input_fp16 = false;
/// Whether output SSBO is a packed 2xfp16 format. Otherwise, regular FP32.
bool output_fp16 = false;
/// Whether to apply 1 / N normalization factor.
bool normalize = false;
} type;
};
}
namespace std
{
template<>
struct hash<GLFFT::Parameters>
{
std::size_t operator()(const GLFFT::Parameters &params) const
{
std::size_t h = 0;
hash<uint8_t> hasher;
for (std::size_t i = 0; i < sizeof(GLFFT::Parameters); i++)
{
h ^= hasher(reinterpret_cast<const uint8_t*>(&params)[i]);
}
return h;
}
};
}
namespace GLFFT
{
class ProgramCache
{
public:
Program* find_program(const Parameters &parameters) const;
void insert_program(const Parameters &parameters, std::unique_ptr<Program> program);
size_t cache_size() const { return programs.size(); }
private:
std::unordered_map<Parameters, std::unique_ptr<Program>> programs;
};
}
#endif

View File

@@ -0,0 +1,6 @@
/* Let GLFFT use GLava's headers */
#define GLFFT_GLSL_LANG_STRING "#version 430 core\n"
extern "C" {
#include "../glava/glad.h"
}

View File

@@ -0,0 +1,310 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "glfft_gl_interface.hpp"
#ifdef GLFFT_GL_DEBUG
#include "glfft_validate.hpp"
#endif
#include <cstdarg>
#include <cstring>
#include <vector>
using namespace GLFFT;
using namespace std;
GLCommandBuffer GLContext::static_command_buffer;
void GLCommandBuffer::bind_program(Program *program)
{
glUseProgram(program ? static_cast<GLProgram*>(program)->name : 0);
}
void GLCommandBuffer::bind_storage_texture(unsigned binding, Texture *texture, Format format)
{
glBindImageTexture(binding, static_cast<GLTexture*>(texture)->name,
0, GL_FALSE, 0, GL_WRITE_ONLY, convert(format));
}
void GLCommandBuffer::bind_texture(unsigned binding, Texture *texture)
{
glActiveTexture(GL_TEXTURE0 + binding);
glBindTexture(GL_TEXTURE_2D, static_cast<GLTexture*>(texture)->name);
}
void GLCommandBuffer::bind_sampler(unsigned binding, Sampler *sampler)
{
glBindSampler(binding, sampler ? static_cast<GLSampler*>(sampler)->name : 0);
}
void GLCommandBuffer::bind_storage_buffer(unsigned binding, Buffer *buffer)
{
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, static_cast<GLBuffer*>(buffer)->name);
}
void GLCommandBuffer::bind_storage_buffer_range(unsigned binding, size_t offset, size_t size, Buffer *buffer)
{
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, static_cast<GLBuffer*>(buffer)->name, offset, size);
}
void GLCommandBuffer::dispatch(unsigned x, unsigned y, unsigned z)
{
glDispatchCompute(x, y, z);
}
void GLCommandBuffer::barrier(Buffer*)
{
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
}
void GLCommandBuffer::barrier(Texture*)
{
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
}
void GLCommandBuffer::barrier()
{
glMemoryBarrier(GL_ALL_BARRIER_BITS);
}
void GLCommandBuffer::push_constant_data(unsigned binding, const void *data, size_t size)
{
glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubos[ubo_index]);
void *ptr = glMapBufferRange(GL_UNIFORM_BUFFER,
0, CommandBuffer::MaxConstantDataSize,
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
if (ptr)
{
std::memcpy(ptr, data, size);
glUnmapBuffer(GL_UNIFORM_BUFFER);
}
if (++ubo_index >= ubo_count)
ubo_index = 0;
}
CommandBuffer* GLContext::request_command_buffer()
{
if (!initialized_ubos)
{
glGenBuffers(MaxBuffersRing, ubos);
for (auto &ubo : ubos)
{
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferData(GL_UNIFORM_BUFFER, CommandBuffer::MaxConstantDataSize, nullptr, GL_STREAM_DRAW);
}
static_command_buffer.set_constant_data_buffers(ubos, MaxBuffersRing);
initialized_ubos = true;
}
return &static_command_buffer;
}
void GLContext::submit_command_buffer(CommandBuffer*)
{}
void GLContext::wait_idle()
{
glFinish();
}
unique_ptr<Texture> GLContext::create_texture(const void *initial_data,
unsigned width, unsigned height,
Format format)
{
return unique_ptr<Texture>(new GLTexture(initial_data, width, height, format));
}
unique_ptr<Buffer> GLContext::create_buffer(const void *initial_data, size_t size, AccessMode access)
{
return unique_ptr<Buffer>(new GLBuffer(initial_data, size, access));
}
unique_ptr<Program> GLContext::compile_compute_shader(const char *source)
{
#ifdef GLFFT_GL_DEBUG
if (!validate_glsl_source(source))
return nullptr;
#endif
GLuint program = glCreateProgram();
if (!program)
{
return nullptr;
}
GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
const char *sources[] = { GLFFT_GLSL_LANG_STRING, source };
glShaderSource(shader, 2, sources, NULL);
glCompileShader(shader);
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
GLint len;
GLsizei out_len;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
vector<char> buf(len);
glGetShaderInfoLog(shader, len, &out_len, buf.data());
log("GLFFT: Shader log:\n%s\n\n", buf.data());
glDeleteShader(shader);
glDeleteProgram(program);
return 0;
}
glAttachShader(program, shader);
glLinkProgram(program);
glDeleteShader(shader);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
GLint len;
GLsizei out_len;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
vector<char> buf(len);
glGetProgramInfoLog(program, len, &out_len, buf.data());
log("Program log:\n%s\n\n", buf.data());
glDeleteProgram(program);
glDeleteShader(shader);
return nullptr;
}
return unique_ptr<Program>(new GLProgram(program));
}
void GLContext::log(const char *fmt, ...)
{
char buffer[4 * 1024];
va_list va;
va_start(va, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
glfft_log("%s", buffer);
}
double GLContext::get_time()
{
return glfft_time();
}
unsigned GLContext::get_max_work_group_threads()
{
GLint value;
glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &value);
return value;
}
const char* GLContext::get_renderer_string()
{
return reinterpret_cast<const char*>(glGetString(GL_RENDERER));
}
const void* GLContext::map(Buffer *buffer, size_t offset, size_t size)
{
glBindBuffer(GL_SHADER_STORAGE_BUFFER, static_cast<GLBuffer*>(buffer)->name);
const void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, offset, size, GL_MAP_READ_BIT);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
return ptr;
}
void GLContext::unmap(Buffer *buffer)
{
glBindBuffer(GL_SHADER_STORAGE_BUFFER, static_cast<GLBuffer*>(buffer)->name);
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
void GLContext::teardown()
{
if (initialized_ubos)
glDeleteBuffers(MaxBuffersRing, ubos);
initialized_ubos = false;
}
GLContext::~GLContext()
{
teardown();
}
GLTexture::GLTexture(const void *initial_data,
unsigned width, unsigned height,
Format format)
{
glGenTextures(1, &name);
glBindTexture(GL_TEXTURE_2D, name);
glTexStorage2D(GL_TEXTURE_2D, 1, convert(format), width, height);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (initial_data)
{
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
convert_format(format), convert_type(format), initial_data);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
GLTexture::~GLTexture()
{
if (owned)
glDeleteTextures(1, &name);
}
GLBuffer::GLBuffer(const void *initial_data, size_t size, AccessMode access)
{
glGenBuffers(1, &name);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, name);
glBufferData(GL_SHADER_STORAGE_BUFFER, size, initial_data, convert(access));
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
GLBuffer::~GLBuffer()
{
if (owned)
glDeleteBuffers(1, &name);
}
GLProgram::GLProgram(GLuint name)
: name(name)
{}
GLProgram::~GLProgram()
{
if (name != 0)
{
glDeleteProgram(name);
}
}
GLSampler::~GLSampler()
{
if (name != 0)
{
glDeleteSamplers(1, &name);
}
}

View File

@@ -0,0 +1,258 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef GLFFT_GL_INTERFACE_HPP__
#define GLFFT_GL_INTERFACE_HPP__
#include "glfft_interface.hpp"
#include "glfft_gl_api_headers.hpp"
/* GLava additions (POSIX) */
extern "C" {
#include <time.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
}
#ifndef GLFFT_GLSL_LANG_STRING
#error GLFFT_GLSL_LANG_STRING must be defined to e.g. "#version 310 es\n" or "#version 430 core\n".
#endif
#ifndef GLFFT_LOG_OVERRIDE
void glfft_log(const char *fmt, ...) {
va_list l;
va_start(l, fmt);
vfprintf(stdout, fmt, l);
va_end(l);
}
#else
#define glfft_log GLFFT_LOG_OVERRIDE
#endif
#ifndef GLFFT_TIME_OVERRIDE
double glfft_time() {
struct timespec tv;
if (clock_gettime(CLOCK_REALTIME, &tv)) {
fprintf(stderr, "clock_gettime(CLOCK_REALTIME, ...): %s\n", strerror(errno));
}
return (double) tv.tv_sec + ((double) tv.tv_nsec / 1000000000.0);
}
#else
#define glfft_time GLFFT_TIME_OVERRIDE
#endif
namespace GLFFT
{
class GLContext;
class GLTexture : public Texture
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLTexture();
GLTexture(GLuint obj) : name(obj), owned(false) {}
GLuint get() const { return name; }
private:
GLTexture(const void *initial_data,
unsigned width, unsigned height,
Format format);
GLuint name;
bool owned = true;
};
// Not really used by test and bench code, but can be useful for API users.
class GLSampler : public Sampler
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLSampler();
GLSampler(GLuint obj) : name(obj) {}
GLuint get() const { return name; }
private:
GLuint name;
};
class GLBuffer : public Buffer
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLBuffer();
GLBuffer(GLuint obj) : name(obj), owned(false) {}
GLuint get() const { return name; }
private:
GLuint name;
GLBuffer(const void *initial_data, size_t size, AccessMode access);
bool owned = true;
};
class GLProgram : public Program
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLProgram();
GLuint get() const { return name; }
private:
GLProgram(GLuint name);
GLuint name;
};
class GLCommandBuffer : public CommandBuffer
{
public:
~GLCommandBuffer() = default;
void set_constant_data_buffers(const GLuint *ubos, unsigned count)
{
this->ubos = ubos;
ubo_index = 0;
ubo_count = count;
}
void bind_program(Program *program) override;
void bind_storage_texture(unsigned binding, Texture *texture, Format format) override;
void bind_texture(unsigned binding, Texture *texture) override;
void bind_sampler(unsigned binding, Sampler *sampler) override;
void bind_storage_buffer(unsigned binding, Buffer *texture) override;
void bind_storage_buffer_range(unsigned binding, size_t offset, size_t length, Buffer *texture) override;
void dispatch(unsigned x, unsigned y, unsigned z) override;
void barrier(Buffer *buffer) override;
void barrier(Texture *buffer) override;
void barrier() override;
void push_constant_data(unsigned binding, const void *data, size_t size) override;
private:
const GLuint *ubos = nullptr;
unsigned ubo_count = 0;
unsigned ubo_index = 0;
};
class GLContext : public Context
{
public:
~GLContext();
std::unique_ptr<Texture> create_texture(const void *initial_data,
unsigned width, unsigned height,
Format format) override;
std::unique_ptr<Buffer> create_buffer(const void *initial_data, size_t size, AccessMode access) override;
std::unique_ptr<Program> compile_compute_shader(const char *source) override;
CommandBuffer* request_command_buffer() override;
void submit_command_buffer(CommandBuffer *cmd) override;
void wait_idle() override;
const char* get_renderer_string() override;
void log(const char *fmt, ...) override;
double get_time() override;
unsigned get_max_work_group_threads() override;
const void* map(Buffer *buffer, size_t offset, size_t size) override;
void unmap(Buffer *buffer) override;
// Not supported in GLES, so override when creating platform-specific context.
bool supports_texture_readback() override { return false; }
void read_texture(void*, Texture*, Format) override {}
protected:
void teardown();
private:
static GLCommandBuffer static_command_buffer;
enum { MaxBuffersRing = 256 };
GLuint ubos[MaxBuffersRing];
bool initialized_ubos = false;
};
static inline GLenum convert(AccessMode mode)
{
switch (mode)
{
case AccessStreamCopy: return GL_STREAM_COPY;
case AccessStaticCopy: return GL_STATIC_COPY;
case AccessStreamRead: return GL_STREAM_READ;
}
return 0;
}
static inline GLenum convert(Format format)
{
switch (format)
{
case FormatR16G16B16A16Float: return GL_RGBA16F;
case FormatR32G32B32A32Float: return GL_RGBA32F;
case FormatR32Float: return GL_R32F;
case FormatR16G16Float: return GL_RG16F;
case FormatR32G32Float: return GL_RG32F;
case FormatR32Uint: return GL_R32UI;
case FormatUnknown: return 0;
}
return 0;
}
static inline GLenum convert_format(Format format)
{
switch (format)
{
case FormatR16G16Float: return GL_RG;
case FormatR32G32Float: return GL_RG;
case FormatR16G16B16A16Float: return GL_RGBA;
case FormatR32G32B32A32Float: return GL_RGBA;
case FormatR32Float: return GL_RED;
case FormatR32Uint: return GL_RED_INTEGER;
case FormatUnknown: return 0;
}
return 0;
}
static inline GLenum convert_type(Format format)
{
switch (format)
{
case FormatR16G16Float: return GL_HALF_FLOAT;
case FormatR16G16B16A16Float: return GL_HALF_FLOAT;
case FormatR32Float: return GL_FLOAT;
case FormatR32G32Float: return GL_FLOAT;
case FormatR32G32B32A32Float: return GL_FLOAT;
case FormatR32Uint: return GL_UNSIGNED_INT;
case FormatUnknown: return 0;
}
return 0;
}
}
#endif

131
glfft/glfft_interface.hpp Normal file
View File

@@ -0,0 +1,131 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef GLFFT_INTERFACE_HPP__
#define GLFFT_INTERFACE_HPP__
#include <memory>
namespace GLFFT
{
class Context;
class Resource
{
public:
virtual ~Resource() = default;
// Non-movable, non-copyable to make things simpler.
Resource(Resource&&) = delete;
void operator=(const Resource&) = delete;
protected:
Resource() = default;
};
class Texture : public Resource {};
class Sampler : public Resource {};
class Buffer : public Resource {};
class Program
{
public:
virtual ~Program() = default;
protected:
friend class Context;
Program() = default;
};
enum AccessMode
{
AccessStreamCopy,
AccessStaticCopy,
AccessStreamRead
};
enum Format
{
FormatUnknown,
FormatR16G16B16A16Float,
FormatR32G32B32A32Float,
FormatR32G32Float,
FormatR32Float,
FormatR16G16Float,
FormatR32Uint
};
class CommandBuffer;
class Context
{
public:
virtual ~Context() = default;
virtual std::unique_ptr<Texture> create_texture(const void *initial_data,
unsigned width, unsigned height,
Format format) = 0;
virtual std::unique_ptr<Buffer> create_buffer(const void *initial_data, size_t size, AccessMode access) = 0;
virtual std::unique_ptr<Program> compile_compute_shader(const char *source) = 0;
virtual CommandBuffer* request_command_buffer() = 0;
virtual void submit_command_buffer(CommandBuffer *cmd) = 0;
virtual void wait_idle() = 0;
virtual const char* get_renderer_string() = 0;
virtual void log(const char *fmt, ...) = 0;
virtual double get_time() = 0;
virtual unsigned get_max_work_group_threads() = 0;
virtual const void* map(Buffer *buffer, size_t offset, size_t size) = 0;
virtual void unmap(Buffer *buffer) = 0;
virtual bool supports_texture_readback() = 0;
virtual void read_texture(void *buffer, Texture *texture, Format format) = 0;
protected:
Context() = default;
};
class CommandBuffer
{
public:
virtual ~CommandBuffer() = default;
virtual void bind_program(Program *program) = 0;
virtual void bind_storage_texture(unsigned binding, Texture *texture, Format format) = 0;
virtual void bind_texture(unsigned binding, Texture *texture) = 0;
virtual void bind_sampler(unsigned binding, Sampler *sampler) = 0;
virtual void bind_storage_buffer(unsigned binding, Buffer *texture) = 0;
virtual void bind_storage_buffer_range(unsigned binding, size_t offset, size_t length, Buffer *texture) = 0;
virtual void dispatch(unsigned x, unsigned y, unsigned z) = 0;
virtual void barrier(Buffer *buffer) = 0;
virtual void barrier(Texture *buffer) = 0;
virtual void barrier() = 0;
enum { MaxConstantDataSize = 64 };
virtual void push_constant_data(unsigned binding, const void *data, size_t size) = 0;
protected:
CommandBuffer() = default;
};
}
#endif

600
glfft/glfft_wisdom.cpp Normal file
View File

@@ -0,0 +1,600 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "glfft_wisdom.hpp"
#include "glfft_interface.hpp"
#include "glfft.hpp"
#include <utility>
/* GLAVA NOTICE: automatic wisdom serialization support may be added at a late date */
#ifdef GLFFT_SERIALIZATION
#include "rapidjson/reader.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/document.h"
using namespace rapidjson;
#endif
#ifdef GLFFT_CLI_ASYNC
#include "glfft_cli.hpp"
#endif
using namespace std;
using namespace GLFFT;
FFTStaticWisdom FFTWisdom::get_static_wisdom_from_renderer(Context *context)
{
FFTStaticWisdom res;
const char *renderer = context->get_renderer_string();
unsigned threads = context->get_max_work_group_threads();
if (strstr(renderer, "GeForce") || strstr(renderer, "Quadro"))
{
context->log("Detected GeForce/Quadro GPU.\n");
res.min_workgroup_size = 32; // Warp threads.
res.min_workgroup_size_shared = 32;
res.max_workgroup_size = min(threads, 256u); // Very unlikely that more than 256 threads will do anything good.
res.min_vector_size = 2;
res.max_vector_size = 2;
res.shared_banked = FFTStaticWisdom::True;
}
else if (strstr(renderer, "Radeon"))
{
context->log("Detected Radeon GPU.\n");
res.min_workgroup_size = 64; // Wavefront threads (GCN).
res.min_workgroup_size_shared = 128;
res.max_workgroup_size = min(threads, 256u); // Very unlikely that more than 256 threads will do anything good.
// TODO: Find if we can restrict this to 2 or 4 always.
res.min_vector_size = 2;
res.max_vector_size = 4;
res.shared_banked = FFTStaticWisdom::True;
}
else if (strstr(renderer, "Mali"))
{
context->log("Detected Mali GPU.\n");
res.min_workgroup_size = 4;
res.min_workgroup_size_shared = 4;
res.max_workgroup_size = 64; // Going beyond 64 threads per WG is not a good idea.
res.min_vector_size = 4;
res.max_vector_size = 4;
res.shared_banked = FFTStaticWisdom::False;
}
// TODO: Add more GPUs.
return res;
}
pair<double, FFTOptions::Performance> FFTWisdom::learn_optimal_options(
Context *context, unsigned Nx, unsigned Ny, unsigned radix,
Mode mode, Target input_target, Target output_target,
const FFTOptions::Type &type)
{
WisdomPass pass = {
{
Nx, Ny, radix, mode, input_target, output_target,
type,
},
0.0,
};
auto itr = library.find(pass);
if (itr != end(library))
{
return make_pair(itr->first.cost, itr->second);
}
else
{
auto result = study(context, pass, type);
pass.cost = result.first;
library[pass] = result.second;
return result;
}
}
void FFTWisdom::learn_optimal_options_exhaustive(Context *context,
unsigned Nx, unsigned Ny,
Type type, Target input_target, Target output_target, const FFTOptions::Type &fft_type)
{
bool learn_resolve = type == ComplexToReal || type == RealToComplex;
Mode vertical_mode = type == ComplexToComplexDual ? VerticalDual : Vertical;
Mode horizontal_mode = type == ComplexToComplexDual ? HorizontalDual : Horizontal;
// Create wisdom for horizontal transforms and vertical transform.
static const unsigned radices[] = { 4, 8, 16, 64 };
for (auto radix : radices)
{
try
{
// If we're doing SSBO -> Image or Image -> SSBO. Create wisdom for the two variants.
// Learn plain transforms.
if (Ny > 1)
{
learn_optimal_options(context, Nx >> learn_resolve, Ny, radix, vertical_mode, SSBO, SSBO, fft_type);
}
learn_optimal_options(context, Nx >> learn_resolve, Ny, radix, horizontal_mode, SSBO, SSBO, fft_type);
// Learn the first/last pass transforms. Can be fairly significant since accessing textures makes more sense with
// block interleave and larger WG_Y sizes.
if (input_target != SSBO)
{
if (Ny > 1)
{
learn_optimal_options(context, Nx >> learn_resolve, Ny, radix, vertical_mode, input_target, SSBO, fft_type);
}
learn_optimal_options(context, Nx >> learn_resolve, Ny, radix, horizontal_mode, input_target, SSBO, fft_type);
}
if (output_target != SSBO)
{
if (Ny > 1)
{
learn_optimal_options(context, Nx >> learn_resolve, Ny, radix, vertical_mode, SSBO, output_target, fft_type);
}
learn_optimal_options(context, Nx >> learn_resolve, Ny, radix, horizontal_mode, SSBO, output_target, fft_type);
}
}
#ifdef GLFFT_CLI_ASYNC
catch (const AsyncCancellation &)
{
throw;
}
#endif
catch (...)
{
// If our default options cannot successfully create the radix pass (i.e. throws),
// just ignore it for purpose of creating wisdom.
}
}
auto resolve_type = fft_type;
resolve_type.input_fp16 = resolve_type.output_fp16;
Mode resolve_mode = type == ComplexToReal ? ResolveComplexToReal : ResolveRealToComplex;
Target resolve_input_target = SSBO;
// If we have C2R Nx1 transform, the first pass is resolve, so use those types.
if (type == ComplexToReal && Ny == 1)
{
resolve_type = fft_type;
resolve_input_target = input_target;
}
// If we need to do a resolve pass, train this case as well.
if (learn_resolve)
{
try
{
// If Ny == 1 and we're doing RealToComplex, this will be the last pass, so use output_target as target.
if (Ny == 1 && resolve_mode == ResolveRealToComplex)
{
learn_optimal_options(context, Nx >> learn_resolve, Ny, 2, resolve_mode, resolve_input_target, output_target, resolve_type);
}
else
{
learn_optimal_options(context, Nx >> learn_resolve, Ny, 2, resolve_mode, resolve_input_target, SSBO, resolve_type);
}
}
#ifdef GLFFT_CLI_ASYNC
catch (const AsyncCancellation &)
{
throw;
}
#endif
catch (...)
{
// If our default options cannot successfully create the radix pass (i.e. throws),
// just ignore it for purpose of creating wisdom.
}
}
}
double FFTWisdom::bench(Context *context, Resource *output, Resource *input,
const WisdomPass &pass, const FFTOptions &options, const shared_ptr<ProgramCache> &cache) const
{
FFT fft(context, pass.pass.Nx, pass.pass.Ny, pass.pass.radix, pass.pass.input_target != SSBO ? 1 : pass.pass.radix,
pass.pass.mode, pass.pass.input_target, pass.pass.output_target,
cache, options);
return fft.bench(context,
output, input, params.warmup, params.iterations, params.dispatches, params.timeout);
}
static inline unsigned mode_to_size(Mode mode)
{
switch (mode)
{
case VerticalDual:
case HorizontalDual:
case ResolveRealToComplex:
case ResolveComplexToReal:
return 4;
default:
return 2;
}
}
std::pair<double, FFTOptions::Performance> FFTWisdom::study(Context *context, const WisdomPass &pass, FFTOptions::Type type) const
{
auto cache = make_shared<ProgramCache>();
unique_ptr<Resource> output;
unique_ptr<Resource> input;
unsigned mode_size = mode_to_size(pass.pass.mode);
vector<float> tmp(mode_size * pass.pass.Nx * pass.pass.Ny);
if (pass.pass.input_target == SSBO)
{
input = context->create_buffer(tmp.data(), tmp.size() * sizeof(float) >> type.input_fp16, AccessStaticCopy);
}
else
{
Format format = FormatUnknown;
unsigned Nx = pass.pass.Nx;
unsigned Ny = pass.pass.Ny;
switch (pass.pass.mode)
{
case VerticalDual:
case HorizontalDual:
format = FormatR32G32B32A32Float;
break;
case Vertical:
case Horizontal:
format = FormatR32G32Float;
break;
case ResolveComplexToReal:
format = FormatR32G32Float;
Nx *= 2;
break;
default:
throw logic_error("Invalid input mode.\n");
}
input = context->create_texture(tmp.data(), Nx, Ny, format);
}
if (pass.pass.output_target == SSBO)
{
output = context->create_buffer(nullptr, tmp.size() * sizeof(float) >> type.output_fp16, AccessStreamCopy);
}
else
{
Format format = FormatUnknown;
unsigned Nx = pass.pass.Nx;
unsigned Ny = pass.pass.Ny;
switch (pass.pass.mode)
{
case VerticalDual:
case HorizontalDual:
format = FormatR32G32B32A32Float;
break;
case Vertical:
case Horizontal:
format = FormatR32G32Float;
break;
case ResolveRealToComplex:
format = FormatR32G32Float;
Nx *= 2;
break;
default:
throw logic_error("Invalid output mode.\n");
}
output = context->create_texture(nullptr, Nx, Ny, format);
}
// Exhaustive search, look for every sensible combination, and find fastest parameters.
// Get initial best cost with defaults.
FFTOptions::Performance best_perf;
double minimum_cost = bench(context, output.get(), input.get(), pass, { best_perf, type }, cache);
static const FFTStaticWisdom::Tristate shared_banked_values[] = { FFTStaticWisdom::False, FFTStaticWisdom::True };
static const unsigned vector_size_values[] = { 2, 4, 8 };
static const unsigned workgroup_size_x_values[] = { 4, 8, 16, 32, 64, 128, 256 };
static const unsigned workgroup_size_y_values[] = { 1, 2, 4, 8, };
bool test_resolve = pass.pass.mode == ResolveComplexToReal || pass.pass.mode == ResolveRealToComplex;
bool test_dual = pass.pass.mode == VerticalDual || pass.pass.mode == HorizontalDual;
unsigned bench_count = 0;
for (auto shared_banked : shared_banked_values)
{
// Useless test, since shared banked is only relevant for radix 16/64.
if (pass.pass.radix < 16 && shared_banked)
{
continue;
}
bool fair_shared_banked = (pass.pass.radix < 16) ||
(static_wisdom.shared_banked == FFTStaticWisdom::DontCare) ||
(shared_banked == static_wisdom.shared_banked);
if (!fair_shared_banked)
{
continue;
}
for (auto vector_size : vector_size_values)
{
// Resolve passes currently only support vector size 2. Shared banked makes no sense either.
if (test_resolve && (vector_size != 2 || shared_banked))
{
continue;
}
// We can only use vector_size 8 with FP16.
if (vector_size == 8 && (!type.fp16 || !type.input_fp16 || !type.output_fp16))
{
continue;
}
// Makes little sense to test since since vector_size will be bumped to 4 anyways.
if (test_dual && vector_size < 4)
{
continue;
}
for (auto workgroup_size_x : workgroup_size_x_values)
{
for (auto workgroup_size_y : workgroup_size_y_values)
{
unsigned workgroup_size = workgroup_size_x * workgroup_size_y;
unsigned min_workgroup_size = pass.pass.radix >= 16 ? static_wisdom.min_workgroup_size_shared :
static_wisdom.min_workgroup_size;
unsigned min_vector_size = test_dual ? max(4u, static_wisdom.min_vector_size) : static_wisdom.min_vector_size;
unsigned max_vector_size = test_dual ? max(4u, static_wisdom.max_vector_size) : static_wisdom.max_vector_size;
bool fair_workgroup_size = workgroup_size <= static_wisdom.max_workgroup_size &&
workgroup_size >= min_workgroup_size;
if (pass.pass.Ny == 1 && workgroup_size_y > 1)
{
fair_workgroup_size = false;
}
if (!fair_workgroup_size)
{
continue;
}
// If we have dual mode, accept vector sizes larger than max.
bool fair_vector_size = test_resolve || (vector_size <= max_vector_size &&
vector_size >= min_vector_size);
if (!fair_vector_size)
{
continue;
}
FFTOptions::Performance perf;
perf.shared_banked = shared_banked;
perf.vector_size = vector_size;
perf.workgroup_size_x = workgroup_size_x;
perf.workgroup_size_y = workgroup_size_y;
try
{
// If workgroup sizes are too big for our test, this will throw.
double cost = bench(context, output.get(), input.get(), pass, { perf, type }, cache);
bench_count++;
#if 1
context->log("\nWisdom run (mode = %u, radix = %u):\n", pass.pass.mode, pass.pass.radix);
context->log(" Width: %4u\n", pass.pass.Nx);
context->log(" Height: %4u\n", pass.pass.Ny);
context->log(" Shared banked: %3s\n", shared_banked ? "yes" : "no");
context->log(" Vector size: %u\n", vector_size);
context->log(" Workgroup size: (%u, %u)\n", workgroup_size_x, workgroup_size_y);
context->log(" Cost: %8.3g\n", cost);
#endif
if (cost < minimum_cost)
{
#if 1
context->log(" New optimal solution! (%g -> %g)\n", minimum_cost, cost);
#endif
best_perf = perf;
minimum_cost = cost;
}
}
#ifdef GLFFT_CLI_ASYNC
catch (const AsyncCancellation &)
{
throw;
}
#endif
catch (...)
{
// If we pass in bogus parameters,
// FFT will throw and we just ignore this.
}
}
}
}
}
context->log("Tested %u variants!\n", bench_count);
return make_pair(minimum_cost, best_perf);
}
const pair<const WisdomPass, FFTOptions::Performance>* FFTWisdom::find_optimal_options(unsigned Nx, unsigned Ny, unsigned radix,
Mode mode, Target input_target, Target output_target, const FFTOptions::Type &type) const
{
WisdomPass pass = {
{
Nx, Ny, radix, mode, input_target, output_target,
type,
},
0.0,
};
auto itr = library.find(pass);
return itr != end(library) ? (&(*itr)) : nullptr;
}
const FFTOptions::Performance& FFTWisdom::find_optimal_options_or_default(unsigned Nx, unsigned Ny, unsigned radix,
Mode mode, Target input_target, Target output_target, const FFTOptions &base_options) const
{
WisdomPass pass = {
{
Nx, Ny, radix, mode, input_target, output_target,
base_options.type,
},
0.0,
};
auto itr = library.find(pass);
#if 0
if (itr == end(library))
{
context->log("Didn't find options for (%u x %u, radix %u, mode %u, input_target %u, output_target %u)\n",
Nx, Ny, radix, unsigned(mode), unsigned(input_target), unsigned(output_target));
}
#endif
return itr != end(library) ? itr->second : base_options.performance;
}
#ifdef GLFFT_SERIALIZATION
std::string FFTWisdom::archive() const
{
StringBuffer s;
PrettyWriter<StringBuffer> writer{s};
writer.StartObject();
writer.String("library");
// Serialize all wisdom accumulated to a string.
writer.StartArray();
for (auto &entry : library)
{
writer.StartObject();
writer.String("scenario");
writer.StartObject();
writer.String("nx");
writer.Uint(entry.first.pass.Nx);
writer.String("ny");
writer.Uint(entry.first.pass.Ny);
writer.String("radix");
writer.Uint(entry.first.pass.radix);
writer.String("mode");
writer.Uint(entry.first.pass.mode);
writer.String("input_target");
writer.Uint(entry.first.pass.input_target);
writer.String("output_target");
writer.Uint(entry.first.pass.output_target);
writer.EndObject();
writer.String("type");
writer.StartObject();
writer.String("fp16");
writer.Bool(entry.first.pass.type.fp16);
writer.String("input_fp16");
writer.Bool(entry.first.pass.type.input_fp16);
writer.String("output_fp16");
writer.Bool(entry.first.pass.type.output_fp16);
writer.String("normalize");
writer.Bool(entry.first.pass.type.normalize);
writer.EndObject();
writer.String("performance");
writer.StartObject();
writer.String("shared_banked");
writer.Bool(entry.second.shared_banked);
writer.String("vector_size");
writer.Uint(entry.second.vector_size);
writer.String("workgroup_size_x");
writer.Uint(entry.second.workgroup_size_x);
writer.String("workgroup_size_y");
writer.Uint(entry.second.workgroup_size_y);
writer.EndObject();
writer.String("cost");
writer.Double(entry.first.cost);
writer.EndObject();
}
writer.EndArray();
writer.EndObject();
return s.GetString();
}
void FFTWisdom::extract(const char *json)
{
Document document;
document.Parse(json);
// Exception safe, we don't want to risk throwing in the middle of the
// loop, leaving the library is broken state.
unordered_map<WisdomPass, FFTOptions::Performance> new_library;
auto &lib = document["library"];
// y u no begin(), end() :(
for (Value::ConstValueIterator itr = lib.Begin(); itr != lib.End(); ++itr)
{
auto &v = *itr;
WisdomPass pass;
FFTOptions::Performance perf;
pass.cost = v["cost"].GetDouble();
auto &scenario = v["scenario"];
pass.pass.Nx = scenario["nx"].GetUint();
pass.pass.Ny = scenario["ny"].GetUint();
pass.pass.radix = scenario["radix"].GetUint();
pass.pass.mode = static_cast<Mode>(scenario["mode"].GetUint());
pass.pass.input_target = static_cast<Target>(scenario["input_target"].GetUint());
pass.pass.output_target = static_cast<Target>(scenario["output_target"].GetUint());
auto &type = v["type"];
pass.pass.type.fp16 = type["fp16"].GetBool();
pass.pass.type.input_fp16 = type["input_fp16"].GetBool();
pass.pass.type.output_fp16 = type["output_fp16"].GetBool();
pass.pass.type.normalize = type["normalize"].GetBool();
auto &performance = v["performance"];
perf.shared_banked = performance["shared_banked"].GetBool();
perf.vector_size = performance["vector_size"].GetUint();
perf.workgroup_size_x = performance["workgroup_size_x"].GetUint();
perf.workgroup_size_y = performance["workgroup_size_y"].GetUint();
new_library[pass] = perf;
}
// Exception safe.
swap(library, new_library);
}
#endif

149
glfft/glfft_wisdom.hpp Normal file
View File

@@ -0,0 +1,149 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef GLFFT_WISDOM_HPP__
#define GLFFT_WISDOM_HPP__
#include <unordered_map>
#include <utility>
#include <string>
#include "glfft_common.hpp"
#include "glfft_interface.hpp"
namespace GLFFT
{
struct WisdomPass
{
struct
{
unsigned Nx;
unsigned Ny;
unsigned radix;
Mode mode;
Target input_target;
Target output_target;
FFTOptions::Type type;
} pass;
double cost;
bool operator==(const WisdomPass &other) const
{
return std::memcmp(&pass, &other.pass, sizeof(pass)) == 0;
}
};
}
namespace std
{
template<>
struct hash<GLFFT::WisdomPass>
{
std::size_t operator()(const GLFFT::WisdomPass &params) const
{
std::size_t h = 0;
hash<uint8_t> hasher;
for (std::size_t i = 0; i < sizeof(params.pass); i++)
{
h ^= hasher(reinterpret_cast<const uint8_t*>(&params.pass)[i]);
}
return h;
}
};
}
namespace GLFFT
{
// Adds information which depends on the GPU vendor.
// This can speed up learning process, since there will be fewer "obviously wrong" settings to test.
struct FFTStaticWisdom
{
enum Tristate { True = 1, False = 0, DontCare = -1 };
unsigned min_workgroup_size = 1;
unsigned min_workgroup_size_shared = 1;
unsigned max_workgroup_size = 128; // GLES 3.1 mandates support for this.
unsigned min_vector_size = 2;
unsigned max_vector_size = 4;
Tristate shared_banked = DontCare;
};
class FFTWisdom
{
public:
std::pair<double, FFTOptions::Performance> learn_optimal_options(Context *ctx,
unsigned Nx, unsigned Ny, unsigned radix,
Mode mode, Target input_target, Target output_target, const FFTOptions::Type &type);
void learn_optimal_options_exhaustive(Context *ctx,
unsigned Nx, unsigned Ny,
Type type, Target input_target, Target output_target, const FFTOptions::Type &fft_type);
const std::pair<const WisdomPass, FFTOptions::Performance>* find_optimal_options(unsigned Nx, unsigned Ny, unsigned radix,
Mode mode, Target input_target, Target output_target, const FFTOptions::Type &base_options) const;
const FFTOptions::Performance& find_optimal_options_or_default(unsigned Nx, unsigned Ny, unsigned radix,
Mode mode, Target input_target, Target output_target, const FFTOptions &base_options) const;
void set_static_wisdom(FFTStaticWisdom static_wisdom) { this->static_wisdom = static_wisdom; }
static FFTStaticWisdom get_static_wisdom_from_renderer(Context *context);
void set_bench_params(unsigned warmup,
unsigned iterations, unsigned dispatches, double timeout)
{
params.warmup = warmup;
params.iterations = iterations;
params.dispatches = dispatches;
params.timeout = timeout;
}
#ifdef GLFFT_SERIALIZATION
// Serialization interface.
std::string archive() const;
void extract(const char *json);
#endif
private:
std::unordered_map<WisdomPass, FFTOptions::Performance> library;
std::pair<double, FFTOptions::Performance> study(Context *context,
const WisdomPass &pass, FFTOptions::Type options) const;
double bench(Context *cmd, Resource *output, Resource *input,
const WisdomPass &pass, const FFTOptions &options,
const std::shared_ptr<ProgramCache> &cache) const;
FFTStaticWisdom static_wisdom;
struct
{
unsigned warmup = 2;
unsigned iterations = 20;
unsigned dispatches = 50;
double timeout = 1.0;
} params;
};
}
#endif

View File

@@ -1,372 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "glsl_ext.h"
struct sbuf {
char* buf;
size_t at; /* index of final null character */
size_t bsize; /* actual buffer size */
};
#define append(sbuf, str) n_append(sbuf, strlen(str), str)
static inline void expand_for(struct sbuf* sbuf, size_t len) {
bool resize = false;
while (len + 1 > sbuf->bsize - sbuf->at) {
sbuf->bsize *= 2;
resize = true;
}
if (resize)
sbuf->buf = realloc(sbuf->buf, sbuf->bsize);
}
/* append 'n' bytes from 'str' to the resizable buffer */
static void n_append(struct sbuf* sbuf, size_t len, const char* str) {
expand_for(sbuf, len);
memcpy(sbuf->buf + sbuf->at, str, len);
sbuf->at += len;
sbuf->buf[sbuf->at] = '\0';
}
#define s_append(sbuf, fmt, ...) se_append(sbuf, 64, fmt, __VA_ARGS__)
/* append the formatted string to the resizable buffer, where elen is extra space for formatted chars */
static void se_append(struct sbuf* sbuf, size_t elen, const char* fmt, ...) {
size_t space = strlen(fmt) + elen;
expand_for(sbuf, space);
va_list args;
va_start(args, fmt);
int written;
if ((written = vsnprintf(sbuf->buf + sbuf->at, space, fmt, args)) < 0)
abort();
sbuf->at += space;
va_end(args);
}
#define parse_error(line, f, fmt, ...) \
fprintf(stderr, "[%s:%d] " fmt "\n", f, (int) line, __VA_ARGS__); \
abort()
#define parse_error_s(line, f, s) \
fprintf(stderr, "[%s:%d] " s "\n", f, (int) line); \
abort()
struct schar {
char* buf;
size_t sz;
};
/* handle raw arguments for #include and #request directives */
/* NOTE: munmap needs to be called on the result */
static struct schar directive(struct glsl_ext* ext, char** args,
size_t args_sz, bool include,
size_t line, const char* f) {
if (include) {
if (args_sz == 0) {
parse_error_s(line, f, "No arguments provided to #include directive!");
}
char* target = args[0];
/* Handle `:` config specifier */
size_t tsz = strlen(target);
if (tsz && target[0] == ':' && ext->cfd) {
target = &target[1];
ext->cd = ext->cfd;
}
char path[strlen(ext->cd) + tsz + 2];
snprintf(path, sizeof(path) / sizeof(char), "%s/%s", ext->cd, target);
int fd = open(path, O_RDONLY);
if (fd == -1) {
parse_error(line, f, "failed to load GLSL shader source specified by #include directive '%s': %s\n",
path, strerror(errno));
}
struct stat st;
fstat(fd, &st);
char* map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (!map) {
parse_error(line, f, "failed to map GLSL shader source specified by #include directive '%s': %s\n",
path, strerror(errno));
}
struct glsl_ext next = {
.source = map,
.source_len = st.st_size,
.cd = ext->cd,
.cfd = ext->cfd,
.handlers = ext->handlers
};
/* recursively process */
ext_process(&next, target);
struct schar ret = {
.buf = next.processed,
.sz = next.p_len
};
return ret;
} else {
if (args_sz > 0) {
char* request = args[0];
struct request_handler* handler;
bool found = false;
size_t t, i;
for (t = 0; (handler = &ext->handlers[t])->name != NULL; ++t) {
if(!strcmp(handler->name, request)) {
found = true;
void** processed_args = malloc(strlen(handler->fmt) * sizeof(void*));
char c;
size_t i;
for (i = 0; (c = handler->fmt[i]) != '\0'; ++i) {
if (args_sz <= 1 + i) {
parse_error(line, f,
"failed to execute request '%s': expected format '%s'\n",
request, handler->fmt);
}
char* raw = args[1 + i];
switch (c) {
case 'i':
{
int v = (int) strtol(raw, NULL, 0);
processed_args[i] = malloc(sizeof(int));
*(int*) processed_args[i] = v;
break;
}
case 'f':
{
float f = strtof(raw, NULL);
processed_args[i] = malloc(sizeof(float));
*(float*) processed_args[i] = f;
break;
}
case 's': { *(char**) &processed_args[i] = raw; break; }
case 'b':
{
bool v;
if (!strcmp(raw, "true")) {
v = true;
} else if (!strcmp(raw, "false")) {
v = false;
} else if (strlen(raw) == 1) {
switch (raw[0]) {
case 't': { v = true; break; }
case 'f': { v = false; break; }
case '1': { v = true; break; }
case '0': { v = false; break; }
default:
parse_error_s(line, f, "tried to parse invalid raw string into a boolean");
}
} else {
parse_error_s(line, f, "tried to parse invalid raw string into a boolean");
}
processed_args[i] = malloc(sizeof(bool));
*(bool*) processed_args[i] = v;
break;
}
}
}
handler->handler(request, processed_args);
for (i = 0; (c = handler->fmt[i]) != '\0'; ++i)
if (c != 's')
free(processed_args[i]);
free(processed_args);
}
}
if (!found) {
parse_error(line, f, "unknown request type '%s'", request);
}
}
struct schar ret = {
.buf = NULL,
.sz = 0
};
return ret;
}
}
/* state machine parser */
void ext_process(struct glsl_ext* ext, const char* f) {
#define LINE_START 0
#define IGNORING 1
#define MACRO 2
#define REQUEST 3
#define INCLUDE 4
struct sbuf sbuf = {
.buf = malloc(256),
.at = 0,
.bsize = 256
};
size_t source_len = ext->source_len;
size_t t;
char at;
int state = LINE_START;
size_t macro_start_idx, arg_start_idx;
size_t line = 1;
bool quoted = false, arg_start;
char** args = NULL;
size_t args_sz = 0;
for (t = 0; t <= source_len; ++t) {
at = source_len == t ? '\0' : ext->source[t];
if (at == '\n')
++line;
switch (state) {
case LINE_START: /* processing start of line */
{
switch (at) {
case '#': {
macro_start_idx = t;
state = MACRO;
continue; }
case ' ':
case '\t':
case '\n':
goto copy;
default: {
state = IGNORING;
goto copy; }
}
}
case IGNORING: /* copying GLSL source or unrelated preprocessor syntax */
if (at == '\n') {
state = LINE_START;
}
goto copy;
case MACRO: /* processing start of macro */
{
switch (at) {
case '\n':
case ' ':
case '\t':
case '\0':
{
/* end parsing directive */
if (!strncmp("#request", &ext->source[macro_start_idx], t - macro_start_idx)
|| !strncmp("#REQUEST", &ext->source[macro_start_idx], t - macro_start_idx)) {
state = REQUEST;
goto prepare_arg_parse;
} else if (!strncmp("#include", &ext->source[macro_start_idx], t - macro_start_idx)
|| !strncmp("#INCLUDE", &ext->source[macro_start_idx], t - macro_start_idx)) {
state = INCLUDE;
goto prepare_arg_parse;
} else {
n_append(&sbuf, t - macro_start_idx, &ext->source[macro_start_idx]);
state = IGNORING;
goto copy;
}
prepare_arg_parse:
{
arg_start_idx = t + 1;
arg_start = true;
args = malloc(sizeof(char*));
args_sz = 0;
*args = NULL;
}
}
case 'a' ... 'z':
case 'A' ... 'Z':
continue;
default:
/* invalid char, malformed! */
parse_error(line, f, "Unexpected character '%c' while parsing GLSL directive", at);
}
}
/* scope-violating macro to copy the result of the currently parsed argument */
#define copy_arg(end) \
do { if (end - arg_start_idx > 0) { \
++args_sz; \
args = realloc(args, sizeof(char*) * args_sz); \
args[args_sz - 1] = malloc((end - arg_start_idx) + 1); \
memcpy(args[args_sz - 1], &ext->source[arg_start_idx], end - arg_start_idx); \
args[args_sz - 1][end - arg_start_idx] = '\0'; \
} } while (0)
case REQUEST:
case INCLUDE:
{
switch (at) {
case ' ':
case '\t':
case '\n':
case '\0':
if (!quoted) {
/* end arg */
copy_arg(t);
arg_start = true;
arg_start_idx = t + 1;
} else arg_start = false;
if (at == '\n') {
/* end directive */
size_t a;
struct schar r = directive(ext, args, args_sz, state == INCLUDE, line, f);
for (a = 0; a < args_sz; ++a) {
free(args[a]);
}
args_sz = 0;
/* if something was returned (ie. included file), paste the results */
if (r.buf) {
n_append(&sbuf, r.sz, r.buf);
append(&sbuf, "\n");
}
munmap(r.buf, r.sz);
state = LINE_START;
}
break;
case '"':
if (quoted) {
/* end arg */
copy_arg(t);
quoted = false;
arg_start = true;
arg_start_idx = t + 1;
} else if (arg_start) {
++arg_start_idx;
quoted = true;
} else arg_start = false;
break;
default:
arg_start = false;
}
continue;
}
}
copy:
if (at != '\0')
n_append(&sbuf, 1, &at);
}
ext->processed = sbuf.buf;
ext->p_len = sbuf.at;
if (args)
free(args);
}
void ext_free(struct glsl_ext* ext) {
free(ext->processed);
}

View File

@@ -1,37 +0,0 @@
#define RHANDLER(name, args, ...) \
({ void _handler(const char* name, void** args) __VA_ARGS__ _handler; })
struct request_handler {
const char* name;
/*
handler format:
'i' - signed integer (void* -> int*)
'f' - float (void* -> float*)
's' - string (void* -> const char*)
'b' - bool (void* -> bool*)
example:
.fmt = "sii" // takes a string, and then two integers
.fmt = "ffb" // takes two floats, then a boolean
*/
const char* fmt;
void (*handler)(const char* name, void** args);
};
struct glsl_ext {
char* processed; /* OUT: null terminated processed source */
size_t p_len; /* OUT: length of processed buffer, excluding null char */
const char* source; /* IN: raw data passed via ext_process */
size_t source_len; /* IN: raw source len */
const char* cd; /* IN: current directory */
const char* cfd; /* IN: config directory, if NULL it is assumed to cd */
/* IN: NULL (where the last element's 'name' member is NULL) terminated
array of request handlers */
struct request_handler* handlers;
};
void ext_process(struct glsl_ext* ext, const char* f);
void ext_free (struct glsl_ext* ext);

202
meson.build Normal file
View File

@@ -0,0 +1,202 @@
project(
'glava',
['c', 'cpp'],
version: run_command('git', 'describe', '--tags').stdout().strip(),
default_options:['buildtype=release', 'strip=true', 'optimization=2'])
cc = meson.get_compiler('c')
if get_option('glad')
if get_option('buildtype').startswith('debug')
run_command('./glad_generate.sh', 'c-debug')
else
run_command('./glad_generate.sh')
endif
endif
if get_option('buildtype').startswith('debug')
add_project_arguments('-DGLAVA_DEBUG', language: 'c')
endif
glava_dependencies = [
dependency('threads'),
cc.find_library('pulse'),
cc.find_library('pulse-simple'),
cc.find_library('dl'),
cc.find_library('m'),
cc.find_library('X11'),
cc.find_library('Xext')
]
if cc.get_id() == 'clang'
add_project_arguments('-fblocks', language: 'c')
glava_dependencies += cc.find_library('BlocksRuntime')
endif
shader_dir = get_option('shader_install_dir')
glava_version = meson.project_version()
if glava_version == ''
glava_version = 'unknown'
endif
if host_machine.system() == 'linux' or host_machine.system() == 'bsd'
add_project_arguments('-DGLAVA_UNIX', language: ['cpp', 'c'])
endif
# Note: the OSX install directives only exist for future platform support
if host_machine.system() == 'darwin'
add_project_arguments('-DGLAVA_OSX', language: ['cpp', 'c'])
error('OSX targets are not supported, see issue #86.')
# shader_dir = '/Library/glava/'
endif
if get_option('enable_glfw')
add_project_arguments('-DGLAVA_GLFW', language: ['cpp', 'c'])
glava_dependencies += cc.find_library('glfw')
endif
if not get_option('disable_glx')
add_project_arguments('-DGLAVA_GLX', language: ['cpp', 'c'])
glava_dependencies += cc.find_library('Xrender')
endif
if get_option('standalone')
add_project_arguments('-DGLAVA_STANDALONE', language: ['cpp', 'c'])
endif
resource_dir = get_option('resource_install_dir')
if get_option('standalone')
resource_dir = '..'
endif
add_project_arguments(
'-DGLAVA_VERSION="' + glava_version + '"',
'-DSHADER_INSTALL_PATH="' + shader_dir + '/glava"',
'-DGLAVA_RESOURCE_PATH="' + resource_dir + '/resources"',
# todo: add back
# '-fvisibility=hidden',
language: ['cpp', 'c'])
glfft = static_library(
'glfft',
sources: run_command('find', 'glfft', '-type', 'f', '-name', '*.cpp', '-print')
.stdout().strip().split('\n'),
c_args: ['-std=c++11'],
dependencies: [ cc.find_library('dl') ])
libglava = shared_library(
'glava',
sources: run_command('find', 'glava', '-type', 'f', '-name', '*.c', '-print')
.stdout().strip().split('\n'),
dependencies: glava_dependencies,
install: true)
executable(
'glava',
sources: 'glava-cli/cli.c',
link_with: libglava,
c_args: ['-I' + meson.source_root() + '/glava', '-std=c++11'],
install: true)
if not get_option('disable_config')
# Generator and target for lua objects used by `glava-config`.
# This has been written such that ninja can detect when sources
# need to be rebuilt.
luac_input_ext = 'lua'
luac_output_ext = 'lua'
glava_config_lua_sources = run_command(
'find', 'glava-config', '-type', 'f', '-name', '*.' + luac_input_ext, '-print'
).stdout().strip().split('\n')
glava_config_lua_targets = []
foreach s: run_command(
'basename', '-s.' + luac_input_ext, glava_config_lua_sources
).stdout().strip().split('\n')
glava_config_lua_targets += s + '.' + luac_output_ext
endforeach
luac_name = 'luac' + get_option('lua_version')
luac_args = ['-o', '@OUTPUT@', '@INPUT@']
lua_dir = get_option('lua_implementation')
lua_ver = get_option('lua_version')
lua_impl = get_option('lua_implementation')
lua_inc = get_option('lua_implementation') + get_option('lua_version')
if get_option('lua_implementation') == 'luajit'
# LuaJIT compiler produces better bytecode; use that
luac_name = 'luajit'
lua_impl += '-'
luac_args = ['-b', '@INPUT@', '@OUTPUT@']
if get_option('buildtype').startswith('debug')
luac_args += '-g'
endif
# LuaJIT uses /usr/share/lua/5.1; ignore version
lua_dir = 'lua'
lua_ver = '5.1'
lua_inc = 'luajit-2.0'
elif not get_option('buildtype').startswith('debug')
luac_args = ['-s'] + luac_args
endif
luac_target = custom_target(
'glava-config-luac',
input: generator(
find_program(luac_name),
output: '@BASENAME@.' + luac_output_ext,
arguments: luac_args).process(glava_config_lua_sources),
output: glava_config_lua_targets,
command: [find_program('cp'), '-t', '@OUTDIR@', '@INPUT@'],
build_by_default: true,
install: true,
install_dir: get_option('lua_install_dir') + '/' + lua_dir + '/'
+ lua_ver + '/' + 'glava-config')
executable(
'glava-config',
install: true,
sources: 'glava-config/entry.c',
c_args: '-I/usr/include/' + lua_inc,
dependencies: [
cc.find_library('X11'),
cc.find_library(lua_impl + lua_ver)
])
# Local glava-config environment symlink for standalone execution
if get_option('standalone')
env_target = custom_target(
'glava-config-env', input: [], output: 'glava-env',
depends: luac_target,
command: [find_program('mkdir'), '-p', 'glava-env'],
build_by_default: true)
custom_target(
'glava-config-ln', input: [], output: '.PHONY',
depends: env_target,
command: [find_program('ln'), '-sfT',
'../glava-config-luac@cus',
'glava-env/glava-config'],
build_by_default: true)
endif
endif
if not get_option('disable_obs')
shared_library(
'glava-obs',
install: true,
install_dir: get_option('obs_plugin_install_dir'),
sources: 'glava-obs/entry.c',
link_with: libglava,
c_args: '-I/usr/include/obs',
dependencies: [
dependency('threads'),
cc.find_library('GL'),
cc.find_library('X11'),
cc.find_library('obs'),
cc.find_library('dl')
])
endif
install_subdir('shaders/glava', install_dir: shader_dir)
install_subdir('resources', install_dir: resource_dir)
install_headers('glava/glava.h')

24
meson_options.txt Normal file
View File

@@ -0,0 +1,24 @@
option('enable_glfw', type: 'boolean', value: false,
description: 'Enable legacy GLFW backend')
option('disable_glx', type: 'boolean', value: false,
description: 'Disable GLX backend')
option('standalone', type: 'boolean', value: false,
description: 'Configure build to run without installation')
option('glad', type: 'boolean', value: false,
description: 'Download and build GLAD headers (non-reproducable)')
option('disable_obs', type: 'boolean', value: false,
description: 'Disable OBS Studio plugin support')
option('disable_config', type: 'boolean', value: true,
description: 'Skip building GLava GTK+ configuration tool')
option('shader_install_dir', type: 'string', value: '/etc/xdg',
description: 'GLSL config/module system install directory')
option('lua_install_dir', type: 'string', value: '/usr/share',
description: 'Location to install Lua modules for glava-config')
option('lua_version', type: 'string', value: '5.3',
description: 'Lua version to use for glava-config')
option('lua_implementation', type: 'string', value: 'lua',
description: 'Lua implementation to use (\'lua\' or \'luajit\')')
option('resource_install_dir', type: 'string', value: '/usr/share/glava',
description: 'Location to install generic GLava resources')
option('obs_plugin_install_dir', type: 'string', value: '/usr/lib/obs-plugins',
description: 'Location to install OBS plugin if enabled')

1532
render.c

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
struct gl_data;
typedef struct renderer {
bool alive;
size_t bufsize_request, rate_request, samplesize_request;
char* audio_source_request;
struct gl_data* gl;
} renderer;
struct renderer* rd_new(const char** paths, const char* entry, const char* force_mod);
void rd_update(struct renderer*, float* lb, float* rb, size_t bsz, bool modified);
void rd_destroy(struct renderer*);
void rd_time(struct renderer*);
void* rd_get_impl_window(struct renderer*);

BIN
resources/glava.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
resources/transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

View File

@@ -1,21 +0,0 @@
/* center line thickness (pixels) */
#define C_LINE 1
/* width (in pixels) of each bar */
#define BAR_WIDTH 4
/* width (in pixels) of each bar gap */
#define BAR_GAP 2
/* outline color */
#define BAR_OUTLINE vec4(0.15, 0.15, 0.15, 1)
/* outline width (in pixels, set to 0 to disable outline drawing) */
#define BAR_OUTLINE_WIDTH 0
/* Amplify magnitude of the results each bar displays */
#define AMPLIFY 300
/* Bar color */
#define COLOR (vec4(0.2, 0.4, 0.7, 1) * ((d / 60) + 1))
/* Direction that the bars are facing, 0 for inward, 1 for outward */
#define DIRECTION 0
/* Whether to switch left/right audio buffers */
#define INVERT 0

34
shaders/glava/bars.glsl Normal file
View File

@@ -0,0 +1,34 @@
/* Note: to only render a single channel, see `setmirror` in `rc.glsl`. */
/* Center line thickness (pixels) */
#define C_LINE 1
/* Width (in pixels) of each bar */
#define BAR_WIDTH 5
/* Width (in pixels) of each bar gap */
#define BAR_GAP 1
/* Outline width (in pixels, set to 0 to disable outline drawing) */
#define BAR_OUTLINE_WIDTH 1
/* Amplify magnitude of the results each bar displays */
#define AMPLIFY 300
/* Whether the current settings use the alpha channel;
enabling this is required for alpha to function
correctly on X11 with `"native"` transparency */
#define USE_ALPHA 0
/* How quickly the gradient transitions, in pixels */
#define GRADIENT 80
/* Bar color. By default this provides a blue-white gradient. */
#define COLOR @fg:mix(#3366b2, #a0a0b2, clamp(d / GRADIENT, 0, 1))
/* Outline color. By default this provides a 'glint' outline based on the bar color */
#define BAR_OUTLINE @bg:vec4(COLOR.rgb * 1.5, COLOR.a)
/* Direction that the bars are facing, 0 for inward, 1 for outward */
#define DIRECTION 0
/* Whether to switch left/right audio buffers */
#define INVERT 0
/* Whether to flip the output vertically */
#define FLIP 0
/* Whether to mirror output along `Y = X`, causing output to render on the left side of the window */
/* Use with `FLIP 1` to render on the right side */
#define MIRROR_YX 0
/* Whether to disable mono rendering when `#request setmirror true` is set in `rc.glsl`. */
#define DISABLE_MONO 0

View File

@@ -6,6 +6,7 @@ uniform ivec2 screen;
#request uniform "audio_sz" audio_sz
uniform int audio_sz;
#include "@bars.glsl"
#include ":bars.glsl"
#request uniform "audio_l" audio_l
@@ -28,18 +29,53 @@ out vec4 fragment;
#define TWOPI 6.28318530718
#define PI 3.14159265359
#if DISABLE_MONO == 1
#define _CHANNELS 2
#endif
void main() {
float /* (x, magnitude) of fragment */
dx = (gl_FragCoord.x - (screen.x / 2)),
d = gl_FragCoord.y;
float nbars = floor((screen.x * 0.5F) / float(BAR_WIDTH + BAR_GAP)) * 2;
#if MIRROR_YX == 0
#define AREA_WIDTH screen.x
#define AREA_HEIGHT screen.y
#define AREA_X gl_FragCoord.x
#define AREA_Y gl_FragCoord.y
#else
#define AREA_WIDTH screen.y
#define AREA_HEIGHT screen.x
#define AREA_X gl_FragCoord.y
#define AREA_Y gl_FragCoord.x
#endif
#if _CHANNELS == 2
float dx = (AREA_X - (AREA_WIDTH / 2));
#else
#if INVERT == 1
float dx = AREA_WIDTH - AREA_X;
#else
float dx = AREA_X;
#endif
#endif
#if FLIP == 0
float d = AREA_Y;
#else
float d = AREA_HEIGHT - AREA_Y;
#endif
float section = BAR_WIDTH + BAR_GAP; /* size of section for each bar (including gap) */
float center = section / 2.0F; /* half section, distance to center */
float m = abs(mod(dx, section)); /* position in section */
float md = m - center; /* position in section from center line */
float nbars = floor((AREA_WIDTH * 0.5F) / section) * 2;
float p, s;
if (md < ceil(float(BAR_WIDTH) / 2) && md >= -floor(float(BAR_WIDTH) / 2)) { /* if not in gap */
float p = int(dx / section) / float(nbars / 2); /* position, (-1.0F, 1.0F)) */
p += sign(p) * ((0.5F + center) / screen.x); /* index center of bar position */
s = dx / section;
p = (sign(s) == 1.0 ? ceil(s) : floor(s));
#if _CHANNELS == 2
p /= float(nbars / 2);
#else
p /= float(nbars);
#endif
p += sign(p) * ((0.5F + center) / AREA_WIDTH); /* index center of bar position */
/* Apply smooth function and index texture */
#define smooth_f(tex, p) smooth_audio(tex, audio_sz, p)
float v;
@@ -49,11 +85,13 @@ void main() {
return;
}
/* handle user options and store result of indexing in 'v' */
if (p >= 0.0F) {
if (p > 0.0F) {
#if DIRECTION == 1
p = 1.0F - p;
#endif
#if INVERT > 0
#if _CHANNELS == 1
v = smooth_f(audio_l, p);
#elif INVERT > 0
v = smooth_f(audio_l, p);
#else
v = smooth_f(audio_r, p);
@@ -63,7 +101,9 @@ void main() {
#if DIRECTION == 1
p = 1.0F - p;
#endif
#if INVERT > 0
#if _CHANNELS == 1
v = smooth_f(audio_l, p);
#elif INVERT > 0
v = smooth_f(audio_r, p);
#else
v = smooth_f(audio_l, p);
@@ -74,7 +114,7 @@ void main() {
v *= AMPLIFY; /* amplify result */
if (d < v - BAR_OUTLINE_WIDTH) { /* if within range of the reported frequency, draw */
#if BAR_OUTLINE_WIDTH > 0
if (md < (BAR_WIDTH / 2) - BAR_OUTLINE_WIDTH)
if (md < ceil(float(BAR_WIDTH) / 2) - BAR_OUTLINE_WIDTH && md >= -floor(float(BAR_WIDTH) / 2) + BAR_OUTLINE_WIDTH)
fragment = COLOR;
else
fragment = BAR_OUTLINE;

View File

@@ -0,0 +1,5 @@
#if USE_ALPHA == 0
#error __disablestage
#endif
#include ":util/premultiply.frag"

View File

@@ -1,9 +1,9 @@
/* center radius (pixels) */
#define C_RADIUS 128
/* center line thickness (pixels) */
#define C_LINE 2
#define C_LINE 1.5
/* outline color */
#define OUTLINE vec4(0.20, 0.20, 0.20, 1)
#define OUTLINE @fg:#333333
/* Amplify magnitude of the results each bar displays */
#define AMPLIFY 150
/* Angle (in radians) for how much to rotate the visualizer */
@@ -16,9 +16,3 @@
1 to enable, 0 to disable. Only works with `xroot` transparency,
and improves performance if disabled. */
#define C_SMOOTH 1
/* Gravity step, overrude frin `smooth_parameters.glsl` */
#request setgravitystep 6.0
/* Smoothing factor, override from `smooth_parameters.glsl` */
#request setsmoothfactor 0.01

View File

@@ -7,6 +7,7 @@ uniform ivec2 screen;
uniform int audio_sz;
#include ":util/smooth.glsl"
#include "@circle.glsl"
#include ":circle.glsl"
#request uniform "audio_l" audio_l
@@ -31,7 +32,6 @@ out vec4 fragment;
/* This shader is based on radial.glsl, refer to it for more commentary */
float apply_smooth(float theta) {
float idx = theta + ROTATE;
float dir = mod(abs(idx), TWOPI);
if (dir > PI)
@@ -50,6 +50,7 @@ float apply_smooth(float theta) {
}
void main() {
fragment = vec4(0, 0, 0, 0);
float
dx = gl_FragCoord.x - (screen.x / 2),
dy = gl_FragCoord.y - (screen.y / 2);

View File

@@ -6,11 +6,13 @@ uniform sampler2D tex; /* screen texture */
out vec4 fragment; /* output */
#include "@circle.glsl"
#include ":circle.glsl"
void main() {
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
#if C_SMOOTH > 0
#if _USE_ALPHA
vec4
a0 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 0)), 0),
a1 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 1)), 0),
@@ -27,4 +29,5 @@ void main() {
fragment = avg;
}
#endif
#endif
}

View File

@@ -0,0 +1 @@
#include ":util/premultiply.frag"

View File

@@ -0,0 +1,8 @@
#request setdecorated false
#request setxwintype "normal"
#request addxwinstate "below"
#request addxwinstate "skip_taskbar"
#request addxwinstate "skip_pager"
#request addxwinstate "pinned"
#request setclickthrough true

View File

@@ -0,0 +1,2 @@
#request setxwintype "desktop"
#request addxwinstate "pinned"

View File

@@ -0,0 +1,3 @@
#request setxwintype "desktop"
#request addxwinstate "pinned"
#request addxwinstate "below"

View File

@@ -0,0 +1 @@
#request setxwintype "!-"

View File

@@ -0,0 +1 @@
#request setxwintype "desktop"

View File

@@ -0,0 +1 @@
#request setxwintype "!-"

View File

@@ -0,0 +1 @@
#request setxwintype "!-"

25
shaders/glava/graph.glsl Normal file
View File

@@ -0,0 +1,25 @@
/* Vertical scale, larger values will amplify output */
#define VSCALE 300
/* Rendering direction, either -1 (outwards) or 1 (inwards). */
#define DIRECTION 1
/* Color gradient scale, (optionally) used in `COLOR` macro */
#define GRADIENT 75
/* Color definition. By default this is a gradient formed by mixing two colors.
`pos` represents the pixel position relative to the visualizer baseline. */
#define COLOR @fg:mix(#802A2A, #4F4F92, clamp(pos / GRADIENT, 0, 1))
/* 1 to draw outline, 0 to disable */
#define DRAW_OUTLINE 0
/* 1 to draw edge highlight, 0 to disable */
#define DRAW_HIGHLIGHT 1
/* Whether to anti-alias the border of the graph, creating a smoother curve.
This may have a small impact on performance.
Note: requires `xroot` or `none` opacity to be set */
#define ANTI_ALIAS 0
/* outline color */
#define OUTLINE @bg:#262626
/* 1 to join the two channels together in the middle, 0 to clamp both down to zero */
#define JOIN_CHANNELS 0
/* 1 to invert (vertically), 0 otherwise */
#define INVERT 0

View File

@@ -40,6 +40,7 @@ uniform int audio_sz;
*/
#include ":util/smooth.glsl"
#include "@graph.glsl"
#include ":graph.glsl"
#request uniform "audio_l" audio_l
@@ -80,22 +81,38 @@ out vec4 fragment;
#define TWOPI 6.28318530718
float half_w;
float middle;
highp float pixel = 1.0F / float(screen.x);
void render_side(in sampler1D tex, float idx) {
highp float pixel = 1.0F / float(screen.x);
float get_line_height(in sampler1D tex, float idx) {
float s = smooth_audio_adj(tex, audio_sz, idx / half_w, pixel);
/* scale the data upwards so we can see it */
s *= VSCALE;
/* clamp far ends of the screen down to make the ends of the graph smoother */
s *= clamp((abs((screen.x / 2) - gl_FragCoord.x) / screen.x) * 48, 0.0F, 1.0F);
float fact = clamp((abs((screen.x / 2) - gl_FragCoord.x) / screen.x) * 48, 0.0F, 1.0F);
#if JOIN_CHANNELS > 0
fact = -2 * pow(fact, 3) + 3 * pow(fact, 2); /* To avoid spikes */
s = fact * s + (1 - fact) * middle;
#else
s *= fact;
#endif
s *= clamp((min(gl_FragCoord.x, screen.x - gl_FragCoord.x) / screen.x) * 48, 0.0F, 1.0F);
return s;
}
void render_side(in sampler1D tex, float idx) {
float s = get_line_height(tex, idx);
/* and finally set fragment color if we are in range */
#if INVERT > 0
float pos = float(screen.y) - gl_FragCoord.y;
float d = float(screen.y) - gl_FragCoord.y;
#else
float pos = gl_FragCoord.y;
float d = gl_FragCoord.y;
#endif
#define pos d
if (pos + 1.5 <= s) {
fragment = COLOR;
} else {
@@ -105,6 +122,9 @@ void render_side(in sampler1D tex, float idx) {
void main() {
half_w = (screen.x / 2);
middle = VSCALE * (smooth_audio_adj(audio_l, audio_sz, 1, pixel) + smooth_audio_adj(audio_r, audio_sz, 0, pixel)) / 2;
if (gl_FragCoord.x < half_w) {
render_side(audio_l, LEFT_IDX);
} else {

View File

@@ -0,0 +1,44 @@
in vec4 gl_FragCoord;
#request uniform "prev" tex
uniform sampler2D tex; /* screen texture */
out vec4 fragment; /* output */
#include "@graph.glsl"
#include ":graph.glsl"
#if DRAW_OUTLINE == 0 && DRAW_HIGHLIGHT == 0
#error __disablestage
#endif
void main() {
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
vec4
a0 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 0)), 0),
a1 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 1)), 0),
a2 = texelFetch(tex, ivec2((gl_FragCoord.x + 0), (gl_FragCoord.y + 1)), 0),
a3 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 0)), 0),
a4 = texelFetch(tex, ivec2((gl_FragCoord.x - 1), (gl_FragCoord.y - 0)), 0),
a5 = texelFetch(tex, ivec2((gl_FragCoord.x - 1), (gl_FragCoord.y - 1)), 0),
a6 = texelFetch(tex, ivec2((gl_FragCoord.x - 0), (gl_FragCoord.y - 1)), 0),
a7 = texelFetch(tex, ivec2((gl_FragCoord.x - 1), (gl_FragCoord.y - 0)), 0);
vec4 avg = (a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7) / 8.0;
if (avg.a > 0){
if (fragment.a <= 0) {
/* outline */
#if DRAW_OUTLINE > 0
fragment = OUTLINE;
#endif
} else if (avg.a < 1) {
/* creates a highlight along the edge of the spectrum */
#if DRAW_HIGHLIGHT > 0
fragment.rgb *= avg.a * 2;
#endif
}
}
}

104
shaders/glava/graph/3.frag Normal file
View File

@@ -0,0 +1,104 @@
in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen; /* screen dimensions */
#request uniform "prev" tex
uniform sampler2D tex; /* screen texture */
out vec4 fragment; /* output */
#include "@graph.glsl"
#include ":graph.glsl"
#if ANTI_ALIAS == 0
#error __disablestage
#endif
/* Moves toward the border of the graph, gives the
y coordinate of the last colored pixel */
float get_col_height_up(float x, float oy) {
float y = oy;
#if INVERT > 0
while (y >= 0) {
#else
while (y < screen.y) {
#endif
vec4 f = texelFetch(tex, ivec2(x, y), 0);
if (f.a <= 0) {
#if INVERT > 0
y += 1;
#else
y -= 1;
#endif
break;
}
#if INVERT > 0
y -= 1;
#else
y += 1;
#endif
}
return y;
}
/* Moves toward the base of the graph, gives the
y coordinate of the first colored pixel */
float get_col_height_down(float x, float oy) {
float y = oy;
#if INVERT > 0
while (y < screen.y) {
#else
while (y >= 0) {
#endif
vec4 f = texelFetch(tex, ivec2(x, y), 0);
if (f.a > 0) {
break;
}
#if INVERT > 0
y += 1;
#else
y -= 1;
#endif
}
return y;
}
void main() {
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
#if ANTI_ALIAS > 0
if (fragment.a <= 0) {
bool left_done = false;
float h2;
float a_fact = 0;
if (texelFetch(tex, ivec2(gl_FragCoord.x - 1, gl_FragCoord.y), 0).a > 0) {
float h1 = get_col_height_up(gl_FragCoord.x - 1, gl_FragCoord.y);
h2 = get_col_height_down(gl_FragCoord.x, gl_FragCoord.y);
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, h2), 0);
a_fact = clamp(abs((h1 - gl_FragCoord.y) / (h2 - h1)), 0.0, 1.0);
left_done = true;
}
if (texelFetch(tex, ivec2(gl_FragCoord.x + 1, gl_FragCoord.y), 0).a > 0) {
if (!left_done) {
h2 = get_col_height_down(gl_FragCoord.x, gl_FragCoord.y);
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, h2), 0);
}
float h3 = get_col_height_up(gl_FragCoord.x + 1, gl_FragCoord.y);
a_fact = max(a_fact, clamp(abs((h3 - gl_FragCoord.y) / (h2 - h3)), 0.0, 1.0));
}
fragment.a *= a_fact;
}
#endif
}

View File

@@ -0,0 +1,5 @@
#if ANTI_ALIAS == 0
#error __disablestage
#endif
#include ":util/premultiply.frag"

View File

@@ -4,22 +4,20 @@
/* center line thickness (pixels) */
#define C_LINE 2
/* outline color */
#define OUTLINE vec4(0.20, 0.20, 0.20, 1)
#define OUTLINE @bg:#333333
/* number of bars (use even values for best results) */
#define NBARS 180
#define NBARS 160
/* width (in pixels) of each bar*/
#define BAR_WIDTH 3.5
/* outline color */
#define BAR_OUTLINE OUTLINE
/* outline width (in pixels, set to 0 to disable outline drawing) */
#define BAR_OUTLINE_WIDTH 0
#define BAR_WIDTH 4.5
/* Amplify magnitude of the results each bar displays */
#define AMPLIFY 300
/* Bar color */
#define COLOR (vec4(0.8, 0.2, 0.2, 1) * ((d / 40) + 1))
/* How quickly the gradient transitions, in pixels */
#define GRADIENT 95
/* Bar color. This is a gradient by default. */
#define COLOR @fg:mix(#cc3333, #cca0a0, clamp(d / GRADIENT, 0, 1))
/* Angle (in radians) for how much to rotate the visualizer */
#define ROTATE (PI / 2)
/* Whether to switch left/right audio buffers */
/* Whether to swap left/right audio buffers, set to 1 to enable */
#define INVERT 0
/* Aliasing factors. Higher values mean more defined and jagged lines.
Note: aliasing does not have a notable impact on performance, but requires
@@ -27,9 +25,12 @@
the background. */
#define BAR_ALIAS_FACTOR 1.2
#define C_ALIAS_FACTOR 1.8
/* Offset (Y) of the visualization */
#define CENTER_OFFSET_Y 0
/* Offset (X) of the visualization */
#define CENTER_OFFSET_X 0
/* Gravity step, overrude frin `smooth_parameters.glsl` */
#request setgravitystep 5.0
/* Smoothing factor, override from `smooth_parameters.glsl` */
#request setsmoothfactor 0.02
/* (DEPRECATED) outline color */
#define BAR_OUTLINE OUTLINE
/* (DEPRECATED) outline width (in pixels, set to 0 to disable outline drawing) */
#define BAR_OUTLINE_WIDTH 0

View File

@@ -1,4 +1,4 @@
layout(pixel_center_integer) in vec4 gl_FragCoord;
in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen;
@@ -7,6 +7,7 @@ uniform ivec2 screen;
uniform int audio_sz;
#include ":util/smooth.glsl"
#include "@radial.glsl"
#include ":radial.glsl"
#request uniform "audio_l" audio_l
@@ -29,10 +30,10 @@ out vec4 fragment;
#define PI 3.14159265359
void main() {
#if XROOT > 0
#define APPLY_FRAG(f, c) f = vec4(f.rgb * f.a + c.rgb * (1 - f.a), max(c.a, f.a))
fragment.a = 0;
#if _USE_ALPHA > 0
#define APPLY_FRAG(f, c) f = vec4(f.rgb * f.a + c.rgb * (1 - clamp(f.a, 0, 1)), max(c.a, f.a))
fragment = #00000000;
#else
#define APPLY_FRAG(f, c) f = c
#endif
@@ -41,14 +42,14 @@ void main() {
Alpha layer blending is only applied when `xroot` transparency is enabled. */
float /* translate (x, y) to use (0, 0) as the center of the screen */
dx = gl_FragCoord.x - (screen.x / 2),
dy = gl_FragCoord.y - (screen.y / 2);
dx = gl_FragCoord.x - (screen.x / 2) + CENTER_OFFSET_X,
dy = gl_FragCoord.y - (screen.y / 2) + CENTER_OFFSET_Y;
float theta = atan(dy, dx); /* fragment angle with the center of the screen as the origin */
float d = sqrt((dx * dx) + (dy * dy)); /* distance */
if (d > C_RADIUS - (float(C_LINE) / 2.0F) && d < C_RADIUS + (float(C_LINE) / 2.0F)) {
fragment = OUTLINE;
#if XROOT > 0
fragment.a *= ((float(C_LINE) / 2.0F) - abs(d - C_RADIUS)) * C_ALIAS_FACTOR;
APPLY_FRAG(fragment, OUTLINE);
#if _USE_ALPHA > 0
fragment.a *= clamp(((C_LINE / 2) - abs(C_RADIUS - d)) * C_ALIAS_FACTOR, 0, 1);
#else
return; /* return immediately if there is no alpha blending available */
#endif
@@ -63,8 +64,9 @@ void main() {
float dir = mod(abs(idx), TWOPI); /* absolute position, [0, 2pi) */
if (dir > PI)
idx = -sign(idx) * (TWOPI - dir); /* Re-correct position values to [-pi, pi) */
if (INVERT > 0)
idx = -idx; /* Invert if needed */
#if INVERT == 0
idx = -idx; /* Invert if needed */
#endif
float pos = int(abs(idx) / section) / float(NBARS / 2); /* bar position, [0, 1) */
#define smooth_f(tex) smooth_audio(tex, audio_sz, pos) /* smooth function format */
float v;
@@ -73,7 +75,7 @@ void main() {
v *= AMPLIFY; /* amplify */
#undef smooth_f
/* offset to fragment distance from inner circle */
#if XROOT > 0
#if _USE_ALPHA > 0
#define ALIAS_FACTOR (((BAR_WIDTH / 2) - abs(ym)) * BAR_ALIAS_FACTOR)
d -= C_RADIUS; /* start bar overlapping the inner circle for blending */
#else
@@ -90,7 +92,7 @@ void main() {
#else
r = COLOR;
#endif
#if XROOT > 0
#if _USE_ALPHA > 0
r.a *= ALIAS_FACTOR;
#endif
APPLY_FRAG(fragment, r);
@@ -98,7 +100,7 @@ void main() {
}
#if BAR_OUTLINE_WIDTH > 0
if (d <= v) {
#if XROOT > 0
#if _USE_ALPHA > 0
vec4 r = BAR_OUTLINE;
r.a *= ALIAS_FACTOR;
APPLY_FRAG(fragment, r);

View File

@@ -0,0 +1 @@
#include ":util/premultiply.frag"

View File

@@ -15,23 +15,17 @@
See documentation for more details. */
#request mod bars
/* GLFW window hints */
/* Window hints */
#request setfloating false
#request setdecorated false
#request setdecorated true
#request setfocused false
#request setmaximized false
/* Force GLFW window geometry (locking the window in place),
useful for some pesky WMs that try to reposition the window
when embedding in the desktop. */
#request setforcegeometry false
/* Set window background opacity mode. Possible values are:
"native" - True transparency provided by the compositor.
Requires GLFW 3.3+ and an active compositor. Can
reduce performance on some systems, and will not
blend with the visualizer's alpha layer.
"native" - True transparency provided by the compositor. Can
reduce performance on some systems, depending on
the compositor used.
"xroot" - Maintain a copy of the root window's pixmap
(usually the desktop background) to provide a
@@ -40,18 +34,26 @@
Has very little performance impact.
"none" - Disable window opacity completely. */
#request setopacity "xroot"
#request setopacity "native"
/* Whether to average and mirror left and right audio input channels.
This may cause some modules to only render a single channel. */
#request setmirror false
/* OpenGL context and GLSL shader versions, do not change unless
you *absolutely* know what you are doing. */
#request setversion 3 3
#request setshaderversion 330
/* GLFW window title */
/* Window title */
#request settitle "GLava"
/* GLFW window geometry (x, y, width, height) */
#request setgeometry 0 0 400 600
/* Window geometry (x, y, width, height) */
#request setgeometry 0 0 800 600
/* Window background color (RGBA format).
Does not work with `setopacity "xroot"` */
#request setbg 00000000
/* (X11 only) EWMH Window type. Possible values are:
@@ -60,9 +62,12 @@
This will set _NET_WM_WINDOW_TYPE to _NET_WM_WINDOW_TYPE_(TYPE),
where (TYPE) is the one of the window types listed (after being
converted to uppercase). More information can be found at:
converted to uppercase).
https://standards.freedesktop.org/wm-spec/wm-spec-1.3.html#idm140130317606816
Alternatively, you can set this value to "!", which will cause
the window to be unmanaged. If this is set, then `addxwinstate`
will do nothing, but you can use "!+" and "!-" to stack on top
or below other windows.
*/
#request setxwintype "normal"
@@ -71,7 +76,7 @@
"modal", "sticky", "maximized_vert", "maximized_horz",
"shaded", "skip_taskbar", "skip_pager", "hidden", "fullscreen",
"above", "below", "demands_attention", "focused"
"above", "below", "demands_attention", "focused", "pinned"
This will add _NET_WM_STATE_(TYPE) atoms to _NET_WM_STATE,
where (TYPE) is one of the window states listed (after being
@@ -86,13 +91,25 @@
// #request addxwinstate "skip_taskbar"
// #request addxwinstate "skip_pager"
// #request addxwinstate "above"
// #request addxwinstate "pinned"
/* PulseAudio source. Can be a number or a name of an audio
sink or device to record from. Set to "auto" to use the
default output device. */
/* (X11 only) Use the XShape extension to support clicking through
the GLava window. Useful when you want to interact with other
desktop windows (icons, menus, desktop shells). Enabled by
default when GLava itself is a desktop window. */
#request setclickthrough false
/* Audio source
When the "pulseaudio" backend is set, this can be a number or
a name of an audio sink or device to record from. Set to "auto"
to use the default output device.
When the "fifo" backend is set, "auto" is interpreted as
"/tmp/mpd.fifo". Otherwise, a valid path should be provided. */
#request setsource "auto"
/* GLFW buffer swap interval (vsync), set to '0' to prevent
/* Buffer swap interval (vsync), set to '0' to prevent
waiting for refresh, '1' (or more) to wait for the specified
amount of frames. */
#request setswap 1
@@ -111,12 +128,21 @@
This will delay data output by one update frame, so it can
desync audio with visual effects on low UPS configs. */
#request setinterpolate true
#request setinterpolate false
/* Frame limiter, set to the frames per second (FPS) desired or
simple set to zero (or lower) to disable the frame limiter. */
simply set to zero (or lower) to disable the frame limiter. */
#request setframerate 0
/* Suspends rendering if a fullscreen window is focused while
GLava is still visible (ie. on another monitor). This prevents
rendering from interfering with other graphically intensive
tasks.
If GLava is minimized or completely obscured, it will not
render regardless of this option. */
#request setfullscreencheck false
/* Enable/disable printing framerate every second. 'FPS' stands
for 'Frames Per Second', and 'UPS' stands for 'Updates Per
Second'. Updates are performed when new data is submitted
@@ -169,9 +195,36 @@
Lower sample rates also can make output more choppy, when
not using interpolation. It's generally OK to leave this
value unless you have a strange PulseAudio configuration. */
value unless you have a strange PulseAudio configuration.
This option does nothing when using the "fifo" audio
backend. Instead, an ideal rate should be be configured
in the application generating the output. */
#request setsamplerate 22050
/* Enable GPU acceleration of the audio buffer's fourier transform.
This drastically reduces CPU usage, but should be avoided on
old integrated graphics hardware.
Enabling this also enables acceleration for post-FFT processing
effects, such as gravity, averaging, windowing, and interpolation. */
#request setaccelfft true
/* ** DEPRECATED **
Force window geometry (locking the window in place), useful
for some pesky WMs that try to reposition the window when
embedding in the desktop.
This routinely sends X11 events and should be avoided. */
#request setforcegeometry false
/* ** DEPRECATED **
Force window to be raised (focused in some WMs), useful for
WMs that have their own stacking order for desktop windows.
This routinely sends X11 events and should be avoided. */
#request setforceraised false
/* ** DEPRECATED **
Scale down the audio buffer before any operations are
performed on the data. Higher values are faster.

View File

@@ -13,10 +13,25 @@
- circular heavily rounded points
- sinusoidal rounded at both low and high weighted values
like a sine wave
- linear not rounded at all, just use linear distance
- linear not rounded at all; linear distance
*/
#define ROUND_FORMULA sinusoidal
/* The sampling mode for processing raw FFT input:
- average averages all the inputs in the sample range for
a given point. Produces smooth output, but peaks
are not well represented
- maximum obtains the best value from the closest peak in
the sample range. Very accurate peaks, but
output is jagged and sporadic.
- hybrid uses the results from both `average` and `maximum`
with the weight provided in `SAMPLE_HYBRID_WEIGHT` */
#define SAMPLE_MODE average
/* Weight should be provided in the range (0, 1). Higher values favour
averaged results. `hybrid` mode only. */
#define SAMPLE_HYBRID_WEIGHT 0.65
/* Factor used to scale frequencies. Lower values allows lower
frequencies to occupy more space. */
#define SAMPLE_SCALE 8
@@ -38,7 +53,7 @@
/* How many frames to queue and run through the average function.
Increasing this value will create latency between the audio and the
animation, but will make for much smoother results. */
#request setavgframes 6
#request setavgframes 5
/* Whether to window frames ran through the average function (new & old
frames are weighted less). This massively helps smoothing out

33
shaders/glava/test/1.frag Normal file
View File

@@ -0,0 +1,33 @@
/* Request transforms and basic uniforms to assert nothing here breaks */
#include ":util/smooth.glsl"
in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen;
#request uniform "audio_sz" audio_sz
uniform int audio_sz;
#request uniform "audio_l" audio_l
#request transform audio_l "window"
#request transform audio_l "fft"
#request transform audio_l "gravity"
#request transform audio_l "avg"
uniform sampler1D audio_l;
#request uniform "audio_r" audio_r
#request transform audio_r "window"
#request transform audio_r "fft"
#request transform audio_r "gravity"
#request transform audio_r "avg"
uniform sampler1D audio_r;
out vec4 fragment;
void main() {
float dummy_result0 = smooth_audio(audio_l, audio_sz, gl_FragCoord.x / float(screen.x));
float dummy_result1 = smooth_audio(audio_r, audio_sz, gl_FragCoord.x / float(screen.x));
fragment = vec4(1.0, 0, 0, float(1) / float(3));
}

12
shaders/glava/test/2.frag Normal file
View File

@@ -0,0 +1,12 @@
/* Pass the initial results to a dummy shader to assert that linking works correctly */
in vec4 gl_FragCoord;
#request uniform "prev" tex
uniform sampler2D tex; /* screen texture */
out vec4 fragment; /* output */
void main() {
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
}

View File

@@ -0,0 +1,2 @@
/* Assert that the premultiply step works */
#include ":util/premultiply.frag"

View File

@@ -0,0 +1,27 @@
#request mod test
#request setfloating false
#request setdecorated true
#request setfocused false
#request setmaximized false
#request setopacity "native"
#request setmirror false
#request setversion 3 3
#request setshaderversion 330
#request settitle "GLava"
#request setgeometry 0 0 640 640
#request setbg 00000000
#request setxwintype "desktop"
#request setclickthrough false
#request setsource "auto"
#request setswap 0
#request setinterpolate true
#request setframerate 0
#request setfullscreencheck false
#request setprintframes true
#request setsamplesize 1024
#request setbufsize 4096
#request setsamplerate 22050
#request setforcegeometry false
#request setforceraised false
#request setbufscale 1
#request settesteval 55000055

View File

@@ -0,0 +1,46 @@
out vec4 fragment;
in vec4 gl_FragCoord;
#include ":util/common.glsl"
/*
This averaging shader uses compile-time loop generation to ensure two things:
- We can avoid requiring GL 4.3 features to dynamically index texture arrays
- We ensure no branching occurs in this shader for optimial performance.
The alternative is requiring the GLSL compiler to determine that a loop for
texture array indexes (which must be determined at compile-time in 3.3) can be
expanded if the bounds are constant. This is somewhat vendor-specific so GLava
provides a special `#expand` macro to solve this problem in the preprocessing
stage.
*/
#define SAMPLER(I) uniform sampler1D t##I;
#expand SAMPLER _AVG_FRAMES
#define WIN_FUNC window_frame
void main() {
float r = 0;
/* Disable windowing for two frames (distorts results) */
#if _AVG_FRAMES == 2
#define _AVG_WINDOW 0
#endif
/* Use 'shallow' windowing for 3 frames to ensure the first & last
frames have a reasonable amount of weight */
#if _AVG_FRAMES == 3
#define WIN_FUNC window_shallow
#endif
#if _AVG_WINDOW == 0
#define F(I) r += texelFetch(t##I, int(gl_FragCoord.x), 0).r
#else
#define F(I) r += window(I, _AVG_FRAMES - 1) * texelFetch(t##I, int(gl_FragCoord.x), 0).r
#endif
#expand F _AVG_FRAMES
fragment.r = r / _AVG_FRAMES;
}

View File

@@ -0,0 +1,23 @@
#ifndef _COMMON_GLSL
#define _COMMON_GLSL
#ifndef TWOPI
#define TWOPI 6.28318530718
#endif
#ifndef PI
#define PI 3.14159265359
#endif
/* Window value t that resides in range [0, sz] */
#define window(t, sz) (0.53836 - (0.46164 * cos(TWOPI * t / sz)))
#define window_frame(t, sz) (0.6 - (0.4 * cos(TWOPI * t / sz)))
#define window_shallow(t, sz) (0.7 - (0.3 * cos(TWOPI * t / sz)))
/* Do nothing (used as an option for configuration) */
#define linear(x) (x)
/* Take value x that scales linearly between [0, 1) and return its sinusoidal curve */
#define sinusoidal(x) ((0.5 * sin((PI * (x)) - (PI / 2))) + 0.5)
/* Take value x that scales linearly between [0, 1) and return its circlar curve */
#define circular(x) sqrt(1 - (((x) - 1) * ((x) - 1)))
#endif

View File

@@ -0,0 +1,842 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if defined(FFT_FP16) && defined(GL_ES)
precision mediump float;
#endif
#define BINDING_SSBO_IN 0
#define BINDING_SSBO_OUT 1
#define BINDING_SSBO_AUX 2
#define BINDING_UBO 3
#define BINDING_TEXTURE0 4
#define BINDING_TEXTURE1 5
#define BINDING_IMAGE 6
layout(std140, binding = BINDING_UBO) uniform UBO
{
uvec4 p_stride_padding;
vec4 texture_offset_scale;
} constant_data;
#define uStride constant_data.p_stride_padding.y
// cfloat is the "generic" type used to hold complex data.
// GLFFT supports vec2, vec4 and "vec8" for its complex data
// to be able to work on 1, 2 and 4 complex values in a single vector.
// FFT_VEC2, FFT_VEC4, FFT_VEC8 defines which type we're using.
// The shaders are compiled on-demand.
// FP16 values are packed as 2xfp16 in a uint.
// packHalf2x16 and unpackHalf2x16 are used to bitcast between these formats.
// The complex number format is (real, imag, real, imag, ...) in an interleaved fashion.
// For complex-to-real or real-to-complex transforms, we consider two adjacent real samples to be a complex number as-is.
// Separate "resolve" passes are added to make the transform correct.
#if defined(FFT_VEC2)
#define cfloat vec2
#define cfloat_buffer_fp16 uint
#elif defined(FFT_VEC4)
#define cfloat vec4
#define cfloat_buffer_fp16 uvec2
#elif defined(FFT_VEC8)
#if !defined(FFT_INPUT_FP16) || !defined(FFT_OUTPUT_FP16) || !defined(FFT_FP16)
#error FFT_VEC8 must use FP16 everywhere.
#endif
#define cfloat uvec4
#define cfloat_buffer_fp16 uvec4
#else
#error FFT_VEC2, FFT_VEC4 or FFT_VEC8 must be defined.
#endif
#ifdef FFT_INPUT_FP16
#define cfloat_buffer_in cfloat_buffer_fp16
#else
#define cfloat_buffer_in cfloat
#endif
#ifdef FFT_OUTPUT_FP16
#define cfloat_buffer_out cfloat_buffer_fp16
#else
#define cfloat_buffer_out cfloat
#endif
// Normally this would be sqrt(1 / radix), but we'd have to apply normalization
// for every pass instead of just half of them. Also, 1 / 2^n is "lossless" in FP math.
#ifdef FFT_NORMALIZE
#define FFT_NORM_FACTOR (1.0 / float(FFT_RADIX))
#endif
// FFT_CVECTOR_SIZE defines an interleaving stride for the first pass.
// The first FFT pass with stockham autosort needs to do some shuffling around if we're processing
// more than one complex value per vector.
// This is only needed for horizontal transforms since we vectorize horizontally and different elements
// in the vector are from different transforms when we do vertical transforms.
#if defined(FFT_P1) && !defined(FFT_DUAL) && defined(FFT_HORIZ) && defined(FFT_VEC8)
#define FFT_CVECTOR_SIZE 4
#elif defined(FFT_P1) && ((!defined(FFT_DUAL) && defined(FFT_HORIZ) && defined(FFT_VEC4)) || (defined(FFT_DUAL) && defined(FFT_HORIZ) && defined(FFT_VEC8)))
#define FFT_CVECTOR_SIZE 2
#else
#define FFT_CVECTOR_SIZE 1
#endif
#ifdef GL_ES
#define FFT_HIGHP highp
#else
#define FFT_HIGHP
#endif
#ifdef FFT_VEC8
// Currently unlikely to be useful.
uvec4 PADD(uvec4 a, uvec4 b)
{
return uvec4(
packHalf2x16(unpackHalf2x16(a.x) + unpackHalf2x16(b.x)),
packHalf2x16(unpackHalf2x16(a.y) + unpackHalf2x16(b.y)),
packHalf2x16(unpackHalf2x16(a.z) + unpackHalf2x16(b.z)),
packHalf2x16(unpackHalf2x16(a.w) + unpackHalf2x16(b.w)));
}
uvec4 PSUB(uvec4 a, uvec4 b)
{
return uvec4(
packHalf2x16(unpackHalf2x16(a.x) - unpackHalf2x16(b.x)),
packHalf2x16(unpackHalf2x16(a.y) - unpackHalf2x16(b.y)),
packHalf2x16(unpackHalf2x16(a.z) - unpackHalf2x16(b.z)),
packHalf2x16(unpackHalf2x16(a.w) - unpackHalf2x16(b.w)));
}
uvec4 PMUL(uvec4 a, uvec4 b)
{
return uvec4(
packHalf2x16(unpackHalf2x16(a.x) * unpackHalf2x16(b.x)),
packHalf2x16(unpackHalf2x16(a.y) * unpackHalf2x16(b.y)),
packHalf2x16(unpackHalf2x16(a.z) * unpackHalf2x16(b.z)),
packHalf2x16(unpackHalf2x16(a.w) * unpackHalf2x16(b.w)));
}
uvec4 CONJ_SWIZZLE(uvec4 v)
{
return uvec4(
packHalf2x16(unpackHalf2x16(v.x).yx),
packHalf2x16(unpackHalf2x16(v.y).yx),
packHalf2x16(unpackHalf2x16(v.z).yx),
packHalf2x16(unpackHalf2x16(v.w).yx));
}
uvec4 LDUP_SWIZZLE(uvec4 v)
{
return uvec4(
packHalf2x16(unpackHalf2x16(v.x).xx),
packHalf2x16(unpackHalf2x16(v.y).xx),
packHalf2x16(unpackHalf2x16(v.z).xx),
packHalf2x16(unpackHalf2x16(v.w).xx));
}
uvec4 HDUP_SWIZZLE(uvec4 v)
{
return uvec4(
packHalf2x16(unpackHalf2x16(v.x).yy),
packHalf2x16(unpackHalf2x16(v.y).yy),
packHalf2x16(unpackHalf2x16(v.z).yy),
packHalf2x16(unpackHalf2x16(v.w).yy));
}
// Sign-flip. Works for the cases we're interested in.
uvec4 cmul_minus_j(uvec4 v)
{
return uvec4(0x80000000u) ^ CONJ_SWIZZLE(v);
}
uvec4 cmul_plus_j(uvec4 v)
{
return uvec4(0x00008000u) ^ CONJ_SWIZZLE(v);
}
uvec4 cmul(uvec4 a, uvec4 b)
{
uvec4 r3 = CONJ_SWIZZLE(a);
uvec4 r1 = LDUP_SWIZZLE(b);
uvec4 R0 = PMUL(a, r1);
uvec4 r2 = HDUP_SWIZZLE(b);
uvec4 R1 = PMUL(r2, r3);
return PADD(R0, uvec4(0x8000u) ^ R1);
}
void butterfly(inout uvec4 a, inout uvec4 b, uvec4 w)
{
uvec4 t = cmul(b, w);
b = PSUB(a, t);
a = PADD(a, t);
}
void butterfly(inout uvec4 a, inout uvec4 b, vec4 w)
{
uvec4 t = cmul(b, uvec2(packHalf2x16(w.xy), packHalf2x16(w.zw)).xxyy);
b = PSUB(a, t);
a = PADD(a, t);
}
void butterfly(inout uvec4 a, inout uvec4 b, vec2 w)
{
uvec4 t = cmul(b, uvec4(packHalf2x16(w)));
b = PSUB(a, t);
a = PADD(a, t);
}
void butterfly_p1(inout uvec4 a, inout uvec4 b)
{
uvec4 t = b;
b = PSUB(a, t);
a = PADD(a, t);
}
void butterfly_p1_minus_j(inout uvec4 a, inout uvec4 b)
{
uvec4 t = b;
b = uvec4(0x80000000u) ^ (PSUB(CONJ_SWIZZLE(a), CONJ_SWIZZLE(t)));
a = PADD(a, t);
}
void butterfly_p1_plus_j(inout uvec4 a, inout uvec4 b)
{
uvec4 t = b;
b = uvec4(0x00008000u) ^ (PSUB(CONJ_SWIZZLE(a), CONJ_SWIZZLE(t)));
a = PADD(a, t);
}
#endif
// Complex multiply.
vec4 cmul(vec4 a, vec4 b)
{
vec4 r3 = a.yxwz;
vec4 r1 = b.xxzz;
vec4 R0 = a * r1;
vec4 r2 = b.yyww;
vec4 R1 = r2 * r3;
return R0 + vec4(-R1.x, R1.y, -R1.z, R1.w);
}
vec2 cmul(vec2 a, vec2 b)
{
vec2 r3 = a.yx;
vec2 r1 = b.xx;
vec2 R0 = a * r1;
vec2 r2 = b.yy;
vec2 R1 = r2 * r3;
return R0 + vec2(-R1.x, R1.y);
}
#ifdef FFT_INPUT_TEXTURE
#ifndef FFT_P1
#error Input texture can only be used when P == 1.
#endif
#ifdef GL_ES
#if defined(FFT_INPUT_FP16) || defined(FFT_FP16)
precision mediump sampler2D;
#else
precision highp sampler2D;
#endif
#endif
#define uTexelOffset constant_data.texture_offset_scale.xy
#define uTexelScale constant_data.texture_offset_scale.zw
layout(binding = BINDING_TEXTURE0) uniform sampler2D uTexture;
#ifdef FFT_CONVOLVE
layout(binding = BINDING_TEXTURE1) uniform sampler2D uTexture2;
#endif
cfloat load_texture(sampler2D sampler, uvec2 coord)
{
FFT_HIGHP vec2 uv = vec2(coord) * uTexelScale + uTexelOffset;
// Quite messy, this :)
#if defined(FFT_VEC8)
#if defined(FFT_INPUT_REAL)
return uvec4(
packHalf2x16(vec2(textureLodOffset(sampler, uv, 0.0, ivec2(0, 0)).x, textureLodOffset(sampler, uv, 0.0, ivec2(1, 0)).x)),
packHalf2x16(vec2(textureLodOffset(sampler, uv, 0.0, ivec2(2, 0)).x, textureLodOffset(sampler, uv, 0.0, ivec2(3, 0)).x)),
packHalf2x16(vec2(textureLodOffset(sampler, uv, 0.0, ivec2(4, 0)).x, textureLodOffset(sampler, uv, 0.0, ivec2(5, 0)).x)),
packHalf2x16(vec2(textureLodOffset(sampler, uv, 0.0, ivec2(6, 0)).x, textureLodOffset(sampler, uv, 0.0, ivec2(7, 0)).x)));
#elif defined(FFT_DUAL)
vec4 c0 = textureLodOffset(sampler, uv, 0.0, ivec2(0, 0));
vec4 c1 = textureLodOffset(sampler, uv, 0.0, ivec2(1, 0));
return uvec4(packHalf2x16(c0.xy), packHalf2x16(c0.zw), packHalf2x16(c1.xy), packHalf2x16(c1.zw));
#else
return uvec4(
packHalf2x16(textureLodOffset(sampler, uv, 0.0, ivec2(0, 0)).xy),
packHalf2x16(textureLodOffset(sampler, uv, 0.0, ivec2(1, 0)).xy),
packHalf2x16(textureLodOffset(sampler, uv, 0.0, ivec2(2, 0)).xy),
packHalf2x16(textureLodOffset(sampler, uv, 0.0, ivec2(3, 0)).xy));
#endif
#elif defined(FFT_VEC4)
#if defined(FFT_INPUT_REAL)
return vec4(
textureLodOffset(sampler, uv, 0.0, ivec2(0, 0)).x,
textureLodOffset(sampler, uv, 0.0, ivec2(1, 0)).x,
textureLodOffset(sampler, uv, 0.0, ivec2(2, 0)).x,
textureLodOffset(sampler, uv, 0.0, ivec2(3, 0)).x);
#elif defined(FFT_DUAL)
return textureLod(sampler, uv, 0.0);
#else
return vec4(
textureLodOffset(sampler, uv, 0.0, ivec2(0, 0)).xy,
textureLodOffset(sampler, uv, 0.0, ivec2(1, 0)).xy);
#endif
#elif defined(FFT_VEC2)
#if defined(FFT_INPUT_REAL)
return vec2(
textureLodOffset(sampler, uv, 0.0, ivec2(0, 0)).x,
textureLodOffset(sampler, uv, 0.0, ivec2(1, 0)).x);
#else
return textureLod(sampler, uv, 0.0).xy;
#endif
#endif
}
cfloat load_texture(uvec2 coord)
{
#ifdef FFT_CONVOLVE
// Convolution in frequency domain is multiplication.
cfloat c0 = load_texture(uTexture, coord);
cfloat c1 = load_texture(uTexture2, coord);
return cmul(c0, c1);
#else
return load_texture(uTexture, coord);
#endif
}
// Implement a dummy load_global, or we have to #ifdef out lots of dead code elsewhere.
#ifdef FFT_VEC8
cfloat load_global(uint offset)
{
return cfloat(0u);
}
#else
cfloat load_global(uint offset)
{
return cfloat(0.0);
}
#endif
#else
layout(std430, binding = BINDING_SSBO_IN) readonly buffer Block
{
cfloat_buffer_in data[];
} fft_in;
#ifdef FFT_CONVOLVE
layout(std430, binding = BINDING_SSBO_AUX) readonly buffer Block2
{
cfloat_buffer_in data[];
} fft_in2;
cfloat load_global(uint offset)
{
// Convolution in frequency domain is multiplication.
#if defined(FFT_INPUT_FP16) && defined(FFT_VEC2)
return cmul(unpackHalf2x16(fft_in.data[offset]), unpackHalf2x16(fft_in2.data[offset]));
#elif defined(FFT_INPUT_FP16) && defined(FFT_VEC4)
uvec2 data = fft_in.data[offset];
uvec2 data2 = fft_in2.data[offset];
return cmul(vec4(unpackHalf2x16(data.x), unpackHalf2x16(data.y)), vec4(unpackHalf2x16(data2.x), unpackHalf2x16(data2.y)));
#else
return cmul(fft_in.data[offset], fft_in2.data[offset]);
#endif
}
#else
cfloat load_global(uint offset)
{
#if defined(FFT_INPUT_FP16) && defined(FFT_VEC2)
return unpackHalf2x16(fft_in.data[offset]);
#elif defined(FFT_INPUT_FP16) && defined(FFT_VEC4)
uvec2 data = fft_in.data[offset];
return vec4(unpackHalf2x16(data.x), unpackHalf2x16(data.y));
#else
return fft_in.data[offset];
#endif
}
#endif
#endif
#ifndef FFT_OUTPUT_IMAGE
layout(std430, binding = BINDING_SSBO_OUT) writeonly buffer BlockOut
{
cfloat_buffer_out data[];
} fft_out;
void store_global(uint offset, cfloat v)
{
#ifdef FFT_NORM_FACTOR
#ifdef FFT_VEC8
v = PMUL(uvec4(packHalf2x16(vec2(FFT_NORM_FACTOR))), v);
#else
v *= FFT_NORM_FACTOR;
#endif
#endif
#if defined(FFT_OUTPUT_FP16) && defined(FFT_VEC2)
fft_out.data[offset] = packHalf2x16(v);
#elif defined(FFT_OUTPUT_FP16) && defined(FFT_VEC4)
fft_out.data[offset] = uvec2(packHalf2x16(v.xy), packHalf2x16(v.zw));
#else
fft_out.data[offset] = v;
#endif
}
#endif
#ifdef FFT_OUTPUT_IMAGE
#ifdef GL_ES
#ifdef FFT_OUTPUT_REAL
precision highp image2D;
#else
precision mediump image2D;
#endif
precision highp uimage2D;
#endif
//#ifdef FFT_P1
//#error FFT_OUTPUT_IMAGE is not supported in first pass.
//#endif
// Currently, GLFFT only supports outputing to "fixed" formats like these.
// Should be possible to add options for this to at least choose between FP16/FP32 output,
// and maybe rgba8_unorm for FFT_DUAL case.
#if defined(FFT_DUAL)
layout(rgba16f, binding = BINDING_IMAGE) uniform writeonly image2D uImage;
#elif defined(FFT_OUTPUT_REAL)
layout(r32f, binding = BINDING_IMAGE) uniform writeonly image2D uImage;
#else
// GLES 3.1 doesn't support rg16f layout for some reason, so work around it ...
layout(r32ui, binding = BINDING_IMAGE) uniform writeonly uimage2D uImage;
#endif
void store(ivec2 coord, vec4 value)
{
#ifdef FFT_NORM_FACTOR
value *= FFT_NORM_FACTOR;
#endif
#if defined(FFT_DUAL)
imageStore(uImage, coord, value);
#elif defined(FFT_HORIZ)
#ifdef FFT_OUTPUT_REAL
imageStore(uImage, coord * ivec2(2, 1) + ivec2(0, 0), value.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(1, 0), value.yyyy);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(2, 0), value.zzzz);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(3, 0), value.wwww);
#else
imageStore(uImage, coord + ivec2(0, 0), uvec4(packHalf2x16(value.xy)));
imageStore(uImage, coord + ivec2(1, 0), uvec4(packHalf2x16(value.zw)));
#endif
#elif defined(FFT_VERT)
#ifdef FFT_OUTPUT_REAL
imageStore(uImage, coord * ivec2(4, 1) + ivec2(0, 0), value.xxxx);
imageStore(uImage, coord * ivec2(4, 1) + ivec2(1, 0), value.yyyy);
imageStore(uImage, coord * ivec2(4, 1) + ivec2(2, 0), value.zzzz);
imageStore(uImage, coord * ivec2(4, 1) + ivec2(3, 0), value.wwww);
#else
imageStore(uImage, coord * ivec2(2, 1) + ivec2(0, 0), uvec4(packHalf2x16(value.xy)));
imageStore(uImage, coord * ivec2(2, 1) + ivec2(1, 0), uvec4(packHalf2x16(value.zw)));
#endif
#else
#error Inconsistent defines.
#endif
}
#ifndef FFT_DUAL
void store(ivec2 coord, vec2 value)
{
#ifdef FFT_NORM_FACTOR
value *= FFT_NORM_FACTOR;
#endif
#if defined(FFT_HORIZ)
#ifdef FFT_OUTPUT_REAL
imageStore(uImage, coord * ivec2(2, 1) + ivec2(0, 0), value.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(1, 0), value.yyyy);
#else
imageStore(uImage, coord, uvec4(packHalf2x16(value.xy)));
#endif
#elif defined(FFT_VERT)
#ifdef FFT_OUTPUT_REAL
imageStore(uImage, coord * ivec2(2, 1) + ivec2(0, 0), value.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(1, 0), value.yyyy);
#else
imageStore(uImage, coord, uvec4(packHalf2x16(value.xy)));
#endif
#else
#error Inconsistent defines.
#endif
}
#endif
#ifdef FFT_VEC8
void store(ivec2 coord, uvec4 value)
{
#ifdef FFT_NORM_FACTOR
value = PMUL(value, uvec4(packHalf2x16(vec2(FFT_NORM_FACTOR))));
#endif
#if defined(FFT_DUAL)
#if defined(FFT_HORIZ)
imageStore(uImage, coord + ivec2(0, 0), vec4(unpackHalf2x16(value.x), unpackHalf2x16(value.y)));
imageStore(uImage, coord + ivec2(1, 0), vec4(unpackHalf2x16(value.z), unpackHalf2x16(value.w)));
#else
imageStore(uImage, coord * ivec2(2, 1) + ivec2(0, 0), vec4(unpackHalf2x16(value.x), unpackHalf2x16(value.y)));
imageStore(uImage, coord * ivec2(2, 1) + ivec2(1, 0), vec4(unpackHalf2x16(value.z), unpackHalf2x16(value.w)));
#endif
#elif defined(FFT_HORIZ)
#ifdef FFT_OUTPUT_REAL
vec2 value0 = unpackHalf2x16(value.x);
vec2 value1 = unpackHalf2x16(value.y);
vec2 value2 = unpackHalf2x16(value.z);
vec2 value3 = unpackHalf2x16(value.w);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(0, 0), value0.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(1, 0), value0.yyyy);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(2, 0), value1.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(3, 0), value1.yyyy);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(4, 0), value2.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(5, 0), value2.yyyy);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(6, 0), value3.xxxx);
imageStore(uImage, coord * ivec2(2, 1) + ivec2(7, 0), value3.yyyy);
#else
imageStore(uImage, coord + ivec2(0, 0), value.xxxx);
imageStore(uImage, coord + ivec2(1, 0), value.yyyy);
imageStore(uImage, coord + ivec2(2, 0), value.zzzz);
imageStore(uImage, coord + ivec2(3, 0), value.wwww);
#endif
#elif defined(FFT_VERT)
#ifdef FFT_OUTPUT_REAL
vec2 value0 = unpackHalf2x16(value.x);
vec2 value1 = unpackHalf2x16(value.y);
vec2 value2 = unpackHalf2x16(value.z);
vec2 value3 = unpackHalf2x16(value.w);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(0, 0), value0.xxxx);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(1, 0), value0.yyyy);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(2, 0), value1.xxxx);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(3, 0), value1.yyyy);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(4, 0), value2.xxxx);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(5, 0), value2.yyyy);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(6, 0), value3.xxxx);
imageStore(uImage, coord * ivec2(8, 1) + ivec2(7, 0), value3.yyyy);
#else
imageStore(uImage, coord * ivec2(4, 1) + ivec2(0, 0), value.xxxx);
imageStore(uImage, coord * ivec2(4, 1) + ivec2(1, 0), value.yyyy);
imageStore(uImage, coord * ivec2(4, 1) + ivec2(2, 0), value.zzzz);
imageStore(uImage, coord * ivec2(4, 1) + ivec2(3, 0), value.wwww);
#endif
#else
#error Inconsistent defines.
#endif
}
#endif
#endif
#define PI 3.14159265359
#define SQRT_1_2 0.70710678118
#ifdef FFT_INVERSE
#define PI_DIR (+PI)
#else
#define PI_DIR (-PI)
#endif
// Some GLES implementations have lower trancendental precision than desired which
// significantly affects the overall FFT precision.
// For these implementations it might make sense to add a LUT UBO with twiddle factors,
// which can be used here.
// 4-component FP16 twiddles, pack in uvec4.
#if !defined(FFT_DUAL) && defined(FFT_HORIZ) && defined(FFT_VEC8)
#define FFT_OUTPUT_STEP 4u
#define FFT_OUTPUT_SHIFT 2u
#define ctwiddle uvec4
ctwiddle twiddle(uint k, uint p)
{
// Trancendentals should always be done in highp.
FFT_HIGHP vec4 angles = PI_DIR * (float(k) + vec4(0.0, 1.0, 2.0, 3.0)) / float(p);
FFT_HIGHP vec4 cos_a = cos(angles);
FFT_HIGHP vec4 sin_a = sin(angles);
return ctwiddle(
packHalf2x16(vec2(cos_a.x, sin_a.x)),
packHalf2x16(vec2(cos_a.y, sin_a.y)),
packHalf2x16(vec2(cos_a.z, sin_a.z)),
packHalf2x16(vec2(cos_a.w, sin_a.w)));
}
#ifdef FFT_INVERSE
#define TWIDDLE_1_8 (uvec4(packHalf2x16(vec2(+SQRT_1_2, +SQRT_1_2))))
#define TWIDDLE_3_8 (uvec4(packHalf2x16(vec2(-SQRT_1_2, +SQRT_1_2))))
#else
#define TWIDDLE_1_8 (uvec4(packHalf2x16(vec2(+SQRT_1_2, -SQRT_1_2))))
#define TWIDDLE_3_8 (uvec4(packHalf2x16(vec2(-SQRT_1_2, -SQRT_1_2))))
#endif
// 2-component twiddles, pack in vec4.
#elif (!defined(FFT_DUAL) && defined(FFT_HORIZ) && defined(FFT_VEC4)) || (defined(FFT_DUAL) && defined(FFT_HORIZ) && defined(FFT_VEC8))
#define FFT_OUTPUT_STEP 2u
#define FFT_OUTPUT_SHIFT 1u
#define ctwiddle vec4
ctwiddle twiddle(uint k, uint p)
{
// Trancendentals should always be done in highp.
FFT_HIGHP vec2 angles = PI_DIR * (float(k) + vec2(0.0, 1.0)) / float(p);
FFT_HIGHP vec2 cos_a = cos(angles);
FFT_HIGHP vec2 sin_a = sin(angles);
return ctwiddle(cos_a.x, sin_a.x, cos_a.y, sin_a.y);
}
#ifdef FFT_INVERSE
#define TWIDDLE_1_8 (vec2(+SQRT_1_2, +SQRT_1_2).xyxy)
#define TWIDDLE_3_8 (vec2(-SQRT_1_2, +SQRT_1_2).xyxy)
#else
#define TWIDDLE_1_8 (vec2(+SQRT_1_2, -SQRT_1_2).xyxy)
#define TWIDDLE_3_8 (vec2(-SQRT_1_2, -SQRT_1_2).xyxy)
#endif
// 1-component twiddle, pack in vec2.
#else
#define FFT_OUTPUT_STEP 1u
#define FFT_OUTPUT_SHIFT 0u
#define ctwiddle vec2
ctwiddle twiddle(uint k, uint p)
{
// Trancendentals should always be done in highp.
FFT_HIGHP float angle = PI_DIR * float(k) / float(p);
return ctwiddle(cos(angle), sin(angle));
}
#ifdef FFT_INVERSE
#define TWIDDLE_1_8 (vec2(+SQRT_1_2, +SQRT_1_2))
#define TWIDDLE_3_8 (vec2(-SQRT_1_2, +SQRT_1_2))
#else
#define TWIDDLE_1_8 (vec2(+SQRT_1_2, -SQRT_1_2))
#define TWIDDLE_3_8 (vec2(-SQRT_1_2, -SQRT_1_2))
#endif
#endif
// Complex multiply by v * -j. Trivial case which can avoid mul/add.
vec4 cmul_minus_j(vec4 v)
{
return vec4(v.y, -v.x, v.w, -v.z);
}
vec2 cmul_minus_j(vec2 v)
{
return vec2(v.y, -v.x);
}
// Complex multiply by v * +j. Trivial case which can avoid mul/add.
vec4 cmul_plus_j(vec4 v)
{
return vec4(-v.y, v.x, -v.w, v.z);
}
vec2 cmul_plus_j(vec2 v)
{
return vec2(-v.y, v.x);
}
#ifdef FFT_INVERSE
#define cmul_dir_j(v) cmul_plus_j(v)
#else
#define cmul_dir_j(v) cmul_minus_j(v)
#endif
// Calculate an in-place butterfly with twiddle factors.
// a ----------- a + wb
// \ /
// \ /
// X
// / \
// / \
// w * b ------- a - wb
//
void butterfly(inout vec4 a, inout vec4 b, vec4 w)
{
vec4 t = cmul(b, w);
b = a - t;
a = a + t;
}
// Computes butterflies, but the twiddle factors for the two butterflies are
// identical.
void butterfly(inout vec4 a, inout vec4 b, vec2 w)
{
butterfly(a, b, w.xyxy);
}
void butterfly(inout vec2 a, inout vec2 b, vec2 w)
{
vec2 t = cmul(b, w);
b = a - t;
a = a + t;
}
// First pass butterfly, special case where w = 1.
void butterfly_p1(inout vec4 a, inout vec4 b)
{
vec4 t = b;
b = a - t;
a = a + t;
}
// First pass butterfly, but also multiply in a twiddle factor of -j to b afterwards.
// Used in P == 1 transforms for radix-4, radix-8 etc.
void butterfly_p1_minus_j(inout vec4 a, inout vec4 b)
{
vec4 t = b;
b = vec4(1.0, -1.0, 1.0, -1.0) * (a.yxwz - t.yxwz);
a = a + t;
}
void butterfly_p1_plus_j(inout vec4 a, inout vec4 b)
{
vec4 t = b;
b = vec4(-1.0, 1.0, -1.0, 1.0) * (a.yxwz - t.yxwz);
a = a + t;
}
void butterfly_p1(inout vec2 a, inout vec2 b)
{
vec2 t = b;
b = a - t;
a = a + t;
}
void butterfly_p1_minus_j(inout vec2 a, inout vec2 b)
{
vec2 t = b;
b = vec2(1.0, -1.0) * (a.yx - t.yx);
a = a + t;
}
void butterfly_p1_plus_j(inout vec2 a, inout vec2 b)
{
vec2 t = b;
b = vec2(-1.0, 1.0) * (a.yx - t.yx);
a = a + t;
}
#ifdef FFT_INVERSE
#define butterfly_p1_dir_j(a, b) butterfly_p1_plus_j(a, b)
#else
#define butterfly_p1_dir_j(a, b) butterfly_p1_minus_j(a, b)
#endif
#ifdef FFT_RESOLVE_REAL_TO_COMPLEX
vec2 r2c_twiddle(uint i, uint p)
{
vec2 w = -twiddle(i, p);
return vec2(-w.y, w.x);
}
// See http://www.engineeringproductivitytools.com/stuff/T0001/PT10.HTM for
// how the real-to-complex and complex-to-real resolve passes work.
// The final real-to-complex transform pass is done by extracting two interleaved FFTs by conjugate symmetry.
// If we have a real sequence:
// (r0, r1, r2, r3, r4, ...), we merge two adjacent real values to a sequence of complex numbers.
// We take the FFT of this complex sequence as normal.
// What we end up with really is:
// FFT((r0, r2, r4, r6, ...)) + FFT(j * (r1, r3, r5, r7, ...)).
// If we know the individual FFTs of the even and the odds we can complete the FFT by a single decimation-in-frequency stage.
// By conjugate symmetry, we can extract the even and odd FFTs and complex our transform.
// Complex-to-real is just the same thing, but in reverse.
void FFT_real_to_complex(uvec2 i)
{
uint stride = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * stride;
if (i.x == 0u)
{
#ifdef FFT_INPUT_TEXTURE
vec2 x = load_texture(i);
#else
vec2 x = load_global(offset);
#endif
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(i), vec2(x.x + x.y, 0.0));
store(ivec2(i) + ivec2(stride, 0), vec2(x.x - x.y, 0.0));
#else
store_global(2u * offset, vec2(x.x + x.y, 0.0));
store_global(2u * offset + stride, vec2(x.x - x.y, 0.0));
#endif
}
else
{
#ifdef FFT_INPUT_TEXTURE
vec2 a = load_texture(i);
vec2 b = load_texture(uvec2(stride - i.x, i.y));
#else
vec2 a = load_global(offset + i.x);
vec2 b = load_global(offset + stride - i.x);
#endif
b = vec2(b.x, -b.y);
vec2 fe = a + b;
vec2 fo = cmul(a - b, r2c_twiddle(i.x, stride));
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(i), 0.5 * (fe + fo));
#else
store_global(2u * offset + i.x, 0.5 * (fe + fo));
#endif
}
}
#endif
#ifdef FFT_RESOLVE_COMPLEX_TO_REAL
vec2 c2r_twiddle(uint i, uint p)
{
vec2 w = twiddle(i, p);
return vec2(-w.y, w.x);
}
void FFT_complex_to_real(uvec2 i)
{
uint stride = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * stride;
#ifdef FFT_INPUT_TEXTURE
vec2 a = load_texture(i);
vec2 b = load_texture(uvec2(stride - i.x, i.y));
#else
vec2 a = load_global(2u * offset + i.x);
vec2 b = load_global(2u * offset + stride - i.x);
#endif
b = vec2(b.x, -b.y);
vec2 even = a + b;
vec2 odd = cmul(a - b, c2r_twiddle(i.x, stride));
store_global(offset + i.x, even + odd);
}
#endif

View File

@@ -0,0 +1,163 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// P is the current accumulated radix factor.
// First pass in an FFT, P == 1, then P will be pass0.radix, then pass0.radix * pass1.radix, and so on ...
// Used to compute twiddle factors.
#ifndef FFT_P1
#define uP constant_data.p_stride_padding.x
#endif
#if FFT_RADIX == 4
// FFT4 implementation.
void FFT4_horiz()
{
#ifdef FFT_P1
FFT4_p1_horiz(gl_GlobalInvocationID.xy);
#else
FFT4_horiz(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT4_vert()
{
#ifdef FFT_P1
FFT4_p1_vert(gl_GlobalInvocationID.xy);
#else
FFT4_vert(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT4()
{
#ifdef FFT_HORIZ
FFT4_horiz();
#else
FFT4_vert();
#endif
}
#endif
#if FFT_RADIX == 8
// FFT8 implementation.
void FFT8_horiz()
{
#ifdef FFT_P1
FFT8_p1_horiz(gl_GlobalInvocationID.xy);
#else
FFT8_horiz(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT8_vert()
{
#ifdef FFT_P1
FFT8_p1_vert(gl_GlobalInvocationID.xy);
#else
FFT8_vert(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT8()
{
#ifdef FFT_HORIZ
FFT8_horiz();
#else
FFT8_vert();
#endif
}
#endif
#if FFT_RADIX == 16
void FFT16_horiz()
{
#ifdef FFT_P1
FFT16_p1_horiz(gl_GlobalInvocationID.xy);
#else
FFT16_horiz(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT16_vert()
{
#ifdef FFT_P1
FFT16_p1_vert(gl_GlobalInvocationID.xy);
#else
FFT16_vert(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT16()
{
#ifdef FFT_HORIZ
FFT16_horiz();
#else
FFT16_vert();
#endif
}
#endif
#if FFT_RADIX == 64
void FFT64_horiz()
{
#ifdef FFT_P1
FFT64_p1_horiz(gl_GlobalInvocationID.xy);
#else
FFT64_horiz(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT64_vert()
{
#ifdef FFT_P1
FFT64_p1_vert(gl_GlobalInvocationID.xy);
#else
FFT64_vert(gl_GlobalInvocationID.xy, uP);
#endif
}
void FFT64()
{
#ifdef FFT_HORIZ
FFT64_horiz();
#else
FFT64_vert();
#endif
}
#endif
void main()
{
#if defined(FFT_RESOLVE_REAL_TO_COMPLEX)
FFT_real_to_complex(gl_GlobalInvocationID.xy);
#elif defined(FFT_RESOLVE_COMPLEX_TO_REAL)
FFT_complex_to_real(gl_GlobalInvocationID.xy);
#elif FFT_RADIX == 4
FFT4();
#elif FFT_RADIX == 8
FFT8();
#elif FFT_RADIX == 16
FFT16();
#elif FFT_RADIX == 64
FFT64();
#else
#error Unimplemented FFT radix.
#endif
}

View File

@@ -0,0 +1,189 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Radix 16 FFT is implemented by doing separate radix-4 FFTs in four threads, then the results are shared via shared memory,
// and the final radix-16 is completed by doing radix-4 FFT again.
// Radix-16 FFT can be implemented directly without shared memory,
// but the register pressure would likely degrade performance significantly over just using shared.
// The radix-16 FFT would normally looks like this:
// cfloat a[i] = load_global(.... + i * quarter_samples);
// However, we interleave these into 4 separate threads (using LocalInvocationID.z) so that every thread
// gets its own FFT-4 transform.
// Z == 0, (0, 4, 8, 12)
// Z == 1, (1, 5, 9, 13)
// Z == 2, (2, 6, 10, 14)
// Z == 3, (3, 7, 11, 15)
// The FFT results are written in stockham autosort fashion to shared memory.
// The final FFT-4 transform is then read from shared memory with the same interleaving pattern used above.
void FFT16_p1_horiz(uvec2 i)
{
uint quarter_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * quarter_samples * 16u;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i + uvec2((block + 0u) * quarter_samples, 0u));
cfloat b = load_texture(i + uvec2((block + 4u) * quarter_samples, 0u));
cfloat c = load_texture(i + uvec2((block + 8u) * quarter_samples, 0u));
cfloat d = load_texture(i + uvec2((block + 12u) * quarter_samples, 0u));
#else
cfloat a = load_global(offset + i.x + (block + 0u) * quarter_samples);
cfloat b = load_global(offset + i.x + (block + 4u) * quarter_samples);
cfloat c = load_global(offset + i.x + (block + 8u) * quarter_samples);
cfloat d = load_global(offset + i.x + (block + 12u) * quarter_samples);
#endif
FFT4_p1(a, b, c, d);
store_shared(a, b, c, d, block, base);
load_shared(a, b, c, d, block, base);
const uint p = 4u;
FFT4(a, b, c, d, FFT_OUTPUT_STEP * block, p);
uint k = (FFT_OUTPUT_STEP * block) & (p - 1u);
uint j = ((FFT_OUTPUT_STEP * block - k) * 4u) + k;
#ifndef FFT_OUTPUT_IMAGE
store_global(offset + 16u * i.x + ((j + 0u * p) >> FFT_OUTPUT_SHIFT), a);
store_global(offset + 16u * i.x + ((j + 1u * p) >> FFT_OUTPUT_SHIFT), c);
store_global(offset + 16u * i.x + ((j + 2u * p) >> FFT_OUTPUT_SHIFT), b);
store_global(offset + 16u * i.x + ((j + 3u * p) >> FFT_OUTPUT_SHIFT), d);
#endif
}
void FFT16_horiz(uvec2 i, uint p)
{
uint quarter_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * quarter_samples * 16u;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
cfloat a = load_global(offset + i.x + (block + 0u) * quarter_samples);
cfloat b = load_global(offset + i.x + (block + 4u) * quarter_samples);
cfloat c = load_global(offset + i.x + (block + 8u) * quarter_samples);
cfloat d = load_global(offset + i.x + (block + 12u) * quarter_samples);
FFT4(a, b, c, d, FFT_OUTPUT_STEP * i.x, p);
store_shared(a, b, c, d, block, base);
load_shared(a, b, c, d, block, base);
uint k = (FFT_OUTPUT_STEP * i.x) & (p - 1u);
uint j = ((FFT_OUTPUT_STEP * i.x - k) * 16u) + k;
FFT4(a, b, c, d, k + block * p, 4u * p);
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(j + (block + 0u) * p, i.y), a);
store(ivec2(j + (block + 4u) * p, i.y), c);
store(ivec2(j + (block + 8u) * p, i.y), b);
store(ivec2(j + (block + 12u) * p, i.y), d);
#else
store_global(offset + ((j + (block + 0u) * p) >> FFT_OUTPUT_SHIFT), a);
store_global(offset + ((j + (block + 4u) * p) >> FFT_OUTPUT_SHIFT), c);
store_global(offset + ((j + (block + 8u) * p) >> FFT_OUTPUT_SHIFT), b);
store_global(offset + ((j + (block + 12u) * p) >> FFT_OUTPUT_SHIFT), d);
#endif
}
void FFT16_p1_vert(uvec2 i)
{
uvec2 quarter_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * quarter_samples.y;
uint offset = stride * i.y;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i + uvec2(0u, (block + 0u) * quarter_samples.y));
cfloat b = load_texture(i + uvec2(0u, (block + 4u) * quarter_samples.y));
cfloat c = load_texture(i + uvec2(0u, (block + 8u) * quarter_samples.y));
cfloat d = load_texture(i + uvec2(0u, (block + 12u) * quarter_samples.y));
#else
cfloat a = load_global(offset + i.x + (block + 0u) * y_stride);
cfloat b = load_global(offset + i.x + (block + 4u) * y_stride);
cfloat c = load_global(offset + i.x + (block + 8u) * y_stride);
cfloat d = load_global(offset + i.x + (block + 12u) * y_stride);
#endif
FFT4_p1(a, b, c, d);
store_shared(a, b, c, d, block, base);
load_shared(a, b, c, d, block, base);
const uint p = 4u;
FFT4(a, b, c, d, block, p);
#ifndef FFT_OUTPUT_IMAGE
store_global((16u * i.y + block + 0u) * stride + i.x, a);
store_global((16u * i.y + block + 4u) * stride + i.x, c);
store_global((16u * i.y + block + 8u) * stride + i.x, b);
store_global((16u * i.y + block + 12u) * stride + i.x, d);
#endif
}
void FFT16_vert(uvec2 i, uint p)
{
uvec2 quarter_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * quarter_samples.y;
uint offset = stride * i.y;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
cfloat a = load_global(offset + i.x + (block + 0u) * y_stride);
cfloat b = load_global(offset + i.x + (block + 4u) * y_stride);
cfloat c = load_global(offset + i.x + (block + 8u) * y_stride);
cfloat d = load_global(offset + i.x + (block + 12u) * y_stride);
FFT4(a, b, c, d, i.y, p);
store_shared(a, b, c, d, block, base);
load_shared(a, b, c, d, block, base);
uint k = i.y & (p - 1u);
uint j = ((i.y - k) * 16u) + k;
FFT4(a, b, c, d, k + block * p, 4u * p);
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(i.x, j + (block + 0u) * p), a);
store(ivec2(i.x, j + (block + 4u) * p), c);
store(ivec2(i.x, j + (block + 8u) * p), b);
store(ivec2(i.x, j + (block + 12u) * p), d);
#else
store_global(stride * (j + (block + 0u) * p) + i.x, a);
store_global(stride * (j + (block + 4u) * p) + i.x, c);
store_global(stride * (j + (block + 8u) * p) + i.x, b);
store_global(stride * (j + (block + 12u) * p) + i.x, d);
#endif
}

View File

@@ -0,0 +1,163 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
void FFT4_p1(inout cfloat a, inout cfloat b, inout cfloat c, inout cfloat d)
{
butterfly_p1(a, c);
butterfly_p1_dir_j(b, d);
butterfly_p1(a, b);
butterfly_p1(c, d);
}
// FFT4 is implemented by in-place radix-2 twice.
void FFT4(inout cfloat a, inout cfloat b, inout cfloat c, inout cfloat d, uint i, uint p)
{
uint k = i & (p - 1u);
ctwiddle w = twiddle(k, p);
butterfly(a, c, w);
butterfly(b, d, w);
ctwiddle w0 = twiddle(k, 2u * p);
ctwiddle w1 = cmul_dir_j(w0);
butterfly(a, b, w0);
butterfly(c, d, w1);
}
void FFT4_p1_horiz(uvec2 i)
{
uint quarter_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * quarter_samples * 4u;
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i);
cfloat b = load_texture(i + uvec2(quarter_samples, 0u));
cfloat c = load_texture(i + uvec2(2u * quarter_samples, 0u));
cfloat d = load_texture(i + uvec2(3u * quarter_samples, 0u));
#else
cfloat a = load_global(offset + i.x);
cfloat b = load_global(offset + i.x + quarter_samples);
cfloat c = load_global(offset + i.x + 2u * quarter_samples);
cfloat d = load_global(offset + i.x + 3u * quarter_samples);
#endif
FFT4_p1(a, b, c, d);
#ifndef FFT_OUTPUT_IMAGE
#if FFT_CVECTOR_SIZE == 4
store_global(offset + 4u * i.x + 0u, cfloat(a.x, c.x, b.x, d.x));
store_global(offset + 4u * i.x + 1u, cfloat(a.y, c.y, b.y, d.y));
store_global(offset + 4u * i.x + 2u, cfloat(a.z, c.z, b.z, d.z));
store_global(offset + 4u * i.x + 3u, cfloat(a.w, c.w, b.w, d.w));
#elif FFT_CVECTOR_SIZE == 2
store_global(offset + 4u * i.x + 0u, cfloat(a.xy, c.xy));
store_global(offset + 4u * i.x + 1u, cfloat(b.xy, d.xy));
store_global(offset + 4u * i.x + 2u, cfloat(a.zw, c.zw));
store_global(offset + 4u * i.x + 3u, cfloat(b.zw, d.zw));
#else
store_global(offset + 4u * i.x + 0u, a);
store_global(offset + 4u * i.x + 1u, c);
store_global(offset + 4u * i.x + 2u, b);
store_global(offset + 4u * i.x + 3u, d);
#endif
#endif
}
void FFT4_p1_vert(uvec2 i)
{
uvec2 quarter_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * quarter_samples.y;
uint offset = stride * i.y;
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i);
cfloat b = load_texture(i + uvec2(0u, quarter_samples.y));
cfloat c = load_texture(i + uvec2(0u, 2u * quarter_samples.y));
cfloat d = load_texture(i + uvec2(0u, 3u * quarter_samples.y));
#else
cfloat a = load_global(offset + i.x + 0u * y_stride);
cfloat b = load_global(offset + i.x + 1u * y_stride);
cfloat c = load_global(offset + i.x + 2u * y_stride);
cfloat d = load_global(offset + i.x + 3u * y_stride);
#endif
FFT4_p1(a, b, c, d);
#ifndef FFT_OUTPUT_IMAGE
store_global((4u * i.y + 0u) * stride + i.x, a);
store_global((4u * i.y + 1u) * stride + i.x, c);
store_global((4u * i.y + 2u) * stride + i.x, b);
store_global((4u * i.y + 3u) * stride + i.x, d);
#endif
}
void FFT4_horiz(uvec2 i, uint p)
{
uint quarter_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * quarter_samples * 4u;
cfloat a = load_global(offset + i.x);
cfloat b = load_global(offset + i.x + quarter_samples);
cfloat c = load_global(offset + i.x + 2u * quarter_samples);
cfloat d = load_global(offset + i.x + 3u * quarter_samples);
FFT4(a, b, c, d, i.x * FFT_OUTPUT_STEP, p);
uint k = (FFT_OUTPUT_STEP * i.x) & (p - 1u);
uint j = ((FFT_OUTPUT_STEP * i.x - k) * 4u) + k;
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(j + 0u * p, i.y), a);
store(ivec2(j + 1u * p, i.y), c);
store(ivec2(j + 2u * p, i.y), b);
store(ivec2(j + 3u * p, i.y), d);
#else
store_global(offset + ((j + 0u * p) >> FFT_OUTPUT_SHIFT), a);
store_global(offset + ((j + 1u * p) >> FFT_OUTPUT_SHIFT), c);
store_global(offset + ((j + 2u * p) >> FFT_OUTPUT_SHIFT), b);
store_global(offset + ((j + 3u * p) >> FFT_OUTPUT_SHIFT), d);
#endif
}
void FFT4_vert(uvec2 i, uint p)
{
uvec2 quarter_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * quarter_samples.y;
uint offset = stride * i.y;
cfloat a = load_global(offset + i.x + 0u * y_stride);
cfloat b = load_global(offset + i.x + 1u * y_stride);
cfloat c = load_global(offset + i.x + 2u * y_stride);
cfloat d = load_global(offset + i.x + 3u * y_stride);
FFT4(a, b, c, d, i.y, p);
uint k = i.y & (p - 1u);
uint j = ((i.y - k) * 4u) + k;
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(i.x, j + 0u * p), a);
store(ivec2(i.x, j + 1u * p), c);
store(ivec2(i.x, j + 2u * p), b);
store(ivec2(i.x, j + 3u * p), d);
#else
store_global(stride * (j + 0u * p) + i.x, a);
store_global(stride * (j + 1u * p) + i.x, c);
store_global(stride * (j + 2u * p) + i.x, b);
store_global(stride * (j + 3u * p) + i.x, d);
#endif
}

View File

@@ -0,0 +1,222 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Basically the same as FFT16, but 2xFFT-8. See comments in fft_radix16.comp for more.
void FFT64_p1_horiz(uvec2 i)
{
uint octa_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * octa_samples * 64u;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i + uvec2((block + 0u) * octa_samples, 0u));
cfloat b = load_texture(i + uvec2((block + 8u) * octa_samples, 0u));
cfloat c = load_texture(i + uvec2((block + 16u) * octa_samples, 0u));
cfloat d = load_texture(i + uvec2((block + 24u) * octa_samples, 0u));
cfloat e = load_texture(i + uvec2((block + 32u) * octa_samples, 0u));
cfloat f = load_texture(i + uvec2((block + 40u) * octa_samples, 0u));
cfloat g = load_texture(i + uvec2((block + 48u) * octa_samples, 0u));
cfloat h = load_texture(i + uvec2((block + 56u) * octa_samples, 0u));
#else
cfloat a = load_global(offset + i.x + (block + 0u) * octa_samples);
cfloat b = load_global(offset + i.x + (block + 8u) * octa_samples);
cfloat c = load_global(offset + i.x + (block + 16u) * octa_samples);
cfloat d = load_global(offset + i.x + (block + 24u) * octa_samples);
cfloat e = load_global(offset + i.x + (block + 32u) * octa_samples);
cfloat f = load_global(offset + i.x + (block + 40u) * octa_samples);
cfloat g = load_global(offset + i.x + (block + 48u) * octa_samples);
cfloat h = load_global(offset + i.x + (block + 56u) * octa_samples);
#endif
FFT8_p1(a, b, c, d, e, f, g, h);
store_shared(a, b, c, d, e, f, g, h, block, base);
load_shared(a, b, c, d, e, f, g, h, block, base);
const uint p = 8u;
FFT8(a, b, c, d, e, f, g, h, FFT_OUTPUT_STEP * block, p);
uint k = (FFT_OUTPUT_STEP * block) & (p - 1u);
uint j = ((FFT_OUTPUT_STEP * block - k) * 8u) + k;
#ifndef FFT_OUTPUT_IMAGE
store_global(offset + 64u * i.x + ((j + 0u * p) >> FFT_OUTPUT_SHIFT), a);
store_global(offset + 64u * i.x + ((j + 1u * p) >> FFT_OUTPUT_SHIFT), e);
store_global(offset + 64u * i.x + ((j + 2u * p) >> FFT_OUTPUT_SHIFT), c);
store_global(offset + 64u * i.x + ((j + 3u * p) >> FFT_OUTPUT_SHIFT), g);
store_global(offset + 64u * i.x + ((j + 4u * p) >> FFT_OUTPUT_SHIFT), b);
store_global(offset + 64u * i.x + ((j + 5u * p) >> FFT_OUTPUT_SHIFT), f);
store_global(offset + 64u * i.x + ((j + 6u * p) >> FFT_OUTPUT_SHIFT), d);
store_global(offset + 64u * i.x + ((j + 7u * p) >> FFT_OUTPUT_SHIFT), h);
#endif
}
void FFT64_horiz(uvec2 i, uint p)
{
uint octa_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * octa_samples * 64u;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
cfloat a = load_global(offset + i.x + (block + 0u) * octa_samples);
cfloat b = load_global(offset + i.x + (block + 8u) * octa_samples);
cfloat c = load_global(offset + i.x + (block + 16u) * octa_samples);
cfloat d = load_global(offset + i.x + (block + 24u) * octa_samples);
cfloat e = load_global(offset + i.x + (block + 32u) * octa_samples);
cfloat f = load_global(offset + i.x + (block + 40u) * octa_samples);
cfloat g = load_global(offset + i.x + (block + 48u) * octa_samples);
cfloat h = load_global(offset + i.x + (block + 56u) * octa_samples);
FFT8(a, b, c, d, e, f, g, h, FFT_OUTPUT_STEP * i.x, p);
store_shared(a, b, c, d, e, f, g, h, block, base);
load_shared(a, b, c, d, e, f, g, h, block, base);
uint k = (FFT_OUTPUT_STEP * i.x) & (p - 1u);
uint j = ((FFT_OUTPUT_STEP * i.x - k) * 64u) + k;
FFT8(a, b, c, d, e, f, g, h, k + block * p, 8u * p);
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(j + (block + 0u) * p, i.y), a);
store(ivec2(j + (block + 8u) * p, i.y), e);
store(ivec2(j + (block + 16u) * p, i.y), c);
store(ivec2(j + (block + 24u) * p, i.y), g);
store(ivec2(j + (block + 32u) * p, i.y), b);
store(ivec2(j + (block + 40u) * p, i.y), f);
store(ivec2(j + (block + 48u) * p, i.y), d);
store(ivec2(j + (block + 56u) * p, i.y), h);
#else
store_global(offset + ((j + (block + 0u) * p) >> FFT_OUTPUT_SHIFT), a);
store_global(offset + ((j + (block + 8u) * p) >> FFT_OUTPUT_SHIFT), e);
store_global(offset + ((j + (block + 16u) * p) >> FFT_OUTPUT_SHIFT), c);
store_global(offset + ((j + (block + 24u) * p) >> FFT_OUTPUT_SHIFT), g);
store_global(offset + ((j + (block + 32u) * p) >> FFT_OUTPUT_SHIFT), b);
store_global(offset + ((j + (block + 40u) * p) >> FFT_OUTPUT_SHIFT), f);
store_global(offset + ((j + (block + 48u) * p) >> FFT_OUTPUT_SHIFT), d);
store_global(offset + ((j + (block + 56u) * p) >> FFT_OUTPUT_SHIFT), h);
#endif
}
void FFT64_p1_vert(uvec2 i)
{
uvec2 octa_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * octa_samples.y;
uint offset = stride * i.y;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i + uvec2(0u, (block + 0u) * octa_samples.y));
cfloat b = load_texture(i + uvec2(0u, (block + 8u) * octa_samples.y));
cfloat c = load_texture(i + uvec2(0u, (block + 16u) * octa_samples.y));
cfloat d = load_texture(i + uvec2(0u, (block + 24u) * octa_samples.y));
cfloat e = load_texture(i + uvec2(0u, (block + 32u) * octa_samples.y));
cfloat f = load_texture(i + uvec2(0u, (block + 40u) * octa_samples.y));
cfloat g = load_texture(i + uvec2(0u, (block + 48u) * octa_samples.y));
cfloat h = load_texture(i + uvec2(0u, (block + 56u) * octa_samples.y));
#else
cfloat a = load_global(offset + i.x + (block + 0u) * y_stride);
cfloat b = load_global(offset + i.x + (block + 8u) * y_stride);
cfloat c = load_global(offset + i.x + (block + 16u) * y_stride);
cfloat d = load_global(offset + i.x + (block + 24u) * y_stride);
cfloat e = load_global(offset + i.x + (block + 32u) * y_stride);
cfloat f = load_global(offset + i.x + (block + 40u) * y_stride);
cfloat g = load_global(offset + i.x + (block + 48u) * y_stride);
cfloat h = load_global(offset + i.x + (block + 56u) * y_stride);
#endif
FFT8_p1(a, b, c, d, e, f, g, h);
store_shared(a, b, c, d, e, f, g, h, block, base);
load_shared(a, b, c, d, e, f, g, h, block, base);
const uint p = 8u;
FFT8(a, b, c, d, e, f, g, h, block, p);
#ifndef FFT_OUTPUT_IMAGE
store_global((64u * i.y + block + 0u) * stride + i.x, a);
store_global((64u * i.y + block + 8u) * stride + i.x, e);
store_global((64u * i.y + block + 16u) * stride + i.x, c);
store_global((64u * i.y + block + 24u) * stride + i.x, g);
store_global((64u * i.y + block + 32u) * stride + i.x, b);
store_global((64u * i.y + block + 40u) * stride + i.x, f);
store_global((64u * i.y + block + 48u) * stride + i.x, d);
store_global((64u * i.y + block + 56u) * stride + i.x, h);
#endif
}
void FFT64_vert(uvec2 i, uint p)
{
uvec2 octa_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * octa_samples.y;
uint offset = stride * i.y;
uint fft = gl_LocalInvocationID.x;
uint block = gl_LocalInvocationID.z;
uint base = get_shared_base(fft);
cfloat a = load_global(offset + i.x + (block + 0u) * y_stride);
cfloat b = load_global(offset + i.x + (block + 8u) * y_stride);
cfloat c = load_global(offset + i.x + (block + 16u) * y_stride);
cfloat d = load_global(offset + i.x + (block + 24u) * y_stride);
cfloat e = load_global(offset + i.x + (block + 32u) * y_stride);
cfloat f = load_global(offset + i.x + (block + 40u) * y_stride);
cfloat g = load_global(offset + i.x + (block + 48u) * y_stride);
cfloat h = load_global(offset + i.x + (block + 56u) * y_stride);
FFT8(a, b, c, d, e, f, g, h, i.y, p);
store_shared(a, b, c, d, block, base);
load_shared(a, b, c, d, block, base);
uint k = i.y & (p - 1u);
uint j = ((i.y - k) * 64u) + k;
FFT8(a, b, c, d, e, f, g, h, k + block * p, 8u * p);
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(i.x, j + (block + 0u) * p), a);
store(ivec2(i.x, j + (block + 8u) * p), e);
store(ivec2(i.x, j + (block + 16u) * p), c);
store(ivec2(i.x, j + (block + 24u) * p), g);
store(ivec2(i.x, j + (block + 32u) * p), b);
store(ivec2(i.x, j + (block + 40u) * p), f);
store(ivec2(i.x, j + (block + 48u) * p), d);
store(ivec2(i.x, j + (block + 56u) * p), h);
#else
store_global(stride * (j + (block + 0u) * p) + i.x, a);
store_global(stride * (j + (block + 8u) * p) + i.x, e);
store_global(stride * (j + (block + 16u) * p) + i.x, c);
store_global(stride * (j + (block + 24u) * p) + i.x, g);
store_global(stride * (j + (block + 32u) * p) + i.x, b);
store_global(stride * (j + (block + 40u) * p) + i.x, f);
store_global(stride * (j + (block + 48u) * p) + i.x, d);
store_global(stride * (j + (block + 56u) * p) + i.x, h);
#endif
}

View File

@@ -0,0 +1,246 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
void FFT8_p1(inout cfloat a, inout cfloat b, inout cfloat c, inout cfloat d, inout cfloat e, inout cfloat f, inout cfloat g, inout cfloat h)
{
butterfly_p1(a, e);
butterfly_p1(b, f);
butterfly_p1_dir_j(c, g);
butterfly_p1_dir_j(d, h);
butterfly_p1(a, c);
butterfly_p1_dir_j(b, d);
butterfly_p1(e, g);
butterfly_p1(f, h);
butterfly_p1(a, b);
butterfly_p1(c, d);
butterfly(e, f, TWIDDLE_1_8);
butterfly(g, h, TWIDDLE_3_8);
}
void FFT8(inout cfloat a, inout cfloat b, inout cfloat c, inout cfloat d, inout cfloat e, inout cfloat f, inout cfloat g, inout cfloat h, uint i, uint p)
{
uint k = i & (p - 1u);
ctwiddle w = twiddle(k, p);
butterfly(a, e, w);
butterfly(b, f, w);
butterfly(c, g, w);
butterfly(d, h, w);
ctwiddle w0 = twiddle(k, 2u * p);
ctwiddle w1 = cmul_dir_j(w0);
butterfly(a, c, w0);
butterfly(b, d, w0);
butterfly(e, g, w1);
butterfly(f, h, w1);
ctwiddle W0 = twiddle(k, 4u * p);
ctwiddle W1 = cmul(W0, TWIDDLE_1_8);
ctwiddle W2 = cmul_dir_j(W0);
ctwiddle W3 = cmul_dir_j(W1);
butterfly(a, b, W0);
butterfly(c, d, W2);
butterfly(e, f, W1);
butterfly(g, h, W3);
}
void FFT8_p1_horiz(uvec2 i)
{
uint octa_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * octa_samples * 8u;
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i);
cfloat b = load_texture(i + uvec2(octa_samples, 0u));
cfloat c = load_texture(i + uvec2(2u * octa_samples, 0u));
cfloat d = load_texture(i + uvec2(3u * octa_samples, 0u));
cfloat e = load_texture(i + uvec2(4u * octa_samples, 0u));
cfloat f = load_texture(i + uvec2(5u * octa_samples, 0u));
cfloat g = load_texture(i + uvec2(6u * octa_samples, 0u));
cfloat h = load_texture(i + uvec2(7u * octa_samples, 0u));
#else
cfloat a = load_global(offset + i.x);
cfloat b = load_global(offset + i.x + octa_samples);
cfloat c = load_global(offset + i.x + 2u * octa_samples);
cfloat d = load_global(offset + i.x + 3u * octa_samples);
cfloat e = load_global(offset + i.x + 4u * octa_samples);
cfloat f = load_global(offset + i.x + 5u * octa_samples);
cfloat g = load_global(offset + i.x + 6u * octa_samples);
cfloat h = load_global(offset + i.x + 7u * octa_samples);
#endif
FFT8_p1(a, b, c, d, e, f, g, h);
#ifndef FFT_OUTPUT_IMAGE
#if FFT_CVECTOR_SIZE == 4
store_global(offset + 8u * i.x + 0u, cfloat(a.x, e.x, c.x, g.x));
store_global(offset + 8u * i.x + 1u, cfloat(b.x, f.x, d.x, h.x));
store_global(offset + 8u * i.x + 2u, cfloat(a.y, e.y, c.y, g.y));
store_global(offset + 8u * i.x + 3u, cfloat(b.y, f.y, d.y, h.y));
store_global(offset + 8u * i.x + 4u, cfloat(a.z, e.z, c.z, g.z));
store_global(offset + 8u * i.x + 5u, cfloat(b.z, f.z, d.z, h.z));
store_global(offset + 8u * i.x + 6u, cfloat(a.w, e.w, c.w, g.w));
store_global(offset + 8u * i.x + 7u, cfloat(b.w, f.w, d.w, h.w));
#elif FFT_CVECTOR_SIZE == 2
store_global(offset + 8u * i.x + 0u, cfloat(a.xy, e.xy));
store_global(offset + 8u * i.x + 1u, cfloat(c.xy, g.xy));
store_global(offset + 8u * i.x + 2u, cfloat(b.xy, f.xy));
store_global(offset + 8u * i.x + 3u, cfloat(d.xy, h.xy));
store_global(offset + 8u * i.x + 4u, cfloat(a.zw, e.zw));
store_global(offset + 8u * i.x + 5u, cfloat(c.zw, g.zw));
store_global(offset + 8u * i.x + 6u, cfloat(b.zw, f.zw));
store_global(offset + 8u * i.x + 7u, cfloat(d.zw, h.zw));
#else
store_global(offset + 8u * i.x + 0u, a);
store_global(offset + 8u * i.x + 1u, e);
store_global(offset + 8u * i.x + 2u, c);
store_global(offset + 8u * i.x + 3u, g);
store_global(offset + 8u * i.x + 4u, b);
store_global(offset + 8u * i.x + 5u, f);
store_global(offset + 8u * i.x + 6u, d);
store_global(offset + 8u * i.x + 7u, h);
#endif
#endif
}
void FFT8_p1_vert(uvec2 i)
{
uvec2 octa_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * octa_samples.y;
uint offset = stride * i.y;
#ifdef FFT_INPUT_TEXTURE
cfloat a = load_texture(i);
cfloat b = load_texture(i + uvec2(0u, octa_samples.y));
cfloat c = load_texture(i + uvec2(0u, 2u * octa_samples.y));
cfloat d = load_texture(i + uvec2(0u, 3u * octa_samples.y));
cfloat e = load_texture(i + uvec2(0u, 4u * octa_samples.y));
cfloat f = load_texture(i + uvec2(0u, 5u * octa_samples.y));
cfloat g = load_texture(i + uvec2(0u, 6u * octa_samples.y));
cfloat h = load_texture(i + uvec2(0u, 7u * octa_samples.y));
#else
cfloat a = load_global(offset + i.x + 0u * y_stride);
cfloat b = load_global(offset + i.x + 1u * y_stride);
cfloat c = load_global(offset + i.x + 2u * y_stride);
cfloat d = load_global(offset + i.x + 3u * y_stride);
cfloat e = load_global(offset + i.x + 4u * y_stride);
cfloat f = load_global(offset + i.x + 5u * y_stride);
cfloat g = load_global(offset + i.x + 6u * y_stride);
cfloat h = load_global(offset + i.x + 7u * y_stride);
#endif
FFT8_p1(a, b, c, d, e, f, g, h);
#ifndef FFT_OUTPUT_IMAGE
store_global((8u * i.y + 0u) * stride + i.x, a);
store_global((8u * i.y + 1u) * stride + i.x, e);
store_global((8u * i.y + 2u) * stride + i.x, c);
store_global((8u * i.y + 3u) * stride + i.x, g);
store_global((8u * i.y + 4u) * stride + i.x, b);
store_global((8u * i.y + 5u) * stride + i.x, f);
store_global((8u * i.y + 6u) * stride + i.x, d);
store_global((8u * i.y + 7u) * stride + i.x, h);
#endif
}
void FFT8_horiz(uvec2 i, uint p)
{
uint octa_samples = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint offset = i.y * octa_samples * 8u;
cfloat a = load_global(offset + i.x);
cfloat b = load_global(offset + i.x + octa_samples);
cfloat c = load_global(offset + i.x + 2u * octa_samples);
cfloat d = load_global(offset + i.x + 3u * octa_samples);
cfloat e = load_global(offset + i.x + 4u * octa_samples);
cfloat f = load_global(offset + i.x + 5u * octa_samples);
cfloat g = load_global(offset + i.x + 6u * octa_samples);
cfloat h = load_global(offset + i.x + 7u * octa_samples);
FFT8(a, b, c, d, e, f, g, h, FFT_OUTPUT_STEP * i.x, p);
uint k = (FFT_OUTPUT_STEP * i.x) & (p - 1u);
uint j = ((FFT_OUTPUT_STEP * i.x - k) * 8u) + k;
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(j + 0u * p, i.y), a);
store(ivec2(j + 1u * p, i.y), e);
store(ivec2(j + 2u * p, i.y), c);
store(ivec2(j + 3u * p, i.y), g);
store(ivec2(j + 4u * p, i.y), b);
store(ivec2(j + 5u * p, i.y), f);
store(ivec2(j + 6u * p, i.y), d);
store(ivec2(j + 7u * p, i.y), h);
#else
store_global(offset + ((j + 0u * p) >> FFT_OUTPUT_SHIFT), a);
store_global(offset + ((j + 1u * p) >> FFT_OUTPUT_SHIFT), e);
store_global(offset + ((j + 2u * p) >> FFT_OUTPUT_SHIFT), c);
store_global(offset + ((j + 3u * p) >> FFT_OUTPUT_SHIFT), g);
store_global(offset + ((j + 4u * p) >> FFT_OUTPUT_SHIFT), b);
store_global(offset + ((j + 5u * p) >> FFT_OUTPUT_SHIFT), f);
store_global(offset + ((j + 6u * p) >> FFT_OUTPUT_SHIFT), d);
store_global(offset + ((j + 7u * p) >> FFT_OUTPUT_SHIFT), h);
#endif
}
void FFT8_vert(uvec2 i, uint p)
{
uvec2 octa_samples = gl_NumWorkGroups.xy * gl_WorkGroupSize.xy;
uint stride = uStride;
uint y_stride = stride * octa_samples.y;
uint offset = stride * i.y;
cfloat a = load_global(offset + i.x + 0u * y_stride);
cfloat b = load_global(offset + i.x + 1u * y_stride);
cfloat c = load_global(offset + i.x + 2u * y_stride);
cfloat d = load_global(offset + i.x + 3u * y_stride);
cfloat e = load_global(offset + i.x + 4u * y_stride);
cfloat f = load_global(offset + i.x + 5u * y_stride);
cfloat g = load_global(offset + i.x + 6u * y_stride);
cfloat h = load_global(offset + i.x + 7u * y_stride);
FFT8(a, b, c, d, e, f, g, h, i.y, p);
uint k = i.y & (p - 1u);
uint j = ((i.y - k) * 8u) + k;
#ifdef FFT_OUTPUT_IMAGE
store(ivec2(i.x, j + 0u * p), a);
store(ivec2(i.x, j + 1u * p), e);
store(ivec2(i.x, j + 2u * p), c);
store(ivec2(i.x, j + 3u * p), g);
store(ivec2(i.x, j + 4u * p), b);
store(ivec2(i.x, j + 5u * p), f);
store(ivec2(i.x, j + 6u * p), d);
store(ivec2(i.x, j + 7u * p), h);
#else
store_global(stride * (j + 0u * p) + i.x, a);
store_global(stride * (j + 1u * p) + i.x, e);
store_global(stride * (j + 2u * p) + i.x, c);
store_global(stride * (j + 3u * p) + i.x, g);
store_global(stride * (j + 4u * p) + i.x, b);
store_global(stride * (j + 5u * p) + i.x, f);
store_global(stride * (j + 6u * p) + i.x, d);
store_global(stride * (j + 7u * p) + i.x, h);
#endif
}

View File

@@ -0,0 +1,179 @@
/* Copyright (C) 2015 Hans-Kristian Arntzen <maister@archlinux.us>
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Most (all?) desktop GPUs have banked shared memory.
// We want to avoid bank conflicts as much as possible.
// If we don't pad the shared memory, threads in the same warp/wavefront will hit the same
// shared memory banks, and stall as each bank and only process a fixed number of requests per cycle.
// By padding, we "smear" out the requests to more banks, which greatly improves performance.
// For architectures without banked shared memory,
// this design makes no sense, so it's a pretty important performance bit to set correctly.
#ifndef FFT_SHARED_BANKED
#error FFT_SHARED_BANKED must be defined.
#endif
#if FFT_SHARED_BANKED
#define FFT_BANK_CONFLICT_PADDING 1u
#else
#define FFT_BANK_CONFLICT_PADDING 0u
#endif
#define FFT_SHARED_SIZE (uint(FFT_RADIX) + FFT_BANK_CONFLICT_PADDING)
uint get_shared_base(uint fft)
{
return FFT_SHARED_SIZE * (gl_LocalInvocationID.y * gl_WorkGroupSize.x + fft);
}
#if FFT_SHARED_BANKED
// Implementations with banked shared memory like to write 32-bit at a time,
// since that's typically how big transactions each shared memory bank can handle.
// If we try to write vec4s in one go (which will get split up to 4 writes anyways),
// we end up with 4-way bank conflicts no matter what we do.
#if defined(FFT_VEC8)
shared uint tmpx[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
shared uint tmpy[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
shared uint tmpz[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
shared uint tmpw[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
#else
shared float tmpx[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
shared float tmpy[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
#if defined(FFT_VEC4)
shared float tmpz[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
shared float tmpw[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
#endif
#endif
void store_shared(uint offset, cfloat v)
{
tmpx[offset] = v.x;
tmpy[offset] = v.y;
#if defined(FFT_VEC4) || defined(FFT_VEC8)
tmpz[offset] = v.z;
tmpw[offset] = v.w;
#endif
}
void load_shared(uint offset, out cfloat v)
{
v.x = tmpx[offset];
v.y = tmpy[offset];
#if defined(FFT_VEC4) || defined(FFT_VEC8)
v.z = tmpz[offset];
v.w = tmpw[offset];
#endif
}
#else
// For non-banked architectures, just store and load directly.
shared cfloat tmp[FFT_SHARED_SIZE * gl_WorkGroupSize.x * gl_WorkGroupSize.y];
void store_shared(uint offset, cfloat v)
{
tmp[offset] = v;
}
void load_shared(uint offset, out cfloat v)
{
v = tmp[offset];
}
#endif
void store_shared(cfloat a, cfloat b, cfloat c, cfloat d, uint block, uint base)
{
// Interleave and write out in bit-reversed order.
#if FFT_CVECTOR_SIZE == 4
store_shared(base + 4u * block + 0u, cfloat(a.x, c.x, b.x, d.x));
store_shared(base + 4u * block + 1u, cfloat(a.y, c.y, b.y, d.y));
store_shared(base + 4u * block + 2u, cfloat(a.z, c.z, b.z, d.z));
store_shared(base + 4u * block + 3u, cfloat(a.w, c.w, b.w, d.w));
#elif FFT_CVECTOR_SIZE == 2
store_shared(base + 4u * block + 0u, cfloat(a.xy, c.xy));
store_shared(base + 4u * block + 1u, cfloat(b.xy, d.xy));
store_shared(base + 4u * block + 2u, cfloat(a.zw, c.zw));
store_shared(base + 4u * block + 3u, cfloat(b.zw, d.zw));
#else
store_shared(base + 4u * block + 0u, a);
store_shared(base + 4u * block + 1u, c);
store_shared(base + 4u * block + 2u, b);
store_shared(base + 4u * block + 3u, d);
#endif
memoryBarrierShared();
barrier();
}
void load_shared(out cfloat a, out cfloat b, out cfloat c, out cfloat d, uint block, uint base)
{
load_shared(base + block + 0u * gl_WorkGroupSize.z, a);
load_shared(base + block + 1u * gl_WorkGroupSize.z, b);
load_shared(base + block + 2u * gl_WorkGroupSize.z, c);
load_shared(base + block + 3u * gl_WorkGroupSize.z, d);
}
void store_shared(cfloat a, cfloat b, cfloat c, cfloat d, cfloat e, cfloat f, cfloat g, cfloat h, uint block, uint base)
{
// Interleave and write out in bit-reversed order.
#if FFT_CVECTOR_SIZE == 4
store_shared(base + 8u * block + 0u, cfloat(a.x, e.x, c.x, g.x));
store_shared(base + 8u * block + 1u, cfloat(b.x, f.x, d.x, h.x));
store_shared(base + 8u * block + 2u, cfloat(a.y, e.y, c.y, g.y));
store_shared(base + 8u * block + 3u, cfloat(b.y, f.y, d.y, h.y));
store_shared(base + 8u * block + 4u, cfloat(a.z, e.z, c.z, g.z));
store_shared(base + 8u * block + 5u, cfloat(b.z, f.z, d.z, h.z));
store_shared(base + 8u * block + 6u, cfloat(a.w, e.w, c.w, g.w));
store_shared(base + 8u * block + 7u, cfloat(b.w, f.w, d.w, h.w));
#elif FFT_CVECTOR_SIZE == 2
store_shared(base + 8u * block + 0u, cfloat(a.xy, e.xy));
store_shared(base + 8u * block + 1u, cfloat(c.xy, g.xy));
store_shared(base + 8u * block + 2u, cfloat(b.xy, f.xy));
store_shared(base + 8u * block + 3u, cfloat(d.xy, h.xy));
store_shared(base + 8u * block + 4u, cfloat(a.zw, e.zw));
store_shared(base + 8u * block + 5u, cfloat(c.zw, g.zw));
store_shared(base + 8u * block + 6u, cfloat(b.zw, f.zw));
store_shared(base + 8u * block + 7u, cfloat(d.zw, h.zw));
#else
store_shared(base + 8u * block + 0u, a);
store_shared(base + 8u * block + 1u, e);
store_shared(base + 8u * block + 2u, c);
store_shared(base + 8u * block + 3u, g);
store_shared(base + 8u * block + 4u, b);
store_shared(base + 8u * block + 5u, f);
store_shared(base + 8u * block + 6u, d);
store_shared(base + 8u * block + 7u, h);
#endif
memoryBarrierShared();
barrier();
}
void load_shared(out cfloat a, out cfloat b, out cfloat c, out cfloat d, out cfloat e, out cfloat f, out cfloat g, out cfloat h, uint block, uint base)
{
load_shared(base + block + 0u * gl_WorkGroupSize.z, a);
load_shared(base + block + 1u * gl_WorkGroupSize.z, b);
load_shared(base + block + 2u * gl_WorkGroupSize.z, c);
load_shared(base + block + 3u * gl_WorkGroupSize.z, d);
load_shared(base + block + 4u * gl_WorkGroupSize.z, e);
load_shared(base + block + 5u * gl_WorkGroupSize.z, f);
load_shared(base + block + 6u * gl_WorkGroupSize.z, g);
load_shared(base + block + 7u * gl_WorkGroupSize.z, h);
}

View File

@@ -0,0 +1,9 @@
uniform sampler1D tex;
uniform float diff;
out vec4 fragment;
in vec4 gl_FragCoord;
void main() {
fragment.r = texelFetch(tex, int(gl_FragCoord.x), 0).r - diff;
}

View File

@@ -0,0 +1,9 @@
uniform sampler1D tex;
out vec4 fragment;
in vec4 gl_FragCoord;
/* 1D texture mapping */
void main() {
fragment.r = texelFetch(tex, int(gl_FragCoord.x), 0).r;
}

View File

@@ -0,0 +1,15 @@
#if _PREMULTIPLY_ALPHA == 0
#error __disablestage
#endif
#request uniform "prev" tex
uniform sampler2D tex;
out vec4 fragment;
in vec4 gl_FragCoord;
void main() {
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
fragment.rgb *= fragment.a;
}

View File

@@ -0,0 +1,81 @@
#ifndef _SMOOTH_GLSL
#define _SMOOTH_GLSL
#include ":util/common.glsl"
#include "@smooth_parameters.glsl"
#include ":smooth_parameters.glsl"
#define average 0
#define maximum 1
#define hybrid 2
float scale_audio(float idx) {
return -log((-(SAMPLE_RANGE) * idx) + 1) / (SAMPLE_SCALE);
}
float iscale_audio(float idx) {
return -log((SAMPLE_RANGE) * idx) / (SAMPLE_SCALE);
}
/* Note: the _SMOOTH_FACTOR macro is defined by GLava itself, from `#request setsmoothfactor`*/
float smooth_audio(in sampler1D tex, int tex_sz, highp float idx) {
#if _PRE_SMOOTHED_AUDIO < 1
float
smin = scale_audio(clamp(idx - _SMOOTH_FACTOR, 0, 1)) * tex_sz,
smax = scale_audio(clamp(idx + _SMOOTH_FACTOR, 0, 1)) * tex_sz;
float m = ((smax - smin) / 2.0F), s, w;
float rm = smin + m; /* middle */
#if SAMPLE_MODE == average
float avg = 0, weight = 0;
for (s = smin; s <= smax; s += 1.0F) {
w = ROUND_FORMULA(clamp((m - abs(rm - s)) / m, 0, 1));
weight += w;
avg += texelFetch(tex, int(round(s)), 0).r * w;
}
avg /= weight;
return avg;
#elif SAMPLE_MODE == hybrid
float vmax = 0, avg = 0, weight = 0, v;
for (s = smin; s < smax; s += 1.0F) {
w = ROUND_FORMULA(clamp((m - abs(rm - s)) / m, 0, 1));
weight += w;
v = texelFetch(tex, int(round(s)), 0).r * w;
avg += v;
if (vmax < v)
vmax = v;
}
return (vmax * (1 - SAMPLE_HYBRID_WEIGHT)) + ((avg / weight) * SAMPLE_HYBRID_WEIGHT);
#elif SAMPLE_MODE == maximum
float vmax = 0, v;
for (s = smin; s < smax; s += 1.0F) {
w = texelFetch(tex, int(round(s)), 0).r * ROUND_FORMULA(clamp((m - abs(rm - s)) / m, 0, 1));
if (vmax < w)
vmax = w;
}
return vmax;
#endif
#else
return texelFetch(tex, int(round(idx * tex_sz)), 0).r;
#endif
}
/* Applies the audio smooth sampling function three times to the adjacent values */
float smooth_audio_adj(in sampler1D tex, int tex_sz, highp float idx, highp float pixel) {
float
al = smooth_audio(tex, tex_sz, max(idx - pixel, 0.0F)),
am = smooth_audio(tex, tex_sz, idx),
ar = smooth_audio(tex, tex_sz, min(idx + pixel, 1.0F));
return (al + am + ar) / 3.0F;
}
#ifdef TWOPI
#undef TWOPI
#endif
#ifdef PI
#undef PI
#endif
#endif /* _SMOOTH_GLSL */

View File

@@ -6,8 +6,8 @@ uniform int w;
out vec4 fragment;
in vec4 gl_FragCoord;
#undef PRE_SMOOTHED_AUDIO
#define PRE_SMOOTHED_AUDIO 0
#undef _PRE_SMOOTHED_AUDIO
#define _PRE_SMOOTHED_AUDIO 0
#include ":util/smooth.glsl"

10
shaders/glava/wave.glsl Normal file
View File

@@ -0,0 +1,10 @@
/* Min (vertical) line thickness */
#define MIN_THICKNESS 1
/* Max (vertical) line thickness */
#define MAX_THICKNESS 6
/* Base color to use, distance from center will multiply the RGB components */
#define BASE_COLOR @fg:vec4(0.7, 0.2, 0.45, 1)
/* Amplitude */
#define AMPLIFY 500
/* Outline color */
#define OUTLINE @bg:vec4(0.15, 0.15, 0.15, 1)

View File

@@ -11,6 +11,7 @@ uniform sampler1D audio_l;
out vec4 fragment;
#include "@wave.glsl"
#include ":wave.glsl"
#define index(offset) ((texture(audio_l, (gl_FragCoord.x + offset) / screen.x).r - 0.5) * AMPLIFY) + 0.5F

View File

@@ -8,6 +8,7 @@ uniform ivec2 screen; /* screen dimensions */
out vec4 fragment; /* output */
#include "@wave.glsl"
#include ":wave.glsl"
void main() {

Some files were not shown because too many files have changed in this diff Show More