Compare commits
134 Commits
v1.5.5
...
3cc5e226aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cc5e226aa | ||
|
|
4b84e7a928 | ||
|
|
b26f11ae20 | ||
|
|
580e296e86 | ||
|
|
6f1680b335 | ||
|
|
0d48dd059c | ||
|
|
c4b348cf8b | ||
|
|
c591b87c7b | ||
|
|
9abaafda5a | ||
|
|
7bfa551247 | ||
|
|
7dc8a985fc | ||
|
|
a17151dd5d | ||
|
|
05067c74ed | ||
|
|
112e371a9b | ||
|
|
aa875b2a81 | ||
|
|
5ff291fe51 | ||
|
|
563792599a | ||
|
|
d821eeaf0a | ||
|
|
0592a422a1 | ||
|
|
04ca255cd2 | ||
|
|
859bfb0b31 | ||
|
|
25942ec6a3 | ||
|
|
84cf77890f | ||
|
|
20debf2cce | ||
|
|
0695c9cec0 | ||
|
|
e46967cc02 | ||
|
|
90d275f2cd | ||
|
|
4bfbc859f8 | ||
|
|
7d6d7e2987 | ||
|
|
a38f79e279 | ||
|
|
1974869b5f | ||
|
|
69f25eb44b | ||
|
|
129d4d8c91 | ||
|
|
6d74062cb3 | ||
|
|
c9c134a3e2 | ||
|
|
5de224c3e1 | ||
|
|
0a4d0cb099 | ||
|
|
cc1d673e55 | ||
|
|
99f0274e07 | ||
|
|
69ebfe9714 | ||
|
|
f562bc690d | ||
|
|
5630e2314b | ||
|
|
617424c3fa | ||
|
|
14029d784c | ||
|
|
7fcf288a5b | ||
|
|
9d4674d680 | ||
|
|
e8e8b1f701 | ||
|
|
3207db6d27 | ||
|
|
6d48f95995 | ||
|
|
ce77587e3a | ||
|
|
0358143bb7 | ||
|
|
b4cdcb3120 | ||
|
|
f1284a6835 | ||
|
|
1a7a6deb49 | ||
|
|
8259d8bb74 | ||
|
|
26d36170de | ||
|
|
dc2f08a21a | ||
|
|
6d68a1923f | ||
|
|
eca50058a2 | ||
|
|
b6edf9700a | ||
|
|
b2b3d1017a | ||
|
|
6e60bcc698 | ||
|
|
c766c574a6 | ||
|
|
d72deb8fae | ||
|
|
bf2c5cfcbd | ||
|
|
d374192223 | ||
|
|
67305d419e | ||
|
|
a15292be64 | ||
|
|
8cb183fb9a | ||
|
|
d1028c24da | ||
|
|
452deea23d | ||
|
|
13e9f051ce | ||
|
|
4b71de19c4 | ||
|
|
ad66445c28 | ||
|
|
706ec0fcd4 | ||
|
|
315063eb9c | ||
|
|
4cdf1f13f9 | ||
|
|
06644549e2 | ||
|
|
0acf7d4188 | ||
|
|
aa56b89ddf | ||
|
|
04a4da4993 | ||
|
|
0dc321900d | ||
|
|
299605ad1a | ||
|
|
c15535cf3f | ||
|
|
dbadfffd17 | ||
|
|
5c9d974252 | ||
|
|
d640ac5d3c | ||
|
|
094dec9b00 | ||
|
|
c485d80013 | ||
|
|
b37ab5ba09 | ||
|
|
1a595c2f19 | ||
|
|
c9dac68bb4 | ||
|
|
c14c2a835a | ||
|
|
6f688e8b4d | ||
|
|
bfea8e167a | ||
|
|
d07b93d54e | ||
|
|
f7170afb75 | ||
|
|
8ccbf22732 | ||
|
|
5fdf0545a8 | ||
|
|
b77d1a7b3a | ||
|
|
f87c70af2b | ||
|
|
7c30c4a78a | ||
|
|
bc2db4a415 | ||
|
|
3ff1097941 | ||
|
|
041bfdfd55 | ||
|
|
8cbf7bc579 | ||
|
|
91530dfc49 | ||
|
|
a39a1324e1 | ||
|
|
140d98b404 | ||
|
|
f235362f44 | ||
|
|
32853b73d8 | ||
|
|
4705699bb6 | ||
|
|
3ef23f0db8 | ||
|
|
4d51ccbd54 | ||
|
|
6c02d15c80 | ||
|
|
40dac829cc | ||
|
|
bde3101c42 | ||
|
|
f2857e5f21 | ||
|
|
66a9b09b10 | ||
|
|
31a39b6ecd | ||
|
|
dd0f5bf0f9 | ||
|
|
1337253257 | ||
|
|
76325cb126 | ||
|
|
426f70f579 | ||
|
|
1d2b014da7 | ||
|
|
6670c54827 | ||
|
|
f4ad41df32 | ||
|
|
93df114308 | ||
|
|
d21edcfb29 | ||
|
|
e0b4f7d6c7 | ||
|
|
386d32076c | ||
|
|
52af6ac173 | ||
|
|
3da1fb34dd | ||
|
|
2c99124556 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
liberapay: Jarcode
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
55
CONTRIBUTING.md
Normal file
55
CONTRIBUTING.md
Normal 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.
|
||||||
138
Makefile
138
Makefile
@@ -1,127 +1,53 @@
|
|||||||
src = $(wildcard *.c)
|
.PHONY: all install clean ninja
|
||||||
obj = $(src:.c=.o)
|
|
||||||
|
|
||||||
# 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)
|
ifeq ($(BUILD),debug)
|
||||||
CFLAGS_BUILD = -O0 -ggdb -Wall
|
MESON_CONF += --buildtype=debug
|
||||||
GLAD_GEN = c-debug
|
|
||||||
STRIP_CMD = $(info Skipping `strip` for debug builds)
|
|
||||||
else
|
else
|
||||||
CFLAGS_BUILD = -O2 -Wstringop-overflow=0
|
ifdef BUILD
|
||||||
GLAD_GEN = c
|
$(warning WARNING: ignoring build option '$(BUILD)' in compatibility Makefile)
|
||||||
STRIP_CMD = strip --strip-all glava
|
|
||||||
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
|
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifndef EXECDIR
|
|
||||||
EXECDIR = /usr/bin/
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Install type parameter
|
|
||||||
|
|
||||||
ifeq ($(INSTALL),standalone)
|
ifeq ($(INSTALL),standalone)
|
||||||
CFLAGS_INSTALL = -DGLAVA_STANDALONE
|
MESON_CONF += -Dstandalone=true
|
||||||
endif
|
else
|
||||||
|
ifdef INSTALL
|
||||||
ifeq ($(INSTALL),unix)
|
$(warning WARNING: ignoring install option '$(INSTALL)' in compatibility Makefile)
|
||||||
CFLAGS_INSTALL = -DGLAVA_UNIX
|
|
||||||
ifndef SHADERDIR
|
|
||||||
ifdef XDG_CONFIG_DIRS
|
|
||||||
SHADERDIR = /$(firstword $(subst :, ,$(XDG_CONFIG_DIRS)))/glava/
|
|
||||||
else
|
|
||||||
SHADERDIR = /etc/xdg/glava/
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifdef ENABLE_GLFW
|
|
||||||
CFLAGS_GLFW = -DGLAVA_GLFW
|
|
||||||
LDFLAGS_GLFW = -lglfw
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifndef DISABLE_GLX
|
|
||||||
CFLAGS_GLX = -DGLAVA_GLX
|
|
||||||
LDFLAGS_GLX = -lXrender
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq ($(INSTALL),osx)
|
|
||||||
CFLAGS_INSTALL = -DGLAVA_OSX
|
|
||||||
ifndef SHADERDIR
|
|
||||||
SHADERDIR = /Library/glava
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
LDFLAGS += $(ASAN) -lpulse -lpulse-simple -pthread $(LDFLAGS_GLFW) -ldl -lm -lX11 -lXext $(LDFLAGS_GLX)
|
|
||||||
|
|
||||||
PYTHON = python
|
|
||||||
|
|
||||||
GLAVA_VERSION = \"$(shell git describe --tags 2>/dev/null)\"
|
|
||||||
ifeq ($(GLAVA_VERSION),\"\")
|
|
||||||
GLAVA_VERSION = \"unknown\"
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifdef DESTDIR
|
|
||||||
DESTDIR += /
|
|
||||||
endif
|
|
||||||
|
|
||||||
GLAD_INSTALL_DIR = glad
|
|
||||||
GLAD_SRCFILE = glad.c
|
|
||||||
GLAD_ARGS = --generator=$(GLAD_GEN) --extensions=GL_EXT_framebuffer_multisample,GL_EXT_texture_filter_anisotropic
|
|
||||||
CFLAGS_COMMON = -DGLAVA_VERSION="$(GLAVA_VERSION)" -DSHADER_INSTALL_PATH="\"$(SHADERDIR)\""
|
|
||||||
CFLAGS_USE = $(CFLAGS_COMMON) $(CFLAGS_GLX) $(CFLAGS_GLFW) $(CFLAGS_BUILD) $(CFLAGS_INSTALL) $(CFLAGS)
|
|
||||||
|
|
||||||
# Store relevant variables that may change depending on the environment or user input
|
# Store relevant variables that may change depending on the environment or user input
|
||||||
STATE = $(BUILD),$(INSTALL),$(PREFIX),$(ENABLE_GLFW),$(DISABLE_GLX),$(PYTHON),$(CC),$(CFLAGS_USE)
|
STATE = $(BUILD),$(INSTALL),$(PYTHON),$(CC),$(CFLAGS),$(DESTDIR)
|
||||||
# Only update the file if the contents changed, `make` just looks at the timestamp
|
# 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 [[ ! -e build_state ]]; then touch build_state; fi)
|
||||||
$(shell if [ '$(STATE)' != "`cat build_state`" ]; then echo '$(STATE)' > build_state; fi)
|
$(shell if [ '$(STATE)' != "`cat build_state`" ]; then echo '$(STATE)' > build_state; fi)
|
||||||
|
|
||||||
all: glava
|
all: ninja
|
||||||
|
|
||||||
%.o: %.c build_state
|
# Rebuild if the makefile state changes to maintain old behaviour and smooth rebuilds with altered parameters
|
||||||
@$(CC) $(CFLAGS_USE) -o $@ -c $(firstword $<)
|
build: build_state
|
||||||
@echo "CC $@"
|
$(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)
|
||||||
|
|
||||||
glava: $(obj)
|
ninja: build
|
||||||
@$(CC) -o glava $(obj) $(LDFLAGS)
|
ninja -C $(BUILD_DIR)
|
||||||
@echo "CC glava"
|
|
||||||
$(STRIP_CMD)
|
|
||||||
|
|
||||||
.PHONY: glad
|
|
||||||
glad: build_state
|
|
||||||
@cd $(GLAD_INSTALL_DIR) && $(PYTHON) -m glad $(GLAD_ARGS) --local-files --out-path=.
|
|
||||||
@cp glad/*.h .
|
|
||||||
@cp glad/glad.c .
|
|
||||||
|
|
||||||
# Empty build state goal, used to force some of the above rules to re-run if `build_state` was updated
|
|
||||||
build_state: ;
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
rm -f $(obj) glava glad.o build_state
|
|
||||||
|
|
||||||
EXECTARGET = $(shell readlink -m "$(DESTDIR)$(EXECDIR)/glava")
|
|
||||||
SHADERTARGET = $(shell readlink -m "$(DESTDIR)$(SHADERDIR)")
|
|
||||||
|
|
||||||
.PHONY: install
|
|
||||||
install:
|
install:
|
||||||
install -Dm755 glava $(EXECTARGET)
|
ninja -C build install
|
||||||
install -d $(SHADERTARGET)
|
|
||||||
cp -Rv shaders/* $(SHADERTARGET)
|
|
||||||
|
|
||||||
.PHONY: uninstall
|
|
||||||
uninstall:
|
|
||||||
rm $(EXECTARGET)
|
|
||||||
rm -rf $(SHADERTARGET)
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
|||||||
100
README.md
100
README.md
@@ -1,48 +1,67 @@
|
|||||||
|
|
||||||
<img align="left" width="200" height="200" src="https://thumbs.gfycat.com/DefiantInformalIndianspinyloach-size_restricted.gif" />
|
<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 windows or backgrounds. Displayed to the left is the `radial` shader module, and [here is a demonstration video](https://streamable.com/dgpj8). Development is active, and reporting issues is encouranged.
|
**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 Arch Linux [`glava` package](https://www.archlinux.org/packages/community/x86_64/glava/), or the [`glava-git` AUR package](https://aur.archlinux.org/packages/glava-git/))**:**
|
**Compiling:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git clone https://github.com/wacossusca34/glava
|
$ git clone https://github.com/jarcode-foss/glava
|
||||||
$ cd glava
|
$ cd glava
|
||||||
$ CFLAGS="-march=native" make
|
$ meson build --prefix /usr
|
||||||
$ sudo make install
|
$ ninja -C build
|
||||||
$ glava
|
$ 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:**
|
**Requirements:**
|
||||||
|
|
||||||
- X11 (Xext, Xcomposite, & Xrender)
|
- X11 (Xext, Xcomposite, & Xrender)
|
||||||
- PulseAudio
|
- PulseAudio
|
||||||
- Linux or BSD
|
- 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:**
|
**Additional compile time requirements:**
|
||||||
|
|
||||||
- GCC (this program uses GNU C features)
|
- Meson
|
||||||
|
- OBS (disable with `-Ddisable-obs=true`)
|
||||||
|
|
||||||
**Optional requirements:**
|
**Optional requirements:**
|
||||||
|
|
||||||
- GLFW 3.1+ (optional, enable with `ENABLE_GLFW=1`)
|
- 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:
|
**Ubuntu/Debian users:** the following command ensures you have all the needed packages and headers to compile GLava with the default feature set:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install libpulse0 libpulse-dev libglfw3 libglfw3-dev libxext6 libxext-dev libxcomposite-dev python make gcc
|
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.
|
||||||
|
|
||||||
## [Configuration](https://github.com/wacossusca34/glava/wiki)
|
## Installation
|
||||||
|
Some distributions have a package for `glava`. If your distribution is not listed please use the compilation instructions above.
|
||||||
|
|
||||||
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.
|
- 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.
|
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), run it with the `--desktop` flag and then position it accordingly with `#request setgeometry x y width height` in your `rc.glsl`.
|
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.
|
For more information, see the [main configuration page](https://github.com/jarcode-foss/glava/wiki).
|
||||||
|
|
||||||
## Desktop window compatibility
|
## Desktop window compatibility
|
||||||
|
|
||||||
@@ -51,16 +70,18 @@ GLava aims to be compatible with _most_ EWMH compliant window managers. Below is
|
|||||||
| WM | ! | Details
|
| WM | ! | Details
|
||||||
| :---: | --- | --- |
|
| :---: | --- | --- |
|
||||||
| Mutter (GNOME, Budgie) |  | `"native"` (default) opacity should be used
|
| Mutter (GNOME, Budgie) |  | `"native"` (default) opacity should be used
|
||||||
| KWin (KDE) |  | "Show Desktop" [temporarily hides GLava](https://github.com/wacossusca34/glava/issues/4#issuecomment-419729184)
|
| KWin (KDE) |  | "Show Desktop" [temporarily hides GLava](https://github.com/jarcode-foss/glava/issues/4#issuecomment-419729184)
|
||||||
| Openbox (LXDE or standalone) |  | No issues
|
| Openbox (LXDE or standalone) |  | No issues
|
||||||
| Xfwm (XFCE) |  | No issues
|
| Xfwm (XFCE) |  | No issues
|
||||||
| Fluxbox |  | No issues
|
| Fluxbox |  | No issues
|
||||||
| IceWM |  | No issues
|
| IceWM |  | No issues
|
||||||
| Bspwm |  | No issues
|
| Bspwm |  | No issues
|
||||||
|
| SpectrWM |
|
||||||
| Herbstluftwm |  | `hc rule windowtype~'_NET_WM_WINDOW_TYPE_DESKTOP' manage=off` can be used to unmanage desktop windows
|
| Herbstluftwm |  | `hc rule windowtype~'_NET_WM_WINDOW_TYPE_DESKTOP' manage=off` can be used to unmanage desktop windows
|
||||||
| Unity |  | No issues
|
| Unity |  | No issues
|
||||||
| AwesomeWM |  | Defaults to unmanaged
|
| AwesomeWM |  | Defaults to unmanaged
|
||||||
| i3 (and i3-gaps) |  | Defaults to unmanaged
|
| i3 (and i3-gaps) |  | Defaults to unmanaged
|
||||||
|
| spectrwm |  | Defaults to unmanaged
|
||||||
| EXWM |  | EXWM does not have a desktop, and forces window decorations
|
| EXWM |  | EXWM does not have a desktop, and forces window decorations
|
||||||
| Enlightenment |  | Needs testing
|
| Enlightenment |  | Needs testing
|
||||||
| Xmonad |  | Needs testing
|
| Xmonad |  | Needs testing
|
||||||
@@ -68,6 +89,37 @@ GLava aims to be compatible with _most_ EWMH compliant window managers. Below is
|
|||||||
|
|
||||||
Note that some WMs listed without issues have specific overrides when using the `--desktop` flag. See `shaders/env_*.glsl` files for details.
|
Note that some WMs listed without issues have specific overrides when using the `--desktop` flag. See `shaders/env_*.glsl` files for details.
|
||||||
|
|
||||||
|
## Reading from MPD's FIFO output
|
||||||
|
|
||||||
|
Add the following to your `~/.config/mpd.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
## Licensing
|
||||||
|
|
||||||
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:
|
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:
|
||||||
@@ -81,12 +133,20 @@ The below copyright notice applies for the original versions of these files:
|
|||||||
|
|
||||||
`Copyright (c) 2015 Karl Stavestrand <karl@stavestrand.no>`
|
`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:
|
The below copyright applies for the modifications to the files listed above, and the remaining sources in the repository:
|
||||||
|
|
||||||
`Copyright (c) 2017 Levi Webb`
|
`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 most of the Xlib-specific code and placed it into `xwin.c` if anyone decides they wish to attempt at a port.
|
|
||||||
|
|||||||
86
fifo.c
86
fifo.c
@@ -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
17
fifo.h
@@ -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);
|
|
||||||
10
glad_generate.sh
Executable file
10
glad_generate.sh
Executable 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
26
glava-cli/cli.c
Normal 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
320
glava-config/config.lua
Normal 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
109
glava-config/entry.c
Normal 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
79
glava-config/main.lua
Normal 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
38
glava-config/mappings.lua
Normal 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
68
glava-config/utils.lua
Normal 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
984
glava-config/window.lua
Normal 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
263
glava-obs/entry.c
Normal 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;
|
||||||
|
}
|
||||||
321
glava.c
321
glava.c
@@ -1,321 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <signal.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"
|
|
||||||
#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
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#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"
|
|
||||||
"-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"
|
|
||||||
"-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"
|
|
||||||
"-V, --version print application version and exit\n"
|
|
||||||
"\n"
|
|
||||||
GLAVA_VERSION_STRING "\n"
|
|
||||||
" -- Copyright (C) 2017 Levi Webb\n";
|
|
||||||
|
|
||||||
static const char* opt_str = "dhvVe:Cm:b:";
|
|
||||||
static struct option p_opts[] = {
|
|
||||||
{"help", no_argument, 0, 'h'},
|
|
||||||
{"verbose", no_argument, 0, 'v'},
|
|
||||||
{"desktop", no_argument, 0, 'd'},
|
|
||||||
{"entry", required_argument, 0, 'e'},
|
|
||||||
{"force-mod", required_argument, 0, 'm'},
|
|
||||||
{"copy-config", no_argument, 0, 'C'},
|
|
||||||
{"backend", required_argument, 0, 'b'},
|
|
||||||
{"version", no_argument, 0, 'V'},
|
|
||||||
{0, 0, 0, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
static renderer* rd = NULL;
|
|
||||||
|
|
||||||
void handle_term(int signum) {
|
|
||||||
if (rd->alive) {
|
|
||||||
puts("\nInterrupt recieved, closing...");
|
|
||||||
rd->alive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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* backend = NULL;
|
|
||||||
const char* system_shader_paths[] = { user_path, install_path, NULL };
|
|
||||||
bool verbose = false, copy_mode = false, desktop = 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 'e': entry = optarg; break;
|
|
||||||
case 'm': force = optarg; break;
|
|
||||||
case 'b': backend = optarg; break;
|
|
||||||
case '?': exit(EXIT_FAILURE); break;
|
|
||||||
case 'V':
|
|
||||||
puts(GLAVA_VERSION_STRING);
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case 'h':
|
|
||||||
printf(help_str, argc > 0 ? argv[0] : "glava");
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy_mode) {
|
|
||||||
copy_cfg(install_path, user_path, verbose);
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
rd = rd_new(system_shader_paths, entry, force, backend, desktop);
|
|
||||||
|
|
||||||
struct sigaction action = { .sa_handler = handle_term };
|
|
||||||
sigaction(SIGTERM, &action, NULL);
|
|
||||||
sigaction(SIGINT, &action, NULL);
|
|
||||||
|
|
||||||
float b0[rd->bufsize_request], b1[rd->bufsize_request];
|
|
||||||
size_t t;
|
|
||||||
for (t = 0; t < rd->bufsize_request; ++t) {
|
|
||||||
b0[t] = 0.0F;
|
|
||||||
b1[t] = 0.0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct audio_data audio = {
|
|
||||||
.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
|
|
||||||
};
|
|
||||||
if (!audio.source) {
|
|
||||||
get_pulse_default_sink(&audio);
|
|
||||||
printf("Using default PulseAudio sink: %s\n", audio.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_t thread;
|
|
||||||
pthread_create(&thread, NULL, input_pulse, (void*) &audio);
|
|
||||||
|
|
||||||
float lb[rd->bufsize_request], rb[rd->bufsize_request];
|
|
||||||
while (rd->alive) {
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (!rd_update(rd, lb, rb, rd->bufsize_request, modified)) {
|
|
||||||
/* Sleep for 50ms and then attempt to render again */
|
|
||||||
struct timespec tv = {
|
|
||||||
.tv_sec = 0, .tv_nsec = 50 * 1000000
|
|
||||||
};
|
|
||||||
nanosleep(&tv, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audio.terminate = 1;
|
|
||||||
int return_status;
|
|
||||||
if ((return_status = pthread_join(thread, NULL))) {
|
|
||||||
fprintf(stderr, "Failed to join with audio thread: %s\n", strerror(return_status));
|
|
||||||
}
|
|
||||||
|
|
||||||
free(audio.source);
|
|
||||||
rd_destroy(rd);
|
|
||||||
}
|
|
||||||
129
glava/fifo.c
Normal file
129
glava/fifo.c
Normal 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
46
glava/fifo.h
Normal 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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
OpenGL loader generated by glad 0.1.24a0 on Mon Oct 8 17:24:38 2018.
|
OpenGL loader generated by glad 0.1.24a0 on Tue Sep 10 15:02:41 2019.
|
||||||
|
|
||||||
Language/Generator: C/C++
|
Language/Generator: C/C++
|
||||||
Specification: gl
|
Specification: gl
|
||||||
@@ -8,15 +8,16 @@
|
|||||||
Profile: compatibility
|
Profile: compatibility
|
||||||
Extensions:
|
Extensions:
|
||||||
GL_EXT_framebuffer_multisample,
|
GL_EXT_framebuffer_multisample,
|
||||||
GL_EXT_texture_filter_anisotropic
|
GL_EXT_texture_filter_anisotropic,
|
||||||
|
GL_NV_texture_barrier
|
||||||
Loader: True
|
Loader: True
|
||||||
Local files: True
|
Local files: True
|
||||||
Omit khrplatform: False
|
Omit khrplatform: False
|
||||||
|
|
||||||
Commandline:
|
Commandline:
|
||||||
--profile="compatibility" --api="gl=4.6" --generator="c" --spec="gl" --local-files --extensions="GL_EXT_framebuffer_multisample,GL_EXT_texture_filter_anisotropic"
|
--profile="compatibility" --api="gl=4.6" --generator="c" --spec="gl" --local-files --extensions="GL_EXT_framebuffer_multisample,GL_EXT_texture_filter_anisotropic,GL_NV_texture_barrier"
|
||||||
Online:
|
Online:
|
||||||
http://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.6&extensions=GL_EXT_framebuffer_multisample&extensions=GL_EXT_texture_filter_anisotropic
|
http://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.6&extensions=GL_EXT_framebuffer_multisample&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_NV_texture_barrier
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -88,59 +89,21 @@ GLAPI int gladLoadGL(void);
|
|||||||
|
|
||||||
GLAPI int gladLoadGLLoader(GLADloadproc);
|
GLAPI int gladLoadGLLoader(GLADloadproc);
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "khrplatform.h"
|
#include "khrplatform.h"
|
||||||
#ifndef GLEXT_64_TYPES_DEFINED
|
|
||||||
/* This code block is duplicated in glxext.h, so must be protected */
|
|
||||||
#define GLEXT_64_TYPES_DEFINED
|
|
||||||
/* Define int32_t, int64_t, and uint64_t types for UST/MSC */
|
|
||||||
/* (as used in the GL_EXT_timer_query extension). */
|
|
||||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
|
||||||
#include <inttypes.h>
|
|
||||||
#elif defined(__sun__) || defined(__digital__)
|
|
||||||
#include <inttypes.h>
|
|
||||||
#if defined(__STDC__)
|
|
||||||
#if defined(__arch64__) || defined(_LP64)
|
|
||||||
typedef long int int64_t;
|
|
||||||
typedef unsigned long int uint64_t;
|
|
||||||
#else
|
|
||||||
typedef long long int int64_t;
|
|
||||||
typedef unsigned long long int uint64_t;
|
|
||||||
#endif /* __arch64__ */
|
|
||||||
#endif /* __STDC__ */
|
|
||||||
#elif defined( __VMS ) || defined(__sgi)
|
|
||||||
#include <inttypes.h>
|
|
||||||
#elif defined(__SCO__) || defined(__USLC__)
|
|
||||||
#include <stdint.h>
|
|
||||||
#elif defined(__UNIXOS2__) || defined(__SOL64__)
|
|
||||||
typedef long int int32_t;
|
|
||||||
typedef long long int int64_t;
|
|
||||||
typedef unsigned long long int uint64_t;
|
|
||||||
#elif defined(_WIN32) && defined(__GNUC__)
|
|
||||||
#include <stdint.h>
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
typedef __int32 int32_t;
|
|
||||||
typedef __int64 int64_t;
|
|
||||||
typedef unsigned __int64 uint64_t;
|
|
||||||
#else
|
|
||||||
/* Fallback if nothing above works */
|
|
||||||
#include <inttypes.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
typedef unsigned int GLenum;
|
typedef unsigned int GLenum;
|
||||||
typedef unsigned char GLboolean;
|
typedef unsigned char GLboolean;
|
||||||
typedef unsigned int GLbitfield;
|
typedef unsigned int GLbitfield;
|
||||||
typedef void GLvoid;
|
typedef void GLvoid;
|
||||||
typedef signed char GLbyte;
|
typedef khronos_int8_t GLbyte;
|
||||||
typedef short GLshort;
|
typedef khronos_uint8_t GLubyte;
|
||||||
|
typedef khronos_int16_t GLshort;
|
||||||
|
typedef khronos_uint16_t GLushort;
|
||||||
typedef int GLint;
|
typedef int GLint;
|
||||||
typedef int GLclampx;
|
|
||||||
typedef unsigned char GLubyte;
|
|
||||||
typedef unsigned short GLushort;
|
|
||||||
typedef unsigned int GLuint;
|
typedef unsigned int GLuint;
|
||||||
|
typedef khronos_int32_t GLclampx;
|
||||||
typedef int GLsizei;
|
typedef int GLsizei;
|
||||||
typedef float GLfloat;
|
typedef khronos_float_t GLfloat;
|
||||||
typedef float GLclampf;
|
typedef khronos_float_t GLclampf;
|
||||||
typedef double GLdouble;
|
typedef double GLdouble;
|
||||||
typedef double GLclampd;
|
typedef double GLclampd;
|
||||||
typedef void *GLeglClientBufferEXT;
|
typedef void *GLeglClientBufferEXT;
|
||||||
@@ -152,25 +115,17 @@ typedef void *GLhandleARB;
|
|||||||
#else
|
#else
|
||||||
typedef unsigned int GLhandleARB;
|
typedef unsigned int GLhandleARB;
|
||||||
#endif
|
#endif
|
||||||
typedef unsigned short GLhalfARB;
|
typedef khronos_uint16_t GLhalf;
|
||||||
typedef unsigned short GLhalf;
|
typedef khronos_uint16_t GLhalfARB;
|
||||||
typedef GLint GLfixed;
|
typedef khronos_int32_t GLfixed;
|
||||||
typedef khronos_intptr_t GLintptr;
|
typedef khronos_intptr_t GLintptr;
|
||||||
|
typedef khronos_intptr_t GLintptrARB;
|
||||||
typedef khronos_ssize_t GLsizeiptr;
|
typedef khronos_ssize_t GLsizeiptr;
|
||||||
typedef int64_t GLint64;
|
typedef khronos_ssize_t GLsizeiptrARB;
|
||||||
typedef uint64_t GLuint64;
|
typedef khronos_int64_t GLint64;
|
||||||
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
|
typedef khronos_int64_t GLint64EXT;
|
||||||
typedef long GLintptrARB;
|
typedef khronos_uint64_t GLuint64;
|
||||||
#else
|
typedef khronos_uint64_t GLuint64EXT;
|
||||||
typedef ptrdiff_t GLintptrARB;
|
|
||||||
#endif
|
|
||||||
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
|
|
||||||
typedef long GLsizeiptrARB;
|
|
||||||
#else
|
|
||||||
typedef ptrdiff_t GLsizeiptrARB;
|
|
||||||
#endif
|
|
||||||
typedef int64_t GLint64EXT;
|
|
||||||
typedef uint64_t GLuint64EXT;
|
|
||||||
typedef struct __GLsync *GLsync;
|
typedef struct __GLsync *GLsync;
|
||||||
struct _cl_context;
|
struct _cl_context;
|
||||||
struct _cl_event;
|
struct _cl_event;
|
||||||
@@ -5213,6 +5168,13 @@ GLAPI PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMulti
|
|||||||
#define GL_EXT_texture_filter_anisotropic 1
|
#define GL_EXT_texture_filter_anisotropic 1
|
||||||
GLAPI int GLAD_GL_EXT_texture_filter_anisotropic;
|
GLAPI int GLAD_GL_EXT_texture_filter_anisotropic;
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef GL_NV_texture_barrier
|
||||||
|
#define GL_NV_texture_barrier 1
|
||||||
|
GLAPI int GLAD_GL_NV_texture_barrier;
|
||||||
|
typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC)(void);
|
||||||
|
GLAPI PFNGLTEXTUREBARRIERNVPROC glad_glTextureBarrierNV;
|
||||||
|
#define glTextureBarrierNV glad_glTextureBarrierNV
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
577
glava/glava.c
Normal file
577
glava/glava.c
Normal 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
27
glava/glava.h
Normal 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 */
|
||||||
@@ -65,13 +65,15 @@ DECL_WINDOW_HINT_STUB(set_maximized);
|
|||||||
|
|
||||||
extern struct gl_wcb wcb_glfw;
|
extern struct gl_wcb wcb_glfw;
|
||||||
|
|
||||||
|
static bool offscreen(void) { return false; }
|
||||||
|
|
||||||
static void* create_and_bind(const char* name, const char* class,
|
static void* create_and_bind(const char* name, const char* class,
|
||||||
const char* type, const char** states,
|
const char* type, const char** states,
|
||||||
size_t states_sz,
|
size_t states_sz,
|
||||||
int d, int h,
|
int d, int h,
|
||||||
int x, int y,
|
int x, int y,
|
||||||
int version_major, int version_minor,
|
int version_major, int version_minor,
|
||||||
bool clickthrough) {
|
bool clickthrough, bool offscreen) {
|
||||||
|
|
||||||
GLFWwindow* w;
|
GLFWwindow* w;
|
||||||
|
|
||||||
@@ -93,7 +95,10 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
glfwSetWindowPos(w, x, y);
|
glfwSetWindowPos(w, x, y);
|
||||||
glfwMakeContextCurrent(w);
|
glfwMakeContextCurrent(w);
|
||||||
|
|
||||||
gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
|
if (!glad_instantiated) {
|
||||||
|
gladLoadGL();
|
||||||
|
glad_instantiated = true;
|
||||||
|
}
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
738
glava/glsl_ext.c
Normal file
738
glava/glsl_ext.c
Normal 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
68
glava/glsl_ext.h
Normal 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
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "xwin.h"
|
#include "xwin.h"
|
||||||
|
|
||||||
typedef struct __GLXcontextRec *GLXContext;
|
typedef struct __GLXcontextRec* GLXContext;
|
||||||
typedef XID GLXPixmap;
|
typedef XID GLXPixmap;
|
||||||
typedef XID GLXDrawable;
|
typedef XID GLXDrawable;
|
||||||
|
|
||||||
typedef void (*__GLXextFuncPtr)(void);
|
typedef void (*__GLXextFuncPtr)(void);
|
||||||
|
|
||||||
/* GLX 1.3 and later */
|
/* GLX 1.3 and later */
|
||||||
typedef struct __GLXFBConfigRec *GLXFBConfig;
|
typedef struct __GLXFBConfigRec* GLXFBConfig;
|
||||||
typedef XID GLXFBConfigID;
|
typedef XID GLXFBConfigID;
|
||||||
typedef XID GLXContextID;
|
typedef XID GLXContextID;
|
||||||
typedef XID GLXWindow;
|
typedef XID GLXWindow;
|
||||||
@@ -59,7 +59,6 @@ typedef XID GLXPbuffer;
|
|||||||
#define GLX_ACCUM_BLUE_SIZE 16
|
#define GLX_ACCUM_BLUE_SIZE 16
|
||||||
#define GLX_ACCUM_ALPHA_SIZE 17
|
#define GLX_ACCUM_ALPHA_SIZE 17
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Error codes returned by glXGetConfig:
|
* Error codes returned by glXGetConfig:
|
||||||
*/
|
*/
|
||||||
@@ -162,6 +161,7 @@ __GLXextFuncPtr (*glXGetProcAddressARB) (const GLubyte *);
|
|||||||
void (*glXSwapBuffers) (Display* dpy, GLXDrawable drawable);
|
void (*glXSwapBuffers) (Display* dpy, GLXDrawable drawable);
|
||||||
void (*glXDestroyContext) (Display* dpy, GLXContext ctx);
|
void (*glXDestroyContext) (Display* dpy, GLXContext ctx);
|
||||||
Bool (*glXQueryVersion) (Display* dpy, int* major, int* minor);
|
Bool (*glXQueryVersion) (Display* dpy, int* major, int* minor);
|
||||||
|
GLXPixmap (*glXCreateGLXPixmap) (Display* dpy, XVisualInfo* vis, Pixmap pixmap);
|
||||||
|
|
||||||
extern struct gl_wcb wcb_glx;
|
extern struct gl_wcb wcb_glx;
|
||||||
|
|
||||||
@@ -175,13 +175,51 @@ struct glxwin {
|
|||||||
Window w;
|
Window w;
|
||||||
GLXContext context;
|
GLXContext context;
|
||||||
double time;
|
double time;
|
||||||
bool should_close, should_render, bg_changed, clickthrough;
|
bool should_close, should_render, bg_changed, clickthrough, offscreen;
|
||||||
char override_state;
|
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 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) {
|
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);
|
display = XOpenDisplay(NULL);
|
||||||
if (!display) {
|
if (!display) {
|
||||||
fprintf(stderr, "XOpenDisplay(): could not establish connection to X11 server\n");
|
fprintf(stderr, "XOpenDisplay(): could not establish connection to X11 server\n");
|
||||||
@@ -193,29 +231,17 @@ static void init(void) {
|
|||||||
maximized = false;
|
maximized = false;
|
||||||
transparent = false;
|
transparent = false;
|
||||||
|
|
||||||
void* hgl = dlopen("libGL.so", RTLD_LAZY);
|
void* hgl = NULL;
|
||||||
void* hglx = dlopen("libGLX.so", RTLD_LAZY);
|
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 && !hglx) {
|
if (!hgl) {
|
||||||
fprintf(stderr, "Failed to load GLX functions (libGL and libGLX do not exist!)\n");
|
fprintf(stderr, "Failed to load GLX functions (libGL and libGLX do not exist!)\n");
|
||||||
exit(EXIT_FAILURE);
|
glava_abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Depending on the graphics driver, the GLX functions that we need may either be in libGL or
|
#define resolve(name) do { name = (typeof(name)) resolve_f(#name, hgl); } while (0)
|
||||||
libGLX. */
|
#define intern(name, only_if_exists) \
|
||||||
void* resolve_f(const char* symbol) {
|
|
||||||
void* s = NULL;
|
|
||||||
if (hgl) s = dlsym(hgl, symbol);
|
|
||||||
if (!s && hglx) s = dlsym(hglx, symbol);
|
|
||||||
if (!s) {
|
|
||||||
fprintf(stderr, "Failed to resolve GLX symbol: `%s`\n", symbol);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define resolve(name) do { name = (typeof(name)) resolve_f(#name); } while (0)
|
|
||||||
#define intern(name, only_if_exists) \
|
|
||||||
do { ATOM_##name = XInternAtom(display, #name, only_if_exists); } while (0)
|
do { ATOM_##name = XInternAtom(display, #name, only_if_exists); } while (0)
|
||||||
|
|
||||||
resolve(glXChooseFBConfig);
|
resolve(glXChooseFBConfig);
|
||||||
@@ -227,6 +253,7 @@ static void init(void) {
|
|||||||
resolve(glXSwapBuffers);
|
resolve(glXSwapBuffers);
|
||||||
resolve(glXDestroyContext);
|
resolve(glXDestroyContext);
|
||||||
resolve(glXQueryVersion);
|
resolve(glXQueryVersion);
|
||||||
|
resolve(glXCreateGLXPixmap);
|
||||||
|
|
||||||
intern(_MOTIF_WM_HINTS, false);
|
intern(_MOTIF_WM_HINTS, false);
|
||||||
intern(WM_DELETE_WINDOW, true);
|
intern(WM_DELETE_WINDOW, true);
|
||||||
@@ -254,50 +281,76 @@ static void apply_decorations(Window w) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
static void apply_clickthrough(struct glxwin* w) {
|
||||||
if (w->clickthrough) {
|
if (w->clickthrough) {
|
||||||
int ignored;
|
int ignored;
|
||||||
if (XShapeQueryExtension(display, &ignored, &ignored)) {
|
if (XShapeQueryExtension(display, &ignored, &ignored)) {
|
||||||
Region region;
|
Window root = DefaultRootWindow(display);
|
||||||
if ((region = XCreateRegion())) {
|
Window win = w->w;
|
||||||
XShapeCombineRegion(display, w->w, ShapeInput, 0, 0, region, ShapeSet);
|
while (win != None) {
|
||||||
XDestroyRegion(region);
|
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) {
|
static void process_events(struct glxwin* w) {
|
||||||
while (XPending(display) > 0) {
|
while (XPending(display) > 0) {
|
||||||
XEvent ev;
|
XEvent ev;
|
||||||
XNextEvent(display, &ev);
|
XNextEvent(display, &ev);
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case ClientMessage:
|
case ClientMessage:
|
||||||
if (ev.xclient.message_type == ATOM_WM_PROTOCOLS
|
if (ev.xclient.message_type == ATOM_WM_PROTOCOLS
|
||||||
&& ev.xclient.data.l[0] == ATOM_WM_DELETE_WINDOW) {
|
&& ev.xclient.data.l[0] == ATOM_WM_DELETE_WINDOW) {
|
||||||
w->should_close = true;
|
w->should_close = true;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case VisibilityNotify:
|
|
||||||
switch (ev.xvisibility.state) {
|
|
||||||
case VisibilityFullyObscured:
|
|
||||||
w->should_render = false;
|
|
||||||
break;
|
break;
|
||||||
case VisibilityUnobscured:
|
case MapNotify:
|
||||||
case VisibilityPartiallyObscured:
|
apply_clickthrough(w);
|
||||||
w->should_render = true;
|
XFlush(display);
|
||||||
break;
|
break;
|
||||||
default:
|
case VisibilityNotify:
|
||||||
fprintf(stderr, "Invalid VisibilityNotify event state (%d)\n", ev.xvisibility.state);
|
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;
|
break;
|
||||||
}
|
case PropertyNotify:
|
||||||
break;
|
if (ev.xproperty.atom == ATOM__XROOTPMAP_ID) {
|
||||||
case PropertyNotify:
|
w->bg_changed = true;
|
||||||
if (ev.xproperty.atom == ATOM__XROOTPMAP_ID) {
|
}
|
||||||
w->bg_changed = true;
|
break;
|
||||||
}
|
default: break;
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,15 +361,21 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
int d, int h,
|
int d, int h,
|
||||||
int x, int y,
|
int x, int y,
|
||||||
int version_major, int version_minor,
|
int version_major, int version_minor,
|
||||||
bool clickthrough) {
|
bool clickthrough, bool off) {
|
||||||
|
|
||||||
|
/* Assume offscreen rendering if hook has been used */
|
||||||
|
if (offscreen())
|
||||||
|
off = true;
|
||||||
|
|
||||||
struct glxwin* w = malloc(sizeof(struct glxwin));
|
struct glxwin* w = malloc(sizeof(struct glxwin));
|
||||||
*w = (struct glxwin) {
|
*w = (struct glxwin) {
|
||||||
.override_state = '\0',
|
.override_state = '\0',
|
||||||
.time = 0.0D,
|
.time = 0.0,
|
||||||
.should_close = false,
|
.should_close = false,
|
||||||
.should_render = true,
|
.should_render = true,
|
||||||
.bg_changed = false,
|
.bg_changed = false,
|
||||||
.clickthrough = false
|
.clickthrough = false,
|
||||||
|
.offscreen = off
|
||||||
};
|
};
|
||||||
|
|
||||||
XVisualInfo* vi;
|
XVisualInfo* vi;
|
||||||
@@ -331,7 +390,7 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
"\nGLX extension version mismatch on the current display (1.4+ required, %d.%d available)\n"
|
"\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",
|
"This is usually due to an outdated X server or graphics drivers.\n\n",
|
||||||
glx_minor, glx_major);
|
glx_minor, glx_major);
|
||||||
exit(EXIT_FAILURE);
|
glava_abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gl_attrs[] = {
|
static int gl_attrs[] = {
|
||||||
@@ -362,7 +421,7 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
"glXChooseFBConfig(): failed with attrs "
|
"glXChooseFBConfig(): failed with attrs "
|
||||||
"(GLX_CONTEXT_MAJOR_VERSION_ARB, GLX_CONTEXT_MINOR_VERSION_ARB)\n\n",
|
"(GLX_CONTEXT_MAJOR_VERSION_ARB, GLX_CONTEXT_MINOR_VERSION_ARB)\n\n",
|
||||||
version_major, version_minor);
|
version_major, version_minor);
|
||||||
exit(EXIT_FAILURE);
|
glava_abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int t = 0; t < fb_sz; ++t) {
|
for (int t = 0; t < fb_sz; ++t) {
|
||||||
@@ -433,8 +492,6 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
|
|
||||||
apply_decorations(w->w);
|
apply_decorations(w->w);
|
||||||
|
|
||||||
XFree(vi);
|
|
||||||
|
|
||||||
XStoreName(display, w->w, name);
|
XStoreName(display, w->w, name);
|
||||||
|
|
||||||
XSetWMProtocols(display, w->w, &ATOM_WM_DELETE_WINDOW, 1);
|
XSetWMProtocols(display, w->w, &ATOM_WM_DELETE_WINDOW, 1);
|
||||||
@@ -455,15 +512,25 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(w->context = glXCreateContextAttribsARB(display, config, 0, True, context_attrs))) {
|
if (!(w->context = glXCreateContextAttribsARB(display, config, sharelist_assigned ? sharelist_ctx : 0, True, context_attrs))) {
|
||||||
fprintf(stderr, "glXCreateContextAttribsARB(): failed\n");
|
fprintf(stderr, "glXCreateContextAttribsARB(): failed\n");
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
XSync(display, False);
|
XSync(display, False);
|
||||||
|
|
||||||
glXMakeCurrent(display, w->w, w->context);
|
if (w->offscreen) {
|
||||||
gladLoadGL();
|
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();
|
GLXDrawable drawable = glXGetCurrentDrawable();
|
||||||
|
|
||||||
@@ -472,6 +539,8 @@ static void* create_and_bind(const char* name, const char* class,
|
|||||||
if (!transparent)
|
if (!transparent)
|
||||||
XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask);
|
XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask);
|
||||||
|
|
||||||
|
XFree(vi);
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,13 +581,14 @@ static void set_geometry(struct glxwin* w, int x, int y, int d, int h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void set_visible(struct glxwin* w, bool visible) {
|
static void set_visible(struct glxwin* w, bool visible) {
|
||||||
|
if (w->offscreen)
|
||||||
|
return;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
XMapWindow(display, w->w);
|
XMapWindow(display, w->w);
|
||||||
apply_clickthrough(w);
|
|
||||||
switch (w->override_state) {
|
switch (w->override_state) {
|
||||||
case '+': XRaiseWindow(display, w->w); break;
|
case '+': XRaiseWindow(display, w->w); break;
|
||||||
case '-': XLowerWindow(display, w->w); break;
|
case '-': XLowerWindow(display, w->w); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
XFlush(display);
|
XFlush(display);
|
||||||
}
|
}
|
||||||
@@ -528,6 +598,8 @@ static void set_visible(struct glxwin* w, bool visible) {
|
|||||||
static bool should_close (struct glxwin* w) { return w->should_close; }
|
static bool should_close (struct glxwin* w) { return w->should_close; }
|
||||||
static bool bg_changed (struct glxwin* w) { return w->bg_changed; }
|
static bool bg_changed (struct glxwin* w) { return w->bg_changed; }
|
||||||
static bool should_render(struct glxwin* w) {
|
static bool should_render(struct glxwin* w) {
|
||||||
|
if (w->offscreen)
|
||||||
|
return true;
|
||||||
/* For nearly all window managers, windows are 'minimized' by unmapping parent windows.
|
/* 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
|
VisibilityNotify events are not sent in these instances, so we have to read window
|
||||||
attributes to see if our window isn't viewable. */
|
attributes to see if our window isn't viewable. */
|
||||||
@@ -538,7 +610,10 @@ static bool should_render(struct glxwin* w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void swap_buffers(struct glxwin* w) {
|
static void swap_buffers(struct glxwin* w) {
|
||||||
glXSwapBuffers(display, w->w);
|
if (w->offscreen)
|
||||||
|
glXSwapBuffers(display, w->off_glxpm);
|
||||||
|
else
|
||||||
|
glXSwapBuffers(display, w->w);
|
||||||
process_events(w);
|
process_events(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,13 +634,19 @@ static double get_timert(void) {
|
|||||||
if (clock_gettime(CLOCK_REALTIME, &tv)) {
|
if (clock_gettime(CLOCK_REALTIME, &tv)) {
|
||||||
fprintf(stderr, "clock_gettime(CLOCK_REALTIME, ...): %s\n", strerror(errno));
|
fprintf(stderr, "clock_gettime(CLOCK_REALTIME, ...): %s\n", strerror(errno));
|
||||||
}
|
}
|
||||||
return (double) tv.tv_sec + ((double) tv.tv_nsec / 1000000000.0D);
|
return (double) tv.tv_sec + ((double) tv.tv_nsec / 1000000000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void destroy(struct glxwin* w) {
|
static void destroy(struct glxwin* w) {
|
||||||
glXMakeCurrent(display, None, NULL); /* release context */
|
glXMakeCurrent(display, None, NULL); /* release context */
|
||||||
glXDestroyContext(display, w->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);
|
XDestroyWindow(display, w->w);
|
||||||
|
free(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void terminate(void) {
|
static void terminate(void) {
|
||||||
@@ -35,16 +35,11 @@ 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) {
|
||||||
|
|
||||||
/* Ensure loop is ready */
|
/* Ensure loop is ready */
|
||||||
switch (pa_context_get_state(pulseaudio_context))
|
switch (pa_context_get_state(pulseaudio_context)) {
|
||||||
{
|
case PA_CONTEXT_UNCONNECTED: break;
|
||||||
case PA_CONTEXT_UNCONNECTED:
|
case PA_CONTEXT_CONNECTING: break;
|
||||||
break;
|
case PA_CONTEXT_AUTHORIZING: break;
|
||||||
case PA_CONTEXT_CONNECTING:
|
case PA_CONTEXT_SETTING_NAME: break;
|
||||||
break;
|
|
||||||
case PA_CONTEXT_AUTHORIZING:
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_SETTING_NAME:
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_READY: /* extract default sink name */
|
case PA_CONTEXT_READY: /* extract default sink name */
|
||||||
pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, userdata));
|
pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, userdata));
|
||||||
break;
|
break;
|
||||||
@@ -55,11 +50,13 @@ static void pulseaudio_context_state_callback(pa_context* pulseaudio_context, vo
|
|||||||
case PA_CONTEXT_TERMINATED:
|
case PA_CONTEXT_TERMINATED:
|
||||||
pa_mainloop_quit(m_pulseaudio_mainloop, 0);
|
pa_mainloop_quit(m_pulseaudio_mainloop, 0);
|
||||||
break;
|
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_mainloop_api* mainloop_api;
|
||||||
pa_context* pulseaudio_context;
|
pa_context* pulseaudio_context;
|
||||||
@@ -94,8 +91,6 @@ void get_pulse_default_sink(struct audio_data* audio) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pa_mainloop_run(m_pulseaudio_mainloop, &ret);
|
pa_mainloop_run(m_pulseaudio_mainloop, &ret);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sample format for native 'float' type */
|
/* Sample format for native 'float' type */
|
||||||
@@ -111,7 +106,7 @@ void get_pulse_default_sink(struct audio_data* audio) {
|
|||||||
#error "Unsupported float format (requires 32 bit IEEE (little or big endian) floating point support)"
|
#error "Unsupported float format (requires 32 bit IEEE (little or big endian) floating point support)"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void* input_pulse(void* data) {
|
static void* entry(void* data) {
|
||||||
struct audio_data* audio = (struct audio_data*) data;
|
struct audio_data* audio = (struct audio_data*) data;
|
||||||
int i, n;
|
int i, n;
|
||||||
size_t ssz = audio->sample_sz;
|
size_t ssz = audio->sample_sz;
|
||||||
@@ -193,3 +188,5 @@ void* input_pulse(void* data) {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AUDIO_ATTACH(pulseaudio);
|
||||||
@@ -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 get_pulse_default_sink(struct audio_data* audio);
|
||||||
void* input_pulse(void* data);
|
void* input_pulse(void* data);
|
||||||
|
|
||||||
|
#endif
|
||||||
2490
glava/render.c
Normal file
2490
glava/render.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,28 +2,70 @@
|
|||||||
#ifndef RENDER_H
|
#ifndef RENDER_H
|
||||||
#define RENDER_H
|
#define RENDER_H
|
||||||
|
|
||||||
struct gl_data;
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include "glava.h"
|
||||||
|
|
||||||
typedef struct renderer {
|
typedef struct glava_renderer {
|
||||||
bool alive, mirror_input;
|
volatile bool alive;
|
||||||
|
bool mirror_input;
|
||||||
size_t bufsize_request, rate_request, samplesize_request;
|
size_t bufsize_request, rate_request, samplesize_request;
|
||||||
char* audio_source_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;
|
struct gl_data* gl;
|
||||||
} renderer;
|
} glava_renderer;
|
||||||
|
|
||||||
struct renderer* rd_new (const char** paths, const char* entry,
|
extern const struct {
|
||||||
const char* force_mod, const char* force_backend,
|
const char* n;
|
||||||
bool auto_desktop);
|
int i;
|
||||||
bool rd_update (struct renderer*, float* lb, float* rb,
|
} 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);
|
size_t bsz, bool modified);
|
||||||
void rd_destroy (struct renderer*);
|
void rd_destroy (struct glava_renderer*);
|
||||||
void rd_time (struct renderer*);
|
void rd_time (struct glava_renderer*);
|
||||||
void* rd_get_impl_window(struct renderer*);
|
void* rd_get_impl_window(struct glava_renderer*);
|
||||||
struct gl_wcb* rd_get_wcb (struct renderer*);
|
struct gl_wcb* rd_get_wcb (struct glava_renderer*);
|
||||||
|
|
||||||
/* gl_wcb - OpenGL Window Creation Backend interface */
|
/* gl_wcb - OpenGL Window Creation Backend interface */
|
||||||
struct gl_wcb {
|
struct gl_wcb {
|
||||||
const char* name;
|
const char* name;
|
||||||
|
bool (*offscreen) (void);
|
||||||
void (*init) (void);
|
void (*init) (void);
|
||||||
void* (*create_and_bind)(const char* name, const char* class,
|
void* (*create_and_bind)(const char* name, const char* class,
|
||||||
const char* type, const char** states,
|
const char* type, const char** states,
|
||||||
@@ -31,7 +73,7 @@ struct gl_wcb {
|
|||||||
int w, int h,
|
int w, int h,
|
||||||
int x, int y,
|
int x, int y,
|
||||||
int version_major, int version_minor,
|
int version_major, int version_minor,
|
||||||
bool clickthrough);
|
bool clickthrough, bool offscreen);
|
||||||
bool (*should_close) (void* ptr);
|
bool (*should_close) (void* ptr);
|
||||||
bool (*should_render) (void* ptr);
|
bool (*should_render) (void* ptr);
|
||||||
bool (*bg_changed) (void* ptr);
|
bool (*bg_changed) (void* ptr);
|
||||||
@@ -67,6 +109,7 @@ struct gl_wcb {
|
|||||||
#define WCB_ATTACH(B, N) \
|
#define WCB_ATTACH(B, N) \
|
||||||
struct gl_wcb N = { \
|
struct gl_wcb N = { \
|
||||||
.name = B, \
|
.name = B, \
|
||||||
|
WCB_FUNC(offscreen), \
|
||||||
WCB_FUNC(init), \
|
WCB_FUNC(init), \
|
||||||
WCB_FUNC(create_and_bind), \
|
WCB_FUNC(create_and_bind), \
|
||||||
WCB_FUNC(should_close), \
|
WCB_FUNC(should_close), \
|
||||||
@@ -7,11 +7,15 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <sys/ipc.h>
|
#include <sys/ipc.h>
|
||||||
#include <sys/shm.h>
|
#include <sys/shm.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/Xutil.h>
|
#include <X11/Xutil.h>
|
||||||
@@ -25,6 +29,79 @@
|
|||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "xwin.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 */
|
/* Note: currently unused */
|
||||||
Window* __attribute__ ((unused)) xwin_get_desktop_layer(struct gl_wcb* wcb) {
|
Window* __attribute__ ((unused)) xwin_get_desktop_layer(struct gl_wcb* wcb) {
|
||||||
static Window desktop;
|
static Window desktop;
|
||||||
@@ -82,7 +159,7 @@ void xwin_wait_for_wm(void) {
|
|||||||
bool exists = false;
|
bool exists = false;
|
||||||
struct timespec tv = { .tv_sec = 0, .tv_nsec = 50 * 1000000 };
|
struct timespec tv = { .tv_sec = 0, .tv_nsec = 50 * 1000000 };
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (check == None) {
|
if (check == None) {
|
||||||
check = XInternAtom(d, "_NET_SUPPORTING_WM_CHECK", true);
|
check = XInternAtom(d, "_NET_SUPPORTING_WM_CHECK", true);
|
||||||
}
|
}
|
||||||
@@ -140,6 +217,8 @@ const char* xwin_detect_wm(struct gl_wcb* wcb) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int stub_handler(Display* d, XErrorEvent* e) { return 0; }
|
||||||
|
|
||||||
bool xwin_should_render(struct gl_wcb* wcb, void* impl) {
|
bool xwin_should_render(struct gl_wcb* wcb, void* impl) {
|
||||||
bool ret = true, should_close = false;
|
bool ret = true, should_close = false;
|
||||||
Display* d = wcb->get_x11_display();
|
Display* d = wcb->get_x11_display();
|
||||||
@@ -156,12 +235,10 @@ bool xwin_should_render(struct gl_wcb* wcb, void* impl) {
|
|||||||
unsigned long nitems, bytes_after;
|
unsigned long nitems, bytes_after;
|
||||||
unsigned char* data = NULL;
|
unsigned char* data = NULL;
|
||||||
|
|
||||||
int handler(Display* d, XErrorEvent* e) { return 0; }
|
XSetErrorHandler(stub_handler); /* dummy error handler */
|
||||||
|
|
||||||
XSetErrorHandler(handler); /* dummy error handler */
|
|
||||||
|
|
||||||
if (Success != XGetWindowProperty(d, DefaultRootWindow(d), prop, 0, 1, false, AnyPropertyType,
|
if (Success != XGetWindowProperty(d, DefaultRootWindow(d), prop, 0, 1, false, AnyPropertyType,
|
||||||
&actual_type, &actual_format, &nitems, &bytes_after, &data)) {
|
&actual_type, &actual_format, &nitems, &bytes_after, &data)) {
|
||||||
goto close; /* if an error occurs here, the WM probably isn't EWMH compliant */
|
goto close; /* if an error occurs here, the WM probably isn't EWMH compliant */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +263,7 @@ bool xwin_should_render(struct gl_wcb* wcb, void* impl) {
|
|||||||
ret = false;
|
ret = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close:
|
close:
|
||||||
if (data)
|
if (data)
|
||||||
XFree(data);
|
XFree(data);
|
||||||
if (should_close)
|
if (should_close)
|
||||||
@@ -200,8 +277,8 @@ bool xwin_should_render(struct gl_wcb* wcb, void* impl) {
|
|||||||
for (size_t t = 0; t < sizeof(out) / sizeof(char); ++t) { \
|
for (size_t t = 0; t < sizeof(out) / sizeof(char); ++t) { \
|
||||||
char c = in[t]; \
|
char c = in[t]; \
|
||||||
switch (c) { \
|
switch (c) { \
|
||||||
case 'a' ... 'z': c -= 'a' - 'A'; \
|
case 'a' ... 'z': c -= 'a' - 'A'; \
|
||||||
default: out[t] = c; \
|
default: out[t] = c; \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
@@ -265,7 +342,7 @@ static Drawable get_drawable(Display* d, Window w) {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int xwin_copyglbg(struct renderer* rd, unsigned int tex) {
|
unsigned int xwin_copyglbg(struct glava_renderer* rd, unsigned int tex) {
|
||||||
GLuint texture = (GLuint) tex;
|
GLuint texture = (GLuint) tex;
|
||||||
if (!texture)
|
if (!texture)
|
||||||
glGenTextures(1, &texture);
|
glGenTextures(1, &texture);
|
||||||
@@ -295,7 +372,7 @@ unsigned int xwin_copyglbg(struct renderer* rd, unsigned int tex) {
|
|||||||
if ((shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height,
|
if ((shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height,
|
||||||
IPC_CREAT | 0777)) == -1) {
|
IPC_CREAT | 0777)) == -1) {
|
||||||
fprintf(stderr, "shmget() failed: %s\n", strerror(errno));
|
fprintf(stderr, "shmget() failed: %s\n", strerror(errno));
|
||||||
exit(EXIT_FAILURE);
|
glava_abort();
|
||||||
}
|
}
|
||||||
shminfo.shmaddr = image->data = shmat(shminfo.shmid, 0, 0);
|
shminfo.shmaddr = image->data = shmat(shminfo.shmid, 0, 0);
|
||||||
shminfo.readOnly = false;
|
shminfo.readOnly = false;
|
||||||
@@ -314,37 +391,37 @@ unsigned int xwin_copyglbg(struct renderer* rd, unsigned int tex) {
|
|||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
bool invalid = false, aligned = false;
|
bool invalid = false, aligned = false;
|
||||||
GLenum type;
|
GLenum type = 0;
|
||||||
switch (image->bits_per_pixel) {
|
switch (image->bits_per_pixel) {
|
||||||
case 16:
|
case 16:
|
||||||
switch (image->depth) {
|
switch (image->depth) {
|
||||||
case 12: type = GL_UNSIGNED_SHORT_4_4_4_4; break; /* 12-bit (rare) */
|
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 15: type = GL_UNSIGNED_SHORT_5_5_5_1; break; /* 15-bit, hi-color */
|
||||||
case 16: /* 16-bit, hi-color */
|
case 16: /* 16-bit, hi-color */
|
||||||
type = GL_UNSIGNED_SHORT_5_6_5;
|
type = GL_UNSIGNED_SHORT_5_6_5;
|
||||||
aligned = true;
|
aligned = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
case 32:
|
||||||
break;
|
switch (image->depth) {
|
||||||
case 32:
|
case 24: type = GL_UNSIGNED_BYTE; break; /* 24-bit, true color */
|
||||||
switch (image->depth) {
|
case 30: type = GL_UNSIGNED_INT_10_10_10_2; break; /* 30-bit, deep color */
|
||||||
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:
|
||||||
break;
|
if (image->depth == 48) /* 48-bit deep color */
|
||||||
case 64:
|
type = GL_UNSIGNED_SHORT;
|
||||||
if (image->depth == 48) /* 48-bit deep color */
|
else goto invalid;
|
||||||
type = GL_UNSIGNED_SHORT;
|
break;
|
||||||
else goto invalid;
|
/* >64-bit formats */
|
||||||
break;
|
case 128:
|
||||||
/* >64-bit formats */
|
if (image->depth == 96)
|
||||||
case 128:
|
type = GL_UNSIGNED_INT;
|
||||||
if (image->depth == 96)
|
else goto invalid;
|
||||||
type = GL_UNSIGNED_INT;
|
break;
|
||||||
else goto invalid;
|
default:
|
||||||
break;
|
invalid: invalid = true;
|
||||||
default:
|
|
||||||
invalid: invalid = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* buf;
|
uint8_t* buf;
|
||||||
@@ -389,6 +466,7 @@ unsigned int xwin_copyglbg(struct renderer* rd, unsigned int tex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (image) XDestroyImage(image);
|
if (image) XDestroyImage(image);
|
||||||
|
XFree(info);
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
@@ -4,14 +4,18 @@
|
|||||||
#ifndef XWIN_H
|
#ifndef XWIN_H
|
||||||
#define XWIN_H
|
#define XWIN_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "render.h"
|
||||||
|
|
||||||
typedef unsigned long int Window;
|
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);
|
bool xwin_should_render(struct gl_wcb* wcb, void* impl);
|
||||||
void xwin_wait_for_wm(void);
|
void xwin_wait_for_wm(void);
|
||||||
bool xwin_settype(struct gl_wcb* wcb, void* impl, const char* type);
|
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_setdesktop(struct gl_wcb* wcb, void* impl, unsigned long desktop);
|
||||||
void xwin_addstate(struct gl_wcb* wcb, void* impl, const char* state);
|
void xwin_addstate(struct gl_wcb* wcb, void* impl, const char* state);
|
||||||
unsigned int xwin_copyglbg(struct renderer* rd, unsigned int texture);
|
unsigned int xwin_copyglbg(struct glava_renderer* rd, unsigned int texture);
|
||||||
Window* xwin_get_desktop_layer(struct gl_wcb* wcb);
|
Window* xwin_get_desktop_layer(struct gl_wcb* wcb);
|
||||||
const char* xwin_detect_wm(struct gl_wcb* wcb);
|
const char* xwin_detect_wm(struct gl_wcb* wcb);
|
||||||
|
|
||||||
19
glfft/LICENSE_ORIGINAL
Normal file
19
glfft/LICENSE_ORIGINAL
Normal 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
1125
glfft/glfft.cpp
Normal file
File diff suppressed because it is too large
Load Diff
225
glfft/glfft.hpp
Normal file
225
glfft/glfft.hpp
Normal 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 ¶ms);
|
||||||
|
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 ¶ms);
|
||||||
|
|
||||||
|
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
178
glfft/glfft_common.hpp
Normal 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 ¶ms) 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*>(¶ms)[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GLFFT
|
||||||
|
{
|
||||||
|
|
||||||
|
class ProgramCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Program* find_program(const Parameters ¶meters) const;
|
||||||
|
void insert_program(const Parameters ¶meters, std::unique_ptr<Program> program);
|
||||||
|
size_t cache_size() const { return programs.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<Parameters, std::unique_ptr<Program>> programs;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
6
glfft/glfft_gl_api_headers.hpp
Normal file
6
glfft/glfft_gl_api_headers.hpp
Normal 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"
|
||||||
|
}
|
||||||
310
glfft/glfft_gl_interface.cpp
Normal file
310
glfft/glfft_gl_interface.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
258
glfft/glfft_gl_interface.hpp
Normal file
258
glfft/glfft_gl_interface.hpp
Normal 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
131
glfft/glfft_interface.hpp
Normal 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
600
glfft/glfft_wisdom.cpp
Normal 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
149
glfft/glfft_wisdom.hpp
Normal 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 ¶ms) 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*>(¶ms.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
|
||||||
|
|
||||||
506
glsl_ext.c
506
glsl_ext.c
@@ -1,506 +0,0 @@
|
|||||||
|
|
||||||
#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 "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 += written;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
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 GLSL 1
|
|
||||||
#define MACRO 2
|
|
||||||
#define REQUEST 3
|
|
||||||
#define INCLUDE 4
|
|
||||||
#define COLOR 5
|
|
||||||
|
|
||||||
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, cbuf_idx;
|
|
||||||
size_t line = 1;
|
|
||||||
bool quoted = false, arg_start;
|
|
||||||
char cbuf[9];
|
|
||||||
char** args = NULL;
|
|
||||||
size_t args_sz = 0;
|
|
||||||
|
|
||||||
bool prev_slash = false, comment = false, comment_line = false, prev_asterix = false,
|
|
||||||
prev_escape = false, string = false;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
state = COLOR;
|
|
||||||
cbuf_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 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 = at == '\n' ? LINE_START : GLSL;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
38
glsl_ext.h
38
glsl_ext.h
@@ -1,38 +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);
|
|
||||||
bool ext_parse_color(const char* hex, size_t elem_sz, float** results);
|
|
||||||
202
meson.build
Normal file
202
meson.build
Normal 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
24
meson_options.txt
Normal 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')
|
||||||
BIN
resources/glava.bmp
Normal file
BIN
resources/glava.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/transparent.png
Normal file
BIN
resources/transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 690 B |
@@ -1,32 +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 #262626
|
|
||||||
/* 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
|
|
||||||
/* Alpha channel for bars color */
|
|
||||||
#define ALPHA 0.7
|
|
||||||
/* How strong the gradient changes */
|
|
||||||
#define GRADIENT_POWER 60
|
|
||||||
/* Bar color changes with height */
|
|
||||||
#define GRADIENT (d / GRADIENT_POWER + 1)
|
|
||||||
/* Bar color */
|
|
||||||
#define COLOR (#3366b2 * GRADIENT * ALPHA)
|
|
||||||
/* 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
|
|
||||||
|
|
||||||
34
shaders/glava/bars.glsl
Normal file
34
shaders/glava/bars.glsl
Normal 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
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ uniform ivec2 screen;
|
|||||||
#request uniform "audio_sz" audio_sz
|
#request uniform "audio_sz" audio_sz
|
||||||
uniform int audio_sz;
|
uniform int audio_sz;
|
||||||
|
|
||||||
|
#include "@bars.glsl"
|
||||||
#include ":bars.glsl"
|
#include ":bars.glsl"
|
||||||
|
|
||||||
#request uniform "audio_l" audio_l
|
#request uniform "audio_l" audio_l
|
||||||
@@ -28,6 +29,10 @@ out vec4 fragment;
|
|||||||
#define TWOPI 6.28318530718
|
#define TWOPI 6.28318530718
|
||||||
#define PI 3.14159265359
|
#define PI 3.14159265359
|
||||||
|
|
||||||
|
#if DISABLE_MONO == 1
|
||||||
|
#define _CHANNELS 2
|
||||||
|
#endif
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
#if MIRROR_YX == 0
|
#if MIRROR_YX == 0
|
||||||
@@ -42,20 +47,35 @@ void main() {
|
|||||||
#define AREA_Y gl_FragCoord.x
|
#define AREA_Y gl_FragCoord.x
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if _CHANNELS == 2
|
||||||
float dx = (AREA_X - (AREA_WIDTH / 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
|
#if FLIP == 0
|
||||||
float d = AREA_Y;
|
float d = AREA_Y;
|
||||||
#else
|
#else
|
||||||
float d = AREA_HEIGHT - AREA_Y;
|
float d = AREA_HEIGHT - AREA_Y;
|
||||||
#endif
|
#endif
|
||||||
float nbars = floor((AREA_WIDTH * 0.5F) / float(BAR_WIDTH + BAR_GAP)) * 2;
|
|
||||||
float section = BAR_WIDTH + BAR_GAP; /* size of section for each bar (including gap) */
|
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 center = section / 2.0F; /* half section, distance to center */
|
||||||
float m = abs(mod(dx, section)); /* position in section */
|
float m = abs(mod(dx, section)); /* position in section */
|
||||||
float md = m - center; /* position in section from center line */
|
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 */
|
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)) */
|
s = dx / section;
|
||||||
p += sign(p) * ((0.5F + center) / AREA_WIDTH); /* index center of bar position */
|
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 */
|
/* Apply smooth function and index texture */
|
||||||
#define smooth_f(tex, p) smooth_audio(tex, audio_sz, p)
|
#define smooth_f(tex, p) smooth_audio(tex, audio_sz, p)
|
||||||
float v;
|
float v;
|
||||||
@@ -65,11 +85,13 @@ void main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* handle user options and store result of indexing in 'v' */
|
/* handle user options and store result of indexing in 'v' */
|
||||||
if (p >= 0.0F) {
|
if (p > 0.0F) {
|
||||||
#if DIRECTION == 1
|
#if DIRECTION == 1
|
||||||
p = 1.0F - p;
|
p = 1.0F - p;
|
||||||
#endif
|
#endif
|
||||||
#if INVERT > 0
|
#if _CHANNELS == 1
|
||||||
|
v = smooth_f(audio_l, p);
|
||||||
|
#elif INVERT > 0
|
||||||
v = smooth_f(audio_l, p);
|
v = smooth_f(audio_l, p);
|
||||||
#else
|
#else
|
||||||
v = smooth_f(audio_r, p);
|
v = smooth_f(audio_r, p);
|
||||||
@@ -79,7 +101,9 @@ void main() {
|
|||||||
#if DIRECTION == 1
|
#if DIRECTION == 1
|
||||||
p = 1.0F - p;
|
p = 1.0F - p;
|
||||||
#endif
|
#endif
|
||||||
#if INVERT > 0
|
#if _CHANNELS == 1
|
||||||
|
v = smooth_f(audio_l, p);
|
||||||
|
#elif INVERT > 0
|
||||||
v = smooth_f(audio_r, p);
|
v = smooth_f(audio_r, p);
|
||||||
#else
|
#else
|
||||||
v = smooth_f(audio_l, p);
|
v = smooth_f(audio_l, p);
|
||||||
@@ -90,7 +114,7 @@ void main() {
|
|||||||
v *= AMPLIFY; /* amplify result */
|
v *= AMPLIFY; /* amplify result */
|
||||||
if (d < v - BAR_OUTLINE_WIDTH) { /* if within range of the reported frequency, draw */
|
if (d < v - BAR_OUTLINE_WIDTH) { /* if within range of the reported frequency, draw */
|
||||||
#if BAR_OUTLINE_WIDTH > 0
|
#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;
|
fragment = COLOR;
|
||||||
else
|
else
|
||||||
fragment = BAR_OUTLINE;
|
fragment = BAR_OUTLINE;
|
||||||
5
shaders/glava/bars/2.frag
Normal file
5
shaders/glava/bars/2.frag
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#if USE_ALPHA == 0
|
||||||
|
#error __disablestage
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include ":util/premultiply.frag"
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
/* center line thickness (pixels) */
|
/* center line thickness (pixels) */
|
||||||
#define C_LINE 1.5
|
#define C_LINE 1.5
|
||||||
/* outline color */
|
/* outline color */
|
||||||
#define OUTLINE #333333
|
#define OUTLINE @fg:#333333
|
||||||
/* Amplify magnitude of the results each bar displays */
|
/* Amplify magnitude of the results each bar displays */
|
||||||
#define AMPLIFY 150
|
#define AMPLIFY 150
|
||||||
/* Angle (in radians) for how much to rotate the visualizer */
|
/* 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,
|
1 to enable, 0 to disable. Only works with `xroot` transparency,
|
||||||
and improves performance if disabled. */
|
and improves performance if disabled. */
|
||||||
#define C_SMOOTH 1
|
#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
|
|
||||||
@@ -7,6 +7,7 @@ uniform ivec2 screen;
|
|||||||
uniform int audio_sz;
|
uniform int audio_sz;
|
||||||
|
|
||||||
#include ":util/smooth.glsl"
|
#include ":util/smooth.glsl"
|
||||||
|
#include "@circle.glsl"
|
||||||
#include ":circle.glsl"
|
#include ":circle.glsl"
|
||||||
|
|
||||||
#request uniform "audio_l" audio_l
|
#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 */
|
/* This shader is based on radial.glsl, refer to it for more commentary */
|
||||||
|
|
||||||
float apply_smooth(float theta) {
|
float apply_smooth(float theta) {
|
||||||
|
|
||||||
float idx = theta + ROTATE;
|
float idx = theta + ROTATE;
|
||||||
float dir = mod(abs(idx), TWOPI);
|
float dir = mod(abs(idx), TWOPI);
|
||||||
if (dir > PI)
|
if (dir > PI)
|
||||||
@@ -50,6 +50,7 @@ float apply_smooth(float theta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
fragment = vec4(0, 0, 0, 0);
|
||||||
float
|
float
|
||||||
dx = gl_FragCoord.x - (screen.x / 2),
|
dx = gl_FragCoord.x - (screen.x / 2),
|
||||||
dy = gl_FragCoord.y - (screen.y / 2);
|
dy = gl_FragCoord.y - (screen.y / 2);
|
||||||
@@ -6,12 +6,13 @@ uniform sampler2D tex; /* screen texture */
|
|||||||
|
|
||||||
out vec4 fragment; /* output */
|
out vec4 fragment; /* output */
|
||||||
|
|
||||||
|
#include "@circle.glsl"
|
||||||
#include ":circle.glsl"
|
#include ":circle.glsl"
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
|
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
|
||||||
#if C_SMOOTH > 0
|
#if C_SMOOTH > 0
|
||||||
#if USE_ALPHA
|
#if _USE_ALPHA
|
||||||
vec4
|
vec4
|
||||||
a0 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 0)), 0),
|
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),
|
a1 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 1)), 0),
|
||||||
1
shaders/glava/env_spectrwm.glsl
Normal file
1
shaders/glava/env_spectrwm.glsl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#request setxwintype "!-"
|
||||||
25
shaders/glava/graph.glsl
Normal file
25
shaders/glava/graph.glsl
Normal 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
|
||||||
@@ -40,6 +40,7 @@ uniform int audio_sz;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include ":util/smooth.glsl"
|
#include ":util/smooth.glsl"
|
||||||
|
#include "@graph.glsl"
|
||||||
#include ":graph.glsl"
|
#include ":graph.glsl"
|
||||||
|
|
||||||
#request uniform "audio_l" audio_l
|
#request uniform "audio_l" audio_l
|
||||||
@@ -80,22 +81,38 @@ out vec4 fragment;
|
|||||||
#define TWOPI 6.28318530718
|
#define TWOPI 6.28318530718
|
||||||
|
|
||||||
float half_w;
|
float half_w;
|
||||||
|
float middle;
|
||||||
|
highp float pixel = 1.0F / float(screen.x);
|
||||||
|
|
||||||
void render_side(in sampler1D tex, float idx) {
|
float get_line_height(in sampler1D tex, float idx) {
|
||||||
highp float pixel = 1.0F / float(screen.x);
|
|
||||||
float s = smooth_audio_adj(tex, audio_sz, idx / half_w, pixel);
|
float s = smooth_audio_adj(tex, audio_sz, idx / half_w, pixel);
|
||||||
/* scale the data upwards so we can see it */
|
/* scale the data upwards so we can see it */
|
||||||
s *= VSCALE;
|
s *= VSCALE;
|
||||||
/* clamp far ends of the screen down to make the ends of the graph smoother */
|
/* 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);
|
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 */
|
/* and finally set fragment color if we are in range */
|
||||||
#if INVERT > 0
|
#if INVERT > 0
|
||||||
float pos = float(screen.y) - gl_FragCoord.y;
|
float d = float(screen.y) - gl_FragCoord.y;
|
||||||
#else
|
#else
|
||||||
float pos = gl_FragCoord.y;
|
float d = gl_FragCoord.y;
|
||||||
#endif
|
#endif
|
||||||
|
#define pos d
|
||||||
if (pos + 1.5 <= s) {
|
if (pos + 1.5 <= s) {
|
||||||
fragment = COLOR;
|
fragment = COLOR;
|
||||||
} else {
|
} else {
|
||||||
@@ -105,6 +122,9 @@ void render_side(in sampler1D tex, float idx) {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
half_w = (screen.x / 2);
|
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) {
|
if (gl_FragCoord.x < half_w) {
|
||||||
render_side(audio_l, LEFT_IDX);
|
render_side(audio_l, LEFT_IDX);
|
||||||
} else {
|
} else {
|
||||||
44
shaders/glava/graph/2.frag
Normal file
44
shaders/glava/graph/2.frag
Normal 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
104
shaders/glava/graph/3.frag
Normal 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
|
||||||
|
}
|
||||||
5
shaders/glava/graph/4.frag
Normal file
5
shaders/glava/graph/4.frag
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#if ANTI_ALIAS == 0
|
||||||
|
#error __disablestage
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include ":util/premultiply.frag"
|
||||||
@@ -4,22 +4,20 @@
|
|||||||
/* center line thickness (pixels) */
|
/* center line thickness (pixels) */
|
||||||
#define C_LINE 2
|
#define C_LINE 2
|
||||||
/* outline color */
|
/* outline color */
|
||||||
#define OUTLINE #333333
|
#define OUTLINE @bg:#333333
|
||||||
/* number of bars (use even values for best results) */
|
/* number of bars (use even values for best results) */
|
||||||
#define NBARS 180
|
#define NBARS 160
|
||||||
/* width (in pixels) of each bar*/
|
/* width (in pixels) of each bar*/
|
||||||
#define BAR_WIDTH 3.5
|
#define BAR_WIDTH 4.5
|
||||||
/* outline color */
|
|
||||||
#define BAR_OUTLINE OUTLINE
|
|
||||||
/* outline width (in pixels, set to 0 to disable outline drawing) */
|
|
||||||
#define BAR_OUTLINE_WIDTH 0
|
|
||||||
/* Amplify magnitude of the results each bar displays */
|
/* Amplify magnitude of the results each bar displays */
|
||||||
#define AMPLIFY 300
|
#define AMPLIFY 300
|
||||||
/* Bar color */
|
/* How quickly the gradient transitions, in pixels */
|
||||||
#define COLOR (#cc3333 * ((d / 40) + 1))
|
#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 */
|
/* Angle (in radians) for how much to rotate the visualizer */
|
||||||
#define ROTATE (PI / 2)
|
#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
|
#define INVERT 0
|
||||||
/* Aliasing factors. Higher values mean more defined and jagged lines.
|
/* Aliasing factors. Higher values mean more defined and jagged lines.
|
||||||
Note: aliasing does not have a notable impact on performance, but requires
|
Note: aliasing does not have a notable impact on performance, but requires
|
||||||
@@ -27,9 +25,12 @@
|
|||||||
the background. */
|
the background. */
|
||||||
#define BAR_ALIAS_FACTOR 1.2
|
#define BAR_ALIAS_FACTOR 1.2
|
||||||
#define C_ALIAS_FACTOR 1.8
|
#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` */
|
/* (DEPRECATED) outline color */
|
||||||
#request setgravitystep 5.0
|
#define BAR_OUTLINE OUTLINE
|
||||||
|
/* (DEPRECATED) outline width (in pixels, set to 0 to disable outline drawing) */
|
||||||
/* Smoothing factor, override from `smooth_parameters.glsl` */
|
#define BAR_OUTLINE_WIDTH 0
|
||||||
#request setsmoothfactor 0.02
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
layout(pixel_center_integer) in vec4 gl_FragCoord;
|
in vec4 gl_FragCoord;
|
||||||
|
|
||||||
#request uniform "screen" screen
|
#request uniform "screen" screen
|
||||||
uniform ivec2 screen;
|
uniform ivec2 screen;
|
||||||
@@ -7,6 +7,7 @@ uniform ivec2 screen;
|
|||||||
uniform int audio_sz;
|
uniform int audio_sz;
|
||||||
|
|
||||||
#include ":util/smooth.glsl"
|
#include ":util/smooth.glsl"
|
||||||
|
#include "@radial.glsl"
|
||||||
#include ":radial.glsl"
|
#include ":radial.glsl"
|
||||||
|
|
||||||
#request uniform "audio_l" audio_l
|
#request uniform "audio_l" audio_l
|
||||||
@@ -30,7 +31,7 @@ out vec4 fragment;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
#if USE_ALPHA > 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))
|
#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;
|
fragment = #00000000;
|
||||||
#else
|
#else
|
||||||
@@ -41,13 +42,13 @@ void main() {
|
|||||||
Alpha layer blending is only applied when `xroot` transparency is enabled. */
|
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 */
|
float /* translate (x, y) to use (0, 0) as the center of the screen */
|
||||||
dx = gl_FragCoord.x - (screen.x / 2),
|
dx = gl_FragCoord.x - (screen.x / 2) + CENTER_OFFSET_X,
|
||||||
dy = gl_FragCoord.y - (screen.y / 2);
|
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 theta = atan(dy, dx); /* fragment angle with the center of the screen as the origin */
|
||||||
float d = sqrt((dx * dx) + (dy * dy)); /* distance */
|
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)) {
|
if (d > C_RADIUS - (float(C_LINE) / 2.0F) && d < C_RADIUS + (float(C_LINE) / 2.0F)) {
|
||||||
APPLY_FRAG(fragment, OUTLINE);
|
APPLY_FRAG(fragment, OUTLINE);
|
||||||
#if USE_ALPHA > 0
|
#if _USE_ALPHA > 0
|
||||||
fragment.a *= clamp(((C_LINE / 2) - abs(C_RADIUS - d)) * C_ALIAS_FACTOR, 0, 1);
|
fragment.a *= clamp(((C_LINE / 2) - abs(C_RADIUS - d)) * C_ALIAS_FACTOR, 0, 1);
|
||||||
#else
|
#else
|
||||||
return; /* return immediately if there is no alpha blending available */
|
return; /* return immediately if there is no alpha blending available */
|
||||||
@@ -63,8 +64,9 @@ void main() {
|
|||||||
float dir = mod(abs(idx), TWOPI); /* absolute position, [0, 2pi) */
|
float dir = mod(abs(idx), TWOPI); /* absolute position, [0, 2pi) */
|
||||||
if (dir > PI)
|
if (dir > PI)
|
||||||
idx = -sign(idx) * (TWOPI - dir); /* Re-correct position values to [-pi, pi) */
|
idx = -sign(idx) * (TWOPI - dir); /* Re-correct position values to [-pi, pi) */
|
||||||
if (INVERT > 0)
|
#if INVERT == 0
|
||||||
idx = -idx; /* Invert if needed */
|
idx = -idx; /* Invert if needed */
|
||||||
|
#endif
|
||||||
float pos = int(abs(idx) / section) / float(NBARS / 2); /* bar position, [0, 1) */
|
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 */
|
#define smooth_f(tex) smooth_audio(tex, audio_sz, pos) /* smooth function format */
|
||||||
float v;
|
float v;
|
||||||
@@ -73,7 +75,7 @@ void main() {
|
|||||||
v *= AMPLIFY; /* amplify */
|
v *= AMPLIFY; /* amplify */
|
||||||
#undef smooth_f
|
#undef smooth_f
|
||||||
/* offset to fragment distance from inner circle */
|
/* offset to fragment distance from inner circle */
|
||||||
#if USE_ALPHA > 0
|
#if _USE_ALPHA > 0
|
||||||
#define ALIAS_FACTOR (((BAR_WIDTH / 2) - abs(ym)) * BAR_ALIAS_FACTOR)
|
#define ALIAS_FACTOR (((BAR_WIDTH / 2) - abs(ym)) * BAR_ALIAS_FACTOR)
|
||||||
d -= C_RADIUS; /* start bar overlapping the inner circle for blending */
|
d -= C_RADIUS; /* start bar overlapping the inner circle for blending */
|
||||||
#else
|
#else
|
||||||
@@ -90,7 +92,7 @@ void main() {
|
|||||||
#else
|
#else
|
||||||
r = COLOR;
|
r = COLOR;
|
||||||
#endif
|
#endif
|
||||||
#if USE_ALPHA > 0
|
#if _USE_ALPHA > 0
|
||||||
r.a *= ALIAS_FACTOR;
|
r.a *= ALIAS_FACTOR;
|
||||||
#endif
|
#endif
|
||||||
APPLY_FRAG(fragment, r);
|
APPLY_FRAG(fragment, r);
|
||||||
@@ -98,7 +100,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
#if BAR_OUTLINE_WIDTH > 0
|
#if BAR_OUTLINE_WIDTH > 0
|
||||||
if (d <= v) {
|
if (d <= v) {
|
||||||
#if USE_ALPHA > 0
|
#if _USE_ALPHA > 0
|
||||||
vec4 r = BAR_OUTLINE;
|
vec4 r = BAR_OUTLINE;
|
||||||
r.a *= ALIAS_FACTOR;
|
r.a *= ALIAS_FACTOR;
|
||||||
APPLY_FRAG(fragment, r);
|
APPLY_FRAG(fragment, r);
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
"none" - Disable window opacity completely. */
|
"none" - Disable window opacity completely. */
|
||||||
#request setopacity "native"
|
#request setopacity "native"
|
||||||
|
|
||||||
/* Whether to mirror left and right audio input channels from PulseAudio.*/
|
/* 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
|
#request setmirror false
|
||||||
|
|
||||||
/* OpenGL context and GLSL shader versions, do not change unless
|
/* OpenGL context and GLSL shader versions, do not change unless
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
/* Window geometry (x, y, width, height) */
|
/* Window geometry (x, y, width, height) */
|
||||||
#request setgeometry 0 0 800 600
|
#request setgeometry 0 0 800 600
|
||||||
|
|
||||||
/* Window background color (RGB format).
|
/* Window background color (RGBA format).
|
||||||
Does not work with `setopacity "xroot"` */
|
Does not work with `setopacity "xroot"` */
|
||||||
#request setbg 00000000
|
#request setbg 00000000
|
||||||
|
|
||||||
@@ -98,9 +99,14 @@
|
|||||||
default when GLava itself is a desktop window. */
|
default when GLava itself is a desktop window. */
|
||||||
#request setclickthrough false
|
#request setclickthrough false
|
||||||
|
|
||||||
/* PulseAudio source. Can be a number or a name of an audio
|
/* Audio source
|
||||||
sink or device to record from. Set to "auto" to use the
|
|
||||||
default output device. */
|
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"
|
#request setsource "auto"
|
||||||
|
|
||||||
/* Buffer swap interval (vsync), set to '0' to prevent
|
/* Buffer swap interval (vsync), set to '0' to prevent
|
||||||
@@ -122,7 +128,7 @@
|
|||||||
|
|
||||||
This will delay data output by one update frame, so it can
|
This will delay data output by one update frame, so it can
|
||||||
desync audio with visual effects on low UPS configs. */
|
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
|
/* Frame limiter, set to the frames per second (FPS) desired or
|
||||||
simply set to zero (or lower) to disable the frame limiter. */
|
simply set to zero (or lower) to disable the frame limiter. */
|
||||||
@@ -189,9 +195,21 @@
|
|||||||
|
|
||||||
Lower sample rates also can make output more choppy, when
|
Lower sample rates also can make output more choppy, when
|
||||||
not using interpolation. It's generally OK to leave this
|
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
|
#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 **
|
/* ** DEPRECATED **
|
||||||
Force window geometry (locking the window in place), useful
|
Force window geometry (locking the window in place), useful
|
||||||
for some pesky WMs that try to reposition the window when
|
for some pesky WMs that try to reposition the window when
|
||||||
@@ -13,10 +13,25 @@
|
|||||||
- circular heavily rounded points
|
- circular heavily rounded points
|
||||||
- sinusoidal rounded at both low and high weighted values
|
- sinusoidal rounded at both low and high weighted values
|
||||||
like a sine wave
|
like a sine wave
|
||||||
- linear not rounded at all, just use linear distance
|
- linear not rounded at all; linear distance
|
||||||
*/
|
*/
|
||||||
#define ROUND_FORMULA sinusoidal
|
#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
|
/* Factor used to scale frequencies. Lower values allows lower
|
||||||
frequencies to occupy more space. */
|
frequencies to occupy more space. */
|
||||||
#define SAMPLE_SCALE 8
|
#define SAMPLE_SCALE 8
|
||||||
@@ -38,7 +53,7 @@
|
|||||||
/* How many frames to queue and run through the average function.
|
/* How many frames to queue and run through the average function.
|
||||||
Increasing this value will create latency between the audio and the
|
Increasing this value will create latency between the audio and the
|
||||||
animation, but will make for much smoother results. */
|
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
|
/* Whether to window frames ran through the average function (new & old
|
||||||
frames are weighted less). This massively helps smoothing out
|
frames are weighted less). This massively helps smoothing out
|
||||||
33
shaders/glava/test/1.frag
Normal file
33
shaders/glava/test/1.frag
Normal 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
12
shaders/glava/test/2.frag
Normal 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);
|
||||||
|
}
|
||||||
2
shaders/glava/test/3.frag
Normal file
2
shaders/glava/test/3.frag
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/* Assert that the premultiply step works */
|
||||||
|
#include ":util/premultiply.frag"
|
||||||
27
shaders/glava/test_rc.glsl
Normal file
27
shaders/glava/test_rc.glsl
Normal 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
|
||||||
46
shaders/glava/util/average_pass.frag
Normal file
46
shaders/glava/util/average_pass.frag
Normal 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;
|
||||||
|
}
|
||||||
23
shaders/glava/util/common.glsl
Normal file
23
shaders/glava/util/common.glsl
Normal 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
|
||||||
842
shaders/glava/util/fft_common.glsl
Normal file
842
shaders/glava/util/fft_common.glsl
Normal 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
|
||||||
|
|
||||||
163
shaders/glava/util/fft_main.glsl
Normal file
163
shaders/glava/util/fft_main.glsl
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
189
shaders/glava/util/fft_radix16.glsl
Normal file
189
shaders/glava/util/fft_radix16.glsl
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
163
shaders/glava/util/fft_radix4.glsl
Normal file
163
shaders/glava/util/fft_radix4.glsl
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
222
shaders/glava/util/fft_radix64.glsl
Normal file
222
shaders/glava/util/fft_radix64.glsl
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
246
shaders/glava/util/fft_radix8.glsl
Normal file
246
shaders/glava/util/fft_radix8.glsl
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
179
shaders/glava/util/fft_shared.glsl
Normal file
179
shaders/glava/util/fft_shared.glsl
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
9
shaders/glava/util/gravity_pass.frag
Normal file
9
shaders/glava/util/gravity_pass.frag
Normal 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;
|
||||||
|
}
|
||||||
9
shaders/glava/util/pass.frag
Normal file
9
shaders/glava/util/pass.frag
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
#request nativeonly true
|
#if _PREMULTIPLY_ALPHA == 0
|
||||||
|
#error __disablestage
|
||||||
|
#endif
|
||||||
|
|
||||||
#request uniform "prev" tex
|
#request uniform "prev" tex
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
@@ -9,7 +11,5 @@ in vec4 gl_FragCoord;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
|
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
|
||||||
#if PREMULTIPLY_ALPHA > 0
|
|
||||||
fragment.rgb *= fragment.a;
|
fragment.rgb *= fragment.a;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
81
shaders/glava/util/smooth.glsl
Normal file
81
shaders/glava/util/smooth.glsl
Normal 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 */
|
||||||
@@ -6,8 +6,8 @@ uniform int w;
|
|||||||
out vec4 fragment;
|
out vec4 fragment;
|
||||||
in vec4 gl_FragCoord;
|
in vec4 gl_FragCoord;
|
||||||
|
|
||||||
#undef PRE_SMOOTHED_AUDIO
|
#undef _PRE_SMOOTHED_AUDIO
|
||||||
#define PRE_SMOOTHED_AUDIO 0
|
#define _PRE_SMOOTHED_AUDIO 0
|
||||||
|
|
||||||
#include ":util/smooth.glsl"
|
#include ":util/smooth.glsl"
|
||||||
|
|
||||||
10
shaders/glava/wave.glsl
Normal file
10
shaders/glava/wave.glsl
Normal 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)
|
||||||
@@ -11,6 +11,7 @@ uniform sampler1D audio_l;
|
|||||||
|
|
||||||
out vec4 fragment;
|
out vec4 fragment;
|
||||||
|
|
||||||
|
#include "@wave.glsl"
|
||||||
#include ":wave.glsl"
|
#include ":wave.glsl"
|
||||||
|
|
||||||
#define index(offset) ((texture(audio_l, (gl_FragCoord.x + offset) / screen.x).r - 0.5) * AMPLIFY) + 0.5F
|
#define index(offset) ((texture(audio_l, (gl_FragCoord.x + offset) / screen.x).r - 0.5) * AMPLIFY) + 0.5F
|
||||||
@@ -8,6 +8,7 @@ uniform ivec2 screen; /* screen dimensions */
|
|||||||
|
|
||||||
out vec4 fragment; /* output */
|
out vec4 fragment; /* output */
|
||||||
|
|
||||||
|
#include "@wave.glsl"
|
||||||
#include ":wave.glsl"
|
#include ":wave.glsl"
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
/* Vertical scale, larger values will amplify output */
|
|
||||||
#define VSCALE 300
|
|
||||||
/* Rendering direction, either -1 (outwards) or 1 (inwards). */
|
|
||||||
#define DIRECTION 1
|
|
||||||
|
|
||||||
/* The `RCOL_OFF`, `LCOL_OFF` AND `LSTEP` definitions are used to calculate
|
|
||||||
the `COLOR` macro definition for output. You can remove all these values
|
|
||||||
any simply define the `COLOR` macro yourself. */
|
|
||||||
|
|
||||||
/* right color offset */
|
|
||||||
#define RCOL_OFF (gl_FragCoord.x / 3000)
|
|
||||||
/* left color offset */
|
|
||||||
#define LCOL_OFF ((screen.x - gl_FragCoord.x) / 3000)
|
|
||||||
/* vertical color step */
|
|
||||||
#define LSTEP (pos / 170)
|
|
||||||
/* actual color definition */
|
|
||||||
#define COLOR vec4((0.3 + RCOL_OFF) + LSTEP, 0.6 - LSTEP, (0.3 + LCOL_OFF) + LSTEP, 1)
|
|
||||||
/* outline color */
|
|
||||||
#define OUTLINE #262626
|
|
||||||
/* 1 to invert (vertically), 0 otherwise */
|
|
||||||
#define INVERT 0
|
|
||||||
|
|
||||||
/* Gravity step, overrude frin `smooth_parameters.glsl` */
|
|
||||||
#request setgravitystep 2.4
|
|
||||||
|
|
||||||
/* Smoothing factor, override from `smooth_parameters.glsl` */
|
|
||||||
#request setsmoothfactor 0.015
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
|
|
||||||
layout(pixel_center_integer) in vec4 gl_FragCoord;
|
|
||||||
|
|
||||||
#request uniform "prev" tex
|
|
||||||
uniform sampler2D tex; /* screen texture */
|
|
||||||
#request uniform "screen" screen
|
|
||||||
uniform ivec2 screen; /* screen dimensions */
|
|
||||||
|
|
||||||
out vec4 fragment; /* output */
|
|
||||||
|
|
||||||
#include ":graph.glsl"
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragment = texture(tex, vec2(gl_FragCoord.x / screen.x, gl_FragCoord.y / screen.y));
|
|
||||||
|
|
||||||
vec4
|
|
||||||
a0 = texture(tex, vec2((gl_FragCoord.x + 1) / screen.x, (gl_FragCoord.y + 0) / screen.y)),
|
|
||||||
a1 = texture(tex, vec2((gl_FragCoord.x + 1) / screen.x, (gl_FragCoord.y + 1) / screen.y)),
|
|
||||||
a2 = texture(tex, vec2((gl_FragCoord.x + 0) / screen.x, (gl_FragCoord.y + 1) / screen.y)),
|
|
||||||
a3 = texture(tex, vec2((gl_FragCoord.x + 1) / screen.x, (gl_FragCoord.y + 0) / screen.y)),
|
|
||||||
|
|
||||||
a4 = texture(tex, vec2((gl_FragCoord.x - 1) / screen.x, (gl_FragCoord.y - 0) / screen.y)),
|
|
||||||
a5 = texture(tex, vec2((gl_FragCoord.x - 1) / screen.x, (gl_FragCoord.y - 1) / screen.y)),
|
|
||||||
a6 = texture(tex, vec2((gl_FragCoord.x - 0) / screen.x, (gl_FragCoord.y - 1) / screen.y)),
|
|
||||||
a7 = texture(tex, vec2((gl_FragCoord.x - 1) / screen.x, (gl_FragCoord.y - 0) / screen.y));
|
|
||||||
|
|
||||||
vec4 avg = (a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7) / 8.0;
|
|
||||||
if (avg.a > 0){
|
|
||||||
#if INVERT > 0
|
|
||||||
#define EDGE_CHECK (gl_FragCoord.y != 0)
|
|
||||||
#define TEDGE_CHECK (gl_FragCoord.y != screen.y - 1)
|
|
||||||
#else
|
|
||||||
#define EDGE_CHECK (gl_FragCoord.y != screen.y - 1)
|
|
||||||
#define TEDGE_CHECK (gl_FragCoord.y != 0)
|
|
||||||
#endif
|
|
||||||
if (fragment.a <= 0 && EDGE_CHECK) {
|
|
||||||
/* outline */
|
|
||||||
fragment = OUTLINE;
|
|
||||||
} else if (avg.a < 1 && TEDGE_CHECK) {
|
|
||||||
/* creates a nice 'glint' along the edge of the spectrum */
|
|
||||||
fragment.rgb *= avg.a * 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
|
|
||||||
#ifndef _SMOOTH_GLSL /* include gaurd */
|
|
||||||
#define _SMOOTH_GLSL
|
|
||||||
|
|
||||||
#ifndef TWOPI
|
|
||||||
#define TWOPI 6.28318530718
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef PI
|
|
||||||
#define PI 3.14159265359
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include ":smooth_parameters.glsl"
|
|
||||||
|
|
||||||
/* window value t that resides in range [0, sz)*/
|
|
||||||
#define window(t, sz) (0.53836 - (0.46164 * cos(TWOPI * t / (sz - 1))))
|
|
||||||
/* this does nothing, but we keep it as an option for config */
|
|
||||||
#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)))
|
|
||||||
|
|
||||||
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,
|
|
||||||
avg = 0, s, weight = 0;
|
|
||||||
float m = ((smax - smin) / 2.0F);
|
|
||||||
float rm = smin + m; /* middle */
|
|
||||||
for (s = smin; s <= smax; s += 1.0F) {
|
|
||||||
float 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;
|
|
||||||
#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 */
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user