From: Not Zed Date: Wed, 14 Sep 2022 02:46:02 +0000 (+0930) Subject: Forked panamaz for vulkan only code. X-Git-Url: https://code.zedzone.au/cvs?a=commitdiff_plain;h=e377bdf47a50b93b1e0ba9572ba9363819897142;p=vulkanz Forked panamaz for vulkan only code. --- e377bdf47a50b93b1e0ba9572ba9363819897142 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45e0b9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +bin/ +config.make +mandelbrot.pam +movie.avi +/build/ +/dist/ +/.lib/ +/nbproject/private/ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + 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. 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 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 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. + + 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. + + 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. + + 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. + + 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. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "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. + + 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. + + A "covered work" means either the unmodified Program or a work 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. + + 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. + + 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. + + 1. Source 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. + + 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. + + 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. + + 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. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + 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. + + 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. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + 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. + + 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. + + 4. Conveying Verbatim Copies. + + 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. + + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3da2adf --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ + +dist_VERSION=0.0.99 +dist_NAME=vulkanz +dist_EXTRA=README COPYING + +include config.make + +java_MODULES = \ + notzed.xlib notzed.xcb notzed.display \ + notzed.vulkan notzed.vulkan.test + +notzed.vulkan_JDEPMOD = notzed.xlib notzed.xcb +notzed.vulkan.test_JDEPMOD = notzed.vulkan +notzed.xcb_JDEPMOD = +notzed.xlib_JDEPMOD = +notzed.display_JDEPMOD = notzed.xlib notzed.xcb notzed.vulkan + +notzed.vulkan.test_JMAIN = vulkan.test.TestMandelbrot vulkan.test.TestCube vulkan.test.TestSDF + +$(foreach module,$(java_MODULES),$(eval $(module)_JMAINFLAGS=--enable-native-access=notzed.nativez,$(module))) + +notzed.vulkan.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.xlib,notzed.xcb,notzed.vulkan + +include java.make + +maven_central_JARS =org.openjdk.jmh:jmh-core:1.33 org.openjdk.jmh:jmh-generator-annprocess:1.33 + +include maven.make diff --git a/README b/README new file mode 100644 index 0000000..3112661 --- /dev/null +++ b/README @@ -0,0 +1,267 @@ + +Introduction +------------ + +This is various experiments with the panama-foreign abi JEP for +invoking native C functions from Java directly and without JNI. + +The main goal is to experiment with creating a "java friendly" and +mostly type-safe api directly in one step, without requiring +additional wrapping. + +It uses a gcc plugin to compile c headers to obtain most of the api +information, but requires cpp and perl to extract the values of +#define constants as they are not available to the gcc plugin. + +This api information is then converted to Java code using a +config-directed perl script `export-api'. The config file can be used +to create static (all-in-one) or object oriented output. It includes +templated strings as well as perl fragments to generate various source +parts. + +The vulkan api is officially defined by a registry in xml, so there's +a separate generator for that. + +Compile +------- + +Requirements are gcc and GNU cpp, perl, GNU make, and of course a +compatible jdk-foreign jdk. The various modules require headers or +sdks for their corresponding libraries, e.g. ffmpeg-5.x, +vulkan-tools-1.2.x. + +Copy config.make.in to config.make and modify any variables required. + +Then make everything, parallel make should work. + +$ make -j + +Or build one module: + +$ make notzed.nativez + +Or run a demo (see next section): + +$ make run-notzed.vkregistry.test/vulkan.test.TestMandelbrot + +A non-recursive make setup is used although make file fragments are +included from various locations across the modules. All java is +compiled as modules. + +JAVA_HOME must point to a compatible panama-enabled jdk. + +The latest at the time of writing was: + + branch: foreign-jextract + commit: 2e1291680024f12fbf2f0f02b0f79d7b4348b369 + date: Fri Feb 4 11:01:21 2022 +0000 + +All output and intermediate files are in bin/ + +bin/modules//classes - compiled java modules +bin/gen//classes - generated java +bin/gen//gen - generated intermediate non-java + +These are more or less an exploded view of all jmod files: + +bin//bin - commands for all modules +bin//lib - libraries/config and modular jar files for all modules +bin//include - header files for all modules + +Finally: + +bin//jmods - .jmod modules + + +Demos +----- + +Most examples have a demo, see the _JMAIN variables in the +Makefile for the targets. They are executed using: + +$ make run-/ + +Modules +-------- + +notzed.nativez contains some support classes and the code generator. +The gcc plugin source is in src/notzed.nativez/native/ the code +generator is in src/notzed.nativez/{bin,lib}. + +notzed.api is a pseudo-module containing a simple test c api for +experiments, it just builds into a library. + +notzed.apistatic is a 'all in one class' static wrapper for +notzed.api. + +notzed.apiobject is an object-oriented wrapper for notzed.api. + +notzed.clstatic is an 'all in one class' static wrapper for OpenCL +(2.1). The api closesly matches the C api except it converts error +codes into exceptins and can infer certain arguments from others such +as length parameters. This is probably the most complete api. + +notzed.ffmpeg is a partial object-oriented mapping for ffmpeg-5.0 that +closely follows the jjmpeg design. It's enough to read video frames. +The demo requires a file 'movie.api' in the root directory to run. + +notzed.vkheader uses the header-based generator on the vulkan +installed headers. This is still very incomplete work in progress. +Much meta-data is lost such as which functions are extensions during +the generation of the headers. This is incomplete and dead-ended. + +notzed.vkregistry uses a completely different generator which directly +parses the official xml registry specification for vulkan +(/usr/share/vulkan/registry/vk.xml). This is directly converted to +about-as-java-friendly a vulkan api as one can hope for, particularly +the constructors for all the config objects. This is incomplete and +dead-ended. + +notzed.vulkan also uses a different generator which directly parses +the official xml registry specification for vulkan +(/usr/share/vulkan/registry/vk.xml). This version uses templates to +generate the various structures in more concise and relatively +consistenet way. Work in progress. + +Export process +-------------- + +The main generator is written in perl and lives in +src/notzed.nativez/{bin,lib}. + +The process: + +* run the gcc plugin to extract all of the available c structures from + gcc and save them to a perl hash. + + gcc plugins aren't very well documented so it was a lot of trial and + error to get it to output all the type information even starting + with a partial example. The names of parameters for function calls + were particularly problematic. + +* optionally run export-defines to extract #define constants. They + can be grouped/excluded by name or by the filename they belong to. + The the first rule to match will complete the processing for a given + type. + + This is also a surprisingly difficult process because the c + pre-processor can just generate arbitrary c expressions so the only + way to find their correct value is to execute the c. So the export + script generates a c file which is compiled and executed to generate + the perl definitions. + + Currently the types are mapped to a 'compatible' native type by + using the gcc operators __builtin_choose_expr, + __builtin_types_compatible_p, and typeof to implement a + pseudo-function overloading in c - possiblly using c++ is a better + choice here. For now inclusions or exclusions are required to + avoid problematic definitions that confuse these operators. + +These files are then fed to generate-api. It proceeds in multiple +stages. + +First stages are handled by lib/api.pm. + +* Load and preprocess the api definition. + + - Perform a bunch of 'fix up' processing on the data structures such + as inserting anonymous types which are not generated by the plugin. + + - Create a full dependency tree for the objects + specificallyreferenced by the api definition so only objects and + functions of interest are included. + + - Resolve all the various export options using some rules and store + the information about the selected options onto target objects. + +Then generate-api produces the java files from a combination of the +processed data definitions and the api definition. + +* Export 'libraries'. These are static classes of functions and/or + constants. + + - Can include static methods. Methods can have per-method template + overrides. + + - Can include constants. + +* Export 'structures'. These represent C pointers. + + - struct/union have a MemorySegment, a layout, and potentially + accessors for fields. + + - anonymous structures just have a MemoryAddress and no accessors. + + - Can include static and member methods. Methods can have per-method + template overrides. + + - Can include constants. + +* Export 'calls'. These are function interfaces for function pointers. + + - The interface is the java form of the call. + + - If available a typedef name is used, otherwise the names are mapped + to a mangled name of the form Call__. + + - An upcall factory creates an upcall to an instance of a hidden + trampline interface which performs the mapping of arguments to java + and returns to c. + + - A downcall factory creates a downcall which maps the java call to + native and back. + + - Both factories return a record of type FunctionPointer which + contains both a NativeSymbol and the java interface instance so they + can be used from either environment. + +* Export 'constants'. These are referenced enums or any defines which + haven't been included in any other library or struct. + + - enums are mapped to an interface with fields of: + 'static final int FOO = xx' + of + 'static final long FOO = xx' + + - defines are mapped to a matching native type which includes + floating point types and strings in addition to all the integral + types. + +lib/code.pm is used to generate some of the code and handle +templating. Common code templates are defined in lib/code.api but can +be extended or replaced by a given api file. + +lib/method.pm is used to generate per-field (struct) and per-argument +(function) sub-templates for mapping c to and from java. The +sub-templates it uses are defined in lib/types.api but again they can +be extended or replaced by a given api file. + +Status +------ + +It's all very much work in progress and due to the constant changes in +panama will be in flux for some time. + +* bitfields are implemented. +* varargs is not implemented. +* the generator for notzed.vkregistry uses a lot of miserable + write-once perl. +* the scope and object lifecycle stuff is not really sorted out yet. +* the config format and features are still being fiddled with. +* the config file isn't really documented. +* the build system is still being fiddled with, some of the output + directories are mixed up. +* linux-amd64 only at this point in time. + +License +------- + +GNU General Public License, version 3 or later, but see individual +file headers for specifics. + +Links +----- + + * https://www.zedzone/software/panamaz.html - project page. + * https://openjdk.java.net/projects/panama - openjdk panama page. + * https://github.com/openjdk/panama-foreign - openjdk panama source. diff --git a/config.make.in b/config.make.in new file mode 100644 index 0000000..d682dd1 --- /dev/null +++ b/config.make.in @@ -0,0 +1,49 @@ + +TARGET ?= linux-amd64 + +JAVA_HOME ?= /usr/local/jdk +JAVAFX_HOME ?= /usr/local/javafx-sdk +FFMPEG_HOME ?= /opt/ffmpeg-5.0 +NATIVEZ_HOME=bin/$(TARGET) +GCCPLUGINDIR:=$(shell gcc -print-file-name=plugin) + +JAVAMODPATH = bin/$(TARGET)/lib +JAVACFLAGS = +JMAINFLAGS = -Djava.library.path=bin/linux-amd64/lib:$(FFMPEG_HOME)/lib:/usr/lib64 + +JAVA ?= $(JAVA_HOME)/bin/java +JAVAC ?= $(JAVA_HOME)/bin/javac +JAR ?= $(JAVA_HOME)/bin/jar +JMOD ?= $(JAVA_HOME)/bin/jmod + +CFLAGS = -fPIC -Os -Wall +CXXFLAGS =-fPIC -Os -Wall + +# Linux options +# USE_SO_VERSION adds the major version to the library open name for ffmpeg libs on linux. +linux-amd64_CPPFLAGS = \ + -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux \ + -DUSE_SO_VERSION=1 +linux-amd64_CFLAGS = -fPIC -Os -Wall +linux-amd64_CC = cc +linux-amd64_CXXFLAGS = -fPIC -Os -Wall +linux-amd64_CXX = g++ +linux-amd64_LD = ld + +linux-amd64_SO = .so +linux-amd64_LIB = lib + +# Windows options +windows-amd64_CPPFLAGS = \ + -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux \ + -DHAVE_ALIGNED_MALLOC \ + -DWIN32 +windows-amd64_CFLAGS = -Os -Wall +windows-amd64_CC = x86_64-w64-mingw32-gcc +windows-amd64_CXXFLAGS = -Os -Wall +windows-amd64_CXX = x86_64-w64-mingw32-g++ +windows-amd64_LD = x86_64-w64-mingw32-ld +windows-amd64_LDFLAGS = -Wl,--subsystem,windows + +windows-amd64_SO = .dll +windows-amd64_LIB = diff --git a/java.make b/java.make new file mode 100644 index 0000000..c3b9030 --- /dev/null +++ b/java.make @@ -0,0 +1,422 @@ +# +# Copyright (C) 2019,2022 Michael Zucchi +# +# This is the copyright for java.make +# +# 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 . +# + +# General purpose modular java makefile that supports native library +# compilation directly. Non-recrusve implementation. +# +# Uses metamake programming with some file conventions to implement +# auto-make-like features. + +# Define modules +# -------------- +# java_MODULES list of java modules to compile. The sources must +# exist in src//classes. Resource files are +# stored in src//classes. Source-code +# generators must exist in src//gen. Native +# libraries must exist in src//jni. + +# native_MODULES list of native-only "modules". + + +# Global variables + +# JAVA_HOME location of jdk. +# JAVAC java compiler to use. Default is 'javac' on the path. +# JAVACFLAGS javac flags applied to all invocations. +# JAR jar command. +# JARFLAGS jar flags +# JMOD jmod command. +# JMODFLAGS jmod flags. +# JAVAFLAGS java flags for run targets + +# Module specific variables + +# _JDEPMOD Lists modules which this one depends on. + +# _JAVACFLAGS Extra module-specific flags for each command. +# _JARFLAGS +# _JMODFLAGS + +# all paths are relative to the root package name + +# _JAVA Java sources. If not set it is found from src//classes/(*.java) +# _RESOURCES .jar resources. If not set it is found from src//classes/(not *.java) +# _JAVA_GENERATED Java generated sources. +# _RESOURCES_GENERATED Java generated sources. + +# Variables for use in fragments + +# gen.make and jni.make can additionally make use of these variables + +# _gendir Location for files used in Java generation process (per project). +# _genjavadir Location where _JAVA_GENERATED .java files will be created (per project). +# _objdir Location for c objects (per target). +# _incdir Location for output includes, .jmod staging. +# _libdir Location for output libraries, .jmod staging. May point to _bindir. +# _bindir Location for output commands, .jmod staging. + +# Define libraries +# ---------------- + +# Each module can define one or more native libraries. + +# These are compiled after the java sources have been compiled as that +# process also generates any native binding headers. + +# _NATIVE_LIBRARIES list of libraries to build. +# library names match System.loadLibrary(). + +# Global variables + +# _LDFLAGS +# _LDLIBS +# _CPPFLAGS +# _CFLAGS +# _CC +# _CXXFLAGS +# _CXX +# SO shared library suffix +# LIB shared library prefix + +# Utility functions +# +# $(call library-path,,) will resolve to the library file name. + +# Per library variables. + +# _SOURCES .c source files for library. Paths are relative to src//native. +# _CXXSOURCES .c source files for library. Paths are relative to src//native. +# _HEADERS header files for install/jmod +# _COMMANDS commands/bin/scripts for install/jmod + +# _LDFLAGS link flags +# _LIBADD extra objects to add to link line +# _LDLIBS link libraries +# _CPPFLAGS c and c++ pre-processor flags. "-Isrc//jni -Ibin/include/" is implicit. +# _CCFLAGS c compiler flags +# _CXXFLAGS c++ compiler flags + +# _DEPENDENCIES A list of other objects on which this library depends before linking. + +# .c and .cc files have dependencies automatically generated + +# Targets +# ------- + +# make gen only generate java sources +# make clean rm -rf bin +# make dist create dist tar in bin/ +# make | make jar make all jars and jmods + +# Outputs +# ------- + +# All intermediate and output files are written to bin/ + +# This layout is enforced by javac +# bin/include// .h files from javac -h +# bin/modules// .class files from javac + +# This layout is convenient for netbeans +# bin/gen//gen/ .c, exe files for generator free use +# bin/gen//classes/ .java files from generator _JAVA_GENERATED + +# Working files +# bin/status/ marker files for makefile + +# bin///lib .so librareies for jmod _LIBRARIES = libname +# bin///obj .o, .d files for library _SOURCES +# bin///include .h files for jmod _HEADERS +# bin///.jmod .jmod module + +# Output files +# bin//lib/ modular jar files and shared libraries for GNU/linux dev +# bin//include/ header files for exported shared libraries +# bin//bin/ shared libraries for microsoft dev +# bin//jmods/ jmod files for 'jlink' use. + +# ###################################################################### + +all_MODULES = $(java_MODULES) $(native_MODULES) + +E:= +S:=$(E) $(E) +SO=$($(TARGET)_SO) +LIB=$($(TARGET)_LIB) + +# Define some useful variables before including fragments +define common_variables= +$1_gendir:=bin/gen/$1/gen +$1_genjavadir:=bin/gen/$1/classes +$1_objdir:=bin/$1/$(TARGET)/obj +$1_incdir:=bin/$1/$(TARGET)/include +$1_libdir:=$$(if $$(filter windows-%,$(TARGET)),bin/$1/$(TARGET)/bin,bin/$1/$(TARGET)/lib) +$1_bindir:=bin/$1/$(TARGET)/bin +endef + +define java_variables= +ifndef $1_JAVA +$1_JAVA := $$(shell cd src/$1/classes && find * -type f -name '*.java') +endif +ifndef $1_RESOURCES +$1_RESOURCES := $$(shell cd src/$1/classes && find * -type f \! -name '*.java') +endif +endef + +java_libdir:=$(if $(filter windows-%,$(TARGET)),bin/$(TARGET)/bin,bin/$(TARGET)/lib) +java_bindir:=bin/$(TARGET)/bin +java_jardir:=bin/$(TARGET)/lib +java_incdir:=bin/$(TARGET)/include +java_jmoddir:=bin/$(TARGET)/jmods + +$(foreach module,$(java_MODULES) $(native_MODULES),$(eval $(call common_variables,$(module)))) +$(foreach module,$(java_MODULES),$(eval $(call java_variables,$(module)))) + +# ###################################################################### + +all: +jar: +gen: + +.PHONY: all clean jar gen $(java_MODULES) +clean: + rm -rf bin + +# Gen is things that go into the jar (sources and resources) +include $(wildcard $(all_MODULES:%=src/%/gen/gen.make)) +# Native is things that go into the sdk/jmod +include $(wildcard $(all_MODULES:%=src/%/native/native.make)) + +# ###################################################################### + +# create module depencies +# variables: +# _sdk is the target location of an expanded 'sdk' for this module +# it resides in a common location bin// +# _jmod is the target location of a staging area for jmod files +# is resides in a per-module lcoation bin/// +# _java is all the targets that will cause the invocation of javac +# it includes the module source, generated sources, and sentinals for generated sources + +# targets: +# bin/status/.depjava marks all source/generated sources are ready/updated +# bin/status/.depjar all compiled class files and resources are ready/updated +# bin/status/.sdk all files are available in bin/ as if it was an installed image + +define module_vars= +$1_sdk := $(addprefix $(java_bindir)/,$($1_COMMANDS)) $(addprefix $(java_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$(java_libdir)/lib%.so) +$1_jmod := $(addprefix $($1_bindir)/,$($1_COMMANDS)) $(addprefix $($1_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$($1_libdir)/lib%.so) +$1_java :=$($1_JAVA:%=src/$1/classes/%) $($1_JAVA_GENERATED:%=$($1_genjavadir)/%) +$1_resources:= $($1_RESOURCES:%=src/$1/classes/%) $($1_RESOURCES_GENERATED:%=$($1_genjavadir)/%) +$1_depjava := $($1_API:%=bin/status/$1-%.export) $(patsubst %,bin/status/%.classes, $(filter $($1_JDEPMOD),$(java_MODULES))) + +ifneq ("$$(strip $$($1_java) $$($1_depjava))", "") +bin/status/$1.depjava: $$($1_java) $$($1_depjava) + @install -d $$(@D) + touch $$@ +bin/status/$1.depjar: bin/status/$1.classes $$($1_resources) + @install -d $$(@D) + touch $$@ +bin/status/$1.depmod: bin/status/$1.classes $$($1_resources) $$($1_jmod) + @install -d $$(@D) + touch $$@ +bin/status/$1.sdk: $(java_jardir)/$1.jar +jar: $(java_jardir)/$1.jar +gen: bin/status/$1.depjava +$1 all: $(java_jardir)/$1.jar $(java_jmoddir)/$1.jmod +else +# acutally not sure here? +$1 all: bin/status/$1.sdk +endif + +$1-sdk sdk: bin/status/$1.sdk + +bin/status/$1.sdk: $$($1_sdk) $$($1_jmod) + @install -d $$(@D) + touch $$@ + +endef + +#$(foreach m,$(all_MODULES),$(info $(call module_vars,$m))) +$(foreach m,$(all_MODULES),$(eval $(call module_vars,$m))) + +# ###################################################################### +# notzed.nativez export-api +# ###################################################################### + +define api_targets= +bin/status/$1-$2.export: src/$1/gen/$2.api src/$1/gen/$2.h +bin/status/$1-$2.export: + mkdir -p bin/gen/$1/gen bin/status + $(NATIVEZ_HOME)/bin/export-api \ + -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api + touch $$@ + +bin/status/$1-$2.export.d: + @$(NATIVEZ_HOME)/bin/export-api -M -MT "$$(@:.d=) $$@" -MF $$@ \ + -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api 2>/dev/null + +$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include bin/status/$1-$2.export.d) +endef + +$(foreach m,$(all_MODULES),$(foreach a,$($m_API),$(eval $(call api_targets,$m,$a)))) + +# ###################################################################### +# Java +# ###################################################################### + +# Build targets for java modules + +define java_targets= + +# Create (modular) jar +$(java_jardir)/$1.jar: bin/status/$1.depjar + @install -d $$(@D) + $(JAR) cf $$@ \ + $(JARFLAGS) $$($(1)_JARFLAGS) \ + -C bin/modules/$(1) . \ + $(if $($1_RESOURCES),$($1_RESOURCES:%=-C src/$1/classes %)) \ + $(if $($1_RESOURCES_GENERATED),$($1_RESOURCES_GENERATED:%=-C bin/gen/$1/classes %)) + +# Create a jmod +$(java_jmoddir)/$1.jmod: bin/status/$1.depmod + rm -f $$@ + @install -d $$(@D) + $$(JMOD) create \ + $$(JMODFLAGS) $$($(1)_JMODFLAGS) \ + --target-platform $(TARGET) \ + --class-path bin/modules/$(1) \ + $$(if $$(wildcard bin/$(1)/$(TARGET)/include),--header-files bin/$(1)/$(TARGET)/include) \ + $$(if $$(wildcard src/$(1)/legal),--legal-notices src/$(1)/legal) \ + $$(if $$(wildcard bin/$(1)/$(TARGET)/bin),--cmds bin/$(1)/$(TARGET)/bin) \ + $$(if $$(wildcard bin/$(1)/$(TARGET)/lib),--libs bin/$(1)/$(TARGET)/lib) \ + $$@ + +# Create an IDE source zip, paths have to match --module-source-path +$(java_jardir)/$1-sources.zip: bin/status/$1.depjar + @install -d $$(@D) + $(JAR) -c -f $$@ -M \ + $$($1_JAVA:%=-C src/$1/classes %) \ + $$($1_JAVA_GENERATED:%=-C bin/gen/$1/classes %) + +# resources +bin/modules/$1/%: src/$1/classes/% + install -vD $$< $$@ + +# Compile module. +bin/status/$1.classes: bin/status/$1.depjava + @install -d $$(@D) + $(JAVAC) \ + --module-source-path "src/*/classes:bin/gen/*/classes" \ + $(if $(JAVAMODPATH),--module-path $(subst $(S),:,$(JAVAMODPATH))) \ + $(JAVACFLAGS) $($1_JAVACFLAGS) \ + -d bin/modules \ + -m $1 \ + $$($1_JAVA:%=src/$1/classes/%) \ + $$($1_JAVA_GENERATED:%=bin/gen/$1/classes/%) + touch $$@ +endef + +#$(foreach module,$(java_MODULES),$(info $(call java_targets,$(module)))) +$(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module)))) + +# ###################################################################### + +# setup run-* targets +define run_targets= +run-$1/$2: bin/status/$1.sdk $($1_JDEPMOD:%=bin/status/%.sdk) + LD_LIBRARY_PATH=$(FFMPEG_HOME)/lib \ + $(JAVA) \ + $(if $(strip $(JAVAMODPATH) $($1_JAVAMODPATH)),--module-path $(subst $(S),:,$(strip $(JAVAMODPATH) $($1_JAVAMODPATH)))) \ + $(JMAINFLAGS) $($1_JMAINFLAGS) \ + -m $1/$2 \ + $(ARGV) +.PHONY: run-$1/$2 +endef + +#$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(info $(call run_targets,$(module),$(main))))) +$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(eval $(call run_targets,$(module),$(main))))) + +# ###################################################################### +# C and c++ native library support +# ###################################################################### + +define native_library= +# Rule for library $$2 in module $$1 +$2_OBJS = $(patsubst %.c, $($1_objdir)/%.o, $($2_SOURCES)) \ + $(patsubst %.cc, $($1_objdir)/%.o, $($2_CXXSOURCES)) +$2_SRCS = $(addprefix src/$1/native/,$($2_SOURCES)) +$2_SO = $($1_libdir)/$(LIB)$2$(SO) + +# Copy anything from staging area for jmods bin/module//* to sdk area bin//* +$(java_libdir)/%: $($(1)_libdir)/% + @install -d $$(@D) + ln -fs $$(abspath $$<) $$@ +$(java_bindir)/%: $($(1)_bindir)/% + @install -d $$(@D) + ln -fs $$(abspath $$<) $$@ +$(java_incdir)/%: $($(1)_incdir)/% + @install -d $$(@D) + ln -fs $$(abspath $$<) $$@ + +$($1_libdir)/$(LIB)$2$(SO): $$($2_OBJS) $($2_LIBADD) $($2_DEPENDENCIES) + @install -d $$(@D) + $($(TARGET)_CC) -o $$@ -shared \ + $($(TARGET)_LDFLAGS) $($2_LDFLAGS) $$($2_OBJS) $($2_LIBADD) $($(TARGET)_LDLIBS) $($2_LDLIBS) + +$($1_objdir)/%.o: src/$1/native/%.c + @install -d $$(@D) + $($(TARGET)_CC) -Isrc/$1/native -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \ + $($(TARGET)_CFLAGS) $($2_CFLAGS) -c -o $$@ $$< + +$($1_objdir)/%.o: src/$1/native/%.cc + @install -d $$(@D) + $($(TARGET)_CXX) -Isrc/$1/native -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \ + $($(TARGET)_CXXFLAGS) $($2_CXXFLAGS) -c -o $$@ $$< + +# auto-dependencies for c files +$($1_objdir)/%.d: src/$1/native/%.c + @install -d $$(@D) + @rm -f $$@ + @$($(TARGET)_CC) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null + +# auto-dependencies for c++ files +$($1_objdir)/%.d: src/$1/native/%.cc + @install -d $$(@D) + @rm -f $$@ + @$($(TARGET)_CXX) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null + +$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include $$($2_OBJS:.o=.d)) +endef + +#$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(info $(call native_library,$(module),$(library))))) +$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(eval $(call native_library,$(module),$(library))))) + +# ###################################################################### + +dist: + @install -d bin + tar cfz bin/$(dist_NAME)-$(dist_VERSION).tar.gz \ + --transform=s,^,$(dist_NAME)-$(dist_VERSION)/, \ + config.make.in java.make Makefile src \ + $(dist_EXTRA) diff --git a/maven.make b/maven.make new file mode 100644 index 0000000..c9e82e2 --- /dev/null +++ b/maven.make @@ -0,0 +1,74 @@ +# +# Copyright (C) 2021 Michael Zucchi +# +# This is the copyright for maven.make +# +# 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 . +# + +# This lets one download maven packages using simple automake syntax. + +# maven__URL = baseurl + +# Define the base url. maven_central_URL is already defined as +# maven_central_URL:=https://repo1.maven.org/maven2 + +# maven__JARS = group:artifact:version group:artifact:version ... + +# Define the artifacts required from the given maven repository. + +# That's it! + +# It defines several make targets. + +# make maven-init +# Will download the jar files. + +# make maven-verify +# Will download and check the signatures using gpg. The public key +# required for verification must be imported to gpg separately. + +# make distclean +# Will delete .lib + +# define maven central +maven_central_URL:=https://repo1.maven.org/maven2 +maven_repository_URL:=https://mvnrepository.com/artifact + +# find out what repositories the makefile defined +maven_REPOS=$(patsubst maven_%_URL,%,$(filter maven_%_URL,$(.VARIABLES))) + +# (group artifact version baseurl) +define maven_func= +.lib/$2-$3.jar: + mkdir -p .lib + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3.jar || ( rm $$@ ; exit 1 ) +.lib/$2-$3.jar.asc: .lib/$2-$3.jar + wget -O $$@ $(4)/$(subst .,/,$1)/$2/$3/$2-$3.jar.asc + gpg --batch --verify $$@ $$< || ( rm $$@ ; echo "GPG verification failed, you may need to import the public key." ; exit 1 ) +maven-init: .lib/$2-$3.jar +maven-verify: .lib/$2-$3.jar.asc +endef + +maven-init: +maven-verify: + +.PHONY: maven-init maven-verify + +$(foreach repo,$(maven_REPOS),\ + $(foreach jar,$(maven_$(repo)_JARS), \ + $(eval $(call maven_func,$(word 1,$(subst :, ,$(jar))),$(word 2,$(subst :, ,$(jar))),$(word 3,$(subst :, ,$(jar))),$(maven_$(repo)_URL))))) + +distclean: + rm -rf .lib diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..d277f11 --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1884 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +package netbeans; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +public class CoalesceKeyvalue extends Task { + private String property; + + public void setProperty(String property) { + this.property = property; + } + + private String value; + + public void setValue(String value) { + this.value = value; + } + + private String valueSep; + + public void setValueSep(String valueSep) { + this.valueSep = valueSep; + } + + private String entrySep; + + public void setEntrySep(String entrySep) { + this.entrySep = entrySep; + } + + private String multiSep; + + public void setMultiSep(String multiSep) { + this.multiSep = multiSep; + } + + private String outSep; + + public void setOutSep(String outSep) { + this.outSep = outSep; + } + + @Override + public void execute() throws BuildException { + List<String> result = new ArrayList<>(); + Map<String, List<String>> module2Paths = new HashMap<>(); + + for (String entry : value.split(Pattern.quote(entrySep))) { + String[] keyValue = entry.split(Pattern.quote(valueSep), 2); + if (keyValue.length == 1) { + result.add(keyValue[0]); + } else { + module2Paths.computeIfAbsent(keyValue[0], s -> new ArrayList<>()) + .add(keyValue[1].trim()); + } + } + module2Paths.entrySet() + .stream() + .forEach(e -> result.add(e.getKey() + valueSep + e.getValue().stream().collect(Collectors.joining(multiSep)))); + getProject().setProperty(property, result.stream().collect(Collectors.joining(" " + entrySep))); + } + +} + + + + +package netbeans; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +public class ModsourceRegexp extends Task { + private String property; + + public void setProperty(String property) { + this.property = property; + } + + private String filePattern; + + public void setFilePattern(String filePattern) { + this.filePattern = filePattern; + } + + private String modsource; + + public void setModsource(String modsource) { + this.modsource = modsource; + } + + private List<String> expandGroup(String grp) { + List<String> exp = new ArrayList<>(); + String item = ""; + int depth = 0; + + for (int i = 0; i < grp.length(); i++) { + char c = grp.charAt(i); + switch (c) { + case '{': + if (depth++ == 0) { + continue; + } + break; + case '}': + if (--depth == 0) { + exp.add(item); + continue; + } + break; + case ',': + if (depth == 1) { + exp.add(item); + item = ""; + continue; + } + default: + break; + } + item = item + c; + } + return exp; + } + + private List<String> pathVariants(String spec) { + return pathVariants(spec, new ArrayList<>()); + } + + private List<String> pathVariants(String spec, List<String> res) { + int start = spec.indexOf('{'); + if (start == -1) { + res.add(spec); + return res; + } + int depth = 1; + int end; + for (end = start + 1; end < spec.length() && depth > 0; end++) { + char c = spec.charAt(end); + switch (c) { + case '{': depth++; break; + case '}': depth--; break; + } + } + String prefix = spec.substring(0, start); + String suffix = spec.substring(end); + expandGroup(spec.substring(start, end)).stream().forEach(item -> { + pathVariants(prefix + item + suffix, res); + }); + return res; + } + + private String toRegexp2(String spec, String filepattern, String separator) { + List<String> prefixes = new ArrayList<>(); + List<String> suffixes = new ArrayList<>(); + pathVariants(spec).forEach(item -> { + suffixes.add(item); + }); + String tail = ""; + String separatorString = separator; + if ("\\".equals(separatorString)) { + separatorString = "\\\\"; + } + if (filepattern != null && !Objects.equals(filepattern, tail)) { + tail = separatorString + filepattern; + } + return "([^" + separatorString +"]+)\\Q" + separator + "\\E(" + suffixes.stream().collect(Collectors.joining("|")) + ")" + tail; + } + + @Override + public void execute() throws BuildException { + getProject().setProperty(property, toRegexp2(modsource, filePattern, getProject().getProperty("file.separator"))); + } + +} + + + + +package netbeans; + +import java.io.File; +import java.util.Arrays; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.selectors.BaseExtendSelector; + +public class ModuleInfoSelector extends BaseExtendSelector { + + @Override + public boolean isSelected(File basedir, String filename, File file) throws BuildException { + String extension = Arrays.stream(getParameters()) + .filter(p -> "extension".equals(p.getName())) + .map(p -> p.getValue()) + .findAny() + .get(); + return !new File(file, "module-info." + extension).exists(); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @{paths} + + + + + + + + + + + + @{paths} + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set src.gen.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.modules.dir + Must set dist.javadoc.dir + Must set build.test.modules.dir + Must set build.test.results.dir + Must set build.classes.excludes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No main class specified + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..fe2aca6 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=9e6776ee +build.xml.script.CRC32=84612a8f +build.xml.stylesheet.CRC32=32069288@1.17 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=9e6776ee +nbproject/build-impl.xml.script.CRC32=4a65f9d9 +nbproject/build-impl.xml.stylesheet.CRC32=d1ebcf0f@1.17 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..484a1fa --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,101 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=vulkanz +application.vendor=notzed +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +build.modules.dir=${build.dir}/modules +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.modules.dir=${build.dir}/test/modules +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.javadoc.dir=${dist.dir}/javadoc +dist.jlink.dir=${dist.dir}/jlink +dist.jlink.output=${dist.jlink.dir}/vulkanz +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=false +javac.modulepath=\ + ${reference.notzed_nativez.notzed_nativez_jar} +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=18 +javac.target=18 +javac.test.classpath=\ + ${javac.classpath} +javac.test.modulepath=\ + ${javac.modulepath}:\ + ${build.modules.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +# The jlink additional root modules to resolve +jlink.additionalmodules= +# The jlink additional command line parameters +jlink.additionalparam= +jlink.launcher=true +jlink.launcher.name=vulkanz +platform.active=default_platform +project.license=gpl3-notzed +project.notzed_nativez=../nativez +reference.notzed_nativez.notzed_nativez_jar=${project.notzed_nativez}/dist/notzed.nativez.jar +run.classpath= +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs=--enable-native-access=notzed.vulkan,notzed.nativez,notzed.xlib +run.modulepath=\ + ${javac.modulepath}:\ + ${build.modules.dir} +run.test.classpath=\ + ${javac.test.classpath} +run.test.modulepath=\ + ${javac.test.modulepath}:\ + ${build.test.modules.dir} +source.encoding=UTF-8 +src.dir=src +src.dir.path=classes +src.gen.dir=bin/gen +src.gen.dir.path=classes +test.src.dir=src +test.src.dir.path=tests diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..5fcd924 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,26 @@ + + + org.netbeans.modules.java.j2semodule + + + vulkanz + + + + + + + + + + + notzed_nativez + jar + + jar + clean + notzed.nativez.jar + + + + diff --git a/src/notzed.display/classes/au/notzed/display/Display.java b/src/notzed.display/classes/au/notzed/display/Display.java new file mode 100644 index 0000000..55806b8 --- /dev/null +++ b/src/notzed.display/classes/au/notzed/display/Display.java @@ -0,0 +1,43 @@ + +package au.notzed.display; + +import jdk.incubator.foreign.*; +import au.notzed.nativez.*; + +import vulkan.*; +import static vulkan.Vulkan.*; + +import xlib.*; +import static xlib.XLib.*; + +public abstract class Display { + + public abstract Window createWindow(int width, int height); + public abstract void close(); + + public static Display createX11Display() { + return new X11Display(XOpenDisplay(null)); + } + + static class X11Display extends Display { + XDisplay display; + + static { + XInitThreads(); + } + + X11Display(XDisplay display) { + this.display = display; + } + + public Window createWindow(int width, int height) { + return Window.createX11Window(display, width, height); + } + + public void close() { + XCloseDisplay(display); + } + } + + // xcb, sdl etc +} diff --git a/src/notzed.display/classes/au/notzed/display/Event.java b/src/notzed.display/classes/au/notzed/display/Event.java new file mode 100644 index 0000000..4599bf9 --- /dev/null +++ b/src/notzed.display/classes/au/notzed/display/Event.java @@ -0,0 +1,59 @@ +package au.notzed.display; + +// basic event handling +public class Event { + + public final int type; + public final int x, y, width, height; + // shift etc + public final int key; + public final int button; + + public static final int CLOSE = 0; + public static final int KEY = 1; + public static final int POINTER = 2; + public static final int BUTTON = 3; + public static final int EXPOSE = 4; + public static final int RESIZE = 5; + + public Event(int type, int x, int y, int width, int height, int key, int button) { + this.type = type; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.key = key; + this.button = button; + } + + public static Event key(int key) { + return new Event(KEY, 0, 0, 0, 0, key, 0); + } + + public static Event expose(int x, int y, int width, int height) { + return new Event(EXPOSE, x, y, width, height, 0, 0); + } + + public static Event resize(int x, int y, int width, int height) { + return new Event(RESIZE, x, y, width, height, 0, 0); + } + + public static Event close() { + return new Event(CLOSE, 0, 0, 0, 0, 0, 0); + } + + public String toString() { + switch (type) { + case CLOSE: + return "[Event close]"; + case KEY: + return "[Event key=" + key + "]"; + case EXPOSE: + return "[Event expose=" + x + "," + y + "," + width + "," + height + "]"; + case RESIZE: + return "[Event resize=" + x + "," + y + "," + width + "," + height + "]"; + default: + return "[Event]"; + } + } +} diff --git a/src/notzed.display/classes/au/notzed/display/Window.java b/src/notzed.display/classes/au/notzed/display/Window.java new file mode 100644 index 0000000..ef1106b --- /dev/null +++ b/src/notzed.display/classes/au/notzed/display/Window.java @@ -0,0 +1,141 @@ +package au.notzed.display; + +import jdk.incubator.foreign.*; +import au.notzed.nativez.*; + +import vulkan.*; +import static vulkan.Vulkan.*; + +import xlib.*; +import static xlib.XLib.*; + +public abstract class Window { + + public abstract VkSurfaceKHR createVulkanSurface(VkInstance instance, ResourceScope scope); + + public abstract Event nextEvent(boolean blocking); + + public abstract void close(); + + static Window createX11Window(XDisplay display, int width, int height) { + try ( Frame frame = Frame.frame()) { + long visualMask = VisualScreenMask; + IntArray numberOfVisuals = IntArray.createArray(1, frame); + XVisualInfo vInfoTemplate = XVisualInfo.create(frame); + + vInfoTemplate.setScreen(DefaultScreen(display)); + + XVisualInfo visualInfo = XGetVisualInfo(display, visualMask, vInfoTemplate, numberOfVisuals); + long colormap = XCreateColormap(display, RootWindow(display, vInfoTemplate.getScreen()), visualInfo.getVisual(), AllocNone); + + XSetWindowAttributes windowAttributes = XSetWindowAttributes.create(frame); + + windowAttributes.setBackgroundPixmap(0); + windowAttributes.setColormap(colormap); + windowAttributes.setEventMask(KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask); + + long window = XCreateWindow(display, RootWindow(display, vInfoTemplate.getScreen()), + 0, 0, width, height, + 0, visualInfo.getDepth(), InputOutput, visualInfo.getVisual(), + CWBackPixmap | CWEventMask | CWColormap, windowAttributes); + + XSelectInput(display, window, ExposureMask | KeyPressMask | StructureNotifyMask | PointerMotionMask); + + XMapWindow(display, window); + XFlush(display); + + return new X11Window(display, window); + } + } + + static class X11Window extends Window { + + XDisplay display; + long window; + // TODO: move to display + long wm_delete_window; + long wm_protocols; + + X11Window(XDisplay display, long window) { + this.display = display; + this.window = window; + wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", 0); + wm_protocols = XInternAtom(display, "WM_PROTOCOLS", 0); + try ( Frame frame = Frame.frame()) { + LongArray atoms = LongArray.create(frame, wm_delete_window); + XSetWMProtocols(display, window, atoms, 1); + } + } + + @Override + public void close() { + XDestroyWindow(display, window); + } + + @Override + public Event nextEvent(boolean blocking) { + + try ( Frame frame = Frame.frame()) { + XEvent event = XEvent.create(frame); + + while (true) { + if (!blocking && XPending(display) == 0) + return null; + + int res = XNextEvent(display, event); + + //System.out.printf("next event: %d type %d\n", res, event.getType()); + switch (event.getType()) { + case KeyPress: + return Event.key(event.getXkey().getKeycode()); + case Expose: { + XExposeEvent x = event.getXexpose(); + return Event.expose(x.getX(), x.getY(), x.getWidth(), x.getHeight()); + } + case ConfigureNotify: { + // nb: xy isn't useful + XConfigureEvent x = event.getXconfigure(); + return Event.resize(x.getX(), x.getY(), x.getWidth(), x.getHeight()); + } + case UnmapNotify: { + XUnmapEvent x = event.getXunmap(); + return Event.resize(0, 0, 0, 0); + } + case MapNotify: { + try ( Frame sub = Frame.frame()) { + XWindowAttributes at = XWindowAttributes.create(sub); + XGetWindowAttributes(display, window, at); + + return Event.resize(at.getX(), at.getY(), at.getWidth(), at.getHeight()); + } + } + case ClientMessage: { + XClientMessageEvent x = event.getXclient(); + + if (x.getMessageType() == wm_protocols + && x.getData().getLElement(0) == wm_delete_window) { + return Event.close(); + } else { + System.out.printf("unknown client message\n"); + } + break; + } + } + } + } + } + + @Override + public VkSurfaceKHR createVulkanSurface(VkInstance instance, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkXlibSurfaceCreateInfoKHR surfaceinfo = VkXlibSurfaceCreateInfoKHR.create( + 0, + display, + window, + frame); + + return instance.vkCreateXlibSurfaceKHR(surfaceinfo, scope); + } + } + } +} diff --git a/src/notzed.display/classes/module-info.java b/src/notzed.display/classes/module-info.java new file mode 100644 index 0000000..ac0ced3 --- /dev/null +++ b/src/notzed.display/classes/module-info.java @@ -0,0 +1,8 @@ + +module notzed.display { + requires notzed.xlib; + requires notzed.xcb; + requires notzed.vulkan; + + exports au.notzed.display; +} diff --git a/src/notzed.vulkan.test/classes/module-info.java b/src/notzed.vulkan.test/classes/module-info.java new file mode 100644 index 0000000..b21b754 --- /dev/null +++ b/src/notzed.vulkan.test/classes/module-info.java @@ -0,0 +1,7 @@ + +module notzed.vulkan.test { + requires notzed.vulkan; + requires notzed.display; + + requires java.desktop; +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/Cube.java b/src/notzed.vulkan.test/classes/vulkan/test/Cube.java new file mode 100644 index 0000000..e8f824e --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/Cube.java @@ -0,0 +1,55 @@ + +package vulkan.test; + +//struct Vertex { +// float posX, posY, posZ, posW; // Position data +// float r, g, b, a; // Color +//}; + +class Cube { + static final int dataStride = 8 * 4; + static final float[] data = new float[] { + // red face + -1, -1, 1, 1.f, 1.f, 0.f, 0.f, 1.f, + -1, 1, 1, 1.f, 1.f, 0.f, 0.f, 1.f, + 1, -1, 1, 1.f, 1.f, 0.f, 0.f, 1.f, + 1, -1, 1, 1.f, 1.f, 0.f, 0.f, 1.f, + -1, 1, 1, 1.f, 1.f, 0.f, 0.f, 1.f, + 1, 1, 1, 1.f, 1.f, 0.f, 0.f, 1.f, + // green face + -1, -1, -1, 1.f, 0.f, 1.f, 0.f, 1.f, + 1, -1, -1, 1.f, 0.f, 1.f, 0.f, 1.f, + -1, 1, -1, 1.f, 0.f, 1.f, 0.f, 1.f, + -1, 1, -1, 1.f, 0.f, 1.f, 0.f, 1.f, + 1, -1, -1, 1.f, 0.f, 1.f, 0.f, 1.f, + 1, 1, -1, 1.f, 0.f, 1.f, 0.f, 1.f, + // blue face + -1, 1, 1, 1.f, 0.f, 0.f, 1.f, 1.f, + -1, -1, 1, 1.f, 0.f, 0.f, 1.f, 1.f, + -1, 1, -1, 1.f, 0.f, 0.f, 1.f, 1.f, + -1, 1, -1, 1.f, 0.f, 0.f, 1.f, 1.f, + -1, -1, 1, 1.f, 0.f, 0.f, 1.f, 1.f, + -1, -1, -1, 1.f, 0.f, 0.f, 1.f, 1.f, + // yellow face + 1, 1, 1, 1.f, 1.f, 1.f, 0.f, 1.f, + 1, 1, -1, 1.f, 1.f, 1.f, 0.f, 1.f, + 1, -1, 1, 1.f, 1.f, 1.f, 0.f, 1.f, + 1, -1, 1, 1.f, 1.f, 1.f, 0.f, 1.f, + 1, 1, -1, 1.f, 1.f, 1.f, 0.f, 1.f, + 1, -1, -1, 1.f, 1.f, 1.f, 0.f, 1.f, + // magenta face + 1, 1, 1, 1.f, 1.f, 0.f, 1.f, 1.f, + -1, 1, 1, 1.f, 1.f, 0.f, 1.f, 1.f, + 1, 1, -1, 1.f, 1.f, 0.f, 1.f, 1.f, + 1, 1, -1, 1.f, 1.f, 0.f, 1.f, 1.f, + -1, 1, 1, 1.f, 1.f, 0.f, 1.f, 1.f, + -1, 1, -1, 1.f, 1.f, 0.f, 1.f, 1.f, + // cyan face + 1, -1, 1, 1.f, 0.f, 1.f, 1.f, 1.f, + 1, -1, -1, 1.f, 0.f, 1.f, 1.f, 1.f, + -1, -1, 1, 1.f, 0.f, 1.f, 1.f, 1.f, + -1, -1, 1, 1.f, 0.f, 1.f, 1.f, 1.f, + 1, -1, -1, 1.f, 0.f, 1.f, 1.f, 1.f, + -1, -1, -1, 1.f, 0.f, 1.f, 1.f, 1.f, + }; +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/Demo.java b/src/notzed.vulkan.test/classes/vulkan/test/Demo.java new file mode 100644 index 0000000..45ef9bb --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/Demo.java @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * 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 . + */ +package vulkan.test; + +import au.notzed.nativez.FloatArray; +import au.notzed.nativez.Frame; +import au.notzed.nativez.HandleArray; +import au.notzed.nativez.IntArray; +import au.notzed.nativez.Memory; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Stream; +import jdk.incubator.foreign.GroupLayout; +import jdk.incubator.foreign.MemoryLayout; +import jdk.incubator.foreign.MemorySegment; +import jdk.incubator.foreign.ResourceScope; +import jdk.incubator.foreign.SegmentAllocator; +import jdk.incubator.foreign.ValueLayout; +import vulkan.PFN_vkDebugUtilsMessengerCallbackEXT; +import vulkan.VkApplicationInfo; +import vulkan.VkAttachmentDescription; +import vulkan.VkAttachmentReference; +import vulkan.VkBuffer; +import vulkan.VkBufferCopy; +import vulkan.VkBufferCreateInfo; +import vulkan.VkCommandBuffer; +import vulkan.VkCommandBufferAllocateInfo; +import vulkan.VkCommandBufferBeginInfo; +import vulkan.VkCommandPool; +import vulkan.VkCommandPoolCreateInfo; +import vulkan.VkDebugUtilsMessengerCreateInfoEXT; +import vulkan.VkDebugUtilsMessengerEXT; +import vulkan.VkDevice; +import vulkan.VkDeviceCreateInfo; +import vulkan.VkDeviceMemory; +import vulkan.VkDeviceQueueCreateInfo; +import vulkan.VkExtensionProperties; +import vulkan.VkFence; +import vulkan.VkFenceCreateInfo; +import vulkan.VkFramebuffer; +import vulkan.VkFramebufferCreateInfo; +import vulkan.VkGraphicsPipelineCreateInfo; +import vulkan.VkImage; +import vulkan.VkImageView; +import vulkan.VkImageViewCreateInfo; +import vulkan.VkInstance; +import vulkan.VkInstanceCreateInfo; +import vulkan.VkMemoryAllocateInfo; +import vulkan.VkMemoryRequirements; +import vulkan.VkPhysicalDevice; +import vulkan.VkPhysicalDeviceFeatures; +import vulkan.VkPhysicalDeviceMemoryProperties; +import vulkan.VkPhysicalDeviceProperties; +import vulkan.VkPipeline; +import vulkan.VkPipelineColorBlendAttachmentState; +import vulkan.VkPipelineColorBlendStateCreateInfo; +import vulkan.VkPipelineDynamicStateCreateInfo; +import vulkan.VkPipelineInputAssemblyStateCreateInfo; +import vulkan.VkPipelineLayout; +import vulkan.VkPipelineLayoutCreateInfo; +import vulkan.VkPipelineMultisampleStateCreateInfo; +import vulkan.VkPipelineRasterizationStateCreateInfo; +import vulkan.VkPipelineShaderStageCreateInfo; +import vulkan.VkPipelineVertexInputStateCreateInfo; +import vulkan.VkPipelineViewportStateCreateInfo; +import vulkan.VkQueue; +import vulkan.VkQueueFamilyProperties; +import vulkan.VkRenderPass; +import vulkan.VkRenderPassCreateInfo; +import vulkan.VkSemaphore; +import vulkan.VkSemaphoreCreateInfo; +import vulkan.VkShaderModule; +import vulkan.VkShaderModuleCreateInfo; +import vulkan.VkSubmitInfo; +import vulkan.VkSubpassDependency; +import vulkan.VkSubpassDescription; +import vulkan.VkSurfaceCapabilitiesKHR; +import vulkan.VkSurfaceFormatKHR; +import vulkan.VkSurfaceKHR; +import vulkan.VkSwapchainCreateInfoKHR; +import vulkan.VkSwapchainKHR; +import vulkan.VkVertexInputAttributeDescription; +import vulkan.VkVertexInputBindingDescription; +import vulkan.Vulkan; +import static vulkan.Vulkan.*; + +/** + * Basic demo setup. + */ +public class Demo { + + // requested queue types, includes the pseudo-type PRESENT to simplify api + public final static int QUEUE_GRAPHICS = VK_QUEUE_GRAPHICS_BIT; + public final static int QUEUE_COMPUTE = VK_QUEUE_COMPUTE_BIT; + public final static int QUEUE_TRANSFER = VK_QUEUE_TRANSFER_BIT; + public final static int QUEUE_PRESENT = 0x80; + public final static int QUEUE_MASK = QUEUE_GRAPHICS | QUEUE_COMPUTE | QUEUE_TRANSFER; + + public static VkInstance createInstance(int apiVersion, String[] layers, String[] extensions, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkInstanceCreateInfo info = VkInstanceCreateInfo.create( + 0, + VkApplicationInfo.create(null, 0, null, 0, apiVersion, frame), + layers.length, layers, + extensions.length, extensions, + frame + ); + + return VkInstance.vkCreateInstance(info, scope); + } + } + + public static VkDebugUtilsMessengerEXT createLogger(VkInstance instance, ResourceScope scope) throws Exception { + try ( Frame frame = Frame.frame()) { + var cb = PFN_vkDebugUtilsMessengerCallbackEXT.upcall((severity, flags, data) -> { + System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); + return 0; + }, scope); + VkDebugUtilsMessengerCreateInfoEXT info = VkDebugUtilsMessengerCreateInfoEXT.create( + 0, + //VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + cb, + null, + frame); + + return instance.vkCreateDebugUtilsMessengerEXT(info, scope); + } + } + + static float priority(VkPhysicalDeviceProperties props) { + switch (props.getDeviceType()) { + default: + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + return 0; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + return 3; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + return 4; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + return 2; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + return 1; + } + } + + static VkDeviceQueueCreateInfo selectQueues(VkPhysicalDevice dev, VkSurfaceKHR surface, int[] dflags, int[] families, SegmentAllocator frame$) { + try ( Frame frame = Frame.frame()) { + int allFamilies = 0; + + // This tries to find a matching queue for each application queue requested. + // It priorities queues which are 'more exclusive' than others. + VkQueueFamilyProperties p = dev.vkGetPhysicalDeviceQueueFamilyProperties(frame); + for (int i = 0; i < dflags.length; i++) { + int dflag = dflags[i]; + int family = -1; + float bestPri = 0; + + for (int j = 0; j < p.length(); j++) { + int flags = p.getQueueFlagsAtIndex(j); + float pri = 0; + + if (surface != null && dev.vkGetPhysicalDeviceSurfaceSupportKHR(j, surface)) + flags |= QUEUE_PRESENT; + // according to vulkan tutorial either implies transfer + if ((flags & (Vulkan.VK_QUEUE_GRAPHICS_BIT | Vulkan.VK_QUEUE_COMPUTE_BIT)) != 0) + flags |= QUEUE_TRANSFER; + + if ((flags & dflag) == dflag) + pri = 5 - Integer.bitCount(flags & ~dflag); + + if (pri > bestPri) { + bestPri = pri; + family = j; + } + } + + if (family == -1) + throw new RuntimeException("Cannot find required queues"); + + families[i] = family; + allFamilies |= 1 << family; + } + + int nfamilies = Integer.bitCount(allFamilies); + + VkDeviceQueueCreateInfo qinfo = VkDeviceQueueCreateInfo.createArray(nfamilies, frame$); + for (int i = 0; i < nfamilies; i++) { + int family = Integer.numberOfTrailingZeros(allFamilies); + FloatArray qpri = FloatArray.create(frame$, 0.0f); + + qinfo.setQueueFamilyIndexAtIndex(i, family); + qinfo.setQueueCountAtIndex(i, 1); + qinfo.setQueuePrioritiesAtIndex(i, qpri); + + allFamilies = allFamilies & ~(1 << family); + } + + System.out.println(qinfo); + + return qinfo; + } + } + + // instance? scope? + // allocation? + public static record DemoDevice( + VkPhysicalDevice dev, + VkPhysicalDeviceMemoryProperties memory, + VkDevice device, + VkSurfaceKHR surface, + int[] queueFamilies, + VkQueue[] queues, VkCommandPool[] pools) { + + public void close() { + Stream.of(pools).forEach(device::vkDestroyCommandPool); + device.vkDestroyDevice(); + } + + public VkSemaphore createSemaphore(ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkSemaphoreCreateInfo info = VkSemaphoreCreateInfo.create(frame); + return device.vkCreateSemaphore(info, scope); + } + } + + public VkFence createFence(ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkFenceCreateInfo info = VkFenceCreateInfo.create(frame); + return device.vkCreateFence(info, scope); + } + } + + public HandleArray createSemaphores(int count, SegmentAllocator alloc, ResourceScope scope) { + HandleArray list = VkSemaphore.createArray(count, alloc, scope); + try ( Frame frame = Frame.frame()) { + VkSemaphoreCreateInfo info = VkSemaphoreCreateInfo.create(frame); + + for (int i = 0; i < count; i++) + list.set(i, device.vkCreateSemaphore(info, scope)); + } + return list; + } + + public HandleArray createFences(int count, int flags, SegmentAllocator alloc, ResourceScope scope) { + HandleArray list = VkFence.createArray(count, alloc, scope); + try ( Frame frame = Frame.frame()) { + VkFenceCreateInfo info = VkFenceCreateInfo.create(flags, frame); + + for (int i = 0; i < count; i++) + list.set(i, device.vkCreateFence(info, scope)); + } + return list; + } + + public int findMemory(int type, int reqProp) { + for (int i = 0, len = memory.getMemoryTypeCount(); i < len; i++) { + int prop = memory.getMemoryTypes$propertyFlagsAtIndex(i); + + if (((1 << i) & type) != 0 && (prop & reqProp) == reqProp) + return i; + + } + throw new RuntimeException("memory type not found"); + } + } + + // TODO: alloc? + public static DemoDevice createDevice(VkInstance instance, VkPhysicalDeviceFeatures features, VkSurfaceKHR surface, int[] queueFlags, String[] extensions, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + HandleArray devs = instance.vkEnumeratePhysicalDevices(frame, scope); + VkPhysicalDeviceProperties dp = VkPhysicalDeviceProperties.create(frame); + VkPhysicalDeviceFeatures df = VkPhysicalDeviceFeatures.create(frame); + + // First select best device + // - must support features + // - if surface non-null then must support surface output + VkPhysicalDevice dev = null; + VkSurfaceFormatKHR surfaceFormats = null; + IntArray presentModes = null; + + float bestPriority = -1; + for (int i = 0; i < devs.size(); i++) { + VkPhysicalDevice d = devs.getAtIndex(i); + VkSurfaceFormatKHR formats = null; + IntArray modes = null; + float pri; + + d.vkGetPhysicalDeviceProperties(dp); + pri = priority(dp); + if (pri <= bestPriority) + continue; + + if (!checkDeviceExtensionSupport(d, extensions)) + continue; + + d.vkGetPhysicalDeviceFeatures(df); + if (!df.hasFeatures(features)) + continue; + + if (surface != null) { + formats = d.vkGetPhysicalDeviceSurfaceFormatsKHR(surface, frame); + modes = d.vkGetPhysicalDeviceSurfacePresentModesKHR(surface, frame); + + if (formats.isEmpty() || modes.isEmpty()) + continue; + } + + dev = d; + surfaceFormats = formats; + presentModes = modes; + bestPriority = pri; + } + + if (dev == null) + throw new RuntimeException("No suitable device found"); + + int[] families = new int[queueFlags.length]; + VkDeviceQueueCreateInfo qinfo = selectQueues(dev, surface, queueFlags, families, frame); + + VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( + 0, + (int)qinfo.length(), qinfo, + 0, null, // enabled layers + (int)Memory.length(extensions), extensions, + features, + frame); + + var device = dev.vkCreateDevice(devinfo, scope); + + VkCommandPoolCreateInfo poolInfo = VkCommandPoolCreateInfo.create(0, 0, frame); + + // TODO: only create one pool/queue per unique family? + VkQueue[] queues = new VkQueue[families.length]; + VkCommandPool[] pools = new VkCommandPool[families.length]; + + for (int i = 0; i < families.length; i++) { + System.out.printf(" queue[%d] = family[%d]\n", i, families[i]); + queues[i] = device.vkGetDeviceQueue(families[i], 0, scope); + + poolInfo.setQueueFamilyIndex(families[i]); + pools[i] = device.vkCreateCommandPool(poolInfo, scope); + } + + VkPhysicalDeviceMemoryProperties memory = VkPhysicalDeviceMemoryProperties.create((SegmentAllocator)scope); + + dev.vkGetPhysicalDeviceMemoryProperties(memory); + + return new DemoDevice(dev, memory, device, surface, families, queues, pools); + } + } + + public record ColourFormat(int format, int colourSpace) { + + boolean matchAt(VkSurfaceFormatKHR fmt, int i) { + return fmt.getFormatAtIndex(i) == format + & fmt.getColorSpaceAtIndex(i) == colourSpace; + } + + public static ColourFormat SRGB = new ColourFormat(VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR); + + } + + static ColourFormat selectColourFormat(VkSurfaceFormatKHR formats, ColourFormat def, ColourFormat... query) { + int len = (int)formats.length(); + + if (len == 1 && formats.getColorSpaceAtIndex(0) == VK_FORMAT_UNDEFINED) + return def; + + for (ColourFormat q: query) { + for (int i = 0; i < len; i++) { + if (q.matchAt(formats, i)) + return q; + } + } + + return new ColourFormat(formats.getFormatAtIndex(0), formats.getColorSpaceAtIndex(0)); + } + + static int selectPresentMode(IntArray modes, int def, int... query) { + for (int q: query) { + if (modes.contains(q)) + return q; + } + return def; + } + + static int selectFlags(int flag, int def, int... query) { + for (int q: query) { + if ((flag & q) == q) + return q; + } + return def; + } + + static int clampi(int v, int min, int max) { + return Math.max(min, Math.min(max, v)); + } + + public static record DemoChain(VkSwapchainKHR swapchain, + ColourFormat format, + int extWidth, int extHeight, + HandleArray images, + HandleArray views, + VkRenderPass renderPass) { + + } + + static DemoChain createSwapchain(DemoDevice dd, ColourFormat prefFormat, int prefPresentMode, int pixWidth, int pixHeight, SegmentAllocator alloc, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + ColourFormat format = selectColourFormat( + dd.dev.vkGetPhysicalDeviceSurfaceFormatsKHR(dd.surface, frame), + ColourFormat.SRGB, + prefFormat); + + int presentMode = selectPresentMode(dd.dev.vkGetPhysicalDeviceSurfacePresentModesKHR(dd.surface, frame), + VK_PRESENT_MODE_FIFO_KHR, + prefPresentMode, + VK_PRESENT_MODE_FIFO_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR); + + VkSurfaceCapabilitiesKHR caps = VkSurfaceCapabilitiesKHR.create(frame); + + dd.dev.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dd.surface, caps); + + int extWidth = caps.getCurrentExtent$width(); + int extHeight = caps.getCurrentExtent$height(); + + if (extWidth == ~0) { + extWidth = clampi(pixWidth, caps.getMinImageExtent$width(), caps.getMaxImageExtent$width()); + extHeight = clampi(pixHeight, caps.getMinImageExtent$height(), caps.getMaxImageExtent$height()); + } + + int minCount = caps.getMinImageCount(); + int maxCount = caps.getMaxImageCount(); + int imageCount = clampi(minCount + 1, minCount, maxCount == 0 ? Integer.MAX_VALUE : maxCount); + + int composite = selectFlags(caps.getSupportedCompositeAlpha(), + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR); + + VkSwapchainCreateInfoKHR chainInfo = VkSwapchainCreateInfoKHR.create( + 0, + dd.surface, + imageCount, + format.format, format.colourSpace, + extWidth, extHeight, + 1, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + VK_SHARING_MODE_EXCLUSIVE, 0, null,// createDevice requires present+graphics queue the same + caps.getCurrentTransform(), // prefer VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR ? + composite, + presentMode, + VK_TRUE, // clipped + null, // old chain + frame); + + VkSwapchainKHR swapchain = dd.device.vkCreateSwapchainKHR(chainInfo, scope); + + // Create images and image views, here or elsewhere? + HandleArray images = dd.device.vkGetSwapchainImagesKHR(swapchain, alloc, scope); + HandleArray views = VkImageView.createArray(images.length(), alloc, scope); + + VkImageViewCreateInfo viewInfo = VkImageViewCreateInfo.create( + 0, + null, + VK_IMAGE_VIEW_TYPE_2D, + format.format, + VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, + VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, + frame); + + for (int i = 0; i < images.size(); i++) { + viewInfo.setImage(images.get(i)); + views.set(i, dd.device.vkCreateImageView(viewInfo, scope)); + } + + VkRenderPass renderPass = dumbRenderPass(dd.device, format.format, scope); + + return new DemoChain(swapchain, format, extWidth, extHeight, images, views, renderPass); + } + } + + /* graphics pipeline model from vulkan tutorial [programmable shader] + + input assembler + [vertex shader] + [tesselation] + [geometry shader] + rasterisation + [fragment shader] + colour blending + + */ + + /* each shader (code, stage, main) + + specialisation info to initialise constants per-pipeline shader stage init + + */ + static VkRenderPass dumbRenderPass(VkDevice device, int format, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkAttachmentDescription colourAttachment = VkAttachmentDescription.create( + 0, + format, + VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + frame); + + VkAttachmentReference colourRef = VkAttachmentReference.create(0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, frame); + VkSubpassDescription subpass = VkSubpassDescription.create( + 0, + VK_PIPELINE_BIND_POINT_GRAPHICS, + 0, null, + 1, colourRef, null, null, + 0, null, + frame); + + VkSubpassDependency dependency = VkSubpassDependency.create( + VK_SUBPASS_EXTERNAL, 0, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + 0, frame); + + VkRenderPassCreateInfo renderPassInfo = VkRenderPassCreateInfo.create( + 0, + 1, colourAttachment, + 1, subpass, + 0, null, + frame); + + return device.vkCreateRenderPass(renderPassInfo, scope); + } + } + + static VkShaderModule createShaderModule(VkDevice device, IntArray spirv, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkShaderModuleCreateInfo vsInfo = VkShaderModuleCreateInfo.create( + 0, + spirv.length() * 4, + spirv, + frame); + return device.vkCreateShaderModule(vsInfo, scope); + } + } + + static VkPipelineLayout dumbPipelineLayout(VkDevice device, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkPipelineLayoutCreateInfo layoutInfo = VkPipelineLayoutCreateInfo.create(0, 0, null, 0, null, frame); + return device.vkCreatePipelineLayout(layoutInfo, scope); + } + } + + static VkPipeline dumbPipeline(VkDevice device, VkShaderModule vertex, VkShaderModule fragment, VkPipelineLayout layout, VkRenderPass renderPass, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkPipelineShaderStageCreateInfo shader = VkPipelineShaderStageCreateInfo.createArray(2, frame); + + shader.setAtIndex(0, 0, VK_SHADER_STAGE_VERTEX_BIT, vertex, "main", null, frame); + shader.setAtIndex(1, 0, VK_SHADER_STAGE_FRAGMENT_BIT, fragment, "main", null, frame); + + VkPipelineDynamicStateCreateInfo dynamic = VkPipelineDynamicStateCreateInfo.create( + 0, + 2, IntArray.create(frame, VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR), + frame); + + // TODO: parameter? + VkVertexInputBindingDescription binding = VkVertexInputBindingDescription.createArray(1, frame); + VkVertexInputAttributeDescription attribute = VkVertexInputAttributeDescription.createArray(2, frame); + + binding.setAtIndex(0, 0, 5 * 4, Vulkan.VK_VERTEX_INPUT_RATE_VERTEX, frame); + attribute.setAtIndex(0, 0, 0, Vulkan.VK_FORMAT_R32G32_SFLOAT, 0, frame); + attribute.setAtIndex(1, 1, 0, Vulkan.VK_FORMAT_R32G32B32_SFLOAT, 2 * 4, frame); + + VkPipelineVertexInputStateCreateInfo vertextInput = VkPipelineVertexInputStateCreateInfo.create( + 0, + 1, binding, + 2, attribute, + frame); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = VkPipelineInputAssemblyStateCreateInfo.create( + 0, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + VK_FALSE, + frame); + + VkPipelineViewportStateCreateInfo viewport = VkPipelineViewportStateCreateInfo.create( + 0, + 1, null, + 1, null, + frame); + + VkPipelineRasterizationStateCreateInfo rasterisation = VkPipelineRasterizationStateCreateInfo.create( + 0, + VK_FALSE, + VK_FALSE, + VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, + VK_FALSE, 0.0f, 0.0f, 0.0f, + 1.0f, frame); + + VkPipelineMultisampleStateCreateInfo multisample = VkPipelineMultisampleStateCreateInfo.create( + 0, + VK_SAMPLE_COUNT_1_BIT, + VK_FALSE, 1.0f, null, + VK_FALSE, VK_FALSE, + frame); + + VkPipelineColorBlendAttachmentState colourBlendAttachment = VkPipelineColorBlendAttachmentState.create( + VK_FALSE, + VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, + VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + frame); + + VkPipelineColorBlendStateCreateInfo colourBlend = VkPipelineColorBlendStateCreateInfo.create( + 0, + VK_FALSE, VK_LOGIC_OP_NO_OP, + 1, colourBlendAttachment, + 1.0f, 1.0f, 1.0f, 1.0f, + frame); + + VkGraphicsPipelineCreateInfo pipeline = VkGraphicsPipelineCreateInfo.create( + 0, + 2, shader, + vertextInput, inputAssembly, + null, + viewport, + rasterisation, + multisample, + null, + colourBlend, + dynamic, + layout, + renderPass, 0, + null, 0, frame); + + return device.vkCreateGraphicsPipeline(null, pipeline, scope); + + } + } + + public static HandleArray createFramebuffers(DemoDevice device, DemoChain chain, SegmentAllocator alloc, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + var frameBuffers = VkFramebuffer.createArray(chain.views.length(), alloc); + HandleArray attachments = VkImageView.createArray(1, frame); + VkFramebufferCreateInfo framebufferInfo = VkFramebufferCreateInfo.create( + 0, + chain.renderPass, + 1, attachments, + chain.extWidth, chain.extHeight, + 1, + frame); + + for (int i = 0; i < chain.views.size(); i++) { + attachments.setAtIndex(0, chain.views.get(i)); + frameBuffers.setAtIndex(i, device.device.vkCreateFramebuffer(framebufferInfo, scope)); + } + return frameBuffers; + } + } + + public static HandleArray createCommandBuffers(DemoDevice device, int appIndex, int count, SegmentAllocator alloc, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + VkCommandBufferAllocateInfo bufferInfo = VkCommandBufferAllocateInfo.create( + device.pools[appIndex], + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + count, + frame); + + return device.device.vkAllocateCommandBuffers(bufferInfo, (SegmentAllocator)scope, scope); + } + } + + public static void resetCommandBuffers(DemoDevice device, int appIndex, HandleArray commandBuffers) { + try ( Frame frame = Frame.frame()) { + VkCommandBufferAllocateInfo bufferInfo = VkCommandBufferAllocateInfo.create( + device.pools[appIndex], + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + commandBuffers.size(), + frame); + + device.device.vkAllocateCommandBuffers(bufferInfo, commandBuffers); + } + } + + // device? + public record Buffer( + VkBuffer buffer, + VkDeviceMemory memory, + long size, + long offset) { + + public void close(VkDevice device) { + device.vkFreeMemory(memory); + device.vkDestroyBuffer(buffer); + } + } + + // TODO: memory allocator? - limited memory blocks? + public static Buffer createBuffer(DemoDevice device, long size, int usage, int props, int[] sharedFamilies, MemorySegment src, ResourceScope scope) { + try ( Frame frame = Frame.frame()) { + IntArray families = sharedFamilies != null ? IntArray.create(frame, sharedFamilies) : null; + VkBufferCreateInfo create = VkBufferCreateInfo.create(0, size, usage, VK_SHARING_MODE_CONCURRENT, sharedFamilies != null ? sharedFamilies.length : 0, families, frame); + VkBuffer buffer = device.device.vkCreateBuffer(create, scope); + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + + device.device.vkGetBufferMemoryRequirements(buffer, req); + + VkMemoryAllocateInfo allocate = VkMemoryAllocateInfo.create( + req.getSize(), + device.findMemory(req.getMemoryTypeBits(), props), + frame); + + System.out.printf("size = %x\n", size); + + VkDeviceMemory memory = device.device.vkAllocateMemory(allocate, scope); + + device.device.vkBindBufferMemory(buffer, memory, 0); + + if (src != null) { + MemorySegment mem = device.device.vkMapMemory(memory, 0, size, 0, frame.scope()); + + mem.copyFrom(src); + device.device.vkUnmapMemory(memory); + } + + return new Buffer(buffer, memory, size, 0); + } + } + + public static void copyBuffer(DemoDevice device, Buffer src, Buffer dst) { + try ( Frame frame = Frame.frame()) { + VkCommandPool xfer = device.pools()[1]; + VkCommandBufferAllocateInfo alloc = VkCommandBufferAllocateInfo.create(xfer, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1, frame); + HandleArray cmds = device.device.vkAllocateCommandBuffers(alloc, frame, frame.scope()); + VkCommandBuffer cmd = cmds.get(0); + + VkCommandBufferBeginInfo begin = VkCommandBufferBeginInfo.create(Vulkan.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, null, frame); + VkBufferCopy region = VkBufferCopy.create(0, 0, dst.size, frame); + + cmd.vkBeginCommandBuffer(begin); + cmd.vkCmdCopyBuffer(src.buffer, dst.buffer, 1, region); + cmd.vkEndCommandBuffer(); + + VkSubmitInfo submit = VkSubmitInfo.create(0, null, null, 1, cmds, 0, null, frame); + device.queues[1].vkQueueSubmit(1, submit, null); + + device.queues[1].vkQueueWaitIdle(); + + // NB: must be finished before freeing + device.device.vkFreeCommandBuffers(xfer, 1, cmds); + } + + } + + // not worth it, its simple enough + static VkVertexInputAttributeDescription createInputAttributes(SegmentAllocator alloc, int binding, int... loc) { + int len = loc.length / 3; + + VkVertexInputAttributeDescription attribute = VkVertexInputAttributeDescription.createArray(len, alloc); + for (int i = 0; i < len; i++) { + attribute.setAtIndex(i, loc[i * 3 + 0], binding, loc[i * 3 + 1], loc[i * 3 + 2], alloc); + } + return attribute; + } + + static boolean checkDeviceExtensionSupport(VkPhysicalDevice dev, String... query) { + // spec versions? + if (query.length > 0) { + try ( Frame frame = Frame.frame()) { + VkExtensionProperties props = dev.vkEnumerateDeviceExtensionProperties(null, frame); + HashSet set = new HashSet(List.of(query)); + for (int i = 0; i < props.length(); i++) + set.remove(props.getExtensionNameAtIndex(i)); + + if (!set.isEmpty()) + return false; + } + } + + return true; + } + +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/GLMaths.java b/src/notzed.vulkan.test/classes/vulkan/test/GLMaths.java new file mode 100644 index 0000000..3414740 --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/GLMaths.java @@ -0,0 +1,131 @@ + +package vulkan.test; + +import static java.lang.Math.*; +import java.util.Arrays; + +public class GLMaths { + public static void identity4f(float []matrix) { + Arrays.fill(matrix, 0.0f); + for (int i = 0; i < 4; i++) + matrix[i * 4 + i] = 1.0f; + } + + public static float length3f(float [] a) { + float sum = 0; + for (int i = 0; i < 3; i++) + sum += a[i] * a[i]; + return (float)sqrt(sum); + } + + public static void sub3f(float [] c, float [] a, float [] b) { + for (int i = 0; i < 3; i++) + c[i] = a[i] - b[i]; + } + + public static void norm3f(float [] vec) { + float fix = 1.0f / length3f(vec); + for (int i = 0; i < 3; i++) + vec[i] *= fix; + } + + public static void cross3f(float [] c, float [] a, float [] b) { + c[0] = a[1] * b[2] - a[2] * b[1]; + c[1] = a[2] * b[0] - a[0] * b[2]; + c[2] = a[0] * b[1] - a[1] * b[0]; + } + + public static float dot3f(float [] a, float [] b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + public static float [] mult4x4f(float [] c, float [] b, float [] a) { + c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + c[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + c[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + c[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + c[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + c[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + c[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + c[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + + return c; + } + + public static void lookAt(float []mat, float []eye, float []centre, float []up) { + float forward[] = new float[3], side[] = new float[3], u[] = new float[3]; + + sub3f(forward, centre, eye); + norm3f(forward); + cross3f(side, forward, up); + norm3f(side); + cross3f(u, side, forward); + + mat[0] = side[0]; + mat[4] = side[1]; + mat[8] = side[2]; + + mat[1] = u[0]; + mat[5] = u[1]; + mat[9] = u[2]; + + mat[2] = -forward[0]; + mat[6] = -forward[1]; + mat[10] = -forward[2]; + + mat[12] = -dot3f(side, eye); + mat[13] = -dot3f(u, eye); + mat[14] = dot3f(forward, eye); + + mat[3] = 0.0f; + mat[7] = 0.0f; + mat[11] = 0.0f; + + mat[15] = 1.0f; + } + + public static void frustum(float []mat, float left, float right, float bottom, float top, float znear, float zfar) { + float temp, temp2, temp3, temp4; + + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + mat[0] = temp / temp2; + mat[1] = 0.0f; + mat[2] = 0.0f; + mat[3] = 0.0f; + mat[4] = 0.0f; + mat[5] = temp / temp3; + mat[6] = 0.0f; + mat[7] = 0.0f; + mat[8] = (right + left) / temp2; + mat[9] = (top + bottom) / temp3; + mat[10] = (-zfar - znear) / temp4; + mat[11] = -1.0f; + mat[12] = 0.0f; + mat[13] = 0.0f; + mat[14] = (-temp * zfar) / temp4; + mat[15] = 0.0f; + } + + public static void perspective(float []mat, float fovy, float aspect, float znear, float zfar) { + float ymax, xmax; + + ymax = znear * (float)tan(fovy * 0.5f); + xmax = ymax * aspect; + + frustum(mat, -xmax, xmax, -ymax, ymax, znear, zfar); + } +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/TestCube.java b/src/notzed.vulkan.test/classes/vulkan/test/TestCube.java new file mode 100644 index 0000000..f68a177 --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/TestCube.java @@ -0,0 +1,1041 @@ +/* +The MIT License (MIT) + +Copyright (C) 2017 Eric Arnebäck +Copyright (C) 2019 Michael Zucchi + +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. + + */ + + /* + * This is a Java conversion of a C conversion of this: + * https://github.com/Erkaman/vulkan_minimal_compute + * + * It's been simplified a bit and converted to the 'zvk' api. + */ +package vulkan.test; + +import au.notzed.display.Display; +import jdk.incubator.foreign.*; +import au.notzed.nativez.*; + +import vulkan.*; +import static vulkan.Vulkan.*; + +import au.notzed.display.*; +import static vulkan.test.GLMaths.*; + +public class TestCube { + + static final boolean debug = true; + + final static int NUM_SAMPLES = VK_SAMPLE_COUNT_1_BIT; + final static int NUM_DESCRIPTOR_SETS = 1; + + ResourceScope scope = ResourceScope.newSharedScope(); + + int width = 800; + int height = 800; + float projection[] = new float[16]; + float view[] = new float[16]; + float model[] = new float[16]; + float clip[] = new float[]{ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f + }; + float mvp[] = new float[16]; + + VkInstance instance; + VkPhysicalDevice physicalDevice; + VkPhysicalDeviceMemoryProperties memory_properties; + VkPhysicalDeviceFeatures device_features; + + int present_queue_index; + int graphics_queue_index; + + VkDevice device; + VkSwapchainKHR chain; + + VkQueue graphics_queue; + VkQueue present_queue; + + int chainImageFormat; + HandleArray chainImage; + HandleArray chainImageView; + + int depthFormat; + VkImage depthImage; + VkImageView depthView; + VkDeviceMemory depthMemory; + + VkCommandPool cmd_pool; + HandleArray cmd; + + BufferMemory uniform; + VkPipelineLayout pipeline_layout; + + VkDescriptorSetLayout desc_layout; + VkDescriptorPool desc_pool; + HandleArray desc_set; + + VkRenderPass render_pass; + HandleArray framebuffers; + + BufferMemory vertex; + HandleArray vertexBuffer = VkBuffer.createArray(1, (SegmentAllocator)scope); + VkVertexInputBindingDescription vi_binding = VkVertexInputBindingDescription.createArray(1, (SegmentAllocator)scope); + VkVertexInputAttributeDescription vi_attribs = VkVertexInputAttributeDescription.createArray(2, (SegmentAllocator)scope); + + IntArray cube_vs; + IntArray cube_fs; + HandleArray shader = VkShaderModule.createArray(2, (SegmentAllocator)scope); + + HandleArray pipeline = VkPipeline.createArray(1, (SegmentAllocator)scope); + + VkSemaphore chainSemaphore; + VkFence drawFence; + + record BufferMemory(VkBuffer buffer, VkDeviceMemory memory, long size) { + + public void free(VkDevice device) { + device.vkFreeMemory(memory); + device.vkDestroyBuffer(buffer); + } + } + + VkDebugUtilsMessengerEXT logger; + + void init_debug() throws Exception { + if (!debug) + return; + try ( Frame frame = Frame.frame()) { + var cb = PFN_vkDebugUtilsMessengerCallbackEXT.upcall((severity, flags, data) -> { + System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); + return 0; + }, scope); + VkDebugUtilsMessengerCreateInfoEXT info = VkDebugUtilsMessengerCreateInfoEXT.create( + 0, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + cb, + null, + frame); + + logger = instance.vkCreateDebugUtilsMessengerEXT(info, scope); + } + + //typedef VkBool32 (*PFN_vkDebugUtilsMessengerCallbackEXT)(VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT *, void *); + } + + void init_instance() throws Exception { + try ( Frame frame = Frame.frame()) { + VkInstanceCreateInfo info = VkInstanceCreateInfo.create( + 0, + VkApplicationInfo.create("cube", 1, "cube-engine", 2, VK_API_VERSION_1_0, frame), + 1, new String[]{"VK_LAYER_KHRONOS_validation"}, + 3, new String[]{"VK_KHR_surface", "VK_KHR_xlib_surface", "VK_EXT_debug_utils"}, + frame + ); + + instance = VkInstance.vkCreateInstance(info, scope); + System.out.printf("instance = %s\n", instance); + } + } + + Display display; + Window window; + VkSurfaceKHR surface; + + void init_surface() throws Exception { + display = Display.createX11Display(); + window = display.createWindow(width, height); + surface = window.createVulkanSurface(instance, scope); + System.out.printf("surface: %s\n", surface); + } + + void init_device() throws Exception { + try ( Frame frame = Frame.frame()) { + IntArray count$h = IntArray.create(frame, 1); + IntArray present$h = IntArray.create(frame, 1); + HandleArray devs; + int count; + int res; + + devs = instance.vkEnumeratePhysicalDevices(frame, scope); + + // Search for device and queue indices + int devid = -1; + int present_queue = -1; + int graphics_queue = -1; + for (int i = 0; i < devs.length(); i++) { + VkPhysicalDevice dev = devs.getAtIndex(i); + VkQueueFamilyProperties famprops; + + famprops = dev.vkGetPhysicalDeviceQueueFamilyProperties(frame); + + for (int j = 0; j < famprops.length(); j++) { + boolean present = dev.vkGetPhysicalDeviceSurfaceSupportKHR(j, surface); + + if (present && present_queue == -1) + present_queue = j; + if ((famprops.getQueueFlagsAtIndex(j) & VK_QUEUE_GRAPHICS_BIT) != 0) { + graphics_queue = j; + if (present) { + present_queue = j; + break; + } + } + } + if (present_queue != -1 && graphics_queue != -1) { + devid = i; + break; + } + } + + if (devid == -1) + throw new Exception("Cannot find a suitable device"); + + physicalDevice = devs.getAtIndex(devid); + present_queue_index = present_queue; + graphics_queue_index = graphics_queue; + + // NOTE: app scope + memory_properties = VkPhysicalDeviceMemoryProperties.create((SegmentAllocator)scope); + physicalDevice.vkGetPhysicalDeviceMemoryProperties(memory_properties); + device_features = VkPhysicalDeviceFeatures.create((SegmentAllocator)scope); + physicalDevice.vkGetPhysicalDeviceFeatures(device_features); + + FloatArray qpri = FloatArray.create(frame, 0.0f); + VkDeviceQueueCreateInfo qinfo = VkDeviceQueueCreateInfo.create( + 0, + graphics_queue, + 1, qpri, + frame); + String[] extensions = { + "VK_KHR_swapchain" + }; + VkPhysicalDeviceFeatures features = VkPhysicalDeviceFeatures.create(frame); + features.setDepthClamp(1); + VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( + 0, + 1, qinfo, + 0, null, + extensions.length, extensions, + features, + frame); + + device = physicalDevice.vkCreateDevice(devinfo, scope); + + System.out.printf("device = %s\n", device); + + /* ************************************************************** */ + int format; + VkSurfaceFormatKHR surfFormats = physicalDevice.vkGetPhysicalDeviceSurfaceFormatsKHR(surface, frame); + // If the format list includes just one entry of VK_FORMAT_UNDEFINED, + // the surface has no preferred format. Otherwise, at least one + // supported format will be returned. + if (surfFormats.length() == 1 && surfFormats.getFormatAtIndex(0) == VK_FORMAT_UNDEFINED) { + format = VK_FORMAT_B8G8R8A8_UNORM; + } else { + format = surfFormats.getFormatAtIndex(0); + } + + VkSurfaceCapabilitiesKHR surfCapabilities = VkSurfaceCapabilitiesKHR.create(frame); + + physicalDevice.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, surfCapabilities); + IntArray presentModes = physicalDevice.vkGetPhysicalDeviceSurfacePresentModesKHR(surface, frame); + + VkExtent2D swapchainExtent; + // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF. + if (surfCapabilities.getCurrentExtent().getWidth() == 0xFFFFFFFF) { + // If the surface size is undefined, the size is set to + // the size of the images requested. + swapchainExtent = VkExtent2D.create( + clampi(width, surfCapabilities.getMinImageExtent().getWidth(), surfCapabilities.getMaxImageExtent().getWidth()), + clampi(height, surfCapabilities.getMinImageExtent().getHeight(), surfCapabilities.getMaxImageExtent().getHeight()), + frame); + } else { + // If the surface size is defined, the swap chain size must match + swapchainExtent = surfCapabilities.getCurrentExtent(); + } + int compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + int compositeAlphaFlags[] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,}; + for (int flag: compositeAlphaFlags) { + if ((surfCapabilities.getSupportedCompositeAlpha() & flag) != 0) { + compositeAlpha = flag; + break; + } + } + + VkSwapchainCreateInfoKHR chaininfo = VkSwapchainCreateInfoKHR.create( + 0, + surface, + surfCapabilities.getMinImageCount(), + format, + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + swapchainExtent.getWidth(), swapchainExtent.getHeight(), + 1, //.imageArrayLayers = 1, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + VK_SHARING_MODE_EXCLUSIVE, + // assumes queues are same. + 0, null, + (surfCapabilities.getSupportedTransforms() & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) != 0 + ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : surfCapabilities.getCurrentTransform(), + compositeAlpha, + VK_PRESENT_MODE_FIFO_KHR, + VK_TRUE, + null, + frame); + + chain = device.vkCreateSwapchainKHR(chaininfo, scope); + + int chainImageCount; + + chainImage = device.vkGetSwapchainImagesKHR(chain, (SegmentAllocator)scope, scope); + chainImageCount = (int)chainImage.length(); + chainImageView = VkImageView.createArray(chainImageCount, (SegmentAllocator)scope); + + VkImageViewCreateInfo viewinfo = VkImageViewCreateInfo.create( + 0, + null, + VK_IMAGE_VIEW_TYPE_2D, + format, + VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, + VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, + frame); + + for (int i = 0; i < chainImageCount; i++) { + viewinfo.setImage(chainImage.get(i)); + + chainImageView.setAtIndex(i, device.vkCreateImageView(viewinfo, scope)); + } + + chainImageFormat = format; + } + } + + void init_device_queue() { + graphics_queue = device.vkGetDeviceQueue(graphics_queue_index, 0, scope); + if (graphics_queue_index == present_queue_index) { + present_queue = graphics_queue; + } else { + present_queue = device.vkGetDeviceQueue(present_queue_index, 0, scope); + } + } + + void init_command() throws Exception { + try ( Frame frame = Frame.frame()) { + VkCommandPoolCreateInfo poolinfo = VkCommandPoolCreateInfo.create( + 0, + graphics_queue_index, + frame); + + cmd_pool = device.vkCreateCommandPool(poolinfo, scope); + + VkCommandBufferAllocateInfo cmdinfo = VkCommandBufferAllocateInfo.create( + cmd_pool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1, + frame); + + cmd = device.vkAllocateCommandBuffers(cmdinfo, (SegmentAllocator)scope, scope); + } + } + + // parameterise as init_image? + void init_depth() throws Exception { + try ( Frame frame = Frame.frame()) { + int format = VK_FORMAT_D16_UNORM; + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkImageCreateInfo imageinfo = VkImageCreateInfo.create( + 0, + VK_IMAGE_TYPE_2D, + format, + width, height, 1, + 1, + 1, + NUM_SAMPLES, + 0, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_SHARING_MODE_EXCLUSIVE, + 0, null, + VK_IMAGE_LAYOUT_UNDEFINED, + frame); + + depthImage = device.vkCreateImage(imageinfo, scope); + + device.vkGetImageMemoryRequirements(depthImage, req); + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create( + req.getSize(), + find_memory_type(memory_properties, req.getMemoryTypeBits(), VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), + frame); + + depthMemory = device.vkAllocateMemory(alloc, scope); + + device.vkBindImageMemory(depthImage, depthMemory, 0); + + VkImageViewCreateInfo viewinfo = VkImageViewCreateInfo.create( + 0, + depthImage, + VK_IMAGE_VIEW_TYPE_2D, + VK_FORMAT_D16_UNORM, + VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, + VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, + frame); + + depthView = device.vkCreateImageView(viewinfo, scope); + depthFormat = format; + } + } + + void init_uniform() throws Exception { + uniform = init_buffer(mvp.length * 4, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, MemorySegment.ofArray(mvp)); + } + + void init_descriptor() throws Exception { + try ( Frame frame = Frame.frame()) { + HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); + VkDescriptorSetLayoutBinding layout_binding = VkDescriptorSetLayoutBinding.create( + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, + VK_SHADER_STAGE_VERTEX_BIT, + null, + frame); + VkDescriptorSetLayoutCreateInfo descriptor_layout = VkDescriptorSetLayoutCreateInfo.create( + 0, + 1, layout_binding, + frame); + + desc_layout = device.vkCreateDescriptorSetLayout(descriptor_layout, scope); + layout_table.setAtIndex(0, desc_layout); + + VkPipelineLayoutCreateInfo pipeline_info = VkPipelineLayoutCreateInfo.create( + 0, + 1, layout_table, + 0, null, + frame); + + pipeline_layout = device.vkCreatePipelineLayout(pipeline_info, scope); + + VkDescriptorPoolSize type_count = VkDescriptorPoolSize.create( + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, + frame); + + VkDescriptorPoolCreateInfo descriptor_pool = VkDescriptorPoolCreateInfo.create( + 0, + 1, + 1, type_count, + frame); + + desc_pool = device.vkCreateDescriptorPool(descriptor_pool, scope); + + VkDescriptorSetAllocateInfo alloc_info = VkDescriptorSetAllocateInfo.create( + desc_pool, + 1, layout_table, + frame); + + System.out.println(alloc_info); + + desc_set = device.vkAllocateDescriptorSets(alloc_info, (SegmentAllocator)scope, scope); + + VkDescriptorBufferInfo uniformInfo = VkDescriptorBufferInfo.create(uniform.buffer, 0, uniform.size, frame); + VkWriteDescriptorSet writes = VkWriteDescriptorSet.create( + desc_set.getAtIndex(0), + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + null, + uniformInfo, + null, + frame); + + device.vkUpdateDescriptorSets(1, writes, 0, null); + } + } + + void init_render() throws Exception { + try ( Frame frame = Frame.frame()) { + VkAttachmentDescription attachments = VkAttachmentDescription.createArray(2, frame); + + attachments.setFormat(chainImageFormat); + attachments.setSamples(NUM_SAMPLES); + attachments.setLoadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + attachments.setStoreOp(VK_ATTACHMENT_STORE_OP_STORE); + attachments.setStencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE); + attachments.setStencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + attachments.setInitialLayout(VK_IMAGE_LAYOUT_UNDEFINED); + attachments.setFinalLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + attachments.setFlags(0); + + attachments.setFormatAtIndex(1, depthFormat); + attachments.setSamplesAtIndex(1, NUM_SAMPLES); + attachments.setLoadOpAtIndex(1, VK_ATTACHMENT_LOAD_OP_CLEAR); + attachments.setStoreOpAtIndex(1, VK_ATTACHMENT_STORE_OP_STORE); + attachments.setStencilLoadOpAtIndex(1, VK_ATTACHMENT_LOAD_OP_DONT_CARE); + attachments.setStencilStoreOpAtIndex(1, VK_ATTACHMENT_STORE_OP_DONT_CARE); + attachments.setInitialLayoutAtIndex(1, VK_IMAGE_LAYOUT_UNDEFINED); + attachments.setFinalLayoutAtIndex(1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + attachments.setFlagsAtIndex(1, 0); + + VkAttachmentReference color_reference = VkAttachmentReference.create( + 0, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + frame); + + VkAttachmentReference depth_reference = VkAttachmentReference.create( + 1, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + frame); + + VkSubpassDescription subpass = VkSubpassDescription.create( + 0, + VK_PIPELINE_BIND_POINT_GRAPHICS, + 0, null, + 1, color_reference, null, depth_reference, + 0, null, + frame); + + VkRenderPassCreateInfo rp_info = VkRenderPassCreateInfo.create( + 0, + 2, attachments, + 1, subpass, + 0, null, + frame); + + render_pass = device.vkCreateRenderPass(rp_info, scope); + } + } + + void init_framebuffer() throws Exception { + try ( Frame frame = Frame.frame()) { + HandleArray attachments = VkImageView.createArray(2, frame); + + attachments.setAtIndex(1, depthView); + + VkFramebufferCreateInfo fb_info = VkFramebufferCreateInfo.create( + 0, + render_pass, + 2, attachments, + width, height, 1, + frame); + + framebuffers = VkFramebuffer.createArray(chainImage.length(), (SegmentAllocator)scope); + for (int i = 0; i < chainImage.size(); i++) { + attachments.setAtIndex(0, chainImageView.get(i)); + framebuffers.setAtIndex(i, device.vkCreateFramebuffer(fb_info, scope)); + System.out.printf("framebuffer[%d] = %s\n", i, framebuffers.getAtIndex(i)); + } + } + } + + void init_vertexbuffer() throws Exception { + try ( Frame frame = Frame.frame()) { + vertex = init_buffer(Cube.data.length * 4, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, MemorySegment.ofArray(Cube.data)); + + vertexBuffer.setAtIndex(0, vertex.buffer); + + /* ***************************************** */ + vi_binding.setBinding(0); + vi_binding.setInputRate(VK_VERTEX_INPUT_RATE_VERTEX); + vi_binding.setStride(Cube.dataStride); + + vi_attribs.setBinding(0); + vi_attribs.setLocation(0); + vi_attribs.setFormat(VK_FORMAT_R32G32B32A32_SFLOAT); + vi_attribs.setOffset(0); + vi_attribs.setBindingAtIndex(1, 0); + vi_attribs.setLocationAtIndex(1, 1); + vi_attribs.setFormatAtIndex(1, VK_FORMAT_R32G32B32A32_SFLOAT); + vi_attribs.setOffsetAtIndex(1, 16); + } + } + + void init_pipeline() throws Exception { + int res; + try ( Frame frame = Frame.frame()) { + IntArray dynamicStateEnables = IntArray.create(frame, + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR); + + VkPipelineDynamicStateCreateInfo dynamicState = VkPipelineDynamicStateCreateInfo.create( + 0, + 2, dynamicStateEnables, + frame); + + VkPipelineVertexInputStateCreateInfo vi = VkPipelineVertexInputStateCreateInfo.create( + 0, + (int)vi_binding.length(), vi_binding, + (int)vi_attribs.length(), vi_attribs, + frame); + + VkPipelineInputAssemblyStateCreateInfo ia = VkPipelineInputAssemblyStateCreateInfo.create( + 0, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + 0, + frame); + + VkPipelineRasterizationStateCreateInfo rs = VkPipelineRasterizationStateCreateInfo.create( + 0, + VK_TRUE, + VK_FALSE, + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_BACK_BIT, + VK_FRONT_FACE_CLOCKWISE, + VK_FALSE, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + frame); + + VkPipelineColorBlendAttachmentState att_state = VkPipelineColorBlendAttachmentState.create( + VK_FALSE, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_OP_ADD, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_OP_ADD, + 0xf, + frame); + + VkPipelineColorBlendStateCreateInfo cb = VkPipelineColorBlendStateCreateInfo.create( + 0, + VK_FALSE, + VK_LOGIC_OP_NO_OP, + 1, att_state, + 1.0f, 1.0f, 1.0f, 1.0f, + frame); + + VkPipelineViewportStateCreateInfo vp = VkPipelineViewportStateCreateInfo.create( + 0, + 1, null, + 1, null, + frame); + + VkPipelineDepthStencilStateCreateInfo ds = VkPipelineDepthStencilStateCreateInfo.create( + 0, + VK_TRUE, + VK_TRUE, + VK_COMPARE_OP_LESS_OR_EQUAL, + VK_FALSE, + VK_FALSE, + 0.0f, + 0.0f, + frame); + VkStencilOpState back = ds.getBack(); + + back.setFailOp(VK_STENCIL_OP_KEEP); + back.setPassOp(VK_STENCIL_OP_KEEP); + back.setCompareOp(VK_COMPARE_OP_ALWAYS); + back.setCompareMask(0); + back.setReference(0); + back.setDepthFailOp(VK_STENCIL_OP_KEEP); + back.setWriteMask(0); + + VkStencilOpState front = ds.getFront(); + + front.setFailOp(VK_STENCIL_OP_KEEP); + front.setPassOp(VK_STENCIL_OP_KEEP); + front.setCompareOp(VK_COMPARE_OP_ALWAYS); + front.setCompareMask(0); + front.setReference(0); + front.setDepthFailOp(VK_STENCIL_OP_KEEP); + front.setWriteMask(0); + + VkPipelineMultisampleStateCreateInfo ms = VkPipelineMultisampleStateCreateInfo.create( + 0, + NUM_SAMPLES, + 0, //.sampleShadingEnable = VK_FALSE, + 0.0f, + null, + 0, //.alphaToCoverageEnable = VK_FALSE, + 0, //.alphaToOneEnable = VK_FALSE, + frame + ); + + VkShaderModuleCreateInfo vsInfo = VkShaderModuleCreateInfo.create( + 0, + cube_vs.length() * 4, + cube_vs, + frame); + VkShaderModuleCreateInfo fsInfo = VkShaderModuleCreateInfo.create( + 0, + cube_fs.length() * 4, + cube_fs, + frame); + + shader.setAtIndex(0, device.vkCreateShaderModule(vsInfo, scope)); + shader.setAtIndex(1, device.vkCreateShaderModule(fsInfo, scope)); + + VkPipelineShaderStageCreateInfo shaderStages = VkPipelineShaderStageCreateInfo.createArray(2, (SegmentAllocator)scope); + + shaderStages.setStage(VK_SHADER_STAGE_VERTEX_BIT); + shaderStages.setName("main", (SegmentAllocator)scope); + shaderStages.setModule(shader.get(0)); + + shaderStages.setStageAtIndex(1, VK_SHADER_STAGE_FRAGMENT_BIT); + shaderStages.setNameAtIndex(1, "main", (SegmentAllocator)scope); + shaderStages.setModuleAtIndex(1, shader.get(1)); + + VkGraphicsPipelineCreateInfo pipeline = VkGraphicsPipelineCreateInfo.create( + 0, + 2, shaderStages, + vi, + ia, + null, + vp, + rs, + ms, + ds, + cb, + dynamicState, + pipeline_layout, + render_pass, + 0, + null, + 0, + frame); + + res = device.vkCreateGraphicsPipelines(null, 1, pipeline, this.pipeline); + + VkSemaphoreCreateInfo seminfo = VkSemaphoreCreateInfo.create(0, frame); + chainSemaphore = device.vkCreateSemaphore(seminfo, scope); + + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(0, frame); + drawFence = device.vkCreateFence(fenceInfo, scope); + } + } + + void execute_begin_command_buffer() throws Exception { + /* DEPENDS on init_command() */ + try ( Frame frame = Frame.frame()) { + VkCommandBufferBeginInfo cmd_buf_info = VkCommandBufferBeginInfo.create( + 0, + null, + frame); + + cmd.getAtIndex(0).vkBeginCommandBuffer(cmd_buf_info); + } + } + + void execute_end_command_buffer() throws Exception { + cmd.getAtIndex(0).vkEndCommandBuffer(); + } + + final static long FENCE_TIMEOUT = 100000000; + + void execute_queue_command_buffer() throws Exception { + int res; + + /* Queue the command buffer for execution */ + /* FIXME: frame shoudl provide or take explicit scope */ + try ( ResourceScope scope = ResourceScope.newConfinedScope(); Frame frame = Frame.frame()) { + IntArray pipe_stage_flags = IntArray.create(frame, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(0, frame); + HandleArray fences = VkFence.createArray(1, frame); + + fences.setAtIndex(0, device.vkCreateFence(fenceInfo, scope)); + + VkSubmitInfo submit_info = VkSubmitInfo.create( + 1, null, pipe_stage_flags, + (int)cmd.length(), cmd, + 0, null, + frame); + + graphics_queue.vkQueueSubmit(1, submit_info, drawFence); + + do { + res = device.vkWaitForFences(1, fences, 1, FENCE_TIMEOUT); + } while (res == VK_TIMEOUT); + + device.vkDestroyFence(fences.getAtIndex(0)); + } + } + + void cmd_viewport() { + try ( Frame frame = Frame.frame()) { + VkCommandBuffer cmd = this.cmd.getAtIndex(0); + VkViewport viewport = VkViewport.create( + 0, 0, width, height, 0.0f, 1.0f, + frame); + cmd.vkCmdSetViewport(0, 1, viewport); + } + } + + void cmd_scissors() { + try ( Frame frame = Frame.frame()) { + VkCommandBuffer cmd = this.cmd.getAtIndex(0); + VkRect2D scissor = VkRect2D.create(0, 0, width, height, frame); + cmd.vkCmdSetScissor(0, 1, scissor); + } + } + + void cmd_paint() throws Exception { + int res; + try ( Frame frame = Frame.frame()) { + int chainIndex; + IntArray chainIndices = IntArray.createArray(1, frame); + VkCommandBuffer cmd = this.cmd.getAtIndex(0); + + device.vkAcquireNextImageKHR(chain, ~0L, chainSemaphore, null, chainIndices); + chainIndex = chainIndices.getAtIndex(0); + LongArray offsets = LongArray.createArray(1, frame); + + VkClearValue clear_values = VkClearValue.createArray(2, frame); + FloatArray col = clear_values.getColor().getFloat32(); + col.setAtIndex(0, 0.2f); + col.setAtIndex(1, 0.2f); + col.setAtIndex(2, 0.2f); + col.setAtIndex(3, 0.2f); + VkClearDepthStencilValue depthStencil = clear_values.getAtIndex(1).getDepthStencil(); + depthStencil.setDepth(1.0f); + depthStencil.setStencil(0); + + System.out.printf("render framebuffer[%d] = %s\n", chainIndex, framebuffers.getAtIndex(chainIndex)); + + VkRenderPassBeginInfo rp_begin = VkRenderPassBeginInfo.create( + render_pass, + framebuffers.getAtIndex(chainIndex), + 0, 0, width, height, + 2, clear_values, + frame); + + cmd.vkCmdBeginRenderPass(rp_begin, VK_SUBPASS_CONTENTS_INLINE); + + cmd.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.getAtIndex(0)); + cmd.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, NUM_DESCRIPTOR_SETS, desc_set, 0, null); + cmd.vkCmdBindVertexBuffers(0, 1, vertexBuffer, offsets); + + cmd_viewport(); + cmd_scissors(); + + cmd.vkCmdDraw(12 * 3, 1, 0, 0); + cmd.vkCmdEndRenderPass(); + + cmd.vkEndCommandBuffer(); + + IntArray pipe_stage_flags = IntArray.create(frame, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + HandleArray semaphores = VkSemaphore.createArray(1, frame);//, chainSemaphore, scope); + + semaphores.setAtIndex(0, chainSemaphore); + + VkSubmitInfo submit_info = VkSubmitInfo.create( + 1, semaphores, pipe_stage_flags, + (int)this.cmd.length(), this.cmd, + 0, null, + frame); + + HandleArray fences = VkFence.createArray(1, frame); + + fences.setAtIndex(0, drawFence); + + // Queue the command buffer for execution + device.vkResetFences(1, fences); + + graphics_queue.vkQueueSubmit(1, submit_info, drawFence); + + // Make sure command buffer is finished before presenting + do { + res = device.vkWaitForFences(1, fences, VK_TRUE, FENCE_TIMEOUT); + } while (res == VK_TIMEOUT); + + // Now present the image in the window + HandleArray chains = VkSwapchainKHR.createArray(1, frame); + chains.setAtIndex(0, chain); + VkPresentInfoKHR present = VkPresentInfoKHR.create( + 0, null, + 1, chains, chainIndices, null, + frame); + + present_queue.vkQueuePresentKHR(present); + } + } + + /** + * Buffers are created in three steps: + * 1) create buffer, specifying usage and size + * 2) allocate memory based on memory requirements + * 3) bind memory + *

+ */ + BufferMemory init_buffer(long dataSize, int usage, int properties, MemorySegment init) throws Exception { + try ( Frame frame = Frame.frame()) { + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkBufferCreateInfo buf_info = VkBufferCreateInfo.create( + 0, + dataSize, + usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, null, + frame); + + VkBuffer buffer = device.vkCreateBuffer(buf_info, scope); + + device.vkGetBufferMemoryRequirements(buffer, req); + + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create( + req.getSize(), + find_memory_type(memory_properties, req.getMemoryTypeBits(), properties), + frame); + + VkDeviceMemory memory = device.vkAllocateMemory(alloc, scope); + + if (init != null) { + MemorySegment mem = device.vkMapMemory(memory, 0, dataSize, 0, scope); + mem.copyFrom(init); + device.vkUnmapMemory(memory); + } + + device.vkBindBufferMemory(buffer, memory, 0); + + return new BufferMemory(buffer, memory, dataSize); + } + } + + void shutdown() { + device.vkDestroyFence(drawFence); + device.vkDestroySemaphore(chainSemaphore); + + device.vkDestroyPipeline(pipeline.getAtIndex(0)); + for (int i = 0; i < shader.size(); i++) + device.vkDestroyShaderModule(shader.getAtIndex(i)); + + vertex.free(device); + uniform.free(device); + + for (int i = 0; i < framebuffers.size(); i++) + device.vkDestroyFramebuffer(framebuffers.getAtIndex(i)); + + device.vkDestroyRenderPass(render_pass); + + device.vkDestroyDescriptorPool(desc_pool); + device.vkDestroyPipelineLayout(pipeline_layout); + device.vkDestroyDescriptorSetLayout(desc_layout); + + device.vkDestroyImageView(depthView); + device.vkFreeMemory(depthMemory); + device.vkDestroyImage(depthImage); + + for (int i = 0; i < chainImageView.size(); i++) + device.vkDestroyImageView(chainImageView.getAtIndex(i)); + + device.vkDestroySwapchainKHR(chain); + + device.vkDestroyCommandPool(cmd_pool); + device.vkDestroyDevice(); + + instance.vkDestroySurfaceKHR(surface); + window.close(); + display.close(); + + if (logger != null) + instance.vkDestroyDebugUtilsMessengerEXT(logger); + instance.vkDestroyInstance(); + } + + /** + * This finds the memory type index for the memory on a specific device. + */ + static int find_memory_type(VkPhysicalDeviceMemoryProperties memory, int typeMask, int query) { + VkMemoryType mtypes = memory.getMemoryTypes(); + + for (int i = 0; i < memory.getMemoryTypeCount(); i++) { + if (((1 << i) & typeMask) != 0 && ((mtypes.getPropertyFlagsAtIndex(i) & query) == query)) + return i; + } + return -1; + } + + static int clampi(int v, int min, int max) { + return v < min ? min : v < max ? v : max; + } + + void init_matrices() { + float eye[] = new float[]{-5, 3, -10}; + float centre[] = new float[]{0, 0, 0}; + float up[] = new float[]{0, -1, 0}; + float t0[] = new float[16], t1[] = new float[16]; + + perspective(projection, (float)(Math.PI * 0.25), 1.0f, 0.1f, 100.0f); + lookAt(view, eye, centre, up); + identity4f(model); + mult4x4f(t0, clip, projection); + mult4x4f(t1, t0, view); + mult4x4f(mvp, t1, model); + } + + void demo() throws Exception { + cube_vs = ShaderIO.loadCube_vert((SegmentAllocator)scope); + cube_fs = ShaderIO.loadCube_frag((SegmentAllocator)scope); + + init_matrices(); + + init_instance(); + init_debug(); + init_surface(); + init_device(); + init_device_queue(); + + init_command(); + init_depth(); + init_uniform(); + + init_descriptor(); + init_render(); + init_framebuffer(); + init_vertexbuffer(); + init_pipeline(); + + execute_begin_command_buffer(); + + cmd_paint(); + + System.out.println("Any key to quit"); + Event e; +outer: while ((e = window.nextEvent(true)) != null) { + switch (e.type) { + case Event.KEY: + case Event.CLOSE: + break outer; + } + } + + shutdown(); + } + + public static void main(String[] args) throws Throwable { + System.loadLibrary("vulkan"); + System.loadLibrary("X11"); + + new TestCube().demo(); + } +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java b/src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java new file mode 100755 index 0000000..cc139fb --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java @@ -0,0 +1,585 @@ +/* +The MIT License (MIT) + +Copyright (C) 2017 Eric Arnebäck +Copyright (C) 2019 Michael Zucchi + +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. + + */ + + /* + * This is a Java conversion of a C conversion of this: + * https://github.com/Erkaman/vulkan_minimal_compute + * + * It's been simplified a bit and converted to the 'zvk' api. + */ +package vulkan.test; + +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.image.MemoryImageSource; +import javax.swing.JFrame; +import javax.swing.JPanel; +import jdk.incubator.foreign.*; +import au.notzed.nativez.*; +import java.util.ArrayList; +import java.util.List; + +import vulkan.*; +import static vulkan.Vulkan.*; + +public class TestMandelbrot { + + static final boolean debug = true; + ResourceScope scope = ResourceScope.newSharedScope(); + + int WIDTH = 1920 * 1; + int HEIGHT = 1080 * 1; + + VkInstance instance; + VkPhysicalDevice physicalDevice; + + VkDevice device; + VkQueue computeQueue; + + long dstBufferSize = WIDTH * HEIGHT * 4; + //VkBuffer dstBuffer; + //VkDeviceMemory dstMemory; + BufferMemory dst; + + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorPool descriptorPool; + HandleArray descriptorSets; + + int computeQueueIndex; + VkPhysicalDeviceMemoryProperties deviceMemoryProperties; + + String mandelbrot_entry = "main"; + IntArray mandelbrot_cs; + + VkShaderModule mandelbrotShader; + VkPipelineLayout pipelineLayout; + HandleArray computePipeline = VkPipeline.createArray(1, (SegmentAllocator)scope); + + VkCommandPool commandPool; + HandleArray commandBuffers; + + record BufferMemory(VkBuffer buffer, VkDeviceMemory memory) { + + } + + VkDebugUtilsMessengerEXT logger; + + void init_debug() throws Exception { + if (!debug) + return; + try ( Frame frame = Frame.frame()) { + var cb = PFN_vkDebugUtilsMessengerCallbackEXT.upcall((severity, flags, data) -> { + System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); + return 0; + }, scope); + VkDebugUtilsMessengerCreateInfoEXT info = VkDebugUtilsMessengerCreateInfoEXT.create( + 0, + //VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + cb, + null, + frame); + + logger = instance.vkCreateDebugUtilsMessengerEXT(info, scope); + } + } + + void init_instance() throws Exception { + try ( Frame frame = Frame.frame()) { + VkInstanceCreateInfo info = VkInstanceCreateInfo.create( + 0, + VkApplicationInfo.create("test", 1, "test-engine", 2, VK_API_VERSION_1_0, frame), + 1, + new String[]{"VK_LAYER_KHRONOS_validation"}, + debug ? 1 : 0, + debug ? new String[]{"VK_EXT_debug_utils"} : null, + frame + ); + + instance = VkInstance.vkCreateInstance(info, scope); + } + } + + void init_device() throws Exception { + try ( Frame frame = Frame.frame()) { + HandleArray devs; + int count; + int res; + + devs = instance.vkEnumeratePhysicalDevices(frame, scope); + + int best = 0; + int devid = -1; + int queueid = -1; + + for (int i = 0; i < devs.length(); i++) { + VkPhysicalDevice dev = devs.getAtIndex(i); + VkQueueFamilyProperties famprops = dev.vkGetPhysicalDeviceQueueFamilyProperties(frame); + int family_count = (int)famprops.length(); + + for (int j = 0; j < family_count; j++) { + var flags = famprops.getAtIndex(j).getQueueFlags(); + int score = 0; + + if ((flags & VK_QUEUE_COMPUTE_BIT) != 0) + score += 1; + if ((flags & VK_QUEUE_GRAPHICS_BIT) == 0) + score += 1; + + if (score > best) { + score = best; + devid = i; + queueid = j; + } + } + } + + if (devid == -1) + throw new Exception("Cannot find a suitable device"); + + computeQueueIndex = queueid; + physicalDevice = devs.getAtIndex(devid); + + FloatArray qpri = FloatArray.create(frame, 0.0f); + VkDeviceQueueCreateInfo qinfo = VkDeviceQueueCreateInfo.create( + 0, + queueid, + 1, + qpri, + frame); + VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( + 0, + 1, qinfo, + 0, null, + 0, null, + null, + frame); + + device = physicalDevice.vkCreateDevice(devinfo, scope); + + System.out.printf("device = %s\n", device.address()); + + // NOTE: app scope + deviceMemoryProperties = VkPhysicalDeviceMemoryProperties.create((SegmentAllocator)scope); + physicalDevice.vkGetPhysicalDeviceMemoryProperties(deviceMemoryProperties); + + computeQueue = device.vkGetDeviceQueue(queueid, 0, scope); + } + } + + /** + * Buffers are created in three steps: + * 1) create buffer, specifying usage and size + * 2) allocate memory based on memory requirements + * 3) bind memory + *

+ */ + BufferMemory init_buffer(long dataSize, int usage, int properties) throws Exception { + try ( Frame frame = Frame.frame()) { + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkBufferCreateInfo buf_info = VkBufferCreateInfo.create( + 0, + dataSize, + usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, null, + frame); + + VkBuffer buffer = device.vkCreateBuffer(buf_info, scope); + + device.vkGetBufferMemoryRequirements(buffer, req); + + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create( + req.getSize(), + find_memory_type(deviceMemoryProperties, req.getMemoryTypeBits(), properties), + frame); + + VkDeviceMemory memory = device.vkAllocateMemory(alloc, scope); + + device.vkBindBufferMemory(buffer, memory, 0); + + return new BufferMemory(buffer, memory); + } + } + + /** + * Descriptors are used to bind and describe memory blocks + * to shaders. + *

+ * *Pool is used to allocate descriptors, it is per-device. + * *Layout is used to group descriptors for a given pipeline, + * The descriptors describe individually-addressable blocks. + */ + void init_descriptor() throws Exception { + try ( Frame frame = Frame.frame()) { + /* Create descriptorset layout */ + VkDescriptorSetLayoutBinding layout_binding = VkDescriptorSetLayoutBinding.create( + 0, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + VK_SHADER_STAGE_COMPUTE_BIT, + null, + frame); + + VkDescriptorSetLayoutCreateInfo descriptor_layout = VkDescriptorSetLayoutCreateInfo.create( + 0, + 1, layout_binding, + frame); + + descriptorSetLayout = device.vkCreateDescriptorSetLayout(descriptor_layout, scope); + + /* Create descriptor pool */ + VkDescriptorPoolSize type_count = VkDescriptorPoolSize.create( + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + frame); + + VkDescriptorPoolCreateInfo descriptor_pool = VkDescriptorPoolCreateInfo.create( + 0, + 1, + 1, type_count, + frame); + + descriptorPool = device.vkCreateDescriptorPool(descriptor_pool, scope); + + /* Allocate from pool */ + HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); + + layout_table.setAtIndex(0, descriptorSetLayout); + + VkDescriptorSetAllocateInfo alloc_info = VkDescriptorSetAllocateInfo.create( + descriptorPool, + 1, layout_table, + frame); + + descriptorSets = device.vkAllocateDescriptorSets(alloc_info, (SegmentAllocator)scope, scope); + + /* Bind a buffer to the descriptor */ + VkDescriptorBufferInfo bufferInfo = VkDescriptorBufferInfo.create( + dst.buffer, + 0, + dstBufferSize, + frame); + + VkWriteDescriptorSet writeSet = VkWriteDescriptorSet.create( + descriptorSets.getAtIndex(0), + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + null, + bufferInfo, + null, + frame); + + System.out.println(writeSet); + + device.vkUpdateDescriptorSets(1, writeSet, 0, null); + } + } + + /** + * Create the compute pipeline. This is the shader and data layouts for it. + */ + void init_pipeline() throws Exception { + try ( Frame frame = Frame.frame()) { + /* Set shader code */ + VkShaderModuleCreateInfo vsInfo = VkShaderModuleCreateInfo.create( + 0, + mandelbrot_cs.length() * 4, + mandelbrot_cs, + frame); + + mandelbrotShader = device.vkCreateShaderModule(vsInfo, scope); + + /* Link shader to layout */ + HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); + + layout_table.setAtIndex(0, descriptorSetLayout); + + VkPipelineLayoutCreateInfo pipelineinfo = VkPipelineLayoutCreateInfo.create( + 0, + 1, layout_table, + 0, null, + frame); + + pipelineLayout = device.vkCreatePipelineLayout(pipelineinfo, scope); + + /* Create pipeline */ + VkComputePipelineCreateInfo pipeline = VkComputePipelineCreateInfo.create( + 0, + pipelineLayout, + null, + 0, + frame); + + VkPipelineShaderStageCreateInfo stage = pipeline.getStage(); + + stage.setStage(VK_SHADER_STAGE_COMPUTE_BIT); + stage.setModule(mandelbrotShader); + stage.setName(mandelbrot_entry, frame); + + device.vkCreateComputePipelines(null, 1, pipeline, computePipeline); + } + } + + /** + * Create a command buffer, this is somewhat like a display list. + */ + void init_command_buffer() throws Exception { + try ( Frame frame = Frame.frame()) { + VkCommandPoolCreateInfo poolinfo = VkCommandPoolCreateInfo.create( + 0, + computeQueueIndex, + frame); + + commandPool = device.vkCreateCommandPool(poolinfo, scope); + + VkCommandBufferAllocateInfo cmdinfo = VkCommandBufferAllocateInfo.create( + commandPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1, + frame); + + // should it take a scope? + commandBuffers = device.vkAllocateCommandBuffers(cmdinfo, (SegmentAllocator)scope, scope); + + /* Fill command buffer with commands for later operation */ + VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.create( + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + null, + frame); + + commandBuffers.get(0).vkBeginCommandBuffer(beginInfo); + + /* Bind the compute operation and data */ + commandBuffers.get(0).vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.get(0)); + commandBuffers.get(0).vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, descriptorSets, 0, null); + + /* Run it */ + commandBuffers.get(0).vkCmdDispatch(WIDTH, HEIGHT, 1); + + commandBuffers.get(0).vkEndCommandBuffer(); + } + } + + /** + * Execute the pre-created command buffer. + *

+ * A fence is used to wait for completion. + */ + void execute() throws Exception { + try ( Frame frame = Frame.frame()) { + VkSubmitInfo submitInfo = VkSubmitInfo.create(frame); + + submitInfo.setCommandBufferCount(1); + submitInfo.setCommandBuffers(commandBuffers); + + /* Create fence to mark the task completion */ + VkFence fence; + HandleArray fences = VkFence.createArray(1, frame); + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(frame); + + // maybe this should take a HandleArray rather than being a constructor + // FIXME: some local scope + fence = device.vkCreateFence(fenceInfo, scope); + fences.set(0, fence); + + /* Await completion */ + computeQueue.vkQueueSubmit(1, submitInfo, fence); + + int VK_TRUE = 1; + int res; + do { + res = device.vkWaitForFences(1, fences, VK_TRUE, 1000000); + } while (res == VK_TIMEOUT); + + device.vkDestroyFence(fence); + } + } + + void shutdown() { + device.vkDestroyCommandPool(commandPool); + device.vkDestroyPipeline(computePipeline.getAtIndex(0)); + device.vkDestroyPipelineLayout(pipelineLayout); + device.vkDestroyShaderModule(mandelbrotShader); + + device.vkDestroyDescriptorPool(descriptorPool); + device.vkDestroyDescriptorSetLayout(descriptorSetLayout); + + device.vkFreeMemory(dst.memory()); + device.vkDestroyBuffer(dst.buffer()); + + device.vkDestroyDevice(); + if (logger != null) + instance.vkDestroyDebugUtilsMessengerEXT(logger); + instance.vkDestroyInstance(); + } + + /** + * Accesses the gpu buffer, converts it to RGB byte, and saves it as a pam file. + */ + void save_result() throws Exception { + try ( ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment mem = device.vkMapMemory(dst.memory(), 0, dstBufferSize, 0, scope); + byte[] pixels = new byte[WIDTH * HEIGHT * 3]; + + System.out.printf("map %d bytes\n", dstBufferSize); + + for (int i = 0; i < WIDTH * HEIGHT; i++) { + pixels[i * 3 + 0] = mem.get(Memory.BYTE, i * 4 + 0); + pixels[i * 3 + 1] = mem.get(Memory.BYTE, i * 4 + 1); + pixels[i * 3 + 2] = mem.get(Memory.BYTE, i * 4 + 2); + } + + device.vkUnmapMemory(dst.memory()); + + pam_save("mandelbrot.pam", WIDTH, HEIGHT, 3, pixels); + } + } + + void show_result() throws Exception { + try ( ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment mem = device.vkMapMemory(dst.memory(), 0, dstBufferSize, 0, scope); + int[] pixels = new int[WIDTH * HEIGHT]; + + System.out.printf("map %d bytes\n", dstBufferSize); + + MemorySegment.ofArray(pixels).copyFrom(mem); + + device.vkUnmapMemory(dst.memory()); + + swing_show(WIDTH, HEIGHT, pixels); + } + } + + /** + * Trivial pnm format image output. + */ + void pam_save(String name, int width, int height, int depth, byte[] pixels) throws IOException { + try ( FileOutputStream fos = new FileOutputStream(name)) { + fos.write(String.format("P6\n%d\n%d\n255\n", width, height).getBytes()); + fos.write(pixels); + System.out.printf("wrote: %s\n", name); + } + } + + static class DataImage extends JPanel { + + final int w, h, stride; + final MemoryImageSource source; + final Image image; + final int[] pixels; + + public DataImage(int w, int h, int[] pixels) { + this.w = w; + this.h = h; + this.stride = w; + this.pixels = pixels; + this.source = new MemoryImageSource(w, h, pixels, 0, w); + this.source.setAnimated(true); + this.source.setFullBufferUpdates(true); + this.image = Toolkit.getDefaultToolkit().createImage(source); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.drawImage(image, 0, 0, this); + } + } + + void swing_show(int w, int h, int[] pixels) { + JFrame window; + DataImage image = new DataImage(w, h, pixels); + + window = new JFrame("mandelbrot"); + window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + window.setContentPane(image); + window.setSize(w, h); + window.setVisible(true); + } + + /** + * This finds the memory type index for the memory on a specific device. + */ + static int find_memory_type(VkPhysicalDeviceMemoryProperties memory, int typeMask, int query) { + VkMemoryType mtypes = memory.getMemoryTypes(); + + for (int i = 0; i < memory.getMemoryTypeCount(); i++) { + if (((1 << i) & typeMask) != 0 && ((mtypes.getAtIndex(i).getPropertyFlags() & query) == query)) + return i; + } + return -1; + } + + void demo() throws Exception { + mandelbrot_cs = ShaderIO.loadMandelbrot_comp((SegmentAllocator)scope); + + init_instance(); + init_debug(); + + init_device(); + + dst = init_buffer(dstBufferSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + init_descriptor(); + + init_pipeline(); + init_command_buffer(); + + System.out.printf("Calculating %dx%d\n", WIDTH, HEIGHT); + execute(); + //System.out.println("Saving ..."); + //save_result(); + System.out.println("Showing ..."); + show_result(); + System.out.println("Done."); + + shutdown(); + } + + public static void main(String[] args) throws Throwable { + System.loadLibrary("vulkan"); + + new TestMandelbrot().demo(); + } +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/TestSDF.java b/src/notzed.vulkan.test/classes/vulkan/test/TestSDF.java new file mode 100644 index 0000000..c292253 --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/TestSDF.java @@ -0,0 +1,1057 @@ +/* +The MIT License (MIT) + +Copyright (C) 2017 Eric Arnebäck +Copyright (C) 2019 Michael Zucchi + +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. + + */ + + /* + * This is a Java conversion of a C conversion of this: + * https://github.com/Erkaman/vulkan_minimal_compute + * + * It's been simplified a bit and converted to the 'zvk' api. + */ +package vulkan.test; + +import au.notzed.display.Display; +import au.notzed.display.Event; +import au.notzed.display.Window; +import jdk.incubator.foreign.*; +import au.notzed.nativez.*; + +import vulkan.*; +import static vulkan.Vulkan.*; + +import static vulkan.test.GLMaths.*; + +public class TestSDF { + + static final boolean debug = true; + + final static int NUM_SAMPLES = VK_SAMPLE_COUNT_1_BIT; + final static int NUM_DESCRIPTOR_SETS = 1; + + ResourceScope scope = ResourceScope.newSharedScope(); + + int width = 800; + int height = 800; + float projection[] = new float[16]; + float view[] = new float[16]; + float model[] = new float[16]; + float clip[] = new float[]{ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f + }; + float mvp[] = new float[16]; + + VkInstance instance; + VkPhysicalDevice physicalDevice; + VkPhysicalDeviceMemoryProperties memory_properties; + VkPhysicalDeviceFeatures device_features; + + int present_queue_index; + int graphics_queue_index; + + VkDevice device; + VkSwapchainKHR chain; + + VkQueue graphics_queue; + VkQueue present_queue; + + int chainImageFormat; + HandleArray chainImage; + HandleArray chainImageView; + + int depthFormat; + VkImage depthImage; + VkImageView depthView; + VkDeviceMemory depthMemory; + + VkCommandPool cmd_pool; + HandleArray cmd; + + BufferMemory uniform; + VkPipelineLayout pipeline_layout; + + VkDescriptorSetLayout desc_layout; + VkDescriptorPool desc_pool; + HandleArray desc_set; + + VkRenderPass render_pass; + HandleArray framebuffers; + + BufferMemory vertex; + HandleArray vertexBuffer = VkBuffer.createArray(1, (SegmentAllocator)scope); + VkVertexInputBindingDescription vi_binding = VkVertexInputBindingDescription.createArray(1, (SegmentAllocator)scope); + VkVertexInputAttributeDescription vi_attribs = VkVertexInputAttributeDescription.createArray(1, (SegmentAllocator)scope); + + IntArray sdf_vs; + IntArray sdf_fs; + HandleArray shader = VkShaderModule.createArray(2, (SegmentAllocator)scope); + + HandleArray pipeline = VkPipeline.createArray(1, (SegmentAllocator)scope); + + VkSemaphore chainSemaphore; + VkFence drawFence; + + record BufferMemory(VkBuffer buffer, VkDeviceMemory memory, long size) { + + public void free(VkDevice device) { + device.vkFreeMemory(memory); + device.vkDestroyBuffer(buffer); + } + } + + VkDebugUtilsMessengerEXT logger; + + void init_debug() throws Exception { + if (!debug) + return; + try ( Frame frame = Frame.frame()) { + var cb = PFN_vkDebugUtilsMessengerCallbackEXT.upcall((severity, flags, data) -> { + System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); + return 0; + }, scope); + VkDebugUtilsMessengerCreateInfoEXT info = VkDebugUtilsMessengerCreateInfoEXT.create( + 0, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + cb, + null, + frame); + + logger = instance.vkCreateDebugUtilsMessengerEXT(info, scope); + } + + //typedef VkBool32 (*PFN_vkDebugUtilsMessengerCallbackEXT)(VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT *, void *); + } + + void init_instance() throws Exception { + try ( Frame frame = Frame.frame()) { + VkInstanceCreateInfo info = VkInstanceCreateInfo.create( + 0, + VkApplicationInfo.create("cube", 1, "cube-engine", 2, VK_API_VERSION_1_0, frame), + 1, new String[]{"VK_LAYER_KHRONOS_validation"}, + 3, new String[]{"VK_KHR_surface", "VK_KHR_xlib_surface", "VK_EXT_debug_utils"}, + frame + ); + + instance = VkInstance.vkCreateInstance(info, scope); + System.out.printf("instance = %s\n", instance); + } + } + + Display display; + Window window; + VkSurfaceKHR surface; + + void init_surface() throws Exception { + display = Display.createX11Display(); + window = display.createWindow(width, height); + surface = window.createVulkanSurface(instance, scope); + System.out.printf("surface: %s\n", surface); + } + + void init_device() throws Exception { + try ( Frame frame = Frame.frame()) { + IntArray count$h = IntArray.create(frame, 1); + IntArray present$h = IntArray.create(frame, 1); + HandleArray devs; + int count; + int res; + + devs = instance.vkEnumeratePhysicalDevices(frame, scope); + + // Search for device and queue indices + int devid = -1; + int present_queue = -1; + int graphics_queue = -1; + for (int i = 0; i < devs.length(); i++) { + VkPhysicalDevice dev = devs.getAtIndex(i); + VkQueueFamilyProperties famprops; + + famprops = dev.vkGetPhysicalDeviceQueueFamilyProperties(frame); + + for (int j = 0; j < famprops.length(); j++) { + boolean present = dev.vkGetPhysicalDeviceSurfaceSupportKHR(j, surface); + + if (present && present_queue == -1) + present_queue = j; + if ((famprops.getQueueFlagsAtIndex(j) & VK_QUEUE_GRAPHICS_BIT) != 0) { + graphics_queue = j; + if (present) { + present_queue = j; + break; + } + } + } + if (present_queue != -1 && graphics_queue != -1) { + devid = i; + break; + } + } + + if (devid == -1) + throw new Exception("Cannot find a suitable device"); + + physicalDevice = devs.getAtIndex(devid); + present_queue_index = present_queue; + graphics_queue_index = graphics_queue; + + // NOTE: app scope + memory_properties = VkPhysicalDeviceMemoryProperties.create((SegmentAllocator)scope); + physicalDevice.vkGetPhysicalDeviceMemoryProperties(memory_properties); + device_features = VkPhysicalDeviceFeatures.create((SegmentAllocator)scope); + physicalDevice.vkGetPhysicalDeviceFeatures(device_features); + + FloatArray qpri = FloatArray.create(frame, 0.0f); + VkDeviceQueueCreateInfo qinfo = VkDeviceQueueCreateInfo.create( + 0, + graphics_queue, + 1, qpri, + frame); + String[] extensions = { + "VK_KHR_swapchain" + }; + VkPhysicalDeviceFeatures features = VkPhysicalDeviceFeatures.create(frame); + features.setDepthClamp(1); + VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( + 0, + 1, qinfo, + 0, null, + extensions.length, extensions, + features, + frame); + + device = physicalDevice.vkCreateDevice(devinfo, scope); + + System.out.printf("device = %s\n", device); + + /* ************************************************************** */ + int format; + VkSurfaceFormatKHR surfFormats = physicalDevice.vkGetPhysicalDeviceSurfaceFormatsKHR(surface, frame); + // If the format list includes just one entry of VK_FORMAT_UNDEFINED, + // the surface has no preferred format. Otherwise, at least one + // supported format will be returned. + if (surfFormats.length() == 1 && surfFormats.getFormatAtIndex(0) == VK_FORMAT_UNDEFINED) { + format = VK_FORMAT_B8G8R8A8_UNORM; + } else { + format = surfFormats.getFormatAtIndex(0); + } + + VkSurfaceCapabilitiesKHR surfCapabilities = VkSurfaceCapabilitiesKHR.create(frame); + + physicalDevice.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, surfCapabilities); + IntArray presentModes = physicalDevice.vkGetPhysicalDeviceSurfacePresentModesKHR(surface, frame); + + VkExtent2D swapchainExtent; + // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF. + if (surfCapabilities.getCurrentExtent().getWidth() == 0xFFFFFFFF) { + // If the surface size is undefined, the size is set to + // the size of the images requested. + swapchainExtent = VkExtent2D.create( + clampi(width, surfCapabilities.getMinImageExtent().getWidth(), surfCapabilities.getMaxImageExtent().getWidth()), + clampi(height, surfCapabilities.getMinImageExtent().getHeight(), surfCapabilities.getMaxImageExtent().getHeight()), + frame); + } else { + // If the surface size is defined, the swap chain size must match + swapchainExtent = surfCapabilities.getCurrentExtent(); + } + int compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + int compositeAlphaFlags[] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,}; + for (int flag: compositeAlphaFlags) { + if ((surfCapabilities.getSupportedCompositeAlpha() & flag) != 0) { + compositeAlpha = flag; + break; + } + } + + VkSwapchainCreateInfoKHR chaininfo = VkSwapchainCreateInfoKHR.create( + 0, + surface, + surfCapabilities.getMinImageCount(), + format, + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + swapchainExtent.getWidth(), swapchainExtent.getHeight(), + 1, //.imageArrayLayers = 1, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + VK_SHARING_MODE_EXCLUSIVE, + // assumes queues are same. + 0, null, + (surfCapabilities.getSupportedTransforms() & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) != 0 + ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : surfCapabilities.getCurrentTransform(), + compositeAlpha, + VK_PRESENT_MODE_FIFO_KHR, + VK_TRUE, + null, + frame); + + chain = device.vkCreateSwapchainKHR(chaininfo, scope); + + int chainImageCount; + + chainImage = device.vkGetSwapchainImagesKHR(chain, (SegmentAllocator)scope, scope); + chainImageCount = (int)chainImage.length(); + chainImageView = VkImageView.createArray(chainImageCount, (SegmentAllocator)scope); + + VkImageViewCreateInfo viewinfo = VkImageViewCreateInfo.create( + 0, + null, + VK_IMAGE_VIEW_TYPE_2D, + format, + VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, + VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, + frame); + + for (int i = 0; i < chainImageCount; i++) { + viewinfo.setImage(chainImage.get(i)); + + chainImageView.setAtIndex(i, device.vkCreateImageView(viewinfo, scope)); + } + + chainImageFormat = format; + } + } + + void init_device_queue() { + graphics_queue = device.vkGetDeviceQueue(graphics_queue_index, 0, scope); + if (graphics_queue_index == present_queue_index) { + present_queue = graphics_queue; + } else { + present_queue = device.vkGetDeviceQueue(present_queue_index, 0, scope); + } + } + + void init_command() throws Exception { + try ( Frame frame = Frame.frame()) { + VkCommandPoolCreateInfo poolinfo = VkCommandPoolCreateInfo.create( + 0, + graphics_queue_index, + frame); + + cmd_pool = device.vkCreateCommandPool(poolinfo, scope); + + VkCommandBufferAllocateInfo cmdinfo = VkCommandBufferAllocateInfo.create( + cmd_pool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1, + frame); + + cmd = device.vkAllocateCommandBuffers(cmdinfo, (SegmentAllocator)scope, scope); + } + } + + // parameterise as init_image? + void init_depth() throws Exception { + try ( Frame frame = Frame.frame()) { + int format = VK_FORMAT_D16_UNORM; + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkImageCreateInfo imageinfo = VkImageCreateInfo.create( + 0, + VK_IMAGE_TYPE_2D, + format, + width, height, 1, + 1, + 1, + NUM_SAMPLES, + 0, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_SHARING_MODE_EXCLUSIVE, + 0, null, + VK_IMAGE_LAYOUT_UNDEFINED, + frame); + + depthImage = device.vkCreateImage(imageinfo, scope); + + device.vkGetImageMemoryRequirements(depthImage, req); + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create( + req.getSize(), + find_memory_type(memory_properties, req.getMemoryTypeBits(), VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), + frame); + + depthMemory = device.vkAllocateMemory(alloc, scope); + + device.vkBindImageMemory(depthImage, depthMemory, 0); + + VkImageViewCreateInfo viewinfo = VkImageViewCreateInfo.create( + 0, + depthImage, + VK_IMAGE_VIEW_TYPE_2D, + VK_FORMAT_D16_UNORM, + VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, + VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, + frame); + + depthView = device.vkCreateImageView(viewinfo, scope); + depthFormat = format; + } + } + + void init_uniform() throws Exception { + uniform = init_buffer(mvp.length * 4, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, MemorySegment.ofArray(mvp)); + } + + void init_descriptor() throws Exception { + try ( Frame frame = Frame.frame()) { + HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); + VkDescriptorSetLayoutBinding layout_binding = VkDescriptorSetLayoutBinding.create( + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, + VK_SHADER_STAGE_VERTEX_BIT, + null, + frame); + VkDescriptorSetLayoutCreateInfo descriptor_layout = VkDescriptorSetLayoutCreateInfo.create( + 0, + 1, layout_binding, + frame); + + desc_layout = device.vkCreateDescriptorSetLayout(descriptor_layout, scope); + layout_table.setAtIndex(0, desc_layout); + + VkPipelineLayoutCreateInfo pipeline_info = VkPipelineLayoutCreateInfo.create( + 0, + 1, layout_table, + 0, null, + frame); + + pipeline_layout = device.vkCreatePipelineLayout(pipeline_info, scope); + + VkDescriptorPoolSize type_count = VkDescriptorPoolSize.create( + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, + frame); + + VkDescriptorPoolCreateInfo descriptor_pool = VkDescriptorPoolCreateInfo.create( + 0, + 1, + 1, type_count, + frame); + + desc_pool = device.vkCreateDescriptorPool(descriptor_pool, scope); + + VkDescriptorSetAllocateInfo alloc_info = VkDescriptorSetAllocateInfo.create( + desc_pool, + 1, layout_table, + frame); + + System.out.println(alloc_info); + + desc_set = device.vkAllocateDescriptorSets(alloc_info, (SegmentAllocator)scope, scope); + + VkDescriptorBufferInfo uniformInfo = VkDescriptorBufferInfo.create(uniform.buffer, 0, uniform.size, frame); + VkWriteDescriptorSet writes = VkWriteDescriptorSet.create( + desc_set.getAtIndex(0), + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + null, + uniformInfo, + null, + frame); + + device.vkUpdateDescriptorSets(1, writes, 0, null); + } + } + + void init_render() throws Exception { + try ( Frame frame = Frame.frame()) { + VkAttachmentDescription attachments = VkAttachmentDescription.createArray(2, frame); + + attachments.setFormat(chainImageFormat); + attachments.setSamples(NUM_SAMPLES); + attachments.setLoadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + attachments.setStoreOp(VK_ATTACHMENT_STORE_OP_STORE); + attachments.setStencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE); + attachments.setStencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + attachments.setInitialLayout(VK_IMAGE_LAYOUT_UNDEFINED); + attachments.setFinalLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + attachments.setFlags(0); + + attachments.setFormatAtIndex(1, depthFormat); + attachments.setSamplesAtIndex(1, NUM_SAMPLES); + attachments.setLoadOpAtIndex(1, VK_ATTACHMENT_LOAD_OP_CLEAR); + attachments.setStoreOpAtIndex(1, VK_ATTACHMENT_STORE_OP_STORE); + attachments.setStencilLoadOpAtIndex(1, VK_ATTACHMENT_LOAD_OP_DONT_CARE); + attachments.setStencilStoreOpAtIndex(1, VK_ATTACHMENT_STORE_OP_DONT_CARE); + attachments.setInitialLayoutAtIndex(1, VK_IMAGE_LAYOUT_UNDEFINED); + attachments.setFinalLayoutAtIndex(1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + attachments.setFlagsAtIndex(1, 0); + + VkAttachmentReference color_reference = VkAttachmentReference.create( + 0, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + frame); + + VkAttachmentReference depth_reference = VkAttachmentReference.create( + 1, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + frame); + + VkSubpassDescription subpass = VkSubpassDescription.create( + 0, + VK_PIPELINE_BIND_POINT_GRAPHICS, + 0, null, + 1, color_reference, + null, + depth_reference, + 0, null, + frame); + + VkRenderPassCreateInfo rp_info = VkRenderPassCreateInfo.create( + 0, + 2, attachments, + 1, subpass, + 0, null, + frame); + + render_pass = device.vkCreateRenderPass(rp_info, scope); + } + } + + void init_framebuffer() throws Exception { + try ( Frame frame = Frame.frame()) { + HandleArray attachments = VkImageView.createArray(2, frame); + + attachments.setAtIndex(1, depthView); + + VkFramebufferCreateInfo fb_info = VkFramebufferCreateInfo.create( + 0, + render_pass, + 2, attachments, + width, height, 1, + frame); + + framebuffers = VkFramebuffer.createArray(chainImage.length(), (SegmentAllocator)scope); + for (int i = 0; i < chainImage.size(); i++) { + attachments.setAtIndex(0, chainImageView.get(i)); + framebuffers.setAtIndex(i, device.vkCreateFramebuffer(fb_info, scope)); + System.out.printf("framebuffer[%d] = %s\n", i, framebuffers.getAtIndex(i)); + } + } + } + + void init_vertexbuffer() throws Exception { + try ( Frame frame = Frame.frame()) { + float[] image = { + -1, -1, 0, 1, + 1, -1, 0, 1, + -1, 1, 0, 1, + -1, 1, 0, 1, + 1, -1, 0, 1, + 1, 1, 0, 1 + // 1, -1, 0, 1, + // -1, 1, 0, 1, + // 1, 1, 0, 1 + }; + vertex = init_buffer(image.length * 4, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, MemorySegment.ofArray(image)); + + vertexBuffer.setAtIndex(0, vertex.buffer); + + /* ***************************************** */ + vi_binding.setBinding(0); + vi_binding.setInputRate(VK_VERTEX_INPUT_RATE_VERTEX); + vi_binding.setStride(4 * 4); + + vi_attribs.setBinding(0); + vi_attribs.setLocation(0); + vi_attribs.setFormat(VK_FORMAT_R32G32B32A32_SFLOAT); + vi_attribs.setOffset(0); + //vi_attribs.setBindingAtIndex(1, 0); + //vi_attribs.setLocationAtIndex(1, 1); + //vi_attribs.setFormatAtIndex(1, VK_FORMAT_R32G32B32A32_SFLOAT); + //vi_attribs.setOffsetAtIndex(1, 16); + } + } + + void init_pipeline() throws Exception { + int res; + try ( Frame frame = Frame.frame()) { + IntArray dynamicStateEnables = IntArray.create(frame, + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR); + + VkPipelineDynamicStateCreateInfo dynamicState = VkPipelineDynamicStateCreateInfo.create( + 0, + 2, dynamicStateEnables, + frame); + + VkPipelineVertexInputStateCreateInfo vi = VkPipelineVertexInputStateCreateInfo.create( + 0, + (int)vi_binding.length(), vi_binding, + (int)vi_attribs.length(), vi_attribs, + frame); + + VkPipelineInputAssemblyStateCreateInfo ia = VkPipelineInputAssemblyStateCreateInfo.create( + 0, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + 0, + frame); + + VkPipelineRasterizationStateCreateInfo rs = VkPipelineRasterizationStateCreateInfo.create( + 0, + VK_TRUE, + VK_FALSE, + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_BACK_BIT, + VK_FRONT_FACE_CLOCKWISE, + VK_FALSE, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + frame); + + VkPipelineColorBlendAttachmentState att_state = VkPipelineColorBlendAttachmentState.create( + VK_FALSE, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_OP_ADD, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_FACTOR_ZERO, + VK_BLEND_OP_ADD, + 0xf, + frame); + + VkPipelineColorBlendStateCreateInfo cb = VkPipelineColorBlendStateCreateInfo.create( + 0, + VK_FALSE, + VK_LOGIC_OP_NO_OP, + 1, att_state, + 1.0f, 1.0f, 1.0f, 1.0f, + frame); + + VkPipelineViewportStateCreateInfo vp = VkPipelineViewportStateCreateInfo.create( + 0, + 1, null, + 1, null, + frame); + + VkPipelineDepthStencilStateCreateInfo ds = VkPipelineDepthStencilStateCreateInfo.create( + 0, + VK_TRUE, + VK_TRUE, + VK_COMPARE_OP_LESS_OR_EQUAL, + VK_FALSE, + VK_FALSE, + 0.0f, + 0.0f, + frame); + VkStencilOpState back = ds.getBack(); + + back.setFailOp(VK_STENCIL_OP_KEEP); + back.setPassOp(VK_STENCIL_OP_KEEP); + back.setCompareOp(VK_COMPARE_OP_ALWAYS); + back.setCompareMask(0); + back.setReference(0); + back.setDepthFailOp(VK_STENCIL_OP_KEEP); + back.setWriteMask(0); + + VkStencilOpState front = ds.getFront(); + + front.setFailOp(VK_STENCIL_OP_KEEP); + front.setPassOp(VK_STENCIL_OP_KEEP); + front.setCompareOp(VK_COMPARE_OP_ALWAYS); + front.setCompareMask(0); + front.setReference(0); + front.setDepthFailOp(VK_STENCIL_OP_KEEP); + front.setWriteMask(0); + + VkPipelineMultisampleStateCreateInfo ms = VkPipelineMultisampleStateCreateInfo.create( + 0, + NUM_SAMPLES, + 0, //.sampleShadingEnable = VK_FALSE, + 0.0f, + null, + 0, //.alphaToCoverageEnable = VK_FALSE, + 0, //.alphaToOneEnable = VK_FALSE, + frame + ); + + VkShaderModuleCreateInfo vsInfo = VkShaderModuleCreateInfo.create( + 0, + sdf_vs.length() * 4, + sdf_vs, + frame); + VkShaderModuleCreateInfo fsInfo = VkShaderModuleCreateInfo.create( + 0, + sdf_fs.length() * 4, + sdf_fs, + frame); + + shader.setAtIndex(0, device.vkCreateShaderModule(vsInfo, scope)); + shader.setAtIndex(1, device.vkCreateShaderModule(fsInfo, scope)); + + VkPipelineShaderStageCreateInfo shaderStages = VkPipelineShaderStageCreateInfo.createArray(2, (SegmentAllocator)scope); + + shaderStages.setStage(VK_SHADER_STAGE_VERTEX_BIT); + shaderStages.setName("main", (SegmentAllocator)scope); + shaderStages.setModule(shader.get(0)); + + shaderStages.setStageAtIndex(1, VK_SHADER_STAGE_FRAGMENT_BIT); + shaderStages.setNameAtIndex(1, "main", (SegmentAllocator)scope); + shaderStages.setModuleAtIndex(1, shader.get(1)); + + VkGraphicsPipelineCreateInfo pipeline = VkGraphicsPipelineCreateInfo.create( + 0, + 2, shaderStages, + vi, + ia, + null, + vp, + rs, + ms, + ds, + cb, + dynamicState, + pipeline_layout, + render_pass, + 0, + null, + 0, + frame); + + res = device.vkCreateGraphicsPipelines(null, 1, pipeline, this.pipeline); + + VkSemaphoreCreateInfo seminfo = VkSemaphoreCreateInfo.create(0, frame); + chainSemaphore = device.vkCreateSemaphore(seminfo, scope); + + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(0, frame); + drawFence = device.vkCreateFence(fenceInfo, scope); + } + } + + void execute_begin_command_buffer() throws Exception { + /* DEPENDS on init_command() */ + try ( Frame frame = Frame.frame()) { + VkCommandBufferBeginInfo cmd_buf_info = VkCommandBufferBeginInfo.create( + 0, + null, + frame); + + cmd.getAtIndex(0).vkBeginCommandBuffer(cmd_buf_info); + } + } + + void execute_end_command_buffer() throws Exception { + cmd.getAtIndex(0).vkEndCommandBuffer(); + } + + final static long FENCE_TIMEOUT = 100000000; + + void execute_queue_command_buffer() throws Exception { + int res; + + /* Queue the command buffer for execution */ + /* FIXME: frame shoudl provide or take explicit scope */ + try ( ResourceScope scope = ResourceScope.newConfinedScope(); Frame frame = Frame.frame()) { + IntArray pipe_stage_flags = IntArray.create(frame, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(0, frame); + HandleArray fences = VkFence.createArray(1, frame); + + fences.setAtIndex(0, device.vkCreateFence(fenceInfo, scope)); + + VkSubmitInfo submit_info = VkSubmitInfo.create( + 1, null, pipe_stage_flags, + (int)cmd.length(), cmd, + 0, null, + frame); + + graphics_queue.vkQueueSubmit(1, submit_info, drawFence); + + do { + res = device.vkWaitForFences(1, fences, 1, FENCE_TIMEOUT); + } while (res == VK_TIMEOUT); + + device.vkDestroyFence(fences.getAtIndex(0)); + } + } + + void cmd_viewport() { + try ( Frame frame = Frame.frame()) { + VkCommandBuffer cmd = this.cmd.getAtIndex(0); + VkViewport viewport = VkViewport.create( + 0, 0, width, height, 0.0f, 1.0f, + frame); + cmd.vkCmdSetViewport(0, 1, viewport); + } + } + + void cmd_scissors() { + try ( Frame frame = Frame.frame()) { + VkCommandBuffer cmd = this.cmd.getAtIndex(0); + VkRect2D scissor = VkRect2D.create(frame); + VkExtent2D extent = scissor.getExtent(); + + extent.setWidth(width); + extent.setHeight(height); + + cmd.vkCmdSetScissor(0, 1, scissor); + } + } + + void cmd_paint() throws Exception { + int res; + try ( Frame frame = Frame.frame()) { + int chainIndex; + IntArray chainIndices = IntArray.createArray(1, frame); + VkCommandBuffer cmd = this.cmd.getAtIndex(0); + + device.vkAcquireNextImageKHR(chain, ~0L, chainSemaphore, null, chainIndices); + chainIndex = chainIndices.getAtIndex(0); + LongArray offsets = LongArray.createArray(1, frame); + + VkClearValue clear_values = VkClearValue.createArray(2, frame); + clear_values.getColor().setFloat32(0.2f, 0.2f, 0.2f, 0.2f, frame); + VkClearDepthStencilValue depthStencil = clear_values.getAtIndex(1).getDepthStencil(); + depthStencil.setDepth(1.0f); + depthStencil.setStencil(0); + + System.out.printf("render framebuffer[%d] = %s\n", chainIndex, framebuffers.getAtIndex(chainIndex)); + + VkRenderPassBeginInfo rp_begin = VkRenderPassBeginInfo.create( + render_pass, + framebuffers.getAtIndex(chainIndex), + 0, 0, width, height, + 2, clear_values, + frame); + + cmd.vkCmdBeginRenderPass(rp_begin, VK_SUBPASS_CONTENTS_INLINE); + + cmd.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.getAtIndex(0)); + cmd.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, NUM_DESCRIPTOR_SETS, desc_set, 0, null); + cmd.vkCmdBindVertexBuffers(0, 1, vertexBuffer, offsets); + + cmd_viewport(); + cmd_scissors(); + + cmd.vkCmdDraw(12 * 3, 1, 0, 0); + cmd.vkCmdEndRenderPass(); + + cmd.vkEndCommandBuffer(); + + IntArray pipe_stage_flags = IntArray.create(frame, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + HandleArray semaphores = VkSemaphore.createArray(1, frame);//, chainSemaphore, scope); + + semaphores.setAtIndex(0, chainSemaphore); + + VkSubmitInfo submit_info = VkSubmitInfo.create( + 1, semaphores, pipe_stage_flags, + (int)this.cmd.length(), this.cmd, + 0, null, + frame); + + HandleArray fences = VkFence.createArray(1, frame); + + fences.setAtIndex(0, drawFence); + + // Queue the command buffer for execution + device.vkResetFences(1, fences); + + graphics_queue.vkQueueSubmit(1, submit_info, drawFence); + + // Make sure command buffer is finished before presenting + do { + res = device.vkWaitForFences(1, fences, VK_TRUE, FENCE_TIMEOUT); + } while (res == VK_TIMEOUT); + + // Now present the image in the window + HandleArray chains = VkSwapchainKHR.createArray(1, frame); + chains.setAtIndex(0, chain); + VkPresentInfoKHR present = VkPresentInfoKHR.create( + 0, null, + 1, chains, chainIndices, null, + frame); + + present_queue.vkQueuePresentKHR(present); + } + } + + /** + * Buffers are created in three steps: + * 1) create buffer, specifying usage and size + * 2) allocate memory based on memory requirements + * 3) bind memory + *

+ */ + BufferMemory init_buffer(long dataSize, int usage, int properties, MemorySegment init) throws Exception { + try ( Frame frame = Frame.frame()) { + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkBufferCreateInfo buf_info = VkBufferCreateInfo.create( + 0, + dataSize, + usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, null, + frame); + + VkBuffer buffer = device.vkCreateBuffer(buf_info, scope); + + device.vkGetBufferMemoryRequirements(buffer, req); + + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create( + req.getSize(), + find_memory_type(memory_properties, req.getMemoryTypeBits(), properties), + frame); + + VkDeviceMemory memory = device.vkAllocateMemory(alloc, scope); + + if (init != null) { + MemorySegment mem = device.vkMapMemory(memory, 0, dataSize, 0, scope); + mem.copyFrom(init); + device.vkUnmapMemory(memory); + } + + device.vkBindBufferMemory(buffer, memory, 0); + + return new BufferMemory(buffer, memory, dataSize); + } + } + + void shutdown() { + device.vkDestroyFence(drawFence); + device.vkDestroySemaphore(chainSemaphore); + + device.vkDestroyPipeline(pipeline.getAtIndex(0)); + for (int i = 0; i < shader.size(); i++) + device.vkDestroyShaderModule(shader.getAtIndex(i)); + + vertex.free(device); + uniform.free(device); + + for (int i = 0; i < framebuffers.size(); i++) + device.vkDestroyFramebuffer(framebuffers.getAtIndex(i)); + + device.vkDestroyRenderPass(render_pass); + + device.vkDestroyDescriptorPool(desc_pool); + device.vkDestroyPipelineLayout(pipeline_layout); + device.vkDestroyDescriptorSetLayout(desc_layout); + + device.vkDestroyImageView(depthView); + device.vkFreeMemory(depthMemory); + device.vkDestroyImage(depthImage); + + for (int i = 0; i < chainImageView.size(); i++) + device.vkDestroyImageView(chainImageView.getAtIndex(i)); + + device.vkDestroySwapchainKHR(chain); + + device.vkDestroyCommandPool(cmd_pool); + device.vkDestroyDevice(); + + instance.vkDestroySurfaceKHR(surface); + window.close(); + display.close(); + + if (logger != null) + instance.vkDestroyDebugUtilsMessengerEXT(logger); + instance.vkDestroyInstance(); + } + + /** + * This finds the memory type index for the memory on a specific device. + */ + static int find_memory_type(VkPhysicalDeviceMemoryProperties memory, int typeMask, int query) { + VkMemoryType mtypes = memory.getMemoryTypes(); + + for (int i = 0; i < memory.getMemoryTypeCount(); i++) { + if (((1 << i) & typeMask) != 0 && ((mtypes.getAtIndex(i).getPropertyFlags() & query) == query)) + return i; + } + return -1; + } + + static int clampi(int v, int min, int max) { + return v < min ? min : v < max ? v : max; + } + + void init_matrices() { + //float eye[] = new float[] {-5, 3, -10}; + float eye[] = new float[]{0, 0, -10}; + float centre[] = new float[]{0, 0, 0}; + float up[] = new float[]{0, -1, 0}; + float t0[] = new float[16], t1[] = new float[16]; + + perspective(projection, (float)(Math.PI * 0.25), 1.0f, 0.1f, 100.0f); + lookAt(view, eye, centre, up); + identity4f(model); + mult4x4f(t0, clip, projection); + mult4x4f(t1, t0, view); + mult4x4f(mvp, t1, model); + } + + void demo() throws Exception { + sdf_vs = ShaderIO.loadSdf_vert((SegmentAllocator)scope); + sdf_fs = ShaderIO.loadSdf_frag((SegmentAllocator)scope); + + init_matrices(); + + init_instance(); + init_debug(); + + init_surface(); + init_device(); + init_device_queue(); + init_command(); + init_depth(); + init_uniform(); + + init_descriptor(); + init_render(); + init_framebuffer(); + init_vertexbuffer(); + init_pipeline(); + + execute_begin_command_buffer(); + + cmd_paint(); + + System.out.println("Any key to quit"); + Event e; +outer: while ((e = window.nextEvent(true)) != null) { + switch (e.type) { + case Event.KEY: + case Event.CLOSE: + break outer; + } + } + + shutdown(); + } + + public static void main(String[] args) throws Throwable { + System.loadLibrary("vulkan"); + System.loadLibrary("X11"); + + new TestSDF().demo(); + } +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/Tutorial.java b/src/notzed.vulkan.test/classes/vulkan/test/Tutorial.java new file mode 100644 index 0000000..09d77b7 --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/Tutorial.java @@ -0,0 +1,369 @@ +/* + Going through the Vulkan Tutorial. +*/ +package vulkan.test; + +import au.notzed.display.Display; +import au.notzed.display.Event; +import au.notzed.display.Window; +import au.notzed.nativez.Frame; + +import au.notzed.nativez.HandleArray; +import au.notzed.nativez.IntArray; +import au.notzed.nativez.LongArray; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import jdk.incubator.foreign.MemorySegment; +import jdk.incubator.foreign.ResourceScope; +import jdk.incubator.foreign.SegmentAllocator; +import vulkan.VkBuffer; +import vulkan.VkClearValue; +import vulkan.VkCommandBuffer; +import vulkan.VkCommandBufferBeginInfo; +import vulkan.VkDevice; +import vulkan.VkFence; +import vulkan.VkFramebuffer; +import vulkan.VkInstance; +import vulkan.VkPhysicalDeviceFeatures; +import vulkan.VkPipeline; +import vulkan.VkPipelineLayout; +import vulkan.VkPresentInfoKHR; +import vulkan.VkRect2D; +import vulkan.VkRenderPassBeginInfo; +import vulkan.VkSemaphore; +import vulkan.VkShaderModule; +import vulkan.VkSubmitInfo; +import vulkan.VkSurfaceKHR; +import vulkan.VkSwapchainKHR; +import vulkan.VkViewport; +import vulkan.Vulkan; +import static vulkan.Vulkan.VK_API_VERSION_1_2; +import static vulkan.Vulkan.VK_FENCE_CREATE_SIGNALED_BIT; +import static vulkan.Vulkan.VK_KHR_SWAPCHAIN_EXTENSION_NAME; +import static vulkan.Vulkan.VK_PIPELINE_BIND_POINT_GRAPHICS; +import static vulkan.Vulkan.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +import static vulkan.Vulkan.VK_PRESENT_MODE_FIFO_KHR; +import static vulkan.Vulkan.VK_SUBOPTIMAL_KHR; +import static vulkan.Vulkan.VK_SUBPASS_CONTENTS_INLINE; +import static vulkan.Vulkan.VK_SUCCESS; +import static vulkan.Vulkan.VK_TRUE; +//import static vulkan.Vulkan.*; + +public class Tutorial { + + static int width = 724, height = 580; + static final int MAX_FRAMES_INFLIGHT = 2; + + static String[] deviceTypes = { + "VK_PHYSICAL_DEVICE_TYPE_OTHER", + "VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU", + "VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU", + "VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU", + "VK_PHYSICAL_DEVICE_TYPE_CPU" + }; + + static final float[] vertices = { + 0.0f, -0.5f, 1, 0, 0, + 0.5f, 0.5f, 0, 1, 0, + -0.5f, 0.5f, 0, 0, 1 + }; + + public static record RenderInfo( + Demo.DemoDevice device, + Demo.DemoChain chain, + VkPipeline pipeline, + VkPipelineLayout pipelineLayout, + HandleArray commandBuffers, + HandleArray frameBuffers, + Runnable[] resources, + ResourceScope scope) { + + public void close() { + VkDevice d = device.device(); + frameBuffers.stream().forEach(d::vkDestroyFramebuffer); + d.vkFreeCommandBuffers(device.pools()[0], commandBuffers.size(), commandBuffers); + d.vkDestroyPipeline(pipeline); + d.vkDestroyPipelineLayout(pipelineLayout); + + for (int i = resources.length - 1; i >= 0; i--) + resources[i].run(); + + // here? chain? + d.vkDestroyRenderPass(chain.renderPass()); + chain.views().forEach(d::vkDestroyImageView); + d.vkDestroySwapchainKHR(chain.swapchain()); + + scope.close(); + } + } + + static void rebuildCommandBuffersx(RenderInfo info, int extWidth, int extHeight) { + Demo.DemoChain chain = info.chain(); + HandleArray commandBuffers = info.commandBuffers(); + HandleArray frameBuffers = info.frameBuffers(); + VkPipeline pipeline = info.pipeline; + + Demo.resetCommandBuffers(info.device, 0, commandBuffers); + + try ( Frame frame = Frame.frame()) { + VkCommandBufferBeginInfo begin = VkCommandBufferBeginInfo.create(0, null, frame); + VkClearValue clear = VkClearValue.createColour(0, 0, 0, 1, frame); + VkRenderPassBeginInfo render = VkRenderPassBeginInfo.create( + chain.renderPass(), null, + 0, 0, extWidth, extHeight, + 1, clear, + frame); + VkViewport viewports = VkViewport.create(0, 0, extWidth, extHeight, 0, 1, frame); + VkRect2D scissors = VkRect2D.create(0, 0, extWidth, extHeight, frame); + + for (int i = 0; i < commandBuffers.size(); i++) { + VkCommandBuffer cmd = commandBuffers.get(i); + + render.setFramebuffer(frameBuffers.get(i)); + + cmd.vkBeginCommandBuffer(begin); + cmd.vkCmdBeginRenderPass(render, VK_SUBPASS_CONTENTS_INLINE); // SECONDARY... for sub-rendering? + + cmd.vkCmdSetViewport(0, 1, viewports); + cmd.vkCmdSetScissor(0, 1, scissors); + + cmd.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmd.vkCmdDraw(3, 1, 0, 0); + + cmd.vkCmdEndRenderPass(); + cmd.vkEndCommandBuffer(); + } + } + } + + static RenderInfo createRenderInfo(Demo.DemoDevice device, int pixWidth, int pixHeight, VkShaderModule vertex, VkShaderModule fragment) { + ResourceScope scope = ResourceScope.newSharedScope(); + try ( Frame frame = Frame.frame()) { + SegmentAllocator alloc = SegmentAllocator.newNativeArena(scope); + List cleanup = new ArrayList<>(); + + Demo.DemoChain chain = Demo.createSwapchain( + device, + Demo.ColourFormat.SRGB, + //VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR, + pixWidth, pixHeight, alloc, scope); + + VkPipelineLayout layout = Demo.dumbPipelineLayout(device.device(), scope); + VkPipeline pipeline = Demo.dumbPipeline(device.device(), vertex, fragment, layout, chain.renderPass(), scope); + + // could move to chain? + HandleArray frameBuffers = Demo.createFramebuffers(device, chain, alloc, scope); + HandleArray commandBuffers = Demo.createCommandBuffers(device, 0, frameBuffers.size(), alloc, scope); + + // could move creation to Chain? + VkCommandBufferBeginInfo begin = VkCommandBufferBeginInfo.create(0, null, frame); + VkClearValue clear = VkClearValue.createColour(0, 0, 0.25f, 1, frame); + VkRenderPassBeginInfo render = VkRenderPassBeginInfo.create( + chain.renderPass(), null, + 0, 0, chain.extWidth(), chain.extHeight(), + 1, clear, + frame); + VkViewport viewports = VkViewport.create(0, 0, chain.extWidth(), chain.extHeight(), 0, 1, frame); + VkRect2D scissors = VkRect2D.create(0, 0, chain.extWidth(), chain.extHeight(), frame); + + Demo.Buffer vertexStaging = Demo.createBuffer(device, vertices.length * 4, Vulkan.VK_BUFFER_USAGE_TRANSFER_SRC_BIT, Vulkan.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, device.queueFamilies(), MemorySegment.ofArray(vertices), scope); + Demo.Buffer vertexBuffer = Demo.createBuffer(device, vertices.length * 4, Vulkan.VK_BUFFER_USAGE_TRANSFER_DST_BIT | Vulkan.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, Vulkan.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, device.queueFamilies(), null, scope); + HandleArray buffers = VkBuffer.createArray(1, alloc, scope); + LongArray buffersOffset = LongArray.createArray(1, alloc); + + buffers.set(0, vertexBuffer.buffer()); + + Demo.copyBuffer(device, vertexStaging, vertexBuffer); + vertexStaging.close(device.device()); + cleanup.add(() -> vertexBuffer.close(device.device())); + + for (int i = 0; i < commandBuffers.size(); i++) { + VkCommandBuffer cmd = commandBuffers.get(i); + + render.setFramebuffer(frameBuffers.get(i)); + + cmd.vkBeginCommandBuffer(begin); + cmd.vkCmdBeginRenderPass(render, VK_SUBPASS_CONTENTS_INLINE); // SECONDARY... for sub-rendering? + + cmd.vkCmdSetViewport(0, 1, viewports); + cmd.vkCmdSetScissor(0, 1, scissors); + + cmd.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmd.vkCmdBindVertexBuffers(0, 1, buffers, buffersOffset); + cmd.vkCmdDraw(3, 1, 0, 0); + + cmd.vkCmdEndRenderPass(); + cmd.vkEndCommandBuffer(); + } + return new RenderInfo(device, chain, pipeline, layout, commandBuffers, frameBuffers, cleanup.toArray(Runnable[]::new), scope); + } catch (Throwable t) { + scope.close(); + throw new RuntimeException(t); + } + } + + public static void main(String[] args) throws IOException, InterruptedException { + ResourceScope scope = ResourceScope.globalScope(); + + System.loadLibrary("vulkan"); + System.loadLibrary("X11"); + + try ( Frame frame = Frame.frame()) { + VkInstance instance = Demo.createInstance(VK_API_VERSION_1_2, + new String[]{"VK_LAYER_KHRONOS_validation"}, + new String[]{"VK_KHR_surface", "VK_KHR_xlib_surface", "VK_EXT_debug_utils"}, + scope); + + Display display; + Window window; + VkSurfaceKHR surface; + + display = Display.createX11Display(); + window = display.createWindow(width, height); + surface = window.createVulkanSurface(instance, scope); + + VkPhysicalDeviceFeatures features = VkPhysicalDeviceFeatures.create(frame); + + features.setDepthClamp(VK_TRUE); + + // Select 3 application queues + // graphics, compute, present + Demo.DemoDevice device = Demo.createDevice(instance, features, surface, + new int[]{Demo.QUEUE_GRAPHICS | Demo.QUEUE_PRESENT, Demo.QUEUE_TRANSFER}, + new String[]{ + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }, scope); + + VkShaderModule vertex = Demo.createShaderModule(device.device(), ShaderIO.loadDemo_vert(frame), scope); + VkShaderModule fragment = Demo.createShaderModule(device.device(), ShaderIO.loadDemo_frag(frame), scope); + HandleArray chains = VkSwapchainKHR.createArray(1, frame, scope); + + HandleArray imageReady = device.createSemaphores(MAX_FRAMES_INFLIGHT, frame, scope); + HandleArray renderDone = device.createSemaphores(MAX_FRAMES_INFLIGHT, frame, scope); + HandleArray renderFence = device.createFences(MAX_FRAMES_INFLIGHT, VK_FENCE_CREATE_SIGNALED_BIT, frame, scope); + + RenderInfo render = createRenderInfo(device, width, height, vertex, fragment); + + Demo.DemoChain chain = render.chain(); + HandleArray commandBuffers = render.commandBuffers(); + + chains.setAtIndex(0, chain.swapchain()); + + HandleArray imageFence = VkFence.createArray(chain.images().size(), frame, scope); + + VkSubmitInfo submitDraw = VkSubmitInfo.create( + 1, null, // imageRead.current + IntArray.create(frame, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT), + 1, null, // commandBuffers.current + 1, null, // renderDone.current + frame); + + IntArray imageIndex = IntArray.createArray(1, frame); + VkPresentInfoKHR present = VkPresentInfoKHR.create( + 1, null, //renderDone, + 1, chains, imageIndex, + null, + frame); + + int currentFrame = 0; + long last = System.nanoTime(); +outer: while (true) { + int res; + + System.out.println(currentFrame); + for (Event e = window.nextEvent(true); e != null; e = window.nextEvent(false)) { + System.out.println(e); + switch (e.type) { + case Event.KEY: + if (e.key == 9) + break outer; + break; + case Event.CLOSE: + break outer; + case Event.RESIZE: + if (e.width == 0) { + System.out.println("iconise"); + while (true) { + e = window.nextEvent(true); + if (e.type == Event.RESIZE && e.width > 0 && e.height > 0) + break; + if (e.type == Event.CLOSE) + break outer; + } + } + width = e.width; + height = e.height; + System.out.printf("resize: %dx%d\n", width, height); + break; + } + } + + long next = System.nanoTime(); + //System.out.printf(" %12.9f\n", (next - last) * 1E-9); + last = next; + + res = device.device().vkAcquireNextImageKHR(chain.swapchain(), ~0, imageReady.get(currentFrame), null, imageIndex); + if (res == VK_SUCCESS || res == VK_SUBOPTIMAL_KHR) { + int index = imageIndex.get(0); + VkFence renderBusy = renderFence.get(currentFrame); + + // This logic is to handle multiple inflight frames + { + HandleArray imageBusy = imageFence.asSlice(index, 1); + HandleArray renderCurrent = renderFence.asSlice(currentFrame, 1); + + if (imageFence.get(index) != null) + device.device().vkWaitForFences(1, imageBusy, VK_TRUE, ~0); + else + device.device().vkWaitForFences(1, renderCurrent, VK_TRUE, ~0); + + imageFence.set(index, renderBusy); + device.device().vkResetFences(1, renderCurrent); + } + + submitDraw.setWaitSemaphores(imageReady.asSlice(currentFrame)); + submitDraw.setSignalSemaphores(renderDone.asSlice(currentFrame)); + submitDraw.setCommandBuffers(commandBuffers.asSlice(index)); + + present.setWaitSemaphores(renderDone.asSlice(currentFrame)); + + device.queues()[0].vkQueueSubmit(1, submitDraw, renderBusy); + device.queues()[0].vkQueuePresentKHR(present); + + //device.device().vkWaitForFences(1, renderFence, VK_TRUE, ~0); + // resize + if (res == VK_SUBOPTIMAL_KHR) { + device.device().vkDeviceWaitIdle(); + + System.out.println("reconfigure"); + render.close(); + render = createRenderInfo(device, width, height, vertex, fragment); + chain = render.chain(); + chains.setAtIndex(0, chain.swapchain()); + commandBuffers = render.commandBuffers(); + } + + } else { + System.out.println(Vulkan.getErrorMessage(res)); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_INFLIGHT; + } + device.device().vkDeviceWaitIdle(); + + Stream.concat(imageReady.stream(), renderDone.stream()).forEach(device.device()::vkDestroySemaphore); + renderFence.stream().forEach(device.device()::vkDestroyFence); + + device.device().vkDestroyShaderModule(fragment); + device.device().vkDestroyShaderModule(vertex); + + render.close(); + device.close(); + instance.vkDestroySurfaceKHR(surface); + instance.vkDestroyInstance(); + } + } +} diff --git a/src/notzed.vulkan.test/gen/gen.make b/src/notzed.vulkan.test/gen/gen.make new file mode 100644 index 0000000..b0c5235 --- /dev/null +++ b/src/notzed.vulkan.test/gen/gen.make @@ -0,0 +1,24 @@ + +notzed.vulkan.test_SHADERS = \ + vulkan/test/mandelbrot.comp.bin \ + vulkan/test/cube.vert.bin \ + vulkan/test/cube.frag.bin \ + vulkan/test/sdf.vert.bin \ + vulkan/test/sdf.frag.bin \ + vulkan/test/demo.vert.bin \ + vulkan/test/demo.frag.bin + +notzed.vulkan.test_JAVA_GENERATED = \ + vulkan/test/ShaderIO.java + +notzed.vulkan.test_RESOURCES_GENERATED = \ + $(notzed.vulkan.test_SHADERS) + +notzed.vulkan.test_SPIRV = $(notzed.vulkan.test_SHADERS:%=bin/gen/notzed.vulkan.test/classes/%) + +bin/gen/notzed.vulkan.test/classes/%.bin: src/notzed.vulkan.test/shaders/% + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/gen/notzed.vulkan.test/classes/vulkan/test/ShaderIO.java: $(notzed.vulkan.test_SPIRV) src/notzed.vulkan.test/gen/generate-shaderio + src/notzed.vulkan.test/gen/generate-shaderio $@ $(notzed.vulkan.test_SPIRV) diff --git a/src/notzed.vulkan.test/gen/generate-shaderio b/src/notzed.vulkan.test/gen/generate-shaderio new file mode 100755 index 0000000..c2c2485 --- /dev/null +++ b/src/notzed.vulkan.test/gen/generate-shaderio @@ -0,0 +1,52 @@ +#!/usr/bin/perl + +# probably should take scope + +use strict; + +use File::Path qw(make_path); +use File::Basename; + +my $path = shift; + +open my $f, ">", "$path~" || die; + +print $f <= 0) { + my $file = shift; + my $func = ucfirst(basename($file, '.bin')); + my $name = basename($file); + my $size = (stat($file))[7]; + + die "No such file: $file" if !-f $file; + + $func =~ s/\./_/gon; + + print $f "\tstatic IntArray load$func(SegmentAllocator alloc) throws IOException { return loadSPIRV(\"$name\", $size, alloc); }\n"; +} + +print $f "}\n"; + +close $f; + +rename "$path~",$path; diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/cube.frag b/src/notzed.vulkan.test/shaders/vulkan/test/cube.frag new file mode 100644 index 0000000..de24544 --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/cube.frag @@ -0,0 +1,8 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (location = 0) in vec4 color; +layout (location = 0) out vec4 outColor; +void main() { + outColor = color; +} diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/cube.vert b/src/notzed.vulkan.test/shaders/vulkan/test/cube.vert new file mode 100644 index 0000000..5d21e1e --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/cube.vert @@ -0,0 +1,14 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (std140, binding = 0) uniform bufferVals { + mat4 mvp; +} data; +layout (location = 0) in vec4 pos; +layout (location = 1) in vec4 inColor; +layout (location = 0) out vec4 outColor; + +void main() { + outColor = inColor; + gl_Position = data.mvp * pos; +} diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/demo.frag b/src/notzed.vulkan.test/shaders/vulkan/test/demo.frag new file mode 100644 index 0000000..e335fa8 --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/demo.frag @@ -0,0 +1,8 @@ +#version 450 + +layout (location = 0) in vec3 colour; +layout (location = 0) out vec4 pixel; + +void main() { + pixel = vec4(colour, 1); +} diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/demo.vert b/src/notzed.vulkan.test/shaders/vulkan/test/demo.vert new file mode 100644 index 0000000..48bd441 --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/demo.vert @@ -0,0 +1,11 @@ +#version 450 + +layout (location=0) in vec2 inPosition; +layout (location=1) in vec3 inColour; + +layout (location=0) out vec3 colour; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + colour = inColour; +} diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/mandelbrot.comp b/src/notzed.vulkan.test/shaders/vulkan/test/mandelbrot.comp new file mode 100644 index 0000000..6a45590 --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/mandelbrot.comp @@ -0,0 +1,60 @@ +#version 450 + +#define WIDTH (1920*1) +#define HEIGHT (1080*1) +#define LWS_X 8 +#define LWS_Y 8 +#define LIMIT 10000 + +layout (local_size_x = LWS_X, local_size_y = LWS_Y, local_size_z = 1 ) in; + +layout(std430, binding = 0) buffer buf { + uint imageData[]; +}; + +void main() { + + /* + In order to fit the work into workgroups, some unnecessary threads are launched. + We terminate those threads here. + */ + if(gl_GlobalInvocationID.x >= WIDTH || gl_GlobalInvocationID.y >= HEIGHT) + return; + + float x = float(gl_GlobalInvocationID.x) / float(WIDTH); + float y = float(gl_GlobalInvocationID.y) / float(HEIGHT); + + /* + What follows is code for rendering the mandelbrot set. + */ + vec2 uv = vec2(x, (y - 0.5) * (12.0 / 19.0) + 0.5); + float n = 0.0; + vec2 c = vec2(-.445, 0.0) + (uv - 0.5)*(4.0); + vec2 z = vec2(0.0); + const int M = LIMIT; + + for (int i = 0; i 4) + break; + n++; + } + + // we use a simple cosine palette to determine color: + // http://iquilezles.org/www/articles/palettes/palettes.htm + float t = float(n) * 500.0 / float(M); + vec3 d = vec3(0.5, 0.5, 0.5); + vec3 e = vec3(0.5, 0.5, 0.5); + vec3 f = vec3(1.0, 1.0, 1.0); + vec3 g = vec3(0.00, 0.33, 0.67); + + vec4 color = vec4( d + e*cos( 6.28318*(f*t+g) ) ,1.0); + + if (n == M) + color = vec4(0, 0, 0, 1); + + // store the rendered mandelbrot set into a storage buffer: + imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x] = packUnorm4x8(color); + //imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x].value = color; +} diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/sdf.frag b/src/notzed.vulkan.test/shaders/vulkan/test/sdf.frag new file mode 100644 index 0000000..b453bd2 --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/sdf.frag @@ -0,0 +1,21 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (location = 0) out vec4 outColor; + +void main() { + vec2 res = vec2(800, 800); + vec2 uv = (gl_FragCoord.xy - 0.5 * res) * (1.0 / 400.0); + + float d = max(-(length(uv) - 0.5), length(uv + vec2(0.1, 0.1)) - 0.6); + + if (d < 0) { + outColor = vec4(0, 0, 1, 1); + } else { + outColor = vec4(1, 0, 0, 1); + } + + + //outColor = vec4(1, 0, 1, 1); + //outColor = vec4(gl_FragCoord.xy * 1.0 / 500.0, 0, 1); +} diff --git a/src/notzed.vulkan.test/shaders/vulkan/test/sdf.vert b/src/notzed.vulkan.test/shaders/vulkan/test/sdf.vert new file mode 100644 index 0000000..8019a0a --- /dev/null +++ b/src/notzed.vulkan.test/shaders/vulkan/test/sdf.vert @@ -0,0 +1,9 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec4 pos; + +void main() { + gl_Position = pos; +} diff --git a/src/notzed.vulkan/classes/module-info.java b/src/notzed.vulkan/classes/module-info.java new file mode 100644 index 0000000..3e7cdec --- /dev/null +++ b/src/notzed.vulkan/classes/module-info.java @@ -0,0 +1,8 @@ + +module notzed.vulkan { + requires transitive notzed.nativez; + requires notzed.xlib; + requires notzed.xcb; + + exports vulkan; +} diff --git a/src/notzed.vulkan/gen/command-types.api b/src/notzed.vulkan/gen/command-types.api new file mode 100644 index 0000000..e8f02f1 --- /dev/null +++ b/src/notzed.vulkan/gen/command-types.api @@ -0,0 +1,680 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# things to check; vkCmdSetFragmentShadingRateKHR + +# types for function calls + +code method { + invoke {{ + /* method:invoke */ + static final MethodHandle {name}$FH = Memory.downcall("{name}", + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arg}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$FH.invokeExact({invoke-arg}); + {result-test}{ + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +code method-query { + invoke {{ + /* method-query:invoke */ + static final MethodHandle {name}$FH = Memory.downcall("{name}", + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arg}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$FH.invokeExact({query-arg}); + {result-test}{ + {query-init} + {native-result-assign}{name}$FH.invokeExact({invoke-arg}); + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +code method-extension { + dispatch {{ + final NativeSymbol {name}$NS; + }} + invoke {{ + /* method-extension:invoke */ + final static MethodHandle {name}$DH = Memory.downcall( + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arg}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$DH.invokeExact(dispatch.{name}$NS, {invoke-arg}); + {result-test}{ + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +code method-extension-query { + dispatch {{ + final NativeSymbol {name}$NS; + }} + invoke {{ + /* method-extension:invoke */ + final static MethodHandle {name}$DH = Memory.downcall( + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arg}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$DH.invokeExact(dispatch.{name}$NS, {query-arg}); + {result-test}{ + {query-init} + {native-result-assign}{name}$DH.invokeExact(dispatch.{name}$NS, {invoke-arg}); + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +# ###################################################################### # + +# common parts for funcpointer +code funcpointer { + common {{ + public {java-result} call({java-arg}); + + public final static FunctionDescriptor DESCRIPTOR = + {function-descriptor}; + }} + upcall {{ + public static FunctionPointer<{rename}> upcall({rename} target$, ResourceScope scope$) { + interface Trampoline { + {native-result} call({native-arg}); + } + Trampoline trampoline = ({native-arg}) -> { + // frame? scope? + try {trampoline-scope} { + {trampoline-result-define} + {trampoline-result-assign}target$.call({trampoline-arg}); + {trampoline-result-return} + } catch (Throwable t) { + throw new RuntimeException(t); + } + }; + return new FunctionPointer<>( + Memory.upcall( + MethodHandles.lookup(), + trampoline, + "call", + "{trampoline-signature}", + DESCRIPTOR, + scope$), + target$); + } + }} + # vulkan has none + downcall {{ }} +} + +code funcpointer-readwrite insert=funcpointer:common,funcpointer:upcall,funcpointer:downcall { + class {{ + package {package}; + + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + @FunctionalInterface + public interface {name} { + + {common} + + {upcall} + {downcall} + } + }} +} + +# ###################################################################### # + +# values required + +# java-arg java parameter type and and name +# invoke-arg native value for invokeExact argument +# native-arg native parameter type and name +# trampoline-arg java value for upcall invocation +# layout MemoryLayout +# sig JNI signature string + +# parameterisation values +# type java type +# carrier native (jdk.foriegn) type +# length array length + +type value { + java-arg {{ {type} {name} }} + invoke-arg {{ {name} }} + + native-arg {{ {carrier} {name}$ }} + trampoline-arg {{ {name}$ }} +} + +type value-array { + java-arg {{ {type} {name} }} + invoke-arg {{ {name} }} + + native-arg {{ {carrier} {name}$ }} + trampoline-arg {{ {name}$ }} +} + +type value-array2d { + java-arg {{ {type} {name} }} + invoke-arg {{ {name} }} + + native-arg {{ {carrier} {name}$ }} + trampoline-arg {{ {name}$ }} +} + +type uint8_t,char value { + carrier {{ byte }} + type {{ byte }} + layout {{ Memory.BYTE }} +} + +type uint16_t value { + carrier {{ short }} + type {{ short }} + layout {{ Memory.SHORT }} +} + +type uint32_t,int,int32_t value { + carrier {{ int }} + type {{ int }} + layout {{ Memory.INT }} + sig {{ I }} +} + +type uint64_t,int64_t,size_t value { + carrier {{ long }} + type {{ long }} + layout {{ Memory.LONG }} + sig {{ J }} +} + +type float value { + carrier {{ float }} + type {{ float }} + layout {{ Memory.FLOAT }} +} + +type double value { + carrier {{ double }} + type {{ double }} + layout {{ Memory.DOUBLE }} +} + +# ###################################################################### # + +# type uint8_t[],char[] value-array { +# type {{ ByteArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.BYTE) }} +# typei {{ byte }} +# } + +#type uint32_t[],int32_t[] value-array { +# type {{ IntArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.INT) }} +# typei {{ int }} +#} + +# type uint64_t[] value-array { +# type {{ LongArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.LONG) }} +# typei {{ long }} +# } + +# calls float[] =-> pointer +#type float[] value-array { +# # or should it be float[] ? +# type {{ FloatArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.FLOAT) }} +# typei {{ float }} +#} + +# type struct[] inline-array { +# type {{ {baseType} }} +# layout {{ MemoryLayout.sequenceLayout({len1}, {baseType}.LAYOUT) }} +# } + +# type float[][] value-array2d { +# type {{ FloatArray }} +# typei {{ float }} +# layout {{ MemoryLayout.sequenceLayout({len1}, MemoryLayout.sequenceLayout({len2}, Memory.FLOAT)) }} +# } + +# select=len? or what? + +type pointer value { + carrier {{ MemoryAddress }} + layout {{ Memory.POINTER }} + type {{ MemoryAddress }} + sig {{ Ljdk/incubator/foreign/MemoryAddress; }} + + invoke-arg {{ Memory.address({name}) }} +} + +type void { + type {{ void }} + sig {{ V }} + java-arg {{ }} +} + +type void* pointer; + +type funcpointer pointer { + type {{ NativeSymbol }} +} + +# FIXME: clenaup, value-pointer does nothing +type value-pointer pointer { +} + +type uint8_t* value-pointer { + type ByteArray; +} + +type uint32_t*,int32_t*,int* value-pointer { + type IntArray; +} + +type uint64_t* value-pointer { + type LongArray; +} + +type float* value-pointer { + type FloatArray; +} + +type size_t* value-pointer { + type LongArray; +} + +type pointer-length pointer { +} + +type void*-length pointer-length { + type MemorySegment; +} + +type uint8_t*-length pointer-length { + type ByteArray; +} + +type uint32_t*-length,int32_t*-length pointer-length { + type IntArray; +} + +type uint32_t[] pointer-length { + type IntArray; + length {{ {len1} }} +} + +type uint64_t*-length pointer-length { + type LongArray; +} + +type float*-length pointer-length { + type FloatArray; +} + +type float[] pointer-length { + type FloatArray; + length {{ {len1} }} +} + +# special handling for strings, will fail if it isn't +type char* pointer need-frame { + type {{ String }} + + invoke-arg {{ (Addressable)Memory.copyString({name}, frame$) }} + + # null check? + trampoline-arg {{ {name}$.getUtf8String(0) }} + + # this just verifies it's a string type + length eval {{ + if ($v->{len} =~ m/null-terminated/) { + 1; + } else { + print "Warning: not sure if this is a string: $s->{name} $m->{name}\n"; + } + }} + +} + +# type XXchar**-length pointer-length accessor=value-alloc { +# type {{ String[] }} + +# java-set {{ {name}$VH.set(segment, Memory.copyStringArray({name}, alloc$)); {set-length} }} +# java-get {{ Memory.copyStringArray((MemoryAddress){name}$VH.get(segment), {length}) }} + +# set-length eval {{ +# if ($v->{len} =~ m/(.*),null-terminated/) { +# 'set'.ucfirst($1).'({name}.length)'; +# } else { +# die Dumper($v, $s); +# } +# }} +# } + +# FIXME: wrong +type Xuint32_t** pointer { + type {{ HandleArray }} + typei {{ IntArray }} +} + +type uint32_t**-length pointer { + type {{ HandleArray }} + typei {{ IntArray }} +} + +type void** pointer { + type PointerArray; +} + +type void**-length pointer-length { + type PointerArray; +} + +type handle pointer { + type {{ {baseType} }} +} + +type handle* pointer { + type {{ HandleArray<{typei}> }} + typei {{ {baseType} }} + + invoke-arg {{ Memory.address({name}) }} +} + +type handle*-length pointer-length { + type {{ HandleArray<{baseType}> }} + typei {{ {baseType} }} +} + +#type struct inline { +# type {{ {baseType} }} +# layout {{ {baseType}.LAYOUT }} +#} + +type struct* pointer trampoline-scope { + type {{ {baseType} }} + + trampoline-arg {{ {baseType}.create({name}$, scope$$) }} +} + +type struct*-length pointer-length { + type {{ {baseType} }} + typei {{ {baseType} }} +} + +type struct**-length pointer-length { + type {{ HandleArray<{baseType}> }} + typei {{ {baseType} }} +} + +type struct** pointer { +} + +# xlib +type XID,Window,VisualID uint64_t; + +type Display* handle { + type xlib.XDisplay; +} + +# xcb +type xcb_window_t uint32_t; +type xcb_visualid_t uint32_t; +type xcb_connection_t* handle { + type xcb.Connection; +} + +# special types for call overrides +type instance handle is-instance { + java-arg {{ }} + invoke-arg {{ (Addressable)self }} + native-arg {{ }} +} + +# A pointer type which is always ignored/supressed, e.g. VkAllocationCallbacks * +type struct*-ignore,void*-ignore void* { + java-arg {{ }} + invoke-arg {{ (Addressable)MemoryAddress.NULL }} + trampoline-arg {{ }} +} + +type void*-return void* { + native-result-define {{ MemoryAddress result$; }} + native-result-assign {{ result$ = (MemoryAddress) }} + java-result-return {{ return result$; }} +} + +type funcpointer-return funcpointer need-scope { + native-result-define {{ MemoryAddress result$; }} + native-result-assign {{ result$ = (MemoryAddress) }} + java-result-return {{ return NativeSymbol.ofAddress(pName, result$, scope$); }} +} + +type uint32_t-return uint32_t { + native-result-define {{ int result$; }} + native-result-assign {{ result$ = (int) }} + java-result-return {{ return result$; /* uint32_t */ }} + + trampoline-result-define {{ int result$$; }} + trampoline-result-assign {{ result$$ = (int) }} + trampoline-result-return {{ return result$$; /* uint32_t */ }} +} + +type uint64_t-return,size_t-return uint64_t { + native-result-define {{ long result$; }} + native-result-assign {{ result$ = (long) }} + java-result-return {{ return result$; /* uint64_t */ }} +} + +# for handle constructors +type handle*-output handle* need-frame need-scope { + java-arg {{ }} + invoke-arg {{ (Addressable){name} }} + + java-result {{ {baseType} }} + native-init {{ MemorySegment {name} = frame$.allocate(Memory.POINTER); }} + java-result-assign {{ {baseType} result$$ = {baseType}.create({name}.get(Memory.POINTER, 0), scope$); }} + java-result-return {{ return result$$; }} +} + +type uint32_t*-output,int*-output uint32_t* need-frame { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + + java-result {{ int }} + native-init {{ MemorySegment {name} = frame$.allocate(Memory.INT); }} + java-result-assign {{ int result$$ = {name}.getAtIndex(Memory.INT, 0); }} + java-result-return {{ return result$$; }} +} + +type uint64_t*-output uint64_t* need-frame { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + + java-result {{ long }} + native-init {{ MemorySegment {name} = frame$.allocate(Memory.LONG); }} + java-result-assign {{ long result$$ = {name}.getAtIndex(Memory.LONG, 0); }} + java-result-return {{ return result$$; }} +} + +type void*-output handle*-output need-frame { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + + java-result {{ MemoryAddress }} + native-init {{ MemorySegment {name} = frame$.allocate(Memory.POINTER); }} + java-result-assign {{ MemoryAddress result$$ = {name}.getAtIndex(Memory.POINTER, 0); }} + java-result-return {{ return result$$; }} +} + +type VkBool32*-output uint32_t*-output { + java-result {{ boolean }} + java-result-return {{ return result$$ != 0; }} +} + +# for handle constructors of dispatchable types +type dispatch*-output handle*-output { + java-result-assign {{ {baseType} result$$ = {baseType}.create({name}.get(Memory.POINTER, 0), dispatch, scope$); }} +} + +# for query and return types +type handle*-length-query handle*-length need-alloc need-scope { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = {typei}.createArray({length}, alloc$, scope$); }} + java-result-return {{ return {name}; }} +} + +type dispatch*-length-query handle*-length-query need-scope { + query-init {{ {type} {name} = {typei}.createArray({length}, alloc$, dispatch, scope$); }} +} + +type struct*-length-query struct*-length need-alloc { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = {typei}.createArray({length}, alloc$); }} + java-result-return {{ return {name}; }} +} + +type void*-length-query void*-length need-alloc { + java-arg {{ }} + invoke-arg {{ (Addressable){name} }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = alloc$.allocate({length}); }} + java-result-return {{ return {name}; }} +} + +type uint32_t*-length-query uint32_t*-length need-alloc { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = IntArray.createArray({length}, alloc$); }} + java-result-return {{ return {name}; }} +} + +type uint32_t*-querylen uint32_t* need-frame { + type {{ MemorySegment }} + java-arg {{ }} + invoke-arg {{ (Addressable){name}$ }} + + native-init {{ {type} {name}$ = alloc$.allocate(Memory.INT, 0); }} + query-init {{ long {name} = {name}$.get(Memory.INT, 0); }} +} + +type size_t*-querylen uint32_t* need-frame { + type {{ MemorySegment }} + java-arg {{ }} + invoke-arg {{ (Addressable){name}$ }} + + native-init {{ {type} {name}$ = alloc$.allocate(Memory.LONG, 0); }} + query-init {{ long {name} = {name}$.get(Memory.LONG, 0); }} +} + +type void**-output void** need-frame need-scope { + type {{ MemorySegment }} + + java-arg {{ }} + invoke-arg {{ (Addressable){name} }} + + native-init {{ MemorySegment {name} = frame$.allocate(Memory.POINTER); }} + + java-result {{ {type} }} + java-result-assign {{ {type} result$$ = MemorySegment.ofAddress({name}.get(Memory.POINTER, 0), {length}, scope$); }} + java-result-return {{ return result$$; }} +} + +type vkMapMemory-output void**-output { + length {{ size }} +} + +# some tweaks that the auto-discovery code misses/isn't worth adding to +override commands { + vkMapMemory device=type:instance ppData=type:vkMapMemory-output; + + # Don't need userData + PFN_vkDebugUtilsMessengerCallbackEXT pUserData=type:void*-ignore; + PFN_vkDebugReportCallbackEXT pUserData=type:void*-ignore; + PFN_vkDeviceMemoryReportCallbackEXT pUserData=type:void*-ignore; + + # Mapped to a NativeSymbol via 'funcpointer' type + PFN_vkVoidFunction ignore; + + # Don't need VkAllocationCallbacks, don't need these + PFN_vkAllocationFunction ignore; + PFN_vkReallocationFunction ignore; + PFN_vkFreeFunction ignore; + PFN_vkInternalAllocationNotification ignore; + PFN_vkInternalFreeNotification ignore; +} diff --git a/src/notzed.vulkan/gen/gen.make b/src/notzed.vulkan/gen/gen.make new file mode 100644 index 0000000..372ccea --- /dev/null +++ b/src/notzed.vulkan/gen/gen.make @@ -0,0 +1,18 @@ + +bin/status/notzed.vulkan.depjava: \ + bin/status/notzed.vulkan.export + +bin/status/notzed.vulkan.export: \ + $(NATIVEZ_HOME)/lib/config.pm \ + $(NATIVEZ_HOME)/lib/tokenise.pm \ + $(NATIVEZ_HOME)/lib/code.pm \ + $(NATIVEZ_HOME)/lib/api.pm \ + src/notzed.vulkan/gen/generate-vulkan \ + src/notzed.vulkan/gen/vulkan.pm \ + src/notzed.vulkan/gen/struct-types.api \ + src/notzed.vulkan/gen/command-types.api + +bin/status/notzed.vulkan.export: + NATIVEZ_HOME=$(NATIVEZ_HOME) src/notzed.vulkan/gen/generate-vulkan -t vulkan -d bin/gen/notzed.vulkan/classes + mkdir -p $(@D) + touch $@ diff --git a/src/notzed.vulkan/gen/generate-vulkan b/src/notzed.vulkan/gen/generate-vulkan new file mode 100755 index 0000000..89d8876 --- /dev/null +++ b/src/notzed.vulkan/gen/generate-vulkan @@ -0,0 +1,1445 @@ +#!/usr/bin/perl + +use Data::Dumper; + +use File::Path qw(make_path); +use File::Basename; +use Time::HiRes qw(clock_gettime CLOCK_REALTIME); + +use strict; + +use Carp 'verbose'; +use FindBin; +use lib "$FindBin::Bin", 'bin/linux-amd64/lib', "$ENV{NATIVEZ_HOME}/lib"; + +use vulkan; +use config; +use code; + +$SIG{__DIE__} = sub { Carp::confess( @_ ) }; +$SIG{'INT'} = sub { Carp::confess() }; +$Data::Dumper::Indent = 1; + +# ###################################################################### # + +my $enumInfo = { + 'uint32_t' => { + type => 'int' + }, + 'uint64_t' => { + type => 'long', + suffix => 'L' + }, + 'float' => { + type => 'float', + suffix => 'f' + }, + 'const char *' => { + type => 'String' + }, +}; + +my %primitiveMap = ( + 'char' => 'byte', + 'uint8_t' => 'byte', + 'uint16_t' => 'short', + 'int32_t' => 'int', + 'uint32_t' => 'int', + 'int' => 'int', + 'int64_t' => 'long', + 'uint64_t' => 'long', + 'size_t' => 'long', + 'float' => 'float', + 'double' => 'double', + 'VkFlags' => 'int', + 'VkFlags64' => 'long' +); + +# ###################################################################### # +my $now = clock_gettime(CLOCK_REALTIME); +my $next; +my $sys = {}; + +$sys->{output} = 'bin/gen/notzed.vulkan/classes'; +$sys->{package} = 'vulkan'; +$sys->{verbose} = 0; + +my $vk = new vulkan($sys); + +printf "%9.6f load registry\n", (($next = clock_gettime(CLOCK_REALTIME)) - $now); $now = $next; + +my $api = $vk->buildFeatures( + [ 'VK_VERSION_1_0', 'VK_VERSION_1_1', 'VK_VERSION_1_2', 'VK_VERSION_1_3' ], + [ 'xlib', + #'wayland', + 'xcb' ]); + +printf "%9.6f build api view\n", (($next = clock_gettime(CLOCK_REALTIME)) - $now); $now = $next; + +my $structTypes = loadTypes($api, 'struct-types.api'); +my $commandTypes = loadTypes($api, 'command-types.api'); + +printf "%9.6f load templates\n", (($next = clock_gettime(CLOCK_REALTIME)) - $now); $now = $next; + +analyseTypes($vk, $api); + +printf "%9.6f analyse types\n", (($next = clock_gettime(CLOCK_REALTIME)) - $now); $now = $next; + +# Use some heuristics to create overrides for improving the api + +# - object constructor functions return values rather take pointer* values +# - array query functions perform the query and return the array +# - extension functions use a different invocation mechanism +# - drop any 'ignore' types from argument lists + +# - [attempt to] determine which types need to be read/write/arrays + +# Scan structs +my %defaultTemplate; + +foreach my $s (values %{$api->{types}}) { + my $overrides = $structTypes->{overrides}; + my $types = $structTypes->{types}; + + next if (defined($overrides->{$s->{name}})); + + foreach my $m (@{$s->{items}}) { + my $nstar = $m->{deref} =~ tr/*/*/; + + if ($m->{deref} eq 'struct*-length') { + $defaultTemplate{$m->{baseType}}->{array} = 1; + } elsif ($m->{deref} eq 'struct[]') { + $defaultTemplate{$m->{baseType}}->{array} = 1; + } + } + + $defaultTemplate{$s->{name}}->{name} = 'struct-readonly' if ($s->{returnedonly} eq 'true'); +} + +# build default overrides for commands +foreach my $s (values %{$api->{commands}}) { + my $overrides = $commandTypes->{overrides}; + my $types = $commandTypes->{types}; + my $first = $s->{items}->[0]; + my $last = $s->{items}->[$#{$s->{items}}]; + my $llast = $s->{items}->[$#{$s->{items}}-1]; + my $result = $s->{proto}; + my $index = $s->{index}; + + # check type updates anyway + foreach my $m (@{$s->{items}}) { + if ($m->{deref} eq 'struct*-length') { + $defaultTemplate{$m->{baseType}}->{name} = 'struct-readwrite' if !($m->{fullType} =~ m/const/n) && $api->{types}->{$m->{baseType}}->{returnedonly} ne 'true'; + $defaultTemplate{$m->{baseType}}->{array} = 1; + } elsif ($m->{deref} eq 'struct*') { + $defaultTemplate{$m->{baseType}}->{name} = 'struct-readwrite' if !($m->{fullType} =~ m/const/n) && $api->{types}->{$m->{baseType}}->{returnedonly} ne 'true'; + } + } + + next if (defined($overrides->{$s->{name}})); + + my $override = {}; + + # force handles to be instance types + if ($first->{deref} eq 'handle') { + $override->{$first->{name}}->{type} = 'instance'; + } + + # extension function default template + if (defined($s->{extensions})) { + $override->{template} = 'method-extension'; + } + + # Constructor + if ($last->{deref} eq 'handle*') { + if (!$last->{len}) { + my $t = $api->{handles}->{$last->{baseType}}; + print "constructor: $s->{name}\n" if $sys->{verbose}; + $override->{$last->{name}}->{type} = + $t->{type} eq 'VK_DEFINE_HANDLE' && $t->{name} ne 'VkInstance' ? + 'dispatch*-output' : 'handle*-output'; + } elsif ($index->{$last->{len}}) { + print "constructor?: $s->{name}\n";# if $sys->{verbose}; + die; + } else { + print "allocate-constructor?: $s->{name} $last->{len}\n";# if $sys->{verbose}; + } + } + + # turn array-query functions into auto-allocate/return types + # ones we care about with output + # handle*-length + # struct*-length + # uint32_t*-length + # void*-length + + if ($last->{deref} =~ m/-length$/ && (my $len = $index->{$last->{len}})) { + if (index($len->{deref}, '*') >= 0) { + my $protoa = "$result->{fullType} $s->{name}(" + .join(', ', map { "$_->{fullType}" } @{$s->{items}}) + .")"; + print "array-constructor: $protoa\n" if $sys->{verbose} > 1; + + my $otype = ($last->{deref} eq 'handle*-length' && $api->{handles}->{$last->{baseType}}->{type} eq 'VK_DEFINE_HANDLE') + ? 'dispatch*-length-query' : $last->{deref}.'-query'; + my $ltype = $len->{deref}.'-querylen'; + + die "no template $otype" if !defined($commandTypes->{types}->{$otype}); + die "no template $ltype" if !defined($commandTypes->{types}->{$ltype}); + + $override->{template} = defined($s->{extensions}) ? 'method-extension-query' : 'method-query'; + $override->{$last->{name}}->{type} = $otype; + $override->{$len->{name}}->{type} = $ltype; + } + } + + # A whole bunch of query functions + if ($s->{name} =~ m/^vkGet/ && !($last->{fullType} =~ m/const/n) && !(defined($override->{$last->{name}}) && defined($override->{$last->{name}}->{type}))) { + if ($last->{fullType} eq 'VkBool32 *') { + print "output: [$last->{fullType}] [$last->{deref}] $s->{name}\n" if $sys->{verbose}; + $override->{$last->{name}}->{type} = 'VkBool32*-output'; + } elsif ($last->{deref} =~ m/^(int|uint32_t|uint64_t|void)\*$/on) { + print "output: [$last->{fullType}] [$last->{deref}] $s->{name}\n" if $sys->{verbose}; + $override->{$last->{name}}->{type} = $last->{deref}."-output"; + } else { + print "output? [$last->{fullType}] [$last->{deref}] $s->{name}\n" if $sys->{verbose}; + } + } + + # other per-item things + foreach my $m (@{$s->{items}}) { + my $so = $structTypes->{overrides}->{$m->{baseType}}; + + if ($so->{ignore}) { + die "No type '$m->{deref}-ignore'" if !defined($types->{$m->{deref}.'-ignore'}); + $override->{$m->{name}}->{type} = $m->{deref}.'-ignore'; + } + } + + $overrides->{$s->{name}} = $override if %{$override}; +} + +{ + my $overrides = $structTypes->{overrides}; + my $templates = $structTypes->{templates}; + + foreach my $k (keys %defaultTemplate) { + next if defined($overrides->{$k}) && defined($overrides->{$k}->{template}); + + my $t = $defaultTemplate{$k}; + my $name = $t->{name} || "struct-writeonly"; + + $name .= "-array" if $t->{array}; + + print "$name: $k\n" if $sys->{verbose} > 1; + die "No override $k $name" if !$templates->{$name}; + $overrides->{$k}->{template} = $name; + } +} + +printf "%9.6f guess overrides\n", (($next = clock_gettime(CLOCK_REALTIME)) - $now); $now = $next; + +if (0) { + analyseAccessors($api); + exit 1; +} + +if (0) { + open(my $f, '>', 'types.pm'); + print $f Dumper($commandTypes, $structTypes); + close $f; +} + +if (0) { + open(my $f, '>', 'api.pm'); + print $f Dumper($api); + close $f; + print "Dumped api.pm\n"; + exit 1; +} + +if (0) { + open(my $f, '>', 'vk.pm'); + print $f Dumper($vk); + close $f; + die; +} + +exportVulkan($vk, $api, $structTypes); + +# dump out the extension function-pointer tables +{ + my $f = openOutput($sys, 'DispatchInstance'); + my $template = $structTypes->{templates}->{dispatch}; + my @init = (); + my @fieldInit = (); + + foreach my $k (sort keys %{$api->{commands}}) { + my $c = $api->{commands}->{$k}; + + next if !defined($c->{extensions}); + + push @fieldInit, code::formatTemplate($template->{'field-init'}, $c); + push @init, code::formatTemplate($template->{'init'}, $c); + + } + + my $vars = { + package => 'vulkan', + Name => 'DispatchInstance', + init => join("\n\t\t", @init), + 'field-init' => join("\n\t", @fieldInit), + }; + print $f code::formatTemplateStream($template->{class}, $vars); + + closeOutput($sys, 'DispatchInstance', $f); +} + +# structs and unions +foreach my $k (sort keys %{$api->{types}}) { + my $s = $api->{data}->{$k}; + + die if !defined $s; + next if $s->{alias}; + + my $override = $structTypes->{overrides}->{$s->{name}}; + + next if $override->{ignore}; + + my $f = openOutput($sys, $s->{Name}); + print $f formatStruct($vk, $api, $s); + closeOutput($sys, $s->{Name}, $f); +} + +# handles +foreach my $k (sort keys %{$api->{handles}}) { + my $s = $api->{data}->{$k}; + + die if !defined $s; + next if $s->{alias}; + + my $f = openOutput($sys, $s->{name}); + print $f formatHandle($vk, $api, $s); + closeOutput($sys, $s->{name}, $f); +} + +# upcalls +foreach my $k (sort keys %{$api->{funcpointers}}) { + my $s = $api->{data}->{$k}; + + die if !defined $s; + next if $s->{alias}; + + my $override = $commandTypes->{overrides}->{$s->{name}}; + + next if $override->{ignore}; + + my $f = openOutput($sys, $s->{name}); + print $f formatFunctionPointer($vk, $api, $s); + closeOutput($sys, $s->{name}, $f); +} + +printf "%9.6f export classes\n", (($next = clock_gettime(CLOCK_REALTIME)) - $now); $now = $next; + +exit 0; + +# ###################################################################### # + +sub formatStructLayout { + my $types = shift; + my $s = shift; + my $offset = 0; + my @fields = (); + + # This doens't need to worry about overrides + + if ($s->{category} eq 'struct') { + foreach my $m (@{$s->{items}}) { + my $type = $types->{$m->{deref}}; + my $diff = $m->{bitOffset} - $offset; + + push @fields, "MemoryLayout.paddingLayout($diff)" if $diff; + + if ($type) { + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v).".withName(\"$m->{name}\") /* $m->{deref} $m->{fullType} */"; + $offset = $m->{bitOffset} + $m->{bitSize}; + } else { + push @fields, "/* Missing: $m->{deref} $m->{name} */"; + } + } + } else { + foreach my $m (@{$s->{items}}) { + my $type = $types->{$m->{deref}}; + + if ($type) { + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v).".withName(\"$m->{name}\") /* $m->{deref} $m->{fullType} */"; + $offset = ($m->{bitOffset} + $m->{bitSize}) if ($m->{bitOffset} + $m->{bitSize}) > $offset; + } else { + push @fields, "/* Missing: $m->{deref} $m->{name} */"; + } + } + } + + my $diff = $s->{bitSize} - $offset; + + push @fields, "MemoryLayout.paddingLayout($diff)" if $diff; + + return "MemoryLayout.".$s->{category}."Layout(\n\t\t".join(",\n\t\t", @fields).").withName(\"$s->{name}\")"; +} + +# Experiment: see if sharing varhandles would be of any benefit +# - analyse all pointer/int fields for cross-overs +# for vulkan 1.3: +# Total fields: 3370 +# Unique fields: 368 +# so very significant savings possible +sub analyseAccessors { + my $api = shift; + my %handles; + my $total; + my %lookat; + + map { $lookat{$_} = 1 } qw(Memory.POINTER Memory.BYTE Memory.SHORT Memory.INT Memory.LONG Memory.FLOAT Memory.DOUBLE); + + foreach my $s (values %{$api->{types}}) { + my $override = $structTypes->{overrides}->{$s->{name}}; + + next if $s->{alias}; + next if $override->{ignore}; + + foreach my $m (@{$s->{items}}) { + my $deref = $m->{deref}; + my $type = $structTypes->{types}->{$deref}; + my $v = buildVars($s, $m, $type); + my $layout = code::formatTemplate($v->{layout}, $v); + my $index = $m->{bitOffset} / 8; + + die if ($m->{bitoffset} & ($m->{bitSize}-1)); + + if ($lookat{$layout}) { + $index = $m->{bitOffset} / $m->{bitSize}; + } + + my $k = sprintf "$layout \@ %03d", $index; + + $handles{$k}++; + $total++; + } + } + + print "Other unique handles =\n"; + foreach my $h (sort keys %handles) { + $h =~ m/^(.*) \@/; + my $x = $lookat{$1}; + if (!$lookat{$1}) { + printf " %5d $h\n", $handles{$h}; + } + } + print "Unique handles of interest =\n"; + foreach my $h (sort keys %handles) { + $h =~ m/^(.*) \@/; + if ($lookat{$1}) { + printf " %5d $h\n", $handles{$h}; + } + } + my @keys = keys %handles; + print "Total fields: $total\n"; + print "Unique fields: $#keys\n"; +} + +# ###################################################################### # + +sub loadTypes { + my $api = shift; + my $file = shift; + my $config = new config({ includes=>[ $FindBin::Bin ] }, "$FindBin::Bin/$file"); + + my $types = {}; + my $templates = {}; + my $overrides = {}; + + foreach my $t (@{$config->{objects}}) { + if ($t->{type} eq 'type') { + my $nopts = $#{$t->{options}}; + my $type = {}; + + die ("No prototype provided/found for empty type ".Dumper($t)) if ($#{$t->{items}} < 0 && $#{$t->{options}} < 0); + + # Check options / load inherited values + foreach my $o (@{$t->{options}}) { + if ($o =~ m/^accessor=(.*)$/o) { + die "No template $1" if !defined($templates->{$1}); + $type->{accessor} = $templates->{$1}; + } elsif ($o eq 'need-frame') { + $type->{'need-frame'} = 1; + } elsif ($o eq 'need-scope') { + $type->{'need-scope'} = 1; + } elsif ($o eq 'trampoline-scope') { + $type->{'trampoline-scope'} = 1; + } elsif ($o eq 'need-alloc') { + $type->{'need-alloc'} = 1; + } elsif ($o eq 'is-instance') { + $type->{'is-instance'} = 1; + } elsif (defined($types->{$o})) { + $type = { %$type, %{$types->{$o}} }; + } else { + die "Unknown option '$o' in ".Dumper($t); + } + } + + # Load the fields + foreach my $s (@{$t->{items}}) { + if (defined $s->{literal}) { + $type->{$s->{match}} = $s->{literal}; + } elsif ($#{$s->{options}} >= 0) { + $type->{$s->{match}} = $s->{options}->[$#{$s->{options}}]; + } else { + $type->{$s->{match}} = 0; + } + + $type->{"$s->{match}:eval"} = 1 if config::optionFlag('eval', $s); + } + + # Write to all aliases + foreach my $k (split /,/,$t->{name}) { + $types->{$k} = $type; + } + } elsif ($t->{type} eq 'code') { + my $code = { + insert => {}, + }; + + foreach my $o (@{$t->{options}}) { + if ($o =~ m/insert=(.*)/) { + foreach my $x (split /,/,$1) { + if ($x =~ m/(.*):(.*)/) { + die "$x $t->{name} ".Dumper($templates->{$1}) if !defined $templates->{$1}->{$2}; + $code->{insert}->{$2} = $templates->{$1}->{$2}; + } + } + } elsif ($o =~ m/^fields=(.*)/) { + $code->{fields} = $1; + } elsif (defined($templates->{$o})) { + $code = { %$code, %{$templates->{$o}} }; + } else { + die ("Unknown option '$o'"); + } + } + + foreach my $s (@{$t->{items}}) { + if (defined $s->{literal}) { + my $t = $s->{literal}; + + $t =~ s/^\t//gm; + $code->{$s->{match}} = $t; + } else { + delete $code->{$s->{match}}; + } + + $code->{"$s->{match}:eval"} = 1 if config::optionFlag('eval', $s); + } + + $templates->{$t->{name}} = $code; + } elsif ($t->{type} eq 'override') { + foreach my $s (@{$t->{items}}) { + my $c = { }; + foreach my $o (@{$s->{options}}) { + if ($o =~ m/^(.*)=type:(.*)/) { + die "No such type $s->{match} $2\n" if !defined $types->{$2}; + $c->{$1}->{type} = $2; + } elsif ($o =~ m/^(.*)=accessor:(.*)/) { + die "No accessor template $o" if !defined($templates->{$2}); + $c->{$1}->{accessor} = $templates->{$2}; + } elsif ($o =~ m/^template=(.*)/) { + die "No template $o" if !defined($templates->{$1}); + $c->{template} = $1; + } elsif ($o eq 'expand') { + $c->{expand} = 1; + } elsif ($o eq 'ignore') { + $c->{ignore} = 1; + } elsif ($o eq 'append') { + $c->{append} = $s->{literal}; + } + } + $overrides->{$s->{match}} = $c; + } + } + } + + # templates should probably just go in one + { + types => $types, + templates => $templates, + overrides => $overrides, + }; +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +# generate Vulkan.java +sub exportVulkan { + my $vk = shift; + my $api = shift; + my $structTypes = shift; + my $seen = {}; + my $template = $structTypes->{templates}->{Vulkan}; + + my @constants = (); + my @defines = (); + + # create error code->string mappings + { + my $s = $api->{enums}->{VkResult}; + my @infos; + + foreach my $m (sort { $a->{value} <=> $b->{value} } @{$s->{items}}) { + next if ($m->{alias}); + push @infos, "new ErrorInfo($m->{value}, \"$m->{name}\")"; + } + push @constants, "static final ErrorInfo errors[] = {"; + push @constants, join(",\n\t\t", @infos); + push @constants, "};\n"; + } + + # special case for api constants + { + my $s = $api->{data}->{'API Constants'}; + + push @constants, "// API Constants\n"; + + foreach my $m (@{$s->{items}}) { + next if defined($m->{alias}); + next if $seen->{$m->{name}}++; + + my $i = $enumInfo->{$m->{type}}; + my $v = $m->{value}; + + # convert to java + $v =~ s/[()ULF]+//gon if (!($v =~ m/^"/)); + + push @constants,"public final static $i->{type} $m->{name} = $v$i->{suffix};"; + } + } + + # include any defines we have a template for + foreach my $k (sort keys %{$api->{defines}}) { + if ($template->{$k}) { + push @defines, $template->{$k}; + } else { + print "Ignored: $k\n"; + } + } + foreach my $k (sort keys %{$api->{enums}}) { + my $s = $api->{data}->{$k}; + my $type = $s->{fullType} ? $s->{fullType} : 'VkFlags'; + my $i = $enumInfo->{$vk->{data}->{$type}->{type}}; + my $ext; + + next if $s->{alias}; + + push @constants, ""; + push @constants, "// $s->{name} $type"; + + my $index = {}; + map { $index->{$_->{name}} = $_ } (@{$s->{items}}); + + foreach my $m (@{$s->{items}}) { + next if defined($m->{alias}); + next if $seen->{$m->{name}}++; + + my $v = $m->{value}; + + $v = 1<<$m->{bitpos} if !defined($v) && defined($m->{bitpos}); + + die Dumper("Ca't work out value", $m, $s) if !defined($v); + + if (defined($m->{extension}) && $m->{extension} ne $ext) { + $ext = $m->{extension}; + push @constants, "// from $ext"; + } + + push @constants, "public final static $i->{type} $m->{name} = $v$i->{suffix};"; + } + $ext = ""; + foreach my $m (@{$s->{items}}) { + next if !defined($m->{alias}); + next if $seen->{$m->{name}}++; + next if !defined($index->{$m->{alias}}); + + if (defined($m->{extension}) && $m->{extension} ne $ext) { + $ext = $m->{extension}; + push @constants, "// from $ext"; + } + + push @constants, "public final static $i->{type} $m->{name} = $m->{alias};"; + } + } + + my $v = { + package => $sys->{package}, + defines => join("\n\t", @defines), + constants => join("\n\t", @constants), + }; + + my $f = openOutput($sys, 'Vulkan'); + + print $f code::formatTemplateStream($template->{class}, $v); + + closeOutput($sys, 'Vulkan', $f); +} + +# ###################################################################### # +# class.name to class/name.java +sub classToPath { + my $sys = shift; + my $name = shift; + + $name = $sys->{package}.'.'.$name; + $name =~ s@\.@/@g; + $name = $sys->{output}.'/'.$name.'.java'; + $name; +} + +sub closeOutput { + my $sys = shift; + my $name = shift; + my $f = shift; + my $path = classToPath($sys, $name); + + close($f) || die; + rename($path.'~', $path) || die ("rename failed: $!"); +} + +sub openOutput { + my $sys = shift; + my $name = shift; + + my $path = classToPath($sys, $name); + my $dir = dirname($path); + + make_path($dir) if (!-d $dir); + + open(my $f, ">", $path.'~') || die ("Cannot open '$path' for writing"); + print "writing '$path'\n" if $sys->{verbose} > 0; + $f; +} + + +# Calculate canonical derefernce types for each field and parameter +sub analyseFields { + my $vk = shift; + my $api = shift; + my $seen = shift; + my $s = shift; + + # what about const? + # FIXME: bitfields + + my $index = {}; + + map { $index->{$_->{name}} = $_ } @_; + + $s->{index} = $index; + + foreach my $m (@_) { + my $t = $api->{data}->{$m->{baseType}}; + my $nstar = $m->{fullType} =~ tr/*/*/; + my $type; + my $array = ''; + + # Check array sizes + if ($m->{fullType} =~ m/\[(.*)\]\[(.*)\]$/o) { + $array = '[][]'; + $m->{len1} = $1; + $m->{len2} = $2; + } elsif ($m->{fullType} =~ m/\[(.*)\]$/o) { + my $isize = $1; + my $size; + if ($isize =~ m/^\d+$/n) { + $size = $isize; + } else { + $size = $vk->{data}->{'API Constants'}->{index}->{$isize}->{value}; + } + $array = '[]'; + $m->{len1} = $size; + } elsif ($m->{fullType} =~ m/[\[\]]/on) { + die Dumper($m) + } + + if (!defined($t)) { + # will be primitive or external + $type = $m->{baseType} if ($nstar == 0); + $type = "$m->{baseType}*" if ($nstar == 1); + $type = "$m->{baseType}**" if ($nstar == 2); + die if $nstar > 2; + } else { + # unmap aliases + while ($t->{alias}) { + print "alias $t->{name} -> $t->{alias}\n"; + $t = $api->{data}->{$t->{alias}}; + die if !defined $t; + $m->{baseType} = $t->{name}; + } + + if ($t->{category} eq 'enum') { + $t = $vk->{data}->{$t->{fullType}}; + $type = $t->{type} if ($nstar == 0); + $type = "$t->{type}*" if ($nstar == 1); + die if $nstar > 1; + } elsif ($t->{category} =~ m/struct|union/on) { + $m->{type} = $t->{name}; + $type = 'struct' if ($nstar == 0); + $type = 'struct*' if ($nstar == 1); + $type = 'struct**' if ($nstar == 2); + die if $nstar > 2; + } elsif ($t->{category} eq 'handle') { + $m->{type} = $t->{name}; + #if ($t->{type} eq 'VK_DEFINE_HANDLE') { + # $type = 'dhandle' if ($nstar == 0); + # $type = 'dhandle*' if ($nstar == 1); + #} else { + $type = 'handle' if ($nstar == 0); + $type = 'handle*' if ($nstar == 1); + #} + die if $nstar > 1; + } elsif ($t->{category} eq 'basetype') { + # ?? + $type = $t->{type} if ($nstar == 0); + $type = "$t->{type}*" if ($nstar == 1); + die Dumper($m, $t) if $nstar > 1; + } elsif ($t->{category} eq 'funcpointer') { + $m->{type} = $t->{name}; + $type = "funcpointer" if ($nstar == 0); + die if $nstar > 0; + } else { + die Dumper($m, $t); + } + } + + if ($s->{category} eq 'struct') { + my $mover = $structTypes->{overrides}->{$m->{baseType}}; + $type .= "-expand" if $nstar == 0 && $mover->{expand}; + } elsif ($s->{category} eq 'union') { + # VkClearValue - handled manually + } + + # an array type with a length + if ($nstar > 0 && $m->{len}) { + if ($s->{category} =~ m/struct|union/on) { + if ($m->{altlen}) { + if ($m->{altlen} =~ m/^(.*)(VK_UUID_SIZE)(.*)$/) { + $m->{length} = $1.'Vulkan.'.$2.$3; + } elsif ($m->{altlen} =~ m/^([^a-zA-Z_]*)([a-zA-Z_]+)(.*)$/) { + my $len = $index->{$2}; + if (defined $len) { + $m->{length} = $1.'get'.ucfirst($2).'()'.$3; + } + } else { + die "Unhandled len/altlen: ".Dumper($m); + } + } elsif ($m->{len} =~ m/(.*),null-terminated/) { + my $len = $index->{$1}; + if (defined $len) { + $m->{length} = "get$len->{Name}()"; + } + } elsif ($m->{len} eq 'null-terminated') { + # ignore + } elsif ($m->{len} =~ m/^(.*),(\d+)$/) { + # ignore? + } else { + my $len = $index->{$m->{len}}; + if (defined $len) { + my $cast = ($len->{fullType} eq 'uint32_t') ? '(int)' : ''; + + die "Not simple type" if ($len->{fullType} ne $len->{baseType}); + $m->{length} = "get$len->{Name}()"; + } else { + die "what?".Dumper($m); + } + } + } elsif ($s->{category} eq 'command') { + if ($m->{altlen}) { + die; + } else { + $m->{length} = $m->{len} if $index->{$m->{len}}; + $index->{$m->{len}}->{lengthfor} = $m->{name} if $index->{$m->{len}}; + } + } else { + die Dumper($s); + } + $type = $type.'-length' if $m->{length}; + } + + $seen->{$m->{fullType}} = $type.$array; + $m->{deref} = $type.$array; + + # Calculate name, with some fixup hacks + my $name = $m->{name}; + #if ($s->{type} =~ m/struct|union/on) + { + # Strip leading 'p' for pointers + if ($name eq 'ppGeometries') { # && $s->{name} eq 'VkAccelerationStructureBuildGeometryInfoKHR') { + $name = 'PGeometries'; + } elsif ($nstar > 0 && $name =~ m/^p{$nstar}/) { + my $strip = $nstar; + + if ($t->{category} eq 'handle' && $type ne 'handle*-length') { + $strip -= 1; + } + + $name = substr $name, $strip; + } + + $name =~ s/^pfn//o if $type eq 'funcpointer'; + # CamelCase + $name =~ s/(?:^|_)(.)/\U$1/og; + } + $m->{Name} = $name; + } +} + +sub analyseTypes { + my $vk = shift; + my $api = shift; + my $seen = {}; + + foreach my $s (grep { $_->{items} } values %{$api->{types}}) { + analyseFields($vk, $api, $seen, $s, @{$s->{items}}); + + my $name = $s->{name}; + $name =~ s/(?:^|_)(.)/\U$1/og; + $s->{Name} = $name; + + my $first = $s->{items}->[0]; + my $second = $s->{items}->[1]; + + if ($first->{name} eq 'sType' && $second && $second->{name} eq 'pNext') { + $first->{'no-setall'} = 1; + $second->{'no-setall'} = 1; + print "typed: $s->{name}\n" if $sys->{verbose} > 1; + } else { + print "untyped: $s->{name}\n" if $sys->{verbose} > 1; + } + } + + foreach my $c (values %{$api->{funcpointers}}) { + analyseFields($vk, $api, $seen, $c, $c->{proto}, @{$c->{items}}); + } + + foreach my $c (values %{$api->{commands}}) { + $c->{proto}->{name} = 'result$'; + $c->{proto}->{Name} = 'result$'; + analyseFields($vk, $api, $seen, $c, $c->{proto}, @{$c->{items}}); + + # collect all member functions on handles + my $first = $c->{items}->[0]; + my $last = $c->{items}->[$#{$c->{items}}]; + if ($first->{deref} eq 'handle') { + my $t = $api->{handles}->{$first->{baseType}}; + while ($t->{alias}) { + $t = $api->{handles}->{$t->{alias}}; + } + die "No handle found ".Dumper($c) if !defined $t; + push @{$t->{commands}}, $c->{name}; + } elsif ($c->{name} =~ m/vkEnumerateInstance|vkCreateInstance/) { + push @{$api->{handles}->{'VkInstance'}->{commands}}, $c->{name}; + } else { + die "No owner for call ".Dumper($c); + } + } + + if ($sys->{verbose}) { + print "Unique Types:\n"; + my $base = {}; + map { $base->{$_} = 1 } values %$seen; + + foreach my $k (sort keys %$base) { + print " $k\n"; + } + } +} + +# what else? +# vk? api? +# this way-over-evaluates, probably only call once on every field and member instead +sub buildVars { + my $s = shift; + my $m = shift; + my $type = shift; + my $v = { %{$m} }; + + foreach my $k (grep { index($_, ':') == -1 } keys %$type) { + my $t = $type->{$k}; + + if ($type->{"$k:eval"}) { + $v->{$k} = $t; + die "Eval failed: $! $@: ".Dumper($m, $type) if !defined($v->{$k}); + } elsif ($t) { + $v->{$k} = $t; + } + } + + $v; +} + +sub formatStructLayout { + my $types = shift; + my $s = shift; + my $offset = 0; + my @fields = (); + + # This doens't need to worry about overrides + + if ($s->{category} eq 'struct') { + foreach my $m (@{$s->{items}}) { + my $type = $types->{$m->{deref}}; + my $diff = $m->{bitOffset} - $offset; + + push @fields, "MemoryLayout.paddingLayout($diff)" if $diff; + + if ($type) { + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v).".withName(\"$m->{name}\") /* $m->{deref} $m->{fullType} */"; + $offset = $m->{bitOffset} + $m->{bitSize}; + } else { + push @fields, "/* Missing: $m->{deref} $m->{name} */"; + } + } + } else { + foreach my $m (@{$s->{items}}) { + my $type = $types->{$m->{deref}}; + + if ($type) { + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v).".withName(\"$m->{name}\") /* $m->{deref} $m->{fullType} */"; + $offset = ($m->{bitOffset} + $m->{bitSize}) if ($m->{bitOffset} + $m->{bitSize}) > $offset; + } else { + push @fields, "/* Missing: $m->{deref} $m->{name} */"; + } + } + } + + my $diff = $s->{bitSize} - $offset; + + push @fields, "MemoryLayout.paddingLayout($diff)" if $diff; + + return "MemoryLayout.".$s->{category}."Layout(\n\t\t".join(",\n\t\t", @fields).").withName(\"$s->{name}\")"; +} + +sub formatAccessorIf { + my $s = shift; + my $m = shift; + my $info = shift; + my $type = shift; + my $field = shift; + my $v = shift; + my $accessor = $type->{accessor}; + my $template = $accessor->{$field}; + + if ($template) { + $template = eval($template) if ($accessor->{"$field:eval"}); + + die "Error executing template $field $accessor->{$field}: $@" if (!defined($template)); + + push @{$info->{$field}}, code::formatTemplate($template, $v) if $template; + } +} + +sub formatStruct { + my $vk = shift; + my $api = shift; + my $s = shift; + my $templates = $structTypes->{templates}; + my $types = $structTypes->{types}; + my $overrides = $structTypes->{overrides}; + + my $override = $overrides->{$s->{name}}; + my $tempname = $override->{template} ? $override->{template} : 'struct-writeonly'; + my $template = $templates->{$tempname}; + my @fields = split(/,/, $template->{fields}); + my $info; + + map { $info->{$_} = [] } @fields; + + my $setall = defined $info->{'java-setall'}; + + # unions need multiple constructors + if ($setall) { + if ($s->{category} eq 'struct') { + $info->{create}->{create} = { + 'setall-arg' => [], + setall => [], + setallat => [], + }; + } elsif ($s->{category} eq 'union') { + foreach my $m (@{$s->{items}}) { + $info->{create}->{"create$m->{Name}"} = { + 'setall-arg' => [], + setall => [], + setallat => [], + }; + } + } else { + die; + } + } + + my $needAlloc = 0; + + # Collect all fields + foreach my $m (@{$s->{items}}) { + my $nstar = $m->{deref} =~ tr/*/*/; + my $mover = $override->{$m->{name}}; + my $deref = defined($mover->{type}) ? $mover->{type} : $m->{deref}; + my $type = $types->{$deref}; + + die "No type $deref ".Dumper($m, $s) if !$type; + die Dumper($mover, $type) if $mover->{accessor}; + + if ($type->{accessor}) { + my $v = buildVars($s, $m, $type); + my @todump; + + if ($m->{values}) { + @todump = qw(init initat init-array set setat); + } else { + @todump = qw(get getat set setat getorset getorsetat); + + if ($setall && !$m->{'no-setall'}) { + my $create = $s->{category} eq 'struct' ? $info->{create}->{create} : $info->{create}->{"create$m->{Name}"}; + formatAccessorIf($s, $m, $create, $type, 'setall-arg', $v); + formatAccessorIf($s, $m, $create, $type, 'setall', $v); + formatAccessorIf($s, $m, $create, $type, 'setallat', $v); + + $needAlloc = 1 if $type->{'need-alloc'}; + } + } + + push @{$info->{handle}}, code::formatTemplate($v->{handle}, $v)." /* $m->{name} $m->{fullType} ($m->{deref}) */" if $info->{handle} && $v->{handle}; + push @{$info->{handleat}}, code::formatTemplate($v->{handleat}, $v) if $info->{handleat} && $v->{handleat}; + + foreach my $field (@todump) { + if ($info->{$field} && $type->{accessor}->{$field}) { + my $t = $type->{accessor}->{$field}; + + $t = eval $t if $type->{accessor}->{"$field:eval"}; + die "$type->{accessor}->{$field}\n---\n$@" if !defined($t); + + push @{$info->{$field}}, code::formatTemplate($t, $v) if $t; + } + } + } + } + + # create constructors + my $v = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + layout => formatStructLayout($types, $s), + append => defined($override->{append}) ? $override->{append} : '', + }; + + foreach my $field (@fields) { + $v->{$field} = join("\n", @{$info->{$field}}); + } + + # build sub-components using the full $v + my @createAll = (); + foreach my $k (keys %{$template->{insert}}) { + my $t = $template->{insert}->{$k}; + + die if (!defined($t)); + + if ($k eq 'create-all' || $k eq 'set-all' || $k eq 'setat-all') { + if ($setall) { + foreach my $kk (keys %{$info->{create}}) { + my $create = $info->{create}->{$kk}; + + if ($#{$create->{'setall-arg'}} >= 0) { + my $v = { + create => $kk, + set => $kk =~ s/create/set/r, + Name => $s->{Name}, + 'java-setall-arguments' => join (', ', @{$create->{'setall-arg'}}), + 'java-setall' => join ("\n\t\t", @{$create->{setall}}), + 'java-setallat' => join ("\n\t\t", @{$create->{setallat}}), + }; + push @createAll, code::formatTemplateStream($t, $v); + } + } + } + } else { + $v->{$k} = code::formatTemplate($t, $v); + } + } + $v->{'create-all'} = join("\n", @createAll) if $setall; + + #die Dumper($info, $v) if $s->{name} eq 'VkRect2D'; + + join("\n", map { '// '.$_ } split(/\n/,Dumper($s)))."\n". + code::formatTemplateStream($template->{class}, $v); +} + +# TODO: the template here could be mapped from types.api perhaps? +# also same for the various fields, init/getset/etc. +sub formatHandle { + my $vk = shift; + my $api = shift; + my $s = shift; + my $templates = $structTypes->{templates}; + my $overrides = $structTypes->{overrides}; + my $override = $overrides->{$s->{name}}; + my $tempname = $override->{template} ? $override->{template} : 'handle'; + my $template = $templates->{$tempname}; + + my $info = { + init => [], + commands => [], + }; + + if (defined $s->{commands}) { + foreach my $k (sort @{$s->{commands}}) { + my $c = $api->{commands}->{$k}; + push @{$info->{commands}}, formatFunction($api, $commandTypes, $c); + } + } + + my $v = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + init => join ("\n", @{$info->{init}}), + commands => join ("\n", @{$info->{commands}}), + append => defined($override->{append}) ? $override->{append} : '', + }; + + code::formatTemplateStream($template->{class}, $v); +} + +sub formatSignature { + my $s = shift; + my $types = $commandTypes->{types}; + my $d = '('; + + foreach my $m (@{$s->{items}}) { + my $x = $types->{$m->{deref}}; + + die "No sig defined ".Dumper($m) if !defined($x) || !defined($x->{sig}); + + $d .= $x->{sig}; + } + $d .= ')'; + + my $m = $s->{proto}; + my $x = $types->{$m->{deref}}; + die "No sig defined ".Dumper($m) if !defined($x) || !defined($x->{sig}); + $d .= $x->{sig}; +} + +# Forms all the parameter templates +sub collectFunctionInfo { + my $api = shift; + my $ct = shift; + my $s = shift; + my $types = $ct->{types}; + my $overrides = $ct->{overrides}; + my $void = $s->{proto}->{fullType} eq 'void'; + my $override = $overrides->{$s->{name}}; + + my @javaArgs = (); + my @invokeArgs = (); + my @nativeInit = (); + my @queryInit = (); + my @queryArgs = (); + + my @nativeArgs = (); + my @trampArgs = (); + + my $info = { + rename => $s->{name}, + name => $s->{name}, + 'function-descriptor' => formatFunctionDescriptor($ct, $s), + 'native-result-define' => '', + 'native-result-assign' => '', + 'native-result' => 'void', + 'trampoline-result-define' => '', + 'trampoline-result-assign' => '', + 'trampoline-scope' => '', + 'result-test' => '', + 'create-frame' => '', + 'java-result' => 'void', + 'java-result-assign' => '', + 'result-throw' => '', + + 'java-result-return' => 'return;', + 'trampoline-result-return' => 'return;', + }; + + my $hasInstance = 0; + my $needFrame = 0; + my $needAlloc = 0; + my $needScope = 0; + my $trampScope = 0; + + my @arrayFields = qw(java-arg invoke-arg native-init query-init query-arg native-arg trampoline-arg); + my @fixedFields = qw(java-result java-result-return java-result-assign); + + map { $info->{$_} = [] } @arrayFields; + + # Calculate parameters + foreach my $m (@{$s->{items}}) { + my $deref = defined($override->{$m->{name}}) && defined($override->{$m->{name}}->{type}) ? $override->{$m->{name}}->{type} : $m->{deref}; + my $type = $types->{$deref}; + + die "No type found ".Dumper($m, $s, $override) if !$type; + + my $v = buildVars($s, $m, $type); + + foreach my $field (@arrayFields) { + if ($v->{$field}) { + push @{$info->{$field}}, code::formatTemplate($v->{$field}, $v); + } elsif ($field eq 'query-arg') { + push @{$info->{$field}}, code::formatTemplate($v->{'invoke-arg'}, $v); + } + } + + foreach my $field (@fixedFields) { + $info->{$field} = code::formatTemplate($v->{$field}, $v) if ($v->{$field}); + } + + $needScope = 1 if $type->{'need-scope'}; + $needFrame = 1 if $type->{'need-frame'}; + $needAlloc = 1 if $type->{'need-alloc'}; + $hasInstance = 1 if $type->{'is-instance'}; + $trampScope = 1 if $type->{'trampoline-scope'}; + } + + $info->{'static'} = $hasInstance ? '' : 'static '; + + # Handle default return types, others are handled by the fixedFields above + if ($s->{successcodes}) { + my @codes = split(/,/,$s->{successcodes}); + + $info->{'native-result-define'} = 'int result$;'; + $info->{'native-result-assign'} = 'result$ = (int)'; + $info->{'result-test'} = 'if ('.join("||", map { '(result$ == Vulkan.'.$_.')' } @codes).')'; + $info->{'result-throw'} = 'throw new RuntimeException(Vulkan.getErrorMessage(result$));'; + + if ($#codes > 0 && $info->{'java-result'} eq 'void') { + $info->{'java-result'} = 'int'; + $info->{'java-result-return'} = 'return result$;'; + } + } elsif ($s->{proto}->{fullType} ne 'void') { + my $m = $s->{proto}; + my $type = defined($override->{$m->{name}}) ? $override->{$m->{name}}->{type} : $types->{$m->{deref}.'-return'}; + + die "No type '$m->{deref}-return' ".Dumper($m, $s) if !defined($type); + die Dumper($m, $s) if !defined($type->{'java-result-return'}); + + my $v = buildVars($s, $m, $type); + + $info->{'native-result-define'} = code::formatTemplate($v->{'native-result-define'}, $v); + $info->{'native-result-assign'} = code::formatTemplate($v->{'native-result-assign'}, $v); + #$info->{'native-result-define'}.= join("", map { "// $_\n" } split(/\n/, Dumper($m))); + $info->{'java-result'} = code::formatTemplate($v->{type}, $v); + $info->{'java-result-return'} = code::formatTemplate($v->{'java-result-return'}, $v); + + $info->{'native-result'} = code::formatTemplate($v->{'carrier'}, $v); + $info->{'trampoline-result-define'} = code::formatTemplate($v->{'trampoline-result-define'}, $v); + $info->{'trampoline-result-assign'} = code::formatTemplate($v->{'trampoline-result-assign'}, $v); + $info->{'trampoline-result-return'} = code::formatTemplate($v->{'trampoline-result-return'}, $v); + + $needScope = 1 if $type->{'need-scope'}; + } + + $info->{'create-frame'} = '(Frame frame$ = Frame.frame())' if $needFrame; + $info->{'trampoline-scope'} = '(ResourceScope scope$$ = ResourceScope.newConfinedScope())' if $trampScope; + + push @{$info->{'java-arg'}}, 'SegmentAllocator alloc$' if $needAlloc; + push @{$info->{'java-arg'}}, 'ResourceScope scope$' if $needScope; + + foreach my $field (@arrayFields) { + my $with = $field =~ m/-arg$/n ? ",\n\t" : "\n\t"; + $info->{$field} = join $with, @{$info->{$field}}; + } + + $info->{successcodes} = $s->{successcodes} ? $s->{successcodes} : ''; + $info->{errorcodes} = $s->{errorcodes} ? $s->{errorcodes}: ''; + + if ($s->{category} eq 'funcpointer') { + $info->{'trampoline-signature'} = formatSignature($s); + } + + $info; +} + +sub formatFunctionPointer { + my $vk = shift; + my $api = shift; + my $s = shift; + my $template = $commandTypes->{templates}->{'funcpointer-readwrite'}; + my $info = collectFunctionInfo($api, $commandTypes, $s); + + my $v = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + }; + + foreach my $k (keys %{$template->{insert}}) { + my $t = $template->{insert}->{$k}; + + $v->{$k} = code::formatTemplate($t, $info); + } + + code::formatTemplateStream($template->{class}, $v); +} + +sub formatFunctionDescriptor { + my $ct = shift; + my $s = shift; + my $types = $ct->{types}; + my @fields = (); + my $void = $s->{proto}->{fullType} eq 'void'; + my $override = $ct->{overrides}->{$s->{name}}; + + foreach my $m ($void ? () : $s->{proto}, @{$s->{items}}) { + my $deref = defined($override->{$m->{name}}) && defined($override->{$m->{name}}->{type}) ? $override->{$m->{name}}->{type} : $m->{deref}; + my $type = $types->{$deref}; + + die "No type '$deref' found ".Dumper($m, $s, $override) if !$type; + + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v)." /* $m->{deref} $m->{name} */"; + } + + return ($void ? 'FunctionDescriptor.ofVoid(' : 'FunctionDescriptor.of(') + .join(",\n\t\t", @fields).')'; +} + +sub formatFunction { + my $api = shift; + my $ct = shift; + my $s = shift; + my $void = $s->{proto}->{fullType} eq 'void'; + my $override = $ct->{overrides}->{$s->{name}}; + my $tempname = $override->{template} ? $override->{template} : 'method'; + my $template = $ct->{templates}->{$tempname}; + + my $info = collectFunctionInfo($api, $ct, $s); + + #join("\n", map { '// '.$_ } split(/\n/,Dumper($s)))."\n". + " /* template: $tempname */\n". + code::formatTemplate($template->{invoke}, $info); +} diff --git a/src/notzed.vulkan/gen/struct-types.api b/src/notzed.vulkan/gen/struct-types.api new file mode 100644 index 0000000..77c61ae --- /dev/null +++ b/src/notzed.vulkan/gen/struct-types.api @@ -0,0 +1,1454 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# types for structs and unions + +# ###################################################################### # + +#accessor=code template +# {type} - java type +# {java-get} +# {java-set} +# {init}* +# {init-array}* +# {setall-arg} +# {setall} + +# ###################################################################### # + +code value { + get {{ + /* {deref} */ + public {type} get{Name}() { + return {java-get}; + } + }} + set {{ + /* {deref} */ + public void set{Name}({type} {name}) { + {java-set}; + } + }} + getat {{ + public {type} get{Name}AtIndex(long i$) { + return {java-getat}; + } + }} + setat {{ + public void set{Name}AtIndex(long i$, {type} {name}) { + {java-setat}; + } + }} + + # Initialise the sType field if it has one, also include sub- + init eval {{ + #if ($tempname =~ m/write/) { + my $list = [ 'self$.segment', $s->{bitSize} / 8, "Vulkan.$m->{values}" ]; + + foreach my $x (@{$s->{items}}) { + if ($x->{deref} eq 'struct') { + my $y = $api->{types}->{$x->{baseType}}; + if ($#{$y->{items}} >= 0 && $y->{items}->[0]->{values}) { + my $z = $y->{items}->[0]; + push @$list, ($x->{bitOffset} + $z->{bitOffset}) / 8, "Vulkan.$z->{values}"; + } + } + } + + 'Vulkan.initType('.join(", ", @$list).');'; + #} else { + # '' + #} + }} + + # for complex constructors + #setall-arg {{ {type} {name} }} + setall-arg {{ {type} {name} }} + setall {{ self$.set{Name}({name}); }} + setallat {{ self$.set{Name}AtIndex(index$, {name}); }} +} + +# ###################################################################### # + +code value-array { + getorset {{ + /* value-array.getorset {deref} */ + public {type} get{Name}() { + try { + return {java-get}; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + public {typei} get{Name}Element(int i$) { + return {java-geti}; + } + public void set{Name}Element(int i$, {typei} {name}) { + {java-seti}; + } + }} + + getat {{ + /* value-array.getat {deref} */ + public {type} get{Name}AtIndex(long i$) { + try { + return {java-getat}; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} + + setall-arg eval {{ + if ($m->{len1} <= 4) { + join ", ", map { '{typei} {name}$'.$_ } (0 .. $m->{len1}-1); + } else { + "{typei}[] {name} /* $m->{deref} $m->{len1} */"; + } + }} + setall eval {{ + if ($m->{len1} <= 4) { + join "\n\t", map { "self\$.set{Name}Element($_, {name}\$$_);" } (0 .. $m->{len1}-1); + } else { + <{data}->{$m->{baseType}}->{items}}) { + my $name = "$m->{name}\$$a->{name}"; + my $offset = ($m->{bitOffset} + $a->{bitOffset}) / 8; + my $atype = $primitiveMap{$a->{baseType}}; + + if ($atype) { + push @$list, { + name=> $name, + type => $atype, + offset => $offset, + }; + } else { + my $as = $api->{data}->{$a->{baseType}}; + if ($as->{category} eq 'struct') { + foreach my $b (@{$as->{items}}) { + my $btype = $primitiveMap{$b->{baseType}}; + if ($btype) { + push @$list, { + name => "$name\$$b->{name}", + type => $btype, + offset => $offset + $b->{bitOffset} / 8 + }; + } + } + } elsif ($as->{category} eq 'enum') { + $atype = $primitiveMap{$as->{fullType}}; + if ($atype) { + push @$list, { + name=> $name, + type => $atype, + offset => $offset, + }; + } + } + } + } + $list; + }} + + # setBlah(all-simple-blah-fields) + set eval {{ + return 0 if $m->{len1}; + my $setall_arg = eval($type->{accessor}->{'setall-arg'}); + my $setall = eval($type->{accessor}->{setall}); + <{len1}); + + return 0 if $m->{len1}; + + my $list = eval($type->{accessor}->{'analyse-args'}); die "$@" if !defined $list; + + join("\n", map { + my $tname = uc($_->{type}); + my $fname = ucfirst($_->{name}); + <{type} get$fname() { + return this.segment.get(Memory.$tname, $_->{offset}); + } +END + } @$list); + }} + + # setBlahAtIndex(index, all-simple-blah-fields) + setat eval {{ + return 0 if $m->{len1}; + my $setall_arg = eval($type->{accessor}->{'setall-arg'}); + my $setallat = eval($type->{accessor}->{setallat}); + <{len1}; + + my $list = eval($type->{accessor}->{'analyse-args'}); die "$@" if !defined $list; + + join(', ', map { "$_->{type} $_->{name}" } @$list); + }} + + setall eval {{ + return 0 if $m->{len1}; + + my $list = eval($type->{accessor}->{'analyse-args'}); die "$@" if !defined $list; + + join("\n\t", map { 'self$.segment.set(Memory.'.uc($_->{type}).", $_->{offset}, $_->{name});" } @$list); + }} + + setallat eval {{ + use integer; + + return 0 if $m->{len1}; + + my $list = eval($type->{accessor}->{'analyse-args'}); die "$@" if !defined $list; + my $stride = $s->{bitSize} / 8; + + join("\n\t", map { 'self$.segment.set(Memory.'.uc($_->{type}).", index\$ * $stride + $_->{offset}, $_->{name});" } @$list); + }} +} + +# ###################################################################### # + +# value with a SegmentAllocator passed to set() +code value-alloc value { + set {{ + /* {deref} */ + public void set{Name}({type} {name}, SegmentAllocator alloc$) { + {java-set}; + } + }} + setat {{ + /* {deref} */ + public void set{Name}AtIndex(long i$, {type} {name}, SegmentAllocator alloc$) { + {java-setat}; + } + }} + + setall {{ self$.set{Name}({name}, alloc$); }} + setallat {{ self$.set{Name}AtIndex(index$, {name}, alloc$); }} +} + +# ###################################################################### # + +code Vulkan { + class {{ + package {package}; + + import au.notzed.nativez.Memory; + import jdk.incubator.foreign.MemorySegment; + import java.util.Arrays; + + public class Vulkan { + + public static int VK_MAKE_API_VERSION(int variant, int major, int minor, int patch) { + return (variant << 29) | (major << 22) | (minor << 12) | patch; + } + + /** Init stype fields of an object or array */ + static void initType(MemorySegment segment, long size, int stype) { + for (long o = 0; o < segment.byteSize(); o += size) + segment.set(Memory.INT, o, stype); + } + + /** Init stype fields of an object or an array with an embedded typed struct */ + static void initType(MemorySegment segment, long size, int stype, int off1, int stype1) { + for (long o = 0; o < segment.byteSize(); o += size) { + segment.set(Memory.INT, o, stype); + segment.set(Memory.INT, o+off1, stype1); + } + } + + static record ErrorInfo(int id, String name) {} + + public static String getErrorMessage(int errorid) { + ErrorInfo q = new ErrorInfo(errorid, null); + int i = Arrays.binarySearch(errors, q, (a, b) -> a.id - b.id); + + return i >= 0 ? errors[i].name : "Unknown Error"; + } + + {defines} + + {constants} + } + }} + VK_API_VERSION_1_0 {{ public final static int VK_API_VERSION_1_0 = VK_MAKE_API_VERSION(0, 1, 0, 0); }} + VK_API_VERSION_1_1 {{ public final static int VK_API_VERSION_1_1 = VK_MAKE_API_VERSION(0, 1, 1, 0); }} + VK_API_VERSION_1_2 {{ public final static int VK_API_VERSION_1_2 = VK_MAKE_API_VERSION(0, 1, 2, 0); }} + VK_API_VERSION_1_3 {{ public final static int VK_API_VERSION_1_3 = VK_MAKE_API_VERSION(0, 1, 3, 0); }} +} + +code dispatch { + class {{ + // template: dispatch:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + class {Name} { + + {field-init} + + {Name}(VkInstance instance$, ResourceScope scope$) { + {init} + } + } + }} + field-init {{ final NativeSymbol {name}$NS; }} + init {{ {name}$NS = instance$.vkGetInstanceProcAddr("{name}", scope$); }} + +} + +# non-dispatchable handle +code handle { + class {{ + // template: handle:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {name} implements Pointer { + final NativeSymbol self; + + private {name}(MemoryAddress address, ResourceScope scope) { + this.self = NativeSymbol.ofAddress("{name}", address, scope); + {init} + } + + public static {name} create(MemoryAddress address, ResourceScope scope) { + return address != MemoryAddress.NULL ? new {name}(address, scope) : null; + } + + /** Allocate an array where the handle scope is independent of the array allocation */ + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc, ResourceScope scope$) { + return HandleArray.createArray(length, alloc, (a, s) -> create(a, scope$)); + } + + /** Allocate an array where the handle scope matches the array allocation */ + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc) { + return HandleArray.createArray(length, alloc, {name}::create); + } + + public MemoryAddress address() { + return self.address(); + } + + public ResourceScope scope() { + return self.scope(); + } + + public String toString() { + return String.format("[%s %016x]", getClass().getSimpleName(), self.address().toRawLongValue()); + } + } + }} +} + +# VkInstance +code handle-instance { + class {{ + // template: handle-instance:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {name} implements Pointer { + final NativeSymbol self; + final DispatchInstance dispatch; + + private {name}(MemoryAddress address, ResourceScope scope) { + this.self = NativeSymbol.ofAddress("{name}", address, scope); + this.dispatch = new DispatchInstance(this, scope); + {init} + } + + public static {name} create(MemoryAddress address, ResourceScope scope) { + return new {name}(address, scope); + } + + public MemoryAddress address() { + return self.address(); + } + + public ResourceScope scope() { + return self.scope(); + } + + {commands} + } + }} +} + +# dispatchable handle +code handle-dispatch { + class {{ + // template: handle-dispatch:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {name} implements Pointer { + final NativeSymbol self; + final DispatchInstance dispatch; + + private {name}(MemoryAddress address, DispatchInstance dispatch, ResourceScope scope) { + this.self = NativeSymbol.ofAddress("{name}", address, scope); + this.dispatch = dispatch; + {init} + } + + public static {name} create(MemoryAddress address, DispatchInstance dispatch, ResourceScope scope) { + return new {name}(address, dispatch, scope); + } + + // TODO: evaluate how scope fits here + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc, DispatchInstance dispatch, ResourceScope scope) { + return HandleArray.createArray(length, alloc, (a, s) -> create(a, dispatch, s), scope); + } + + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc, VkInstance instance, ResourceScope scope) { + return HandleArray.createArray(length, alloc, (a, s) -> create(a, instance.dispatch, s), scope); + } + + public MemoryAddress address() { + return self.address(); + } + + public ResourceScope scope() { + return self.scope(); + } + + {commands} + + {append} + } + }} +} + +# ###################################################################### # + +# shared struct components +code struct { + header {{ + public MemorySegment segment; + + public static final GroupLayout LAYOUT = {layout}; + + private {Name}(MemorySegment segment) { + this.segment = segment; + } + + public static {Name} create(MemorySegment segment) { + return new {Name}(segment); + } + + public static {Name} create(MemoryAddress address, ResourceScope scope) { + return create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)); + } + + public static {Name} create(SegmentAllocator alloc) { + var self$ = create(alloc.allocate(LAYOUT)); + init(self$); + return self$; + } + + private static void init({Name} self$) { + {init} + } + + public void reset() { + segment.fill((byte)0); + init(this); + } + + // Pointer + @Override + public MemoryAddress address() { + return segment.address(); + } + + // Pointer + @Override + public ResourceScope scope() { + return segment.scope(); + } + + @Override + public String toString() { + return Memory.toString(segment, LAYOUT); + } + }} + create-all {{ + public static {Name} {create}({java-setall-arguments}, SegmentAllocator alloc$) { + {Name} self$ = create(alloc$); + + {java-setall} + + return self$; + } + }} + set-all {{ + public void {set}({java-setall-arguments}, SegmentAllocator alloc$) { + {Name} self$ = this; + + {java-setall} + } + }} + setat-all {{ + public void {set}AtIndex(long index$, {java-setall-arguments}, SegmentAllocator alloc$) { + {Name} self$ = this; + + {java-setallat} + } + }} + array {{ + static {Name} createArray(MemoryAddress addr, long length, ResourceScope scope) { + return create(MemorySegment.ofAddress(addr, length * LAYOUT.byteSize(), scope)); + } + + public static {Name} createArray(long length, SegmentAllocator alloc) { + var self$ = create(alloc.allocateArray(LAYOUT, length)); + init(self$); + return self$; + } + + public final static MethodHandle LAYOUT$SH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(MemoryLayout.PathElement.sequenceElement()); + + // Array + @Override + public long length() { + return segment.byteSize() / LAYOUT.byteSize(); + } + + @Override + public boolean isEmpty() { + return segment.byteSize() == 0; + } + + @Override + public {Name} getAtIndex(long index$) { + try { + return create((MemorySegment)LAYOUT$SH.invokeExact(this.segment, index$)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} +} + +# default - writeonly struct +code struct-writeonly insert=struct:header,struct:create-all + fields=init,set,getorset,handle,java-setall { + class {{ + // template: struct-writeonly:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {create-all} + + {set} + {getorset} + + {handle} + } + }} +} + +code struct-writeonly-array insert=struct:header,struct:create-all,struct:array + fields=init,set,getorset,setat,getorsetat,handle,handleat,java-setall { + class {{ + // template: struct-writeonly-array:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer, Array<{Name}> { + {header} + {array} + + {create-all} + + {set} + {getorset} + + {setat} + {getorsetat} + + {handle} + {handleat} + + {append} + } + }} +} + +code struct-readonly insert=struct:header + fields=init,get,getorset,handle { + class {{ + // template: struct-readonly:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {get} + {getorset} + + {handle} + + {append} + } + }} +} + +code struct-readonly-array insert=struct:header,struct:array + fields=init,get,getorset,getat,getorsetat,handle,handleat { + class {{ + // template: struct-readonly-array:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer, Array<{Name}> { + {header} + {array} + + {get} + {getorset} + + {getat} + {getorsetat} + + {handle} + {handleat} + + {append} + } + }} +} + +code struct-readwrite insert=struct:header,struct:create-all + fields=init,get,set,getorset,handle,java-setall { + class {{ + // template: struct-readwrite:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {create-all} + + {get} + {set} + {getorset} + + {handle} + + {append} + } + }} +} + +code struct-readwrite-array insert=struct:header,struct:create-all,struct:array + fields=init,get,getat,set,setat,getorset,getorsetat,handle,handleat,java-setall { + class {{ + // template: struct-readwrite-array:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer,Array<{Name}> { + {header} + {array} + + {create-all} + + {get} + {set} + {getorset} + + {getat} + {setat} + {getorsetat} + + {handle} + {handleat} + + {append} + } + }} +} + +# read-write with a 'set' method for all members +code struct-readwrite-all insert=struct:header,struct:create-all,struct:set-all + fields=init,get,set,getorset,handle,java-setall { + class {{ + // template: struct-readwrite-all:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {create-all} + + {get} + {set} + {getorset} + + {handle} + + {append} + } + }} +} + +code struct-readwrite-array-all insert=struct:header,struct:create-all,struct:array,struct:set-all,struct:setat-all + fields=init,get,getat,set,setat,setallat,getorset,getorsetat,handle,handleat,java-setall,java-setallat { + class {{ + // template: struct-readwrite-array-all:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer,Array<{Name}> { + {header} + {array} + + {create-all} + + {get} + {set} + {getorset} + + {getat} + {setat} + {getorsetat} + + {handle} + {handleat} + } + }} +} + +code struct-writeonly-array-all insert=struct:header,struct:create-all,struct:array,struct:set-all,struct:setat-all + fields=init,set,setat,setallat,getorset,getorsetat,handle,handleat,java-setall,java-setallat { + class {{ + // template: struct-writeonly-array-all:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer, Array<{Name}> { + {header} + {array} + + {create-all} + + {set} + {getorset} + + {setat} + {getorsetat} + + {handle} + {handleat} + } + }} +} + +# ###################################################################### # + +# Basic value-based accessor +type value accessor=value { + native-value {{ {name} }} + + native-get {{ ({type}){name}$VH.get(this.segment) }} + handle {{ final static VarHandle {name}$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}")); }} + + java-get {{ {native-get} }} + java-set {{ {name}$VH.set(this.segment, {native-value}) }} + + native-getat {{ ({type}){name}$AH.get(this.segment, i$) }} + native-setat {{ {name}$AH.set(this.segment, i$, {native-value}) }} + handleat {{ final static VarHandle {name}$AH = MemoryLayout.sequenceLayout(LAYOUT).varHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("{name}")); }} + + java-getat {{ {native-getat} }} + java-setat {{ {native-setat} }} +} + +type value-array accessor=value-array { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + + native-geti {{ {name}$EH.get(this.segment, i$) }} + + java-geti {{ ({typei}){native-geti} }} + java-seti {{ {name}$EH.set(this.segment, i$, {name}) }} + handle {{ + final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); + final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement()); + }} + + native-getat {{ (MemorySegment){name}$AH.invokeExact(this.segment, i$) }} + handleat {{ + final static MethodHandle {name}$AH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("{name}")); + }} + java-getat {{ {type}.create({native-getat}) }} +} + +type value-array2d accessor=value-array2d { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + java-geti {{ ({typei}){name}$EH.get(this.segment, i$, j$) }} + java-seti {{ {name}$EH.set(this.segment, i$, j$, {name}) }} + handle {{ + final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); + final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.sequenceElement()); + }} +} + +type inline accessor=inline { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + handle {{ final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); }} + + native-getat {{ (MemorySegment){name}$SA.invokeExact(this.segment, i$) }} + java-getat {{ {type}.create({native-getat}) }} + handleat {{ final static MethodHandle {name}$SA = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("{name}")); }} +} + +type inline-array accessor=inline { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + handle {{ final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); }} + + native-getat {{ (MemorySegment){name}$SA.invokeExact(this.segment, i$) }} + java-getat {{ {type}.create({native-getat}) }} + handleat {{ final static MethodHandle {name}$SA = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("{name}")); }} +} + +type uint8_t,char value { + type {{ byte }} + layout {{ Memory.BYTE }} +} + +type uint16_t value { + type {{ short }} + layout {{ Memory.SHORT }} +} + +type uint32_t,int,int32_t value { + type {{ int }} + layout {{ Memory.INT }} +} + +type uint64_t,int64_t,size_t value { + type {{ long }} + layout {{ Memory.LONG }} +} + +type float value { + type {{ float }} + layout {{ Memory.FLOAT }} +} + +type double value { + type {{ double }} + layout {{ Memory.DOUBLE }} +} + +# ###################################################################### # + +type uint8_t[] value-array { + type {{ ByteArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.BYTE) }} + typei {{ byte }} +} + +# these are always embedded 0-terminated strings, so don't treat as array +type char[] inline { + type {{ String }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.BYTE) }} + + java-get {{ ({native-get}).getUtf8String(0L) }} + java-getat {{ ({native-getat}).getUtf8String(0L) }} +} + +type uint32_t[],int32_t[] value-array { + type {{ IntArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.INT) }} + typei {{ int }} +} + +type uint64_t[] value-array { + type {{ LongArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.LONG) }} + typei {{ long }} +} + +type float[] value-array { + type {{ FloatArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.FLOAT) }} + typei {{ float }} +} + +type struct[] inline-array { + type {{ {baseType} }} + layout {{ MemoryLayout.sequenceLayout({len1}, {baseType}.LAYOUT) }} +} + +type struct-expand[] struct[] accessor=inline-expand; + +type float[][] value-array2d { + type {{ FloatArray }} + typei {{ float }} + layout {{ MemoryLayout.sequenceLayout({len1}, MemoryLayout.sequenceLayout({len2}, Memory.FLOAT)) }} +} + +# select=len? or what? + +type pointer value { + layout {{ Memory.POINTER }} + type {{ MemoryAddress }} + + native-value {{ Memory.address({name}) }} + native-get {{ (MemoryAddress){name}$VH.get(this.segment) }} + +# java-get {{ {native-get} }} +# java-set {{ {name}$VH.set(this.segment, Memory.address({name})) }} +} + +type void* pointer; + +type value-pointer pointer { + java-get {{ {type}.create((MemoryAddress){name}$VH.get(this.segment), segment.scope()) }} +} + +type uint8_t* value-pointer { + type ByteArray; +} + +type uint32_t*,int32_t*,int* value-pointer { + type IntArray; +} + +type uint64_t* value-pointer { + type LongArray; +} + +type float* value-pointer { + type FloatArray; +} + +type size_t* value-pointer { + type LongArray; +} + +type pointer-length pointer { + java-get {{ {type}.createArray({native-get}, {length}, this.segment.scope()) }} +} + +type void*-length pointer-length { + type MemorySegment; + java-get {{ MemorySegment.ofAddress({native-get}, {length}, this.segment.scope()) }} +} + +type uint8_t*-length pointer-length { + type ByteArray; +} + +type uint32_t*-length,int32_t*-length pointer-length { + type IntArray; +} + +type uint64_t*-length pointer-length { + type LongArray; +} + +type float*-length pointer-length { + type FloatArray; +} + +# special handling for strings, will fail if it isn't +type char* pointer accessor=value-alloc need-alloc { + type {{ String }} + + java-get {{ ({native-get}).getUtf8String(0) }} + native-value {{ Memory.copyString({name}, alloc$).address() }} + + # this just verifies it's a string type + length eval {{ + if ($v->{len} =~ m/null-terminated/) { + 1; + } else { + die Dumper($v, $s); + } + }} + +} + +type char**-length pointer-length accessor=value-alloc { + type {{ String[] }} + + java-set {{ {name}$VH.set(this.segment, Memory.copyStringArray({name}, alloc$).address()); }} + java-get {{ Memory.copyStringArray((MemoryAddress){name}$VH.get(this.segment), {length}) }} +} + +type funcpointer pointer { + type {{ FunctionPointer<{baseType}> }} + typei {{ {baseType} }} + java-get {{ {baseType}.downcall({native-get}, this.segment.scope()) }} +} + +type void** pointer { +} + +type void**-length pointer-length { + type PointerArray; +} + +type handle pointer { + type {{ {baseType} }} + java-get {{ {type}.create({native-get}, this.segment.scope()) }} +} + +type handle[] value-array accessor=handle-array { + type {{ HandleArray<{typei}> }} + typei {{ {baseType} }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.POINTER) }} + java-get {{ HandleArray.create({native-get}, (a, s) -> {typei}.create(a, instance$.dispatch, s), scope$) }} + + java-geti {{ {typei}.create((MemoryAddress){native-geti}, instance$.dispatch, scope$) }} +} + +type handle* pointer { + type {{ HandleArray<{typei}> }} + typei {{ {baseType} }} + java-get {{ HandleArray.create({native-get}, {typei}::create) }} +} + +type handle*-length pointer-length { + type {{ HandleArray<{baseType}> }} + typei {{ {baseType} }} + java-get {{ HandleArray.createArray({native-get}, {length}, {typei}::create, this.segment.scope()) }} + java-set {{ {name}$VH.set(this.segment, Memory.address({name})); }} +} + +type struct inline { + type {{ {baseType} }} + layout {{ {baseType}.LAYOUT }} +} + +type struct-expand inline accessor=inline-expand { + type {{ {baseType} }} + layout {{ {baseType}.LAYOUT }} +} + +type struct* pointer { + type {{ {baseType} }} + java-get {{ {baseType}.create({native-get}, this.segment.scope()) }} +} + +type struct*-length pointer-length { + type {{ {baseType} }} + typei {{ {baseType} }} +} + +type struct** pointer { +} + +# xlib +type XID,Window,VisualID uint64_t; + +type Display* handle { + type xlib.XDisplay; +} + +# xcb +type xcb_window_t uint32_t; +type xcb_visualid_t uint32_t; +type xcb_connection_t* handle { + type xcb.Connection; +} + +override structs { + VkInstance template=handle-instance; + VkPhysicalDevice template=handle-dispatch; + VkDevice template=handle-dispatch append {{ + public HandleArray vkCreateGraphicsPipelines( + VkPipelineCache pipelineCache, + VkGraphicsPipelineCreateInfo pCreateInfos, + SegmentAllocator alloc$, + ResourceScope scope$) { + HandleArray pPipelines = VkPipeline.createArray(pCreateInfos.length(), alloc$, scope$); + vkCreateGraphicsPipelines(pipelineCache, (int)pCreateInfos.length(), pCreateInfos, pPipelines); + // Vulkan.VK_PIPELINE_COMPILE_REQUIRED_EXT ?? + return pPipelines; + } + + public VkPipeline vkCreateGraphicsPipeline( + VkPipelineCache pipelineCache, + VkGraphicsPipelineCreateInfo pCreateInfo, + ResourceScope scope) { + try (Frame alloc$ = Frame.frame()) { + HandleArray pPipelines = VkPipeline.createArray(1, alloc$, scope); + vkCreateGraphicsPipelines(pipelineCache, 1, pCreateInfo, pPipelines); + // Vulkan.VK_PIPELINE_COMPILE_REQUIRED_EXT ?? + return pPipelines.get(0); + } + } + + public HandleArray vkAllocateCommandBuffers( + VkCommandBufferAllocateInfo pAllocateInfo, + SegmentAllocator alloc$, + ResourceScope scope$) { + + var buffers = VkCommandBuffer.createArray( + (int)VkCommandBufferAllocateInfo.commandBufferCount$VH.get(pAllocateInfo.segment), + alloc$, dispatch, scope$); + + vkAllocateCommandBuffers(pAllocateInfo, buffers); + + return buffers; + } + + public HandleArray vkAllocateDescriptorSets( + VkDescriptorSetAllocateInfo pAllocateInfo, + SegmentAllocator alloc$, + ResourceScope scope) { + + var buffers = VkDescriptorSet.createArray( + (int)VkDescriptorSetAllocateInfo.descriptorSetCount$VH.get(pAllocateInfo.segment), + alloc$, scope); + + vkAllocateDescriptorSets(pAllocateInfo, buffers); + + return buffers; + } + + }} + VkCommandBuffer template=handle-dispatch; + VkQueue template=handle-dispatch; + + VkAllocationCallbacks ignore; + + # We want 'set(all fields) for these types and to include them expanded if inline + VkOffset2D expand template=struct-readwrite-array-all; + VkOffset3D expand template=struct-readwrite-array-all; + VkExtent2D expand template=struct-readwrite-all; + VkExtent3D expand template=struct-readwrite-all; + VkRect2D expand template=struct-readwrite-array-all; + + VkStencilOpState template=struct-readwrite-all; + VkComponentMapping expand template=struct-readwrite-all; + VkImageSubresourceRange expand template=struct-readwrite-all; + + # probably just want writeonly? here + VkClearColorValue expand template=struct-readwrite-all; + VkClearDepthStencilValue expand template=struct-readwrite-all; + + # Yikes, is all this really worth it? + VkClearValue template=struct-writeonly-array append {{ + public static VkClearValue createColour(float r, float g, float b, float a, SegmentAllocator alloc) { + var self = create(alloc); + + VkClearColorValue.float32$EH.set(self.segment, 0, r); + VkClearColorValue.float32$EH.set(self.segment, 1, g); + VkClearColorValue.float32$EH.set(self.segment, 2, b); + VkClearColorValue.float32$EH.set(self.segment, 3, a); + + return self; + } + public static VkClearValue createColour(int r, int g, int b, int a, SegmentAllocator alloc) { + var self = create(alloc); + + VkClearColorValue.int32$EH.set(self.segment, 0, r); + VkClearColorValue.int32$EH.set(self.segment, 1, g); + VkClearColorValue.int32$EH.set(self.segment, 2, b); + VkClearColorValue.int32$EH.set(self.segment, 3, a); + + return self; + } + public static VkClearValue createDepthStencil(float depth, int stencil, SegmentAllocator alloc) { + var self = create(alloc); + + VkClearDepthStencilValue.depth$VH.set(self.segment, depth); + VkClearDepthStencilValue.stencil$VH.set(self.segment, stencil); + + return self; + } + + final static VarHandle colour$float32$EH = MemoryLayout.sequenceLayout(LAYOUT).varHandle( + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("color"), + MemoryLayout.PathElement.groupElement("float32"), + MemoryLayout.PathElement.sequenceElement()); + + final static VarHandle colour$int32$EH = MemoryLayout.sequenceLayout(LAYOUT).varHandle( + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("color"), + MemoryLayout.PathElement.groupElement("int32"), + MemoryLayout.PathElement.sequenceElement()); + + public void setColourAtIndex(int index, float r, float g, float b, float a) { + colour$float32$EH.set(segment, index, 0, r); + colour$float32$EH.set(segment, index, 1, g); + colour$float32$EH.set(segment, index, 2, b); + colour$float32$EH.set(segment, index, 3, a); + } + public void setColourAtIndex(int index, int r, int g, int b, int a) { + colour$int32$EH.set(segment, index, 0, r); + colour$int32$EH.set(segment, index, 1, g); + colour$int32$EH.set(segment, index, 2, b); + colour$int32$EH.set(segment, index, 3, a); + } + + final static VarHandle depthStencil$depth$EH = MemoryLayout.sequenceLayout(LAYOUT).varHandle( + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("depthStencil"), + MemoryLayout.PathElement.groupElement("depth")); + + final static VarHandle depthStencil$stencil$EH = MemoryLayout.sequenceLayout(LAYOUT).varHandle( + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("depthStencil"), + MemoryLayout.PathElement.groupElement("stencil")); + + public void setDepthStencilAtIndex(int index, float depth, int stencil) { + depthStencil$depth$EH.set(segment, index, depth); + depthStencil$stencil$EH.set(segment, index, stencil); + } + + }} + + VkMemoryType template=struct-readonly-array; + VkMemoryHeap template=struct-readonly-array; + VkPhysicalDeviceMemoryProperties template=struct-readonly append {{ + + final static VarHandle memoryTypes$propertyFlags$EH = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("memoryTypes"), + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("propertyFlags")); + + final static VarHandle memoryTypes$heapIndex$EH = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("memoryTypes"), + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("heapIndex")); + + public int getMemoryTypes$propertyFlagsAtIndex(int index) { + return (int)memoryTypes$propertyFlags$EH.get(segment, index); + } + + public int getMemoryTypes$heapIndexAtIndex(int index) { + return (int)memoryTypes$heapIndex$EH.get(segment, index); + } + + final static VarHandle memoryHeaps$size$EH = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("memoryHeaps"), + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("size")); + + final static VarHandle memoryHeaps$flags$EH = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("memoryHeaps"), + MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement("flags")); + + public int getMemoryHeaps$sizeAtIndex(int index) { + return (int)memoryHeaps$size$EH.get(segment, index); + } + + public int getMemoryHeaps$flagsAtIndex(int index) { + return (int)memoryHeaps$flags$EH.get(segment, index); + } + }} + + # possibly way to auto-detect this - all simple types + VkAttachmentDescription template=struct-writeonly-array-all; + VkPipelineShaderStageCreateInfo template=struct-writeonly-array-all; + VkVertexInputAttributeDescription template=struct-writeonly-array-all; + VkVertexInputBindingDescription template=struct-writeonly-array-all; + + # Override default read/write + VkDebugUtilsMessengerCallbackDataEXT template=struct-readonly; + VkDebugUtilsLabelEXT template=struct-readonly-array; + VkDebugUtilsObjectNameInfoEXT template=struct-readonly-array; + + VkPhysicalDeviceFeatures template=struct-readwrite append {{ + /** + * return true if every feature requested in query is present in this. + */ + public boolean hasFeatures(VkPhysicalDeviceFeatures query) { + // s q + // 0 0 1 + // 0 1 0 + // 1 0 1 + // 1 1 1 + // = ((s & q) | ~q) & 1 + + // We know this is all just a list of VkBool32, so peek as an array + int ok = 1; + for (int i=0;ok != 0 && i $sys, + data => {}, + extensions => [], + handles => {}, + types => {}, + commands => {}, + features => [], + funcpointers => {}, + }; + + bless $self, $class; + + loadRegistry($self); + + # build various indices + my $data = $self->{data}; + my $handles = $self->{handles}; + my $types = $self->{types}; + my $commands = $self->{commands}; + my $extensions = $self->{extensions}; + my $funcpointers = $self->{funcpointers}; + + foreach my $t (keys %{$data}) { + my $v = $data->{$t}; + $handles->{$v->{name}} = $v if $v->{category} eq 'handle'; + $types->{$v->{name}} = $v if $v->{category} =~ m/struct|union|platform/; + $commands->{$v->{name}} = $v if $v->{category} eq 'command'; + $funcpointers->{$v->{name}} = $v if $v->{category} eq 'funcpointer'; + } + + # mark extension functions? + foreach my $e (@{$extensions}) { + foreach my $name (map { @{$_->{commands}} } @{$e->{require}}) { + my $r = $data->{$name}; + + die if !defined($r); + + push @{$r->{extensions}}, $e->{name}; + } + } + + # Link up bitmask base types + foreach my $s (grep { $_->{category} eq 'enum' } values %{$data}) { + if ($s->{requires}) { + my $t = $data->{$s->{requires}}; + die Dumper($s) if !defined $t; + die Dumper($s) if !defined $s->{fullType}; + $t->{uses} = $s; + $t->{fullType} = $s->{fullType}; + } elsif ($s->{bitvalues}) { + my $t = $data->{$s->{bitvalues}}; + die Dumper($s) if !defined $t; + die Dumper($s) if !defined $s->{fullType}; + $t->{uses} = $s; + $t->{fullType} = $s->{fullType}; + } elsif (!defined $s->{fullType}) { + $s->{fullType} = 'VkFlags'; + } + } + + $self; +} + +sub buildRequirement { + my $vk = shift; + my $data = shift; + my $req = shift; + my $ext = shift; + my $outconst = $data->{'API Constants'}; + my $allconst = $vk->{data}->{'API Constants'}; + + # Find included types in this requirement + foreach my $c (@{$req->{commands}}, @{$req->{types}}) { + my $d = $vk->{data}->{$c}; + + if (defined $d) { + # for format change? + if ($d->{category} eq 'enum') { + # Copy all aliases across to data + while ($d->{alias}) { + $data->{$d->{name}} = $d; + $d = $vk->{data}->{$d->{alias}}; + } + $d = { %$d }; + $d->{items} = [ @{$d->{items}} ] if defined($d->{items}); + $data->{$d->{name}} = $d; + } else { + $data->{$d->{name}} = $d; + } + } else { + $data->{$c} = { + name => $c, + category => 'define', + }; + } + } + + foreach my $c (@{$req->{enums}}) { + if ($c->{extends}) { + my $d = $data->{$c->{extends}}; + + if (defined($c->{value})) { + } elsif (defined($c->{bitpos})) { + $c->{value} = "".(1<<$c->{bitpos}); + } elsif (defined($c->{extnumber})) { + $c->{value} = "".(1000000000 + + 1000 * ($c->{extnumber} - 1) + + $c->{offset}); + } elsif (defined($c->{offset})) { + $c->{value} = $c->{dir}."".(1000000000 + + 1000 * ($ext->{number} - 1) + + $c->{offset}); + } elsif (defined($c->{alias})) { + } else { + print Dumper($c); + die; + } + + $c->{extension} = $ext->{name} if defined($ext); + + push @{$d->{items}}, $c; + } elsif ($c->{value}) { + if ($c->{value} =~ m/^"/) { + if (!defined $outconst->{index}->{$c->{name}}) { + my $v = { %$c, type=>'const char *' }; + push @{$outconst->{items}}, $v; + $outconst->{index}->{$v->{name}} = $v; + } + } else { + if (!defined $outconst->{index}->{$c->{name}}) { + my $v = { %$c, type=>'uint32_t' }; + push @{$outconst->{items}}, $v; + $outconst->{index}->{$v->{name}} = $v; + } + } + } elsif (!$c->{alias}) { + if (!defined $outconst->{index}->{$c->{name}}) { + my $v = $allconst->{index}->{$c->{name}}; + + die Dumper($c) if !defined $v; + + push @{$outconst->{items}}, $v; + $outconst->{index}->{$c->{name}} = $v; + } + } + } +} + +# Ideally this builds a 'view' of the features +# But it doesn't work properly if something is promoted and uses new names + +sub buildFeatures { + my $vk = shift; + my $vers = shift; + my $plat = shift; + my $data = {}; + my $versions = {}; + my $platform = {}; + + map { $versions->{$_} = 1 } @$vers; + map { $platform->{$_} = 1 } @$plat; + + #print Dumper($vk->{features}); + + $data->{'API Constants'} = { + name => 'API Constants', + category => 'define', + items => [], + index => {}, + }; + + # add constants that the api's dont reference (only 1 so far) + my $outconst = $data->{'API Constants'}; + my $allconst = $vk->{data}->{'API Constants'}; + foreach my $v (map {$allconst->{index}->{$_}} qw(VK_UUID_SIZE)) { + push @{$outconst->{items}}, $v; + $outconst->{index}->{$v->{name}} = $v; + } + + foreach my $feature (grep { $versions->{$_->{name}} } @{$vk->{features}}) { + print "Feature $feature->{name}\n" if ($vk->{sys}->{verbose}); + foreach my $req (@{$feature->{require}}) { + buildRequirement($vk, $data, $req); + } + } + + foreach my $extension (grep { $_->{supported} eq 'vulkan' && (!defined($_->{platform}) || $platform->{$_->{platform}}) + } @{$vk->{extensions}}) { + foreach my $req (grep { (!defined($_->{feature})) || $versions->{$_->{feature}} } + @{$extension->{require}}) { + print "Extension $extension->{name} $req->{feature}\n" if ($vk->{sys}->{verbose}); + buildRequirement($vk, $data, $req, $extension); + } + } + + #print "rest\n"; + #print Dumper($data); + + # TODO: need to remove aliases here? + my $handles = {}; + my $types = {}; + my $commands = {}; + my $enums = {}; + my $funcpointers = {}; + my $defines = {}; + + foreach my $t (keys %{$data}) { + my $v = $data->{$t}; + $handles->{$v->{name}} = $v if $v->{category} eq 'handle'; + $types->{$v->{name}} = $v if $v->{category} =~ m/struct|union/on; + $commands->{$v->{name}} = $v if $v->{category} eq 'command'; + $enums->{$v->{name}} = $v if $v->{category} eq 'enum'; + $funcpointers->{$v->{name}} = $v if $v->{category} eq 'funcpointer'; + $defines->{$v->{name}} = $v if $v->{category} eq 'define'; + } + + if (0) { + open(my $f, '>', 'features.pm'); + print $f Dumper($data); + close $f; + + open(my $f, '>', 'vk.pm'); + print $f Dumper($vk); + close $f; + } + + my $api = { + data => $data, + handles => $handles, + types => $types, + commands => $commands, + funcpointers => $funcpointers, + enums => $enums, + defines => $defines, + }; + + # create sizes for every struct of interest + foreach my $s (values %$types) { + next if $s->{alias}; + + if ($s->{category} eq 'struct') { + structSize($vk, $api, $s); + } elsif ($s->{category} eq 'union') { + unionSize($vk, $api, $s); + } else { + die; + } + } + + return $api; +} + +my $typeInfo = { + 'void *' => { bitSize => 64, bitAlign => 64 }, + 'int' => { bitSize => 32, bitAlign => 32 }, + 'char' => { bitSize => 8, bitAlign => 8 }, + 'uint8_t' => { bitSize => 8, bitAlign => 8 }, + 'uint16_t' => { bitSize => 16, bitAlign => 16 }, + 'int32_t' => { bitSize => 32, bitAlign => 32 }, + 'uint32_t' => { bitSize => 32, bitAlign => 32 }, + 'int64_t' => { bitSize => 64, bitAlign => 64 }, + 'uint64_t' => { bitSize => 64, bitAlign => 64 }, + 'size_t' => { bitSize => 64, bitAlign => 64 }, + 'float' => { bitSize => 32, bitAlign => 32 }, + 'double' => { bitSize => 64, bitAlign => 64 }, + 'size_t' => { bitSize => 64, bitAlign => 64 }, + 'Window' => { bitSize => 64, bitAlign => 64 }, + 'Display' => { bitSize => 64, bitAlign => 64 }, + 'xcb_window_t' => { bitSize => 32, bitAlign => 32 }, + 'xcb_connection_t' => { bitSize => 64, bitAlign => 64 }, +# 'VkFlags' => { bitSize => 32, bitAlign => 32 }, +# 'VkFlags64' => { bitSize => 64, bitAlign => 64 }, +}; + +sub memberSize { + my $vk = shift; + my $api = shift; + my $m = shift; + my $t = $api->{data}->{$m->{baseType}}; + my $nstar = $m->{fullType} =~ tr/*/*/; + my ($nbits) = $m->{fullType} =~ m/:(\d+)$/o; + my $array = 1; + my $info = $typeInfo->{'void *'}; + + # arrays and bitfields + if ($m->{fullType} =~ m/\[(.*)\]\[(.*)\]$/) { + $array = $1 * $2; + } elsif ($m->{fullType} =~ m/\[(\d+)\]$/o) { + $array = $1; + } elsif ($m->{fullType} =~ m/\[(.+)\]$/o) { + $array = $vk->{data}->{'API Constants'}->{index}->{$1}->{value}; + } + + if (!defined($t)) { + if ($nbits) { + die Dumper($m) if $nstar > 0; + $info = { bitSize => $nbits, bitAlign => 1 }; + } else { + $info = $typeInfo->{$m->{baseType}} if ($nstar == 0); + } + } else { + while ($t->{alias}) { + $t = $api->{data}->{$t->{alias}}; + } + + die Dumper($m) if !defined $t; + + if ($t->{category} =~ m/enum|bitmask/on) { + if ($nbits) { + die Dumper($m) if $nstar > 0; + $info = { bitSize => $nbits, bitAlign => 1 }; + } else { + $t = $vk->{data}->{$t->{fullType}}; + $info = $typeInfo->{$t->{type}} if ($nstar == 0); + } + } elsif ($t->{category} eq 'struct') { + $info = structSize($vk, $api, $t) if ($nstar == 0); + } elsif ($t->{category} eq 'union') { + $info = unionSize($vk, $api, $t) if ($nstar == 0); + } elsif ($t->{category} eq 'handle') { + # already set + } elsif ($t->{category} eq 'basetype') { + $info = $typeInfo->{$t->{type}} if ($nstar == 0); + } elsif ($t->{category} eq 'funcpointer') { + # already set + } else { + die Dumper($m, $t); + } + } + + die Dumper($m, $t) if !defined($info); + + #print Dumper($m, $t, $info); + #print "size $m->{name} $m->{fullType} = $info->{bitSize}\n"; + + + return { bitSize => $info->{bitSize} * $array, bitAlign => $info->{bitAlign} }; +} + +sub align { + my $v = shift; + my $a = shift; + + return ($v + $a - 1) & ~($a - 1); +} + +sub structSize { + my $vk = shift; + my $api = shift; + my $s = shift; + my $bitSize = 0; + my $bitAlign = 8; + + if (!defined($s->{bitSize})) { + foreach my $m (@{$s->{items}}) { + use integer; + my $info = memberSize($vk, $api, $m); + + $bitSize = align($bitSize, $info->{bitAlign}); + + $m->{bitOffset} = $bitSize; + $m->{bitSize} = $info->{bitSize}; + + $bitSize = $bitSize + $info->{bitSize}; + $bitAlign = $info->{bitAlign} if $info->{bitAlign} > $bitAlign; + } + + $bitSize = align($bitSize, $bitAlign); + + $s->{bitSize} = $bitSize; + $s->{bitAlign} = $bitAlign; + } else { + $bitSize = $s->{bitSize}; + $bitAlign = $s->{bitAlign}; + } + + return { bitSize => $bitSize, bitAlign => $bitAlign }; +} + +sub unionSize { + my $vk = shift; + my $api = shift; + my $s = shift; + my $bitSize = 0; + my $bitAlign = 8; + + if (!defined($s->{bitSize})) { + foreach my $m (@{$s->{items}}) { + use integer; + my $info = memberSize($vk, $api, $m); + + $m->{bitOffset} = 0; + $m->{bitSize} = $info->{bitSize}; + + $bitSize = $info->{bitSize} if $info->{bitSize} > $bitSize; + $bitAlign = $info->{bitAlign} if $info->{bitAlign} > $bitAlign; + } + + $bitSize = align($bitSize, $bitAlign); + + $s->{bitSize} = $bitSize; + $s->{bitAlign} = $bitAlign; + } else { + $bitSize = $s->{bitSize}; + $bitAlign = $s->{bitAlign}; + } + + return { bitSize => $bitSize, bitAlign => $bitAlign }; +} + +sub loadRegistry { + my $vk = shift; + + my $xml = XML::Parser->new(Style => 'Tree'); + my $doc = $xml->parsefile('/usr/share/vulkan/registry/vk.xml') || die "unable to parse vulkan registry"; + + #print Dumper($doc); + + my $root = $doc->[1]; + my $roota = shift @{$root}; + + my $data = $vk->{data}; + my $alias = $vk->{alias}; + my $extensions = $vk->{extensions}; + my $features = $vk->{features}; + + # This destructively consumes the whole tree so must be one pass + while ($#{$root} >= 0) { + my $xt = shift @{$root}; + my $xn = shift @{$root}; + + next if $xt eq '0'; + + my $xa = shift @{$xn}; + + if ($xt eq 'types') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'type'; + + my $ya = $yn->[0]; + + if ($ya->{category} =~ m/struct|union/) { + if (!defined($ya->{alias})) { + my $s = $ya; + + $s->{items} = []; + + shift @{$yn}; + while ($#{$yn} >= 0) { + my $mt = shift @{$yn}; + my $mm = shift @{$yn}; + + push @{$s->{items}}, loadMember($mm) if $mt eq 'member'; + } + + $data->{$s->{name}} = $s; + } else { + $alias->{$ya->{name}} = $ya->{alias}; + $data->{$ya->{name}} = $ya; + } + } elsif ($ya->{category} =~ m/^(handle|basetype|funcpointer|bitmask)$/n) { + if (!defined($ya->{alias})) { + my $info = loadMember($yn); + my $s = $ya; + + $s->{name} = $info->{name}; + $s->{type} = $info->{baseType} if defined $info->{baseType}; + + $s->{category} = 'enum' if $s->{category} eq 'bitmask'; + analyseFunctionPointer($s) if ($s->{category} eq 'funcpointer'); + + $data->{$s->{name}} = $s; + } else { + $ya->{category} = 'enum' if $ya->{category} eq 'bitmask'; + $alias->{$ya->{name}} = $ya->{alias}; + $data->{$ya->{name}} = $ya; + } + } elsif ($ya->{category} eq 'enum') { + $data->{$ya->{name}} = $ya; + } elsif ($ya->{requires} eq 'vk_platform' || $ya->{name} eq 'int') { + # These are just primitive types, not sure what to do with them, could auto-map them to java i suppose + $ya->{category} = 'platform'; + $data->{$ya->{name}} = $ya; + } else { + #noisy print "Unhandled: $ya->{name}\n"; + } + } + } elsif ($xt eq 'enums') { + if ($xa->{type} =~ m/enum|bitmask/o) { + #print "enum: $xa->{name}\n"; + # these are forward referenced from block so re-use, or just overwrite? + my $e = $data->{$xa->{name}}; + + $e = { %{$xa}, category => "enum" } if (!defined($e)); + $e->{items} = []; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'enum'; + + my $ya = shift @{$yn}; + + #next if $ya->{alias}; + + push @{$e->{items}}, $ya; + } + + $data->{$e->{name}} = $e; + } elsif ($xa->{name} eq 'API Constants') { + my $d = { category => "define", name => $xa->{name}, items =>[], index=>{} }; + + $data->{$xa->{name}} = $d; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'enum'; + + my $ya = shift @{$yn}; + + #next if $ya->{alias}; + + push @{$d->{items}}, $ya; + $d->{index}->{$ya->{name}} = $ya; + } + } + } elsif ($xt eq 'commands') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'command'; + + my $ya = shift @{$yn}; + + if (!defined($ya->{alias})) { + my $cmd = $ya; + + $cmd->{category} = 'command'; + $cmd->{items} = []; + $cmd->{proto} = {}; + + while ($#{$yn} >= 0) { + my $zt = shift @{$yn}; + my $zn = shift @{$yn}; + + if ($zt eq 'proto') { + $cmd->{proto} = loadMember($zn); + } elsif ($zt eq 'param') { + push @{$cmd->{items}}, loadMember($zn); + } + } + + my $name = $cmd->{proto}->{name}; + + # check we parsed it properly + if ($cmd->{proto}->{fullType} eq "") { + print Dumper([$ya, $yn]); + die(); + } + $cmd->{name} = $name; + + $data->{$name} = $cmd; + } else { + # want forward ref or not? + $alias->{$ya->{name}} = $ya->{alias}; + $data->{$ya->{name}} = $ya; + } + } + } elsif ($xt eq 'feature') { + my $feature = $xa; + + $feature->{require} = []; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'require'; + + push @{$feature->{require}}, loadRequire($data, $alias, $yn); + } + + push @{$features}, $feature; + } elsif ($xt eq 'extensions') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'extension'; + + my $ext = shift @{$yn}; + + $ext->{require} = []; + + while ($#{$yn} >= 0) { + my $zt = shift @{$yn}; + my $zn = shift @{$yn}; + + next if $zt ne 'require'; + + push @{$ext->{require}}, loadRequire($data, $alias, $zn); + } + + push @{$extensions}, $ext; + } + } else { + print "vulkan.pm: Ignore node: $xt\n"; + } + } +} + +# find an object including via alias +sub findData { + my $data = shift; + my $alias = shift; + my $name = shift; + + do { + my $s = $data->{$name}; + return $s if defined $s; + #print "alias $name => $alias->{$name}\n"; + $name = $alias->{$name}; + } while ($name); + + die "No match for type '$name'"; +} + +sub makeParameter { + my $name = shift; + my $fullType = shift; + my $type = $fullType; + + $type =~ s/const|\*|\s//gon; + + $fullType =~ s/\s{2,}/ /go; # collapse all whitespace to ' ' + + # canonicalise spaces in c type + #$fullType =~ s/(? $name, + Name => ucfirst($name), + fullType => $fullType, + baseType => $type, + type => $type, + }; +} + +# Convert function typedef into function info +sub analyseFunctionPointer { + my $s = shift; + + if ($s->{fullType} =~ m/^(.+)\s+\(VKAPI_PTR \*\)\((.*)\)$/o) { + my $rt = $1; + my @args = split /,/,$2; + + $s->{proto} = makeParameter('result$', $rt); + $s->{items} = []; + + if ($#args != 0 || $args[0] ne 'void') { + foreach my $a (@args) { + if (my ($fullType, $name) = $a =~ m/^(.*)\s+(\S+)$/o) { + push @{$s->{items}}, makeParameter($name, $fullType); + } else { + die "Unable to parse function pointer argument '$a'\n"; + } + } + } + } else { + die "Unable to parse function pointer prototype '$s->{fullType}'\n"; + } + $s->{Name} = $s->{name}; + + delete $s->{type}; + delete $s->{baseType}; + delete $s->{fullType}; +} + +sub loadMember { + my $nn = shift; + #my $x = (join '',split('\n',Dumper($nn))); $x =~ s/ +/ /g; print "load: $x\n"; + my $m = shift @{$nn}; + my $baseType = ""; + my $fullType = ""; + my $name = ""; + + while ($#{$nn} >= 0) { + my $pt = shift @{$nn}; + my $pn = shift @{$nn}; + + if ($pt eq '0') { + $fullType .= $pn; + } elsif ($pt eq 'type') { + die if $pn->[1] != 0; + $baseType = $pn->[2]; + $fullType .= $baseType; + } elsif ($pt eq 'name') { + die if $pn->[1] != 0; + $name = $pn->[2]; + } elsif ($pt eq 'enum') { + die if $pn->[1] != 0; + $fullType .= $pn->[2]; + } + } + + $fullType =~ s/^typedef (.*);$/\1/os; # strip out 'typedef' part + $fullType =~ s/\s{2,}/ /go; # collapse all whitespace to ' ' + + # canonicalise spaces in c type + #$fullType =~ s/(?{name} = $name; + $m->{baseType} = $baseType; + $m->{fullType} = $fullType; + + $m; +} + +sub loadRequire { + my $data = shift; + my $alias = shift; + my $nn = shift; + my $r = shift @{$nn}; + + $r->{enums} = []; + $r->{types} = []; + $r->{commands} = []; + + while ($#{$nn} >= 0) { + my $mt = shift @{$nn}; + my $mn = shift @{$nn}; + + if ($mt eq 'type') { + my $ma = shift @{$mn}; + push @{$r->{types}}, $ma->{name}; + } elsif ($mt eq 'command') { + my $ma = shift @{$mn}; + push @{$r->{commands}}, $ma->{name}; + } elsif ($mt eq 'enum') { + my $ma = shift @{$mn}; + push @{$r->{enums}}, $ma; + } + } + + $r; +} + +sub findElements { + my $n = shift; + my $name = shift; + my @list; + + while ($#{$n} >= 0) { + my $tag = shift @{$n}; + my $con = shift @{$n}; + + if ($tag eq $name) { + push @list, [$tag, $con]; + } + } + @list; +} + +sub scanElements { + my $n = shift; + + while ($#{$n} >= 0) { + my $tag = shift @{$n}; + my $con = shift @{$n}; + + print "$#{$n} "; + print "tag $tag\n"; + } +} + +1; diff --git a/src/notzed.xcb/classes/module-info.java b/src/notzed.xcb/classes/module-info.java new file mode 100644 index 0000000..4b1e747 --- /dev/null +++ b/src/notzed.xcb/classes/module-info.java @@ -0,0 +1,6 @@ + +module notzed.xcb { + requires transitive notzed.nativez; + + exports xcb; +} diff --git a/src/notzed.xcb/classes/xcb/Connection.java b/src/notzed.xcb/classes/xcb/Connection.java new file mode 100644 index 0000000..27ff8fc --- /dev/null +++ b/src/notzed.xcb/classes/xcb/Connection.java @@ -0,0 +1,28 @@ + +package xcb; + +import jdk.incubator.foreign.*; +import au.notzed.nativez.Pointer; + +/* small hack to get it to compile, unimplemented functions so far */ + +public class Connection implements Pointer { + NativeSymbol symbol; + + public Connection(MemoryAddress addr, ResourceScope scope) { + symbol = NativeSymbol.ofAddress("Connection", addr, scope); + } + + public static Connection create(MemoryAddress addr, ResourceScope scope) { + return new Connection(addr, scope); + } + + public MemoryAddress address() { + return symbol.address(); + } + + public ResourceScope scope() { + return symbol.scope(); + } + +} diff --git a/src/notzed.xlib/classes/module-info.java b/src/notzed.xlib/classes/module-info.java new file mode 100644 index 0000000..d424a8d --- /dev/null +++ b/src/notzed.xlib/classes/module-info.java @@ -0,0 +1,6 @@ + +module notzed.xlib { + requires notzed.nativez; + + exports xlib; +} diff --git a/src/notzed.xlib/gen/gen.make b/src/notzed.xlib/gen/gen.make new file mode 100644 index 0000000..922b827 --- /dev/null +++ b/src/notzed.xlib/gen/gen.make @@ -0,0 +1,3 @@ + +notzed.xlib_API = xlib +notzed.xlib_APIFLAGS = -t xlib -Isrc/notzed.xlib/gen diff --git a/src/notzed.xlib/gen/xlib.api b/src/notzed.xlib/gen/xlib.api new file mode 100644 index 0000000..56f994b --- /dev/null +++ b/src/notzed.xlib/gen/xlib.api @@ -0,0 +1,79 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +%include types.api; +%include code.api; + +struct default=all access=rw rename=s/^_// field:rename=studly-caps { + class rename=XClass; + visualid rename=VisualID; + xid rename=XID; +} + +union default=all access=rw rename=s/^_// field:rename=studly-caps { +} + +struct _XPrivDisplay { + screens array array-size=nscreens access=r; +} + +struct Screen access=rw + template=code:class=struct-array +{ +} + +#struct Visual { +# Class field:rename=XClass; +#} + +#struct XVisualInfo { +# Class rename=XClass; +#} + +library XLib { + define:xlib; + func:XCreateColormap; + func:XCreateWindow; + func:XDestroyWindow; + func:XFlush; + func:XGetVisualInfo; + func:XInitThreads; + func:XInternAtom; + func:XMapWindow; + func:XOpenDisplay; + func:XCloseDisplay; + func:XSelectInput; + + func:XSetWMProtocols; + + func:XPending; + func:XNextEvent; + func:XWindowEvent; + func:XCheckWindowEvent; + + func:XGetWindowAttributes; + +# #define DefaultScreen(dpy) (((_XPrivDisplay)(dpy))->default_screen) +# #define RootWindow(dpy, scr) (ScreenOfDisplay(dpy,scr)->root) +# #define ScreenOfDisplay(dpy, scr)(&((_XPrivDisplay)(dpy))->screens[scr]) + + code: {{ + public static int DefaultScreen(XDisplay dpy) { + XPrivDisplay pdpy = XPrivDisplay.create(dpy.address, dpy.scope); + return pdpy.getDefaultScreen(); + } + + public static long RootWindow(XDisplay dpy, int scr) { + XPrivDisplay pdpy = XPrivDisplay.create(dpy.address, dpy.scope); + Screen screen = pdpy.getScreens().getAtIndex(scr); + return screen.getRoot(); + } + + }} + +} + +define xlib xlib.h { + /Pixel$|^Input|^CW|Mask$|^Alloc/ include; + /KeyPress|KeyRelease|ButtonPress|ButtonRelease|MotionNotify|EnterNotify|LeaveNotify|FocusIn|FocusOut|KeymapNotify|Expose|GraphicsExpose|NoExpose|VisibilityNotify|CreateNotify|DestroyNotify|UnmapNotify|MapNotify|MapRequest|ReparentNotify|ConfigureNotify|ConfigureRequest|GravityNotify|ResizeRequest|CirculateNotify|CirculateRequest|PropertyNotify|SelectionClear|SelectionRequest|SelectionNotify|ColormapNotify|ClientMessage|MappingNotify|GenericEvent/ include; + +} diff --git a/src/notzed.xlib/gen/xlib.h b/src/notzed.xlib/gen/xlib.h new file mode 100644 index 0000000..18f0d17 --- /dev/null +++ b/src/notzed.xlib/gen/xlib.h @@ -0,0 +1,2 @@ + +#include