This commit is contained in:
VetheonGames 2023-09-07 16:16:06 -06:00
parent 403f66b6a2
commit 5499f9f4b9
89 changed files with 23046 additions and 47 deletions

682
LICENSE
View File

@ -1,86 +1,674 @@
AFFERO GENERAL PUBLIC LICENSE
Version 1, March 2002 Copyright © 2002 Affero Inc. 510 Third Street - Suite 225, San Francisco, CA 94107, USA
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
This license is a modified version of the GNU General Public License copyright (C) 1989, 1991 Free Software Foundation, Inc. made with their permission. Section 2(d) has been added to cover use of software over a computer network.
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the Affero General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This Public License applies to most of Affero's software and to any other program whose authors commit to using it. (Some other Affero software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not price. This General Public License is designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
The precise terms and conditions for copying, distribution and modification follow.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
The precise terms and conditions for copying, distribution and
modification follow.
0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this Affero General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
TERMS AND CONDITIONS
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
0. Definitions.
1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
"This License" refers to version 3 of the GNU General Public License.
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
d) If the Program as you received it is intended to interact with users through a computer network and if, in the version you received, any user interacting with the Program was given the opportunity to request transmission to that user of the Program's complete source code, you must not remove that facility from your modified version of the Program or work based on the Program, and must offer an equivalent opportunity for all users interacting with your Program through a computer network to request immediate transmission by HTTP of the complete source code of your modified version or other derivative work.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
A "covered work" means either the unmodified Program or a work based
on the Program.
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
1. Source Code.
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
The Corresponding Source for a work in source code form is that
same work.
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
2. Basic Permissions.
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
9. Affero Inc. may publish revised and/or new versions of the Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by Affero, Inc. If the Program does not specify a version number of this License, you may choose any version ever published by Affero, Inc.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
You may also choose to redistribute modified versions of this program under any version of the Free Software Foundation's GNU General Public License version 3 or higher, so long as that version of the GNU GPL includes terms and conditions substantially equivalent to those of this license.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by Affero, Inc., write to us; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
NO WARRANTY
4. Conveying Verbatim Copies.
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

19
LICENSE_ORIGINAL Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2015 Karl Stavestrand <karl@stavestrand.no>
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.

53
Makefile Normal file
View File

@ -0,0 +1,53 @@
.PHONY: all install clean ninja
# 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)
MESON_CONF += --buildtype=debug
else
ifdef BUILD
$(warning WARNING: ignoring build option '$(BUILD)' in compatibility Makefile)
endif
endif
ifeq ($(INSTALL),standalone)
MESON_CONF += -Dstandalone=true
else
ifdef INSTALL
$(warning WARNING: ignoring install option '$(INSTALL)' in compatibility Makefile)
endif
endif
# Store relevant variables that may change depending on the environment or user input
STATE = $(BUILD),$(INSTALL),$(PYTHON),$(CC),$(CFLAGS),$(DESTDIR)
# Only update the file if the contents changed, `make` just looks at the timestamp
$(shell if [[ ! -e build_state ]]; then touch build_state; fi)
$(shell if [ '$(STATE)' != "`cat build_state`" ]; then echo '$(STATE)' > build_state; fi)
all: ninja
# Rebuild if the makefile state changes to maintain old behaviour and smooth rebuilds with altered parameters
build: build_state
$(warning !!PACKAGE MAINTAINER NOTICE!!)
$(warning Configuring build for compatibility with old makefile. Some new features may be missing.)
$(warning If you are a package maintainer consider using meson directly!)
@rm -rf $(BUILD_DIR)
meson $(BUILD_DIR)
meson configure $(MESON_CONF)
ninja: build
ninja -C $(BUILD_DIR)
install:
ninja -C build install
clean:
rm -rf $(BUILD_DIR)

View File

@ -0,0 +1,7 @@
rm -rf /etc/xdg/glava
rm /usr/bin/glava
rm /usr/local/bin/glava
rm /usr/lib/x86_64-linux-gnu/libglava.so
rm /usr/bin/glava
rm /usr/local/lib/x86_64-linux-gnu/libglava.so
rm -rf ~/.config/glava

10
glad_generate.sh Executable file
View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

129
glava/fifo.c Normal file
View File

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

46
glava/fifo.h Normal file
View File

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

2550
glava/glad.c Normal file

File diff suppressed because it is too large Load Diff

5183
glava/glad.h Normal file

File diff suppressed because it is too large Load Diff

577
glava/glava.c Normal file
View File

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

27
glava/glava.h Normal file
View File

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

151
glava/glfw_wcb.c Normal file
View File

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

738
glava/glsl_ext.c Normal file
View File

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

68
glava/glsl_ext.h Normal file
View File

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

665
glava/glx_wcb.c Normal file
View File

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

282
glava/khrplatform.h Normal file
View File

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

192
glava/pulse_input.c Normal file
View File

@ -0,0 +1,192 @@
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include <pulse/pulseaudio.h>
#include "fifo.h"
static pa_mainloop* m_pulseaudio_mainloop;
static void cb(__attribute__((unused)) pa_context* pulseaudio_context,
const pa_server_info* i,
void* userdata) {
/* Obtain default sink name */
struct audio_data* audio = (struct audio_data*) userdata;
audio->source = malloc(sizeof(char) * 1024);
strcpy(audio->source,i->default_sink_name);
/* Append `.monitor` suffix */
audio->source = strcat(audio->source, ".monitor");
/* Quiting mainloop */
pa_context_disconnect(pulseaudio_context);
pa_context_unref(pulseaudio_context);
pa_mainloop_quit(m_pulseaudio_mainloop, 0);
pa_mainloop_free(m_pulseaudio_mainloop);
}
static void pulseaudio_context_state_callback(pa_context* pulseaudio_context, void* userdata) {
/* Ensure loop is ready */
switch (pa_context_get_state(pulseaudio_context)) {
case PA_CONTEXT_UNCONNECTED: break;
case PA_CONTEXT_CONNECTING: break;
case PA_CONTEXT_AUTHORIZING: break;
case PA_CONTEXT_SETTING_NAME: break;
case PA_CONTEXT_READY: /* extract default sink name */
pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, userdata));
break;
case PA_CONTEXT_FAILED:
printf("failed to connect to pulseaudio server\n");
exit(EXIT_FAILURE);
break;
case PA_CONTEXT_TERMINATED:
pa_mainloop_quit(m_pulseaudio_mainloop, 0);
break;
}
}
static void init(struct audio_data* audio) {
if (audio->source) return;
pa_mainloop_api* mainloop_api;
pa_context* pulseaudio_context;
int ret;
/* Create a mainloop API and connection to the default server */
m_pulseaudio_mainloop = pa_mainloop_new();
mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop);
pulseaudio_context = pa_context_new(mainloop_api, "glava device list");
/* Connect to the PA server */
pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOFLAGS,
NULL);
/* Define a callback so the server will tell us its state */
pa_context_set_state_callback(pulseaudio_context,
pulseaudio_context_state_callback,
(void*)audio);
/* Start mainloop to get default sink */
/* Start with one non blocking iteration in case pulseaudio is not able to run */
if (!(ret = pa_mainloop_iterate(m_pulseaudio_mainloop, 0, &ret))){
printf("Could not open pulseaudio mainloop to "
"find default device name: %d\n"
"check if pulseaudio is running\n",
ret);
exit(EXIT_FAILURE);
}
pa_mainloop_run(m_pulseaudio_mainloop, &ret);
}
/* Sample format for native 'float' type */
#ifndef __STDC_IEC_559__
#error "IEC 60559 standard unsupported on target system"
#endif
#ifdef __ORDER_LITTLE_ENDIAN__
#define FSAMPLE_FORMAT PA_SAMPLE_FLOAT32LE
#elif __ORDER_BIG_ENDIAN__
#define FSAMPLE_FORMAT PA_SAMPLE_FLOAT32BE
#else
#error "Unsupported float format (requires 32 bit IEEE (little or big endian) floating point support)"
#endif
static void* entry(void* data) {
struct audio_data* audio = (struct audio_data*) data;
int i, n;
size_t ssz = audio->sample_sz;
float buf[ssz / 2];
const pa_sample_spec ss = {
.format = FSAMPLE_FORMAT,
.rate = audio->rate,
.channels = 2
};
const pa_buffer_attr pb = {
.maxlength = (uint32_t) -1,
.fragsize = ssz
};
pa_simple* s = NULL;
int error;
if (!(s = pa_simple_new(NULL, "glava", PA_STREAM_RECORD,
audio->source, "audio for glava",
&ss, NULL, &pb, &error))) {
fprintf(stderr, __FILE__ ": Could not open pulseaudio source: %s, %s. "
"To find a list of your pulseaudio sources run 'pacmd list-sources'\n",
audio->source, pa_strerror(error));
exit(EXIT_FAILURE);
}
n = 0;
float* bl = (float*) audio->audio_out_l;
float* br = (float*) audio->audio_out_r;
size_t fsz = audio->audio_buf_sz;
while (1) {
/* Record some data ... */
if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
exit(EXIT_FAILURE);
}
pthread_mutex_lock(&audio->mutex);
/* progressing the audio buffer, making space for new write */
memmove(bl, &bl[ssz / 4], (fsz - (ssz / 4)) * sizeof(float));
memmove(br, &br[ssz / 4], (fsz - (ssz / 4)) * sizeof(float));
/* sorting out channels */
for (n = 0, i = 0; i < ssz / 2; i += 2) {
/* size_t idx = (i / 2) + (at * (BUFSIZE / 2)); */
int idx = (fsz - (ssz / 4)) + n;
if (audio->channels == 1) {
float sample = (buf[i] + buf[i + 1]) / 2;
bl[idx] = sample;
br[idx] = sample;
}
/* stereo storing channels in buffer */
if (audio->channels == 2) {
bl[idx] = buf[i];
br[idx] = buf[i + 1];
}
++n;
}
audio->modified = true;
pthread_mutex_unlock(&audio->mutex);
if (audio->terminate == 1) {
pa_simple_free(s);
break;
}
}
return 0;
}
AUDIO_ATTACH(pulseaudio);

10
glava/pulse_input.h Normal file
View File

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

2490
glava/render.c Normal file

File diff suppressed because it is too large Load Diff

139
glava/render.h Normal file
View File

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

472
glava/xwin.c Normal file
View File

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

22
glava/xwin.h Normal file
View File

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

19
glfft/LICENSE_ORIGINAL Normal file
View File

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

1125
glfft/glfft.cpp Normal file

File diff suppressed because it is too large Load Diff

225
glfft/glfft.hpp Normal file
View File

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

179
glfft/glfft_common.hpp Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,260 @@
/* 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>
#include <errno.h>
#include <stdio.h>
}
#ifndef GLFFT_GLSL_LANG_STRING
#error GLFFT_GLSL_LANG_STRING must be defined to e.g. "#version 310 es\n" or "#version 430 core\n".
#endif
#ifndef GLFFT_LOG_OVERRIDE
void glfft_log(const char *fmt, ...) {
va_list l;
va_start(l, fmt);
vfprintf(stdout, fmt, l);
va_end(l);
}
#else
#define glfft_log GLFFT_LOG_OVERRIDE
#endif
#ifndef GLFFT_TIME_OVERRIDE
double glfft_time() {
struct timespec tv;
if (clock_gettime(CLOCK_REALTIME, &tv)) {
fprintf(stderr, "clock_gettime(CLOCK_REALTIME, ...): %s\n", strerror(errno));
}
return (double) tv.tv_sec + ((double) tv.tv_nsec / 1000000000.0);
}
#else
#define glfft_time GLFFT_TIME_OVERRIDE
#endif
namespace GLFFT
{
class GLContext;
class GLTexture : public Texture
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLTexture();
GLTexture(GLuint obj) : name(obj), owned(false) {}
GLuint get() const { return name; }
private:
GLTexture(const void *initial_data,
unsigned width, unsigned height,
Format format);
GLuint name;
bool owned = true;
};
// Not really used by test and bench code, but can be useful for API users.
class GLSampler : public Sampler
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLSampler();
GLSampler(GLuint obj) : name(obj) {}
GLuint get() const { return name; }
private:
GLuint name;
};
class GLBuffer : public Buffer
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLBuffer();
GLBuffer(GLuint obj) : name(obj), owned(false) {}
GLuint get() const { return name; }
private:
GLuint name;
GLBuffer(const void *initial_data, size_t size, AccessMode access);
bool owned = true;
};
class GLProgram : public Program
{
public:
friend class GLContext;
friend class GLCommandBuffer;
~GLProgram();
GLuint get() const { return name; }
private:
GLProgram(GLuint name);
GLuint name;
};
class GLCommandBuffer : public CommandBuffer
{
public:
~GLCommandBuffer() = default;
void set_constant_data_buffers(const GLuint *ubos, unsigned count)
{
this->ubos = ubos;
ubo_index = 0;
ubo_count = count;
}
void bind_program(Program *program) override;
void bind_storage_texture(unsigned binding, Texture *texture, Format format) override;
void bind_texture(unsigned binding, Texture *texture) override;
void bind_sampler(unsigned binding, Sampler *sampler) override;
void bind_storage_buffer(unsigned binding, Buffer *texture) override;
void bind_storage_buffer_range(unsigned binding, size_t offset, size_t length, Buffer *texture) override;
void dispatch(unsigned x, unsigned y, unsigned z) override;
void barrier(Buffer *buffer) override;
void barrier(Texture *buffer) override;
void barrier() override;
void push_constant_data(unsigned binding, const void *data, size_t size) override;
private:
const GLuint *ubos = nullptr;
unsigned ubo_count = 0;
unsigned ubo_index = 0;
};
class GLContext : public Context
{
public:
~GLContext();
std::unique_ptr<Texture> create_texture(const void *initial_data,
unsigned width, unsigned height,
Format format) override;
std::unique_ptr<Buffer> create_buffer(const void *initial_data, size_t size, AccessMode access) override;
std::unique_ptr<Program> compile_compute_shader(const char *source) override;
CommandBuffer* request_command_buffer() override;
void submit_command_buffer(CommandBuffer *cmd) override;
void wait_idle() override;
const char* get_renderer_string() override;
void log(const char *fmt, ...) override;
double get_time() override;
unsigned get_max_work_group_threads() override;
const void* map(Buffer *buffer, size_t offset, size_t size) override;
void unmap(Buffer *buffer) override;
// Not supported in GLES, so override when creating platform-specific context.
bool supports_texture_readback() override { return false; }
void read_texture(void*, Texture*, Format) override {}
protected:
void teardown();
private:
static GLCommandBuffer static_command_buffer;
enum { MaxBuffersRing = 256 };
GLuint ubos[MaxBuffersRing];
bool initialized_ubos = false;
};
static inline GLenum convert(AccessMode mode)
{
switch (mode)
{
case AccessStreamCopy: return GL_STREAM_COPY;
case AccessStaticCopy: return GL_STATIC_COPY;
case AccessStreamRead: return GL_STREAM_READ;
}
return 0;
}
static inline GLenum convert(Format format)
{
switch (format)
{
case FormatR16G16B16A16Float: return GL_RGBA16F;
case FormatR32G32B32A32Float: return GL_RGBA32F;
case FormatR32Float: return GL_R32F;
case FormatR16G16Float: return GL_RG16F;
case FormatR32G32Float: return GL_RG32F;
case FormatR32Uint: return GL_R32UI;
case FormatUnknown: return 0;
}
return 0;
}
static inline GLenum convert_format(Format format)
{
switch (format)
{
case FormatR16G16Float: return GL_RG;
case FormatR32G32Float: return GL_RG;
case FormatR16G16B16A16Float: return GL_RGBA;
case FormatR32G32B32A32Float: return GL_RGBA;
case FormatR32Float: return GL_RED;
case FormatR32Uint: return GL_RED_INTEGER;
case FormatUnknown: return 0;
}
return 0;
}
static inline GLenum convert_type(Format format)
{
switch (format)
{
case FormatR16G16Float: return GL_HALF_FLOAT;
case FormatR16G16B16A16Float: return GL_HALF_FLOAT;
case FormatR32Float: return GL_FLOAT;
case FormatR32G32Float: return GL_FLOAT;
case FormatR32G32B32A32Float: return GL_FLOAT;
case FormatR32Uint: return GL_UNSIGNED_INT;
case FormatUnknown: return 0;
}
return 0;
}
}
#endif

131
glfft/glfft_interface.hpp Normal file
View File

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

600
glfft/glfft_wisdom.cpp Normal file
View File

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

149
glfft/glfft_wisdom.hpp Normal file
View File

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

202
meson.build Normal file
View File

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

24
meson_options.txt Normal file
View File

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

BIN
resources/glava.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
resources/transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

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

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

135
shaders/glava/bars/1.frag Normal file
View File

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

View File

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

18
shaders/glava/circle.glsl Normal file
View File

@ -0,0 +1,18 @@
/* center radius (pixels) */
#define C_RADIUS 128
/* center line thickness (pixels) */
#define C_LINE 1.5
/* outline color */
#define OUTLINE @fg:#333333
/* Amplify magnitude of the results each bar displays */
#define AMPLIFY 150
/* Angle (in radians) for how much to rotate the visualizer */
#define ROTATE (PI / 2)
/* Whether to switch left/right audio buffers */
#define INVERT 0
/* Whether to fill in the space between the line and inner circle */
#define C_FILL 0
/* Whether to apply a post-processing image smoothing effect
1 to enable, 0 to disable. Only works with `xroot` transparency,
and improves performance if disabled. */
#define C_SMOOTH 1

View File

@ -0,0 +1,84 @@
layout(pixel_center_integer) in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen;
#request uniform "audio_sz" audio_sz
uniform int audio_sz;
#include ":util/smooth.glsl"
#include "@circle.glsl"
#include ":circle.glsl"
#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;
#define TWOPI 6.28318530718
#define PI 3.14159265359
/* This shader is based on radial.glsl, refer to it for more commentary */
float apply_smooth(float theta) {
float idx = theta + ROTATE;
float dir = mod(abs(idx), TWOPI);
if (dir > PI)
idx = -sign(idx) * (TWOPI - dir);
if (INVERT > 0)
idx = -idx;
float pos = abs(idx) / (PI + 0.001F);
#define smooth_f(tex) smooth_audio(tex, audio_sz, pos)
float v;
if (idx > 0) v = smooth_f(audio_l);
else v = smooth_f(audio_r);
v *= AMPLIFY;
#undef smooth_f
return v;
}
void main() {
fragment = vec4(0, 0, 0, 0);
float
dx = gl_FragCoord.x - (screen.x / 2),
dy = gl_FragCoord.y - (screen.y / 2);
float theta = atan(dy, dx);
float d = sqrt((dx * dx) + (dy * dy));
float adv = (1.0F / d) * (C_LINE * 0.5);
float
adj0 = theta + adv,
adj1 = theta - adv;
d -= C_RADIUS;
if (d >= -(float(C_LINE) / 2.0F)) {
float v = apply_smooth(theta);
adj0 = apply_smooth(adj0) - v;
adj1 = apply_smooth(adj1) - v;
float
dmax = max(adj0, adj1),
dmin = min(adj0, adj1);
d -= v;
#if C_FILL > 0
#define BOUNDS (d < (float(C_LINE) / 2.0F))
#else
#define BOUNDS (d > -(float(C_LINE) / 2.0F) && d < (float(C_LINE) / 2.0F)) || (d <= dmax && d >= dmin)
#endif
if (BOUNDS) {
fragment = OUTLINE;
}
}
}

View File

@ -0,0 +1,33 @@
in vec4 gl_FragCoord;
#request uniform "prev" tex
uniform sampler2D tex; /* screen texture */
out vec4 fragment; /* output */
#include "@circle.glsl"
#include ":circle.glsl"
void main() {
fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);
#if C_SMOOTH > 0
#if _USE_ALPHA
vec4
a0 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 0)), 0),
a1 = texelFetch(tex, ivec2((gl_FragCoord.x + 1), (gl_FragCoord.y + 1)), 0),
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 (fragment.a == 0) {
fragment = avg;
}
#endif
#endif
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

133
shaders/glava/graph/1.frag Normal file
View File

@ -0,0 +1,133 @@
layout(pixel_center_integer) in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen; /* screen dimensions */
#request uniform "audio_sz" audio_sz
uniform int audio_sz;
/* When we transform our audio, we need to go through the following steps:
transform -> "window"
First, apply a window function to taper off the ends of the spectrum, helping
avoid artifacts in the FFT output.
transform -> "fft"
Apply the Fast Fourier Transform algorithm to separate raw audio data (waves)
into their respective spectrums.
transform -> "fft"
As part of the FFT process, we return spectrum magnitude on a log(n) scale,
as this is how the (decibel) dB scale functions.
transform -> "gravity"
To help make our data more pleasing to look at, we apply our data received over
time to a buffer, taking the max of either the existing value in the buffer or
the data from the input. We then reduce the data by the 'gravity step', and
return the storage buffer.
This makes frequent and abrupt changes in frequency less distracting, and keeps
short frequency responses on the screen longer.
transform -> "avg"
As a final step, we take the average of several data frames (specified by
'setavgframes') and return the result to further help smooth the resulting
animation. In order to mitigate abrupt changes to the average, the values
at each end of the average buffer can be weighted less with a window function
(the same window function used at the start of this step!). It can be disabled
with 'setavgwindow'.
*/
#include ":util/smooth.glsl"
#include "@graph.glsl"
#include ":graph.glsl"
#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;
/* distance from center */
#define CDIST (abs((screen.x / 2) - gl_FragCoord.x) / screen.x)
/* distance from sides (far) */
#define FDIST (min(gl_FragCoord.x, screen.x - gl_FragCoord.x) / screen.x)
#if DIRECTION < 0
#define LEFT_IDX (gl_FragCoord.x)
#define RIGHT_IDX (-gl_FragCoord.x + screen.x)
/* distance from base frequencies */
#define BDIST FDIST
/* distance from high frequencies */
#define HDIST CDIST
#else
#define LEFT_IDX (half_w - gl_FragCoord.x)
#define RIGHT_IDX (gl_FragCoord.x - half_w)
#define BDIST CDIST
#define HDIST FDIST
#endif
#define TWOPI 6.28318530718
float half_w;
float middle;
highp float pixel = 1.0F / float(screen.x);
float get_line_height(in sampler1D tex, float idx) {
float s = smooth_audio_adj(tex, audio_sz, idx / half_w, pixel);
/* scale the data upwards so we can see it */
s *= VSCALE;
/* clamp far ends of the screen down to make the ends of the graph smoother */
float fact = clamp((abs((screen.x / 2) - gl_FragCoord.x) / screen.x) * 48, 0.0F, 1.0F);
#if JOIN_CHANNELS > 0
fact = -2 * pow(fact, 3) + 3 * pow(fact, 2); /* To avoid spikes */
s = fact * s + (1 - fact) * middle;
#else
s *= fact;
#endif
s *= clamp((min(gl_FragCoord.x, screen.x - gl_FragCoord.x) / screen.x) * 48, 0.0F, 1.0F);
return s;
}
void render_side(in sampler1D tex, float idx) {
float s = get_line_height(tex, idx);
/* and finally set fragment color if we are in range */
#if INVERT > 0
float d = float(screen.y) - gl_FragCoord.y;
#else
float d = gl_FragCoord.y;
#endif
#define pos d
if (pos + 1.5 <= s) {
fragment = COLOR;
} else {
fragment = vec4(0, 0, 0, 0);
}
}
void main() {
half_w = (screen.x / 2);
middle = VSCALE * (smooth_audio_adj(audio_l, audio_sz, 1, pixel) + smooth_audio_adj(audio_r, audio_sz, 0, pixel)) / 2;
if (gl_FragCoord.x < half_w) {
render_side(audio_l, LEFT_IDX);
} else {
render_side(audio_r, RIGHT_IDX);
}
}

View File

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

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

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

View File

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

36
shaders/glava/radial.glsl Normal file
View File

@ -0,0 +1,36 @@
/* center radius (pixels) */
#define C_RADIUS 128
/* center line thickness (pixels) */
#define C_LINE 2
/* outline color */
#define OUTLINE @bg:#333333
/* number of bars (use even values for best results) */
#define NBARS 160
/* width (in pixels) of each bar*/
#define BAR_WIDTH 4.5
/* Amplify magnitude of the results each bar displays */
#define AMPLIFY 300
/* How quickly the gradient transitions, in pixels */
#define GRADIENT 95
/* Bar color. This is a gradient by default. */
#define COLOR @fg:mix(#cc3333, #cca0a0, clamp(d / GRADIENT, 0, 1))
/* Angle (in radians) for how much to rotate the visualizer */
#define ROTATE (PI / 2)
/* Whether to swap left/right audio buffers, set to 1 to enable */
#define INVERT 0
/* Aliasing factors. Higher values mean more defined and jagged lines.
Note: aliasing does not have a notable impact on performance, but requires
`xroot` transparency to be enabled since it relies on alpha blending with
the background. */
#define BAR_ALIAS_FACTOR 1.2
#define C_ALIAS_FACTOR 1.8
/* Offset (Y) of the visualization */
#define CENTER_OFFSET_Y 0
/* Offset (X) of the visualization */
#define CENTER_OFFSET_X 0
/* (DEPRECATED) outline color */
#define BAR_OUTLINE OUTLINE
/* (DEPRECATED) outline width (in pixels, set to 0 to disable outline drawing) */
#define BAR_OUTLINE_WIDTH 0

116
shaders/glava/radial/1.frag Normal file
View File

@ -0,0 +1,116 @@
in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen;
#request uniform "audio_sz" audio_sz
uniform int audio_sz;
#include ":util/smooth.glsl"
#include "@radial.glsl"
#include ":radial.glsl"
#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;
#define TWOPI 6.28318530718
#define PI 3.14159265359
void main() {
#if _USE_ALPHA > 0
#define APPLY_FRAG(f, c) f = vec4(f.rgb * f.a + c.rgb * (1 - clamp(f.a, 0, 1)), max(c.a, f.a))
fragment = #00000000;
#else
#define APPLY_FRAG(f, c) f = c
#endif
/* To handle jagged edges, we alias in the shader by using alpha layer blending.
Alpha layer blending is only applied when `xroot` transparency is enabled. */
float /* translate (x, y) to use (0, 0) as the center of the screen */
dx = gl_FragCoord.x - (screen.x / 2) + CENTER_OFFSET_X,
dy = gl_FragCoord.y - (screen.y / 2) + CENTER_OFFSET_Y;
float theta = atan(dy, dx); /* fragment angle with the center of the screen as the origin */
float d = sqrt((dx * dx) + (dy * dy)); /* distance */
if (d > C_RADIUS - (float(C_LINE) / 2.0F) && d < C_RADIUS + (float(C_LINE) / 2.0F)) {
APPLY_FRAG(fragment, OUTLINE);
#if _USE_ALPHA > 0
fragment.a *= clamp(((C_LINE / 2) - abs(C_RADIUS - d)) * C_ALIAS_FACTOR, 0, 1);
#else
return; /* return immediately if there is no alpha blending available */
#endif
}
if (d > C_RADIUS) {
const float section = (TWOPI / NBARS); /* range (radians) for each bar */
const float center = ((TWOPI / NBARS) / 2.0F); /* center line angle */
float m = mod(theta, section); /* position in section (radians) */
float ym = d * sin(center - m); /* distance from center line (cartesian coords) */
if (abs(ym) < BAR_WIDTH / 2) { /* if within width, draw audio */
float idx = theta + ROTATE; /* position (radians) in texture */
float dir = mod(abs(idx), TWOPI); /* absolute position, [0, 2pi) */
if (dir > PI)
idx = -sign(idx) * (TWOPI - dir); /* Re-correct position values to [-pi, pi) */
#if INVERT == 0
idx = -idx; /* Invert if needed */
#endif
float pos = int(abs(idx) / section) / float(NBARS / 2); /* bar position, [0, 1) */
#define smooth_f(tex) smooth_audio(tex, audio_sz, pos) /* smooth function format */
float v;
if (idx > 0) v = smooth_f(audio_l); /* left buffer */
else v = smooth_f(audio_r); /* right buffer */
v *= AMPLIFY; /* amplify */
#undef smooth_f
/* offset to fragment distance from inner circle */
#if _USE_ALPHA > 0
#define ALIAS_FACTOR (((BAR_WIDTH / 2) - abs(ym)) * BAR_ALIAS_FACTOR)
d -= C_RADIUS; /* start bar overlapping the inner circle for blending */
#else
#define ALIAS_FACTOR 1
d -= C_RADIUS + (float(C_LINE) / 2.0F); /* start bar after circle */
#endif
if (d <= v - BAR_OUTLINE_WIDTH) {
vec4 r;
#if BAR_OUTLINE_WIDTH > 0
if (abs(ym) < (BAR_WIDTH / 2) - BAR_OUTLINE_WIDTH)
r = COLOR;
else
r = BAR_OUTLINE;
#else
r = COLOR;
#endif
#if _USE_ALPHA > 0
r.a *= ALIAS_FACTOR;
#endif
APPLY_FRAG(fragment, r);
return;
}
#if BAR_OUTLINE_WIDTH > 0
if (d <= v) {
#if _USE_ALPHA > 0
vec4 r = BAR_OUTLINE;
r.a *= ALIAS_FACTOR;
APPLY_FRAG(fragment, r);
#else
APPLY_FRAG(fragment, BAR_OUTLINE);
#endif
return;
}
#endif
}
}
fragment = APPLY_FRAG(fragment, vec4(0, 0, 0, 0)); /* default frag color */
}

View File

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

236
shaders/glava/rc.glsl Normal file
View File

@ -0,0 +1,236 @@
/* The module to use. A module is a set of shaders used to produce
the visualizer. The structure for a module is the following:
module_name [directory]
1.frag [file: fragment shader],
2.frag [file: fragment shader],
...
Shaders are loaded in numerical order, starting at '1.frag',
continuing indefinitely. The results of each shader (except
for the final pass) is given to the next shader in the list
as a 2D sampler.
See documentation for more details. */
#request mod bars
/* Window hints */
#request setfloating false
#request setdecorated true
#request setfocused false
#request setmaximized false
/* Set window background opacity mode. Possible values are:
"native" - True transparency provided by the compositor. Can
reduce performance on some systems, depending on
the compositor used.
"xroot" - Maintain a copy of the root window's pixmap
(usually the desktop background) to provide a
pseudo-transparent effect. Useful when no compositor
is available or native transparency isn't nessecary.
Has very little performance impact.
"none" - Disable window opacity completely. */
#request setopacity "native"
/* Whether to average and mirror left and right audio input channels.
This may cause some modules to only render a single channel. */
#request setmirror false
/* OpenGL context and GLSL shader versions, do not change unless
you *absolutely* know what you are doing. */
#request setversion 3 3
#request setshaderversion 330
/* Window title */
#request settitle "GLava"
/* Window geometry (x, y, width, height) */
#request setgeometry 0 0 800 600
/* Window background color (RGBA format).
Does not work with `setopacity "xroot"` */
#request setbg 00000000
/* (X11 only) EWMH Window type. Possible values are:
"desktop", "dock", "toolbar", "menu",
"utility", "splash", "dialog", "normal"
This will set _NET_WM_WINDOW_TYPE to _NET_WM_WINDOW_TYPE_(TYPE),
where (TYPE) is the one of the window types listed (after being
converted to uppercase).
Alternatively, you can set this value to "!", which will cause
the window to be unmanaged. If this is set, then `addxwinstate`
will do nothing, but you can use "!+" and "!-" to stack on top
or below other windows.
*/
#request setxwintype "normal"
/* (X11 only) EWMH Window state atoms (multiple can be specified).
Possible values are:
"modal", "sticky", "maximized_vert", "maximized_horz",
"shaded", "skip_taskbar", "skip_pager", "hidden", "fullscreen",
"above", "below", "demands_attention", "focused", "pinned"
This will add _NET_WM_STATE_(TYPE) atoms to _NET_WM_STATE,
where (TYPE) is one of the window states listed (after being
converted to uppercase).
The lines below (commented out by default) are of relevance
if you are trying to get GLava to behave as a desktop widget
and your WM is not correctly responding to the "desktop" value
for `setxwintype`.
*/
// #request addxwinstate "sticky"
// #request addxwinstate "skip_taskbar"
// #request addxwinstate "skip_pager"
// #request addxwinstate "above"
// #request addxwinstate "pinned"
/* (X11 only) Use the XShape extension to support clicking through
the GLava window. Useful when you want to interact with other
desktop windows (icons, menus, desktop shells). Enabled by
default when GLava itself is a desktop window. */
#request setclickthrough false
/* Audio source
When the "pulseaudio" backend is set, this can be a number or
a name of an audio sink or device to record from. Set to "auto"
to use the default output device.
When the "fifo" backend is set, "auto" is interpreted as
"/tmp/mpd.fifo". Otherwise, a valid path should be provided. */
#request setsource "auto"
/* Buffer swap interval (vsync), set to '0' to prevent
waiting for refresh, '1' (or more) to wait for the specified
amount of frames. */
#request setswap 1
/* Linear interpolation for audio data frames. Drastically
improves smoothness with configurations that yield low UPS
(`setsamplerate` and `setsamplesize`), or monitors that have
high refresh rates.
This feature itself, however, will effect performance as it
will have to interpolate data every frame on the CPU. It will
automatically (and temporarily) disable itself if the update
rate is close to, or higher than the framerate:
if (update_rate / frame_rate > 0.9) disable_interpolation;
This will delay data output by one update frame, so it can
desync audio with visual effects on low UPS configs. */
#request setinterpolate false
/* Frame limiter, set to the frames per second (FPS) desired or
simply set to zero (or lower) to disable the frame limiter. */
#request setframerate 0
/* Suspends rendering if a fullscreen window is focused while
GLava is still visible (ie. on another monitor). This prevents
rendering from interfering with other graphically intensive
tasks.
If GLava is minimized or completely obscured, it will not
render regardless of this option. */
#request setfullscreencheck false
/* Enable/disable printing framerate every second. 'FPS' stands
for 'Frames Per Second', and 'UPS' stands for 'Updates Per
Second'. Updates are performed when new data is submitted
by pulseaudio, and require transformations to be re-applied
(thus being a good measure of how much work your CPU has to
perform over time) */
#request setprintframes true
/* PulseAudio sample buffer size. Lower values result in more
frequent audio updates (also depends on sampling rate), but
will also require all transformations to be applied much
more frequently (CPU intensive).
High (>2048, with 22050 Hz) values will decrease accuracy
(as some signals can be missed by transformations like FFT)
The following settings (@22050 Hz) produce the listed rates:
Sample UPS Description
- 2048 -> 43.0 (low accuracy, cheap), use with < 60 FPS
- 1024 -> 86.1 (high accuracy, expensive), use with >= 60 FPS
- 512 -> 172.3 (extreme accuracy, very expensive), use only
for graphing accurate spectrum data with
custom modules.
If the framerate drops below the update rate, the update rate
will be locked to the framerate (to prevent wasting CPU time).
This behaviour means you can use a 1024 sample size on a 60Hz
monitor with vsync enabled to get 60FPS and 60UPS.
For high refresh rate monitors (120+ Hz), it's recommended to
also stick with the 1024 sample size and use interpolation to
smooth the data, as accuracy beyond this setting is mostly
meaningless for visual purposes.
*/
#request setsamplesize 1024
/* Audio buffer size to be used for processing and shaders.
Increasing this value can have the effect of adding 'gravity'
to FFT output, as the audio signal will remain in the buffer
longer.
This value has a _massive_ effect on FFT performance and
quality for some modules. */
#request setbufsize 4096
/* PulseAudio sample rate. Lower values can add 'gravity' to
FFT output, but can also reduce accuracy. Most hardware
samples at 44100Hz.
Lower sample rates also can make output more choppy, when
not using interpolation. It's generally OK to leave this
value unless you have a strange PulseAudio configuration.
This option does nothing when using the "fifo" audio
backend. Instead, an ideal rate should be be configured
in the application generating the output. */
#request setsamplerate 22050
/* Enable GPU acceleration of the audio buffer's fourier transform.
This drastically reduces CPU usage, but should be avoided on
old integrated graphics hardware.
Enabling this also enables acceleration for post-FFT processing
effects, such as gravity, averaging, windowing, and interpolation. */
#request setaccelfft true
/* ** DEPRECATED **
Force window geometry (locking the window in place), useful
for some pesky WMs that try to reposition the window when
embedding in the desktop.
This routinely sends X11 events and should be avoided. */
#request setforcegeometry false
/* ** DEPRECATED **
Force window to be raised (focused in some WMs), useful for
WMs that have their own stacking order for desktop windows.
This routinely sends X11 events and should be avoided. */
#request setforceraised false
/* ** DEPRECATED **
Scale down the audio buffer before any operations are
performed on the data. Higher values are faster.
This value can affect the output of various transformations,
since it applies (crude) averaging to the data when shrinking
the buffer. It is reccommended to use `setsamplerate` and
`setsamplesize` to improve performance or accuracy instead. */
#request setbufscale 1

View File

@ -0,0 +1,78 @@
/* Settings for smoothing functions and transformations commonly
used to display FFT output.
IMPORTANT: THESE VALUES CAN BE OVERRIDDEN IN MODULE CONFIG
FILES, IF CHANGING VALUES HERE DOES NOT WORK, CHECK
TO MAKE SURE THEY ARE NOT BEING SET ELSEWHERE.
*/
/* The type of formula to use for weighting values when smoothing.
Possible values:
- circular heavily rounded points
- sinusoidal rounded at both low and high weighted values
like a sine wave
- linear not rounded at all; linear distance
*/
#define ROUND_FORMULA sinusoidal
/* The sampling mode for processing raw FFT input:
- average averages all the inputs in the sample range for
a given point. Produces smooth output, but peaks
are not well represented
- maximum obtains the best value from the closest peak in
the sample range. Very accurate peaks, but
output is jagged and sporadic.
- hybrid uses the results from both `average` and `maximum`
with the weight provided in `SAMPLE_HYBRID_WEIGHT` */
#define SAMPLE_MODE average
/* Weight should be provided in the range (0, 1). Higher values favour
averaged results. `hybrid` mode only. */
#define SAMPLE_HYBRID_WEIGHT 0.65
/* Factor used to scale frequencies. Lower values allows lower
frequencies to occupy more space. */
#define SAMPLE_SCALE 8
/* The frequency range to sample. 1.0 would be the entire FFT output,
and lower values reduce the displayed frequencies in a log-like
scale. */
#define SAMPLE_RANGE 0.9
/* Factor for how to scale higher frequencies. Used in a linear equation
which is multiplied by the result of the fft transformation. */
#request setfftscale 10.2
/* Cutoff for the bass end of the audio data when scaling frequencies.
Higher values cause more of the bass frequencies to be skipped when
scaling. */
#request setfftcutoff 0.3
/* How many frames to queue and run through the average function.
Increasing this value will create latency between the audio and the
animation, but will make for much smoother results. */
#request setavgframes 5
/* Whether to window frames ran through the average function (new & old
frames are weighted less). This massively helps smoothing out
spontaneous values in the animation. */
#request setavgwindow true
/* Gravity step, higher values means faster drops. The step is applied
in a rate independant method like so:
val -= (gravitystep) * (seconds per update) */
#request setgravitystep 4.2
/* Smoothing factor. Larger values mean more smoothing in the output,
however high values can be expensive to compute. Values are in
normalized width: [0.0, 1.0) */
#request setsmoothfactor 0.025
/* Whether to use a separate pass for audio data while smoothing. On
most hardware, this will improve performance, but involves doing a
separate render step for each audio texture and will add some driver
(CPU) overhead. */
#request setsmoothpass true

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,16 @@
uniform sampler1D tex;
uniform int sz;
uniform int w;
out vec4 fragment;
in vec4 gl_FragCoord;
#undef _PRE_SMOOTHED_AUDIO
#define _PRE_SMOOTHED_AUDIO 0
#include ":util/smooth.glsl"
void main() {
fragment = vec4(smooth_audio(tex, sz, gl_FragCoord.x / w), 0, 0, 0);
}

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

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

39
shaders/glava/wave/1.frag Normal file
View File

@ -0,0 +1,39 @@
layout(pixel_center_integer) in vec4 gl_FragCoord;
#request uniform "screen" screen
uniform ivec2 screen; /* screen dimensions */
#request uniform "audio_l" audio_l
#request transform audio_l "window"
#request transform audio_l "wrange"
uniform sampler1D audio_l;
out vec4 fragment;
#include "@wave.glsl"
#include ":wave.glsl"
#define index(offset) ((texture(audio_l, (gl_FragCoord.x + offset) / screen.x).r - 0.5) * AMPLIFY) + 0.5F
void main() {
float
os = index(0),
adj0 = index(-1),
adj1 = index(1);
float
s0 = adj0 - os,
s1 = adj1 - os;
float
dmax = max(s0, s1),
dmin = min(s0, s1);
float s = (os + (screen.y * 0.5F) - 0.5F); /* center to screen coords */
float diff = gl_FragCoord.y - s;
if (abs(diff) < clamp(abs(s - (screen.y * 0.5)) * 6, MIN_THICKNESS, MAX_THICKNESS)
|| (diff <= dmax && diff >= dmin)) {
fragment = BASE_COLOR + (abs((screen.y * 0.5F) - s) * 0.02);
} else {
fragment = vec4(0, 0, 0, 0);
}
}

33
shaders/glava/wave/2.frag Normal file
View File

@ -0,0 +1,33 @@
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 "@wave.glsl"
#include ":wave.glsl"
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 || gl_FragCoord.x == 0 || gl_FragCoord.x == screen.x - 1)
fragment = OUTLINE;
}
}