From da10f7c5cdae2720a17f45f112fa7821e74691ee Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Sun, 9 Oct 2022 17:25:45 +0300 Subject: [PATCH] Initial commit --- .gitignore | 14 + CMakeLists.txt | 74 ++ LICENSE | 674 +++++++++++++++ README.md | 34 + assets/texture.png | Bin 0 -> 706 bytes assets/texture2.png | Bin 0 -> 7074 bytes desktop/graphics/glfw_mgmt.cpp | 69 ++ desktop/graphics/glfw_mgmt.h | 13 + desktop/graphics/glfw_mgmt_details.h | 14 + desktop/graphics/shaders/shader.frag | 12 + desktop/graphics/shaders/shader.vert | 62 ++ desktop/graphics/vulkan_adapter.cpp | 336 ++++++++ desktop/graphics/vulkan_adapter.h | 72 ++ desktop/graphics/vulkan_buffer.h | 196 +++++ desktop/graphics/vulkan_common.cpp | 776 ++++++++++++++++++ desktop/graphics/vulkan_common.h | 289 +++++++ desktop/graphics/vulkan_descriptor_set.cpp | 19 + desktop/graphics/vulkan_descriptor_set.h | 23 + desktop/graphics/vulkan_frame.cpp | 171 ++++ desktop/graphics/vulkan_frame.h | 36 + desktop/graphics/vulkan_image.cpp | 247 ++++++ desktop/graphics/vulkan_image.h | 60 ++ desktop/graphics/vulkan_mgmt.cpp | 68 ++ desktop/graphics/vulkan_mgmt.h | 23 + desktop/graphics/vulkan_pick_device.cpp | 98 +++ desktop/graphics/vulkan_pick_device.h | 21 + desktop/graphics/vulkan_pipeline.cpp | 223 +++++ desktop/graphics/vulkan_pipeline.h | 27 + desktop/graphics/vulkan_render_pass.cpp | 83 ++ desktop/graphics/vulkan_render_pass.h | 23 + desktop/graphics/vulkan_swap_chain.cpp | 328 ++++++++ desktop/graphics/vulkan_swap_chain.h | 58 ++ .../graphics/vulkan_texture_descriptors.cpp | 106 +++ desktop/graphics/vulkan_texture_descriptors.h | 29 + desktop/graphics/vulkan_uniform.h | 76 ++ desktop/graphics/vulkan_uniform.inl | 194 +++++ desktop/main.cpp | 35 + docs/BuildingGuide.md | 88 ++ docs/DevelopmentSetupGuide.md | 63 ++ main/game.cpp | 178 ++++ main/game.h | 13 + main/meta.h | 44 + main/rendering.h | 8 + main/rendering/graphics_interface.h | 123 +++ main/rendering/image.cpp | 64 ++ main/rendering/image.h | 25 + main/stb_image.c | 4 + main/util.h | 30 + tools/bashlib.sh | 66 ++ tools/build.sh | 170 ++++ tools/clang-format/clang-format.yml | 4 + tools/clang-format/use-clang-format.sh | 104 +++ tools/cmake/embed.cmake | 56 ++ tools/cppcheck/options.txt | 28 + tools/cppcheck/suppressions.txt | 15 + tools/cppcheck/use-cppcheck.sh | 65 ++ tools/embed/embed.py | 298 +++++++ tools/git/hook_pre_commit.sh | 51 ++ tools/memcheck/suppressions.supp | 28 + tools/setup.sh | 147 ++++ 60 files changed, 6255 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/texture.png create mode 100644 assets/texture2.png create mode 100644 desktop/graphics/glfw_mgmt.cpp create mode 100644 desktop/graphics/glfw_mgmt.h create mode 100644 desktop/graphics/glfw_mgmt_details.h create mode 100644 desktop/graphics/shaders/shader.frag create mode 100644 desktop/graphics/shaders/shader.vert create mode 100644 desktop/graphics/vulkan_adapter.cpp create mode 100644 desktop/graphics/vulkan_adapter.h create mode 100644 desktop/graphics/vulkan_buffer.h create mode 100644 desktop/graphics/vulkan_common.cpp create mode 100644 desktop/graphics/vulkan_common.h create mode 100644 desktop/graphics/vulkan_descriptor_set.cpp create mode 100644 desktop/graphics/vulkan_descriptor_set.h create mode 100644 desktop/graphics/vulkan_frame.cpp create mode 100644 desktop/graphics/vulkan_frame.h create mode 100644 desktop/graphics/vulkan_image.cpp create mode 100644 desktop/graphics/vulkan_image.h create mode 100644 desktop/graphics/vulkan_mgmt.cpp create mode 100644 desktop/graphics/vulkan_mgmt.h create mode 100644 desktop/graphics/vulkan_pick_device.cpp create mode 100644 desktop/graphics/vulkan_pick_device.h create mode 100644 desktop/graphics/vulkan_pipeline.cpp create mode 100644 desktop/graphics/vulkan_pipeline.h create mode 100644 desktop/graphics/vulkan_render_pass.cpp create mode 100644 desktop/graphics/vulkan_render_pass.h create mode 100644 desktop/graphics/vulkan_swap_chain.cpp create mode 100644 desktop/graphics/vulkan_swap_chain.h create mode 100644 desktop/graphics/vulkan_texture_descriptors.cpp create mode 100644 desktop/graphics/vulkan_texture_descriptors.h create mode 100644 desktop/graphics/vulkan_uniform.h create mode 100644 desktop/graphics/vulkan_uniform.inl create mode 100644 desktop/main.cpp create mode 100644 docs/BuildingGuide.md create mode 100644 docs/DevelopmentSetupGuide.md create mode 100644 main/game.cpp create mode 100644 main/game.h create mode 100644 main/meta.h create mode 100644 main/rendering.h create mode 100644 main/rendering/graphics_interface.h create mode 100644 main/rendering/image.cpp create mode 100644 main/rendering/image.h create mode 100644 main/stb_image.c create mode 100644 main/util.h create mode 100644 tools/bashlib.sh create mode 100755 tools/build.sh create mode 100644 tools/clang-format/clang-format.yml create mode 100755 tools/clang-format/use-clang-format.sh create mode 100644 tools/cmake/embed.cmake create mode 100644 tools/cppcheck/options.txt create mode 100644 tools/cppcheck/suppressions.txt create mode 100755 tools/cppcheck/use-cppcheck.sh create mode 100755 tools/embed/embed.py create mode 100755 tools/git/hook_pre_commit.sh create mode 100644 tools/memcheck/suppressions.supp create mode 100755 tools/setup.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7c3dd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Build directory +build + +# Run directory +run + +# Local environment setup file +tools/private.sh + +# Prevent anyone from accidentally uploading CMakeFiles +CMakeFiles + +# Some weirdos use Kate +*.kate-swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..650c997 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 3.10) + +project(progressia) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/cmake") +include(embed) + +add_executable(progressia + desktop/main.cpp + desktop/graphics/glfw_mgmt.cpp + desktop/graphics/vulkan_common.cpp + desktop/graphics/vulkan_frame.cpp + desktop/graphics/vulkan_image.cpp + desktop/graphics/vulkan_mgmt.cpp + desktop/graphics/vulkan_pick_device.cpp + desktop/graphics/vulkan_pipeline.cpp + desktop/graphics/vulkan_render_pass.cpp + desktop/graphics/vulkan_descriptor_set.cpp + desktop/graphics/vulkan_texture_descriptors.cpp + desktop/graphics/vulkan_adapter.cpp + desktop/graphics/vulkan_swap_chain.cpp + + main/game.cpp + + main/rendering/image.cpp + + main/stb_image.c + ${generated}/embedded_resources.cpp +) + +target_include_directories(progressia PRIVATE ${generated}) + +# Compilation settings +set_property(TARGET progressia PROPERTY CXX_STANDARD 17) +target_compile_options(progressia PRIVATE -Wall -Wextra -Wpedantic -Werror) + +# Pass version information +target_compile_definitions(progressia PRIVATE + _MAJOR=0 _MINOR=0 _PATCH=0 _BUILD=1) + +# Debug options +option(VULKAN_ERROR_CHECKING "Enable Vulkan validation layers to detect Vulkan API usage errors at runtime") +if (VULKAN_ERROR_CHECKING) + target_compile_definitions(progressia PRIVATE VULKAN_ERROR_CHECKING) +endif() + +# Libraries + +find_package(PkgConfig REQUIRED) + +# Use Vulkan +find_package(Vulkan REQUIRED) +target_link_libraries(progressia ${Vulkan_LIBRARIES}) +target_include_directories(progressia PUBLIC ${Vulkan_INCLUDE_DIRS}) + +# Use GLFW3 +find_package(glfw3 REQUIRED) +target_link_libraries(progressia glfw) + +# Use GLM +pkg_check_modules(GLM REQUIRED glm) +target_link_libraries(progressia ${GLM_LIBRARIES}) +target_include_directories(progressia PUBLIC ${GLM_INCLUDE_DIRS}) +target_compile_options(progressia PUBLIC ${GLM_CFLAGS_OTHER}) + +# Use STB +pkg_check_modules(STB REQUIRED stb) +target_link_libraries(progressia ${STB_LIBRARIES}) +target_include_directories(progressia PUBLIC ${STB_INCLUDE_DIRS}) +target_compile_options(progressia PUBLIC ${STB_CFLAGS_OTHER}) + +# Use Boost (header only) +find_package(Boost REQUIRED) +target_include_directories(progressia PUBLIC ${Boost_INCLUDE_DIRS}) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -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/README.md b/README.md new file mode 100644 index 0000000..843ff4a --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Progressia +A free, open-source sandbox survival game currently in early development. + +## Description + +The game has barely begun development so much of its features are yet to be +implemented. + +In broader terms, Progressia is a challenging game about survival, exploration +and engineering in a realistic voxel sandbox environment. The game is heavily +inspired by Minecraft technology mods, Factorio, Vintage Story and Minetest. +Progressia's main unique features will include highly composite items and +blocks, a realistically-scaled world, temperature mechanics and a +parallelism-capable server. + +See [Building Guide](docs/BuildingGuide.md) for building instructions. + +## Contributing + +All contributors welcome. Please contact Javapony in + [Telegram](https://t.me/javapony) +or join our + [Discord server](https://discord.gg/M4ukyPYgGP) +for details or help. + +## Libraries + - [Vulkan](https://vulkan.org/) – low-level graphics API + - [GLFW](https://www.glfw.org/) + ([GitHub](https://github.com/glfw/glfw)) – minimalistic windowing library + - [GLM](https://github.com/g-truc/glm) – vector mathematics library + - [STB (GitHub)](https://github.com/nothings/stb) – collection of various + algorithms + - `stb_image` – PNG loading + - [Boost](https://www.boost.org/) – utility library diff --git a/assets/texture.png b/assets/texture.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec49e8457f352f5c4fcd1f0e947eff36afe80b2 GIT binary patch literal 706 zcmV;z0zLhSP)EX>4Tx04R}tkv&MmKpe$iTg4Bm4t6N&kfAzR5G&%SRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRmKj!Ztv~iGtK^f0H=I%ys#yQ%>V!ZDo{*RMF0Q*0093H5fLIHBL4sYL_|dY z0RM=Hh^nfp0RI30|Nrjp?*IP)|Ns9cOw4Kk0004EOGiWihy@);00009a7bBm001r{ z001r{0eGc9b^rhX2XskIMF-{y7!EKBp?SU80000PbVXQnLvL+uWo~o;Lvm$dbY)~9 zcWHEJAV*0}P*;Ht7XSbNXh}ptR2b7^U_b`Ecab?4c<+KBgu}paffop1k|54q1m`X< z118{QKrsMl-UWsWFb>#6sHI@-7cdQQaXE8l=FC;A1O?O5h~@0w-O*8A9vW(FtfWLN o=PuBR7l1wj(O?eTr?_Nz0jNt!@s0lPF#rGn07*qoM6N<$g3u2rf&c&j literal 0 HcmV?d00001 diff --git a/assets/texture2.png b/assets/texture2.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6039de72d3a5c2f527d30707a32a61f627c475 GIT binary patch literal 7074 zcmV;T8(rjyP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3-BaU?sIh5y5fH33?f0~s+MjQRT=WLA;=M#OCF zG^2}TiA>;%BQ9w6-~V>Z|L`w`=1okc=9aVNUu?1Y&JWc-|N4D&Hr~hQm(OeN&*#ne z12qnk z7y8TZ=JQt1n*0tfSRSiZ81&zT&iBfr=XEv|55w?kJT{Uf=b;tDs>fj1_;KMm~4dM`kTSV3YjE|3A6=@plqE#8~r9D%GPsIzn* zq93HirQ{cj^f81?a_9o?&1Z_cZ}rJPPXmM`B86;1AvY~_k`-g3eoCy=P){Mnlu}M5 z)znhYA*;i3E?J1$ODM6Vl1nMIw9-utHP%#fEw$EGd-E-TxT(>Pt+v*B=cb)2cRt;D zL+`_nFycrfk230Lqfc*Um~p0=XPI@j*_U5o(Z?&Vvg&H9Z?`F>9e3J!mtA+;{ZMKr zoOsg7r<{74IsKcpH?9Bh^?%5kd$Zpc|3T*dF>n7Y zYpY+P3pYb*Lj~0ax^GwR?^}=CuHxN{f#0joEHWq;ZWT8dxRcFn;fy{`qgk_GD^R6G z=7cqtO_A8wIY)ysQirKxU&}WV9S4y_v&l)2b)D|JtFFO)S99pY&gHEI=5|iWeM5{0p?Ih_@cP7AX#H|> zHF2D^;+arLU4%R@!SmW$23dBk?1N%RiI<2>oyGdr#ylh0K}g zW0u_jBgi4UxMR+3)gHlBvUi3pW4RGi7+jKUm3fx$Getvp%+R=HJdqrXf->T|f!a>u znzpXO2_$-~L`K{oCTVcHID2OEZO0)zqEye`GuyL@in?=^eadDVFko$c*E~@SaLu7) z+F7lHo6#N4ycw&k!sK-JsQPsfE$(%CT(u40d)9mi0Rd*&t1qf#9>=z$Bm75E?4Azt ziIOo>#ZrovLgVOyeNy{UtC z5xsN(f=-ohUOQ2BN~h)CSE6l;ZZ^vp<&1Txeo=cm=8UkZ;>qS4wSv;vQ>P+Uo~Ezr$c@bsjCo7{L^}g1ZP_<^ zF+xz!(kP%rzNH#hQ0{4uvUa%Th*GF~Q4Yu)yQJ;1%FV7rC(+liDJZyoJtHGV z2_d~7f8QLP!4v3~4j8)B1isvQ7RtB=is#8Q3t%gWh4*3xRp0HV*G^Z#fg6Pm$!G2w zCn6>GN{Nga*gzj*o!|vYXP>^yoetp|et5)g4H#(`V^i zVnYt4$~IX&$*?E1i{;Cu35lUF$unGw@uSW`UKX)Cgqid932T9y0#?!CtXkQc{DEp)Y$A@7zL3W-P&*f8Ca27O3#j`XTZ%qRPlK*w4S`Q81 zcq5oIk%jfyjb)_^iO~yriU$Y+CfsPCiQANwYHmb>HlqC4FeG$qq!d=sfAAQVv7M5p z96|M^W+h%UL1VpD>;~e-Y6e7Ex&vm|p0J5t<2n>_-YUFYs0prdQMD4QEb#$q`mV-j z!;m6f2=~v+Jfwy`aElcVR3Rb?BS}>E8V~0tJ$e@vz$bh~0Yz0IJjyif#pML*TT=b- zWSx4 zDis5mItY@2Ota4of#?o=-?xgAG$iT+4oPSBZX5fZ+qFU7Kvbd%D&2>QHd!XaT+h1APxZn9I_c;e1b8GY;l%tH{RU0H)U*-$#S1-8gBI*u)f+4JXR)zxeb#nN8LgRb}hZ9b}Mb&@yneXW%6u((RV(>~bR zQYKx!j4x0a3Y*>mvKLDzHS;!VLzv872!+j|^H zVyVH9kdM?dpn3^%)BtBvzhdjbCMm*L)Rc65%A;kdSdKxVm(W6)kXMBe#~xsb``Bk^ zFCR^XQQld7Ua@@=c1_-!45H1b^%#ml?hZNqkvE+*F&J&?JyK$au3EnG>Q1%gy#wV> zi4AI+?RIHc3gd#hAtxo~8Ad|BZy3G{B`U49SVzpa&jy;n%3#>$^!bTkSW>4^YDg*Z z2GL$K7KDB$07VQ!lBV61jR>X-5*qz*UqbYwxIRDu4a2$^589KhtsijrsL6t^&l?z_ z+a}_bc5`e5;i_5#cV{iCts+3`QQ`(C)J#Qz+{H=cgT~`4GtlE{RdAulurCTjp{uoYMl z>#1z0JZON}lfL5tY^IJtCAy%fjt`-g*P7CRIZp+xs8|T=)9z~GCS`IZ)+Ufn1zi@t zU!;Mu5g@BA9f&k_Ut~J^m}E_1xZfs&? z(uIEzG9r1>>Zq9ZqmIyrplb{ptnT0MY*z&xVhkAb0*%x_Xsn|glYnne>4V|goWrb{4rl`Pcs$4a-}iBtAhF_ z2<=}{Bk~Hbgu3N|-ke?T6>#&g<00&75|~PN9b2M!X6g~`o>yXpn}#E37VIN7sE2WB8bcH#O^9EuZ?NCQM56h^>SBFpUyM$KdVn#``T^t?pNp}48Zt`+8A zmQ;fCsF`?1z6X^wxh7>Yv2F5-0Dz6lpn`x?cEvh@Hs>H$bk50FiCB3woaV1aXC+ej z@TTp2s9XoMJ3lYusQ-D0y zf*B<+0oo*Wdw~{dwW6s7R@KslCrbZy6P5}_7%y-M^{v7adtGA3&4A=U&>k$4ehYPr zX(3<=A_-|ryds-rWy}Q%`wp5T;`TZ|-R8P1O+7X~VQOfqQH!!;C zNmexFzA#V#hN0KpZ6gUutE8oaVR1@#WLx6})2~Hyufh3(clY|Jx zxBEH<Q$QNbLRLjpZ>%~^MT9^C@%qo;gun=VW z#vmxb@R18h0|x@&#Bd<97FPz2h^Ib>)dM*R?TViFP*O=cn$}7HG;cC{Y2f#98q{}E z@192&@l$1ksKa7G*ogx10yHEvWj_ZJL&L^J`DOU4bLOvJ-_oE zo;yaiVSQ|aLpgw205qZjB0+mYA=9ZJgaSw~%%OF}kpih2#33o}=*vhVOw@q0qVxFJ zRT5g%BG`f>(q3wrOxRfhL`LU@&U1%8vQYyw%rT!#_FDES8FK_8J|a4cxC73hBD<@w zB{%L@jgL%CA$Oj)<00WMjeVZ^DP0i>NR*HGLS)ijoR?}YPCzfU0q~_e%yDA7Kuo2j z%oS5Zau49A-mJX4wr+ooD}$HJ;)N)3AFyP(k}N*Oi+Rol5WycJWntATkgGlmbksYY zjAaGch-Z=!@VqosPfL>JcJ~!mu(A2=F|tMyL;JiQLv`I#u>!e>!1g16TPR8{Jmlkt z1YCJsu*r2IsDYqRR69UU&%XJ&&+#EV)dPA}P+Ii!n7I66FN z^<&QaVXL7`6K=5B7PMO2EkY92OQ+wWs4x=sgg-}(T-5>Tk7uCt&XZ7CYehAPoI|+b zO*%BY26DiPuYnY&VIc9epP^IbI@JQC4z_@hLUR>woeY8W7`b%leb-E-ZshwKBE3TT#Af2ABag!h-A zEAwLmb)2KoSj5ZP$0-64kf;9~)Rb!jb6T1f=oCY0?WY^_l?DmBno%$&H%#v%WdPlu zaof<*kQy$E{EMc51nx(!xmV?Y(3|5mScgX~@U>dr63FL%m}p zx`ujv&V}r#N9OojXyn@7*|K*ZS)OERCk^IErwCo8c?Ko%9`H!F2hY~t;V)`c=P>g# zv>{flYOFlHPW7w!1Mo*6WIz8rtpH-CP9Y|`maHu>s%RjZaYDi6KMYyBM?DewW;;#f z1(d4kUsHV(Cz|GX+jN`&4YE=VK6Fw>DL&3j(-;k7ypOUE-$s=osMC?pF^qgQu5o># zr`sFBHoXoes@RU~?fEm{1fk*M(sUA01o;iQZx*QI4aELGuRT+W;R)aQh2k~xnP!>>8ZT)z7lDZ z*WO%frVTmyRg5!2mL9UVhs=B26d|Y3YzEP0PajWY;@)BI$+h{z3Ze4(M6|x z4ggy)k1{#)8UC(2Skkg|8dsjvIOzWhdbBvr(rnapP68JiI#=w`Qc!k^;@;;R!ec67 zZ}96o8PH_X?TnBsI&WrE{&5h9`n)rcXm_hRg^*2Wkz8N;IE5oyF0Up8)cw>~z_cii zW!RHFKdJ>^-N_WR+dC=B$iU)QG1``XL_tZJY5$Ati_#>F6~6CSzqr5 z0WF;levVlOo)(Avd-79bbU&+aD4yZKV2@Oh;#%`|QHmPxTnIk_ zJplIYp2AC?tas=FX5l{y?w{=q(Djg=16$BT2<{?iHCv-wuX9hlCFUgk=yT9+^*nT{ zbFJX%k7v5pF9;cxGvd75Q^LZ@f^L z;vFvBY1n!vyyZIziMv|;MMag$*i;~X!dF8O%2gH~BnzUDgw?!?hRffRXV3~~erG4=n@&+RXp}02y>eSad^gZEa<4bO1wgWnpw>WFU8GbZ8() zNlj2!fese{00m}AL_t(Y$6b}{avR4Hg}<4dz3&1fK!TEJNv13%e%Rz;@-|WIluh#@ z2?PjW@4IvJk7ZmH{hOMa=|0`(^l9iK4 zqnQX~zz;WX(M&wu?aA{T(@czZf*QgiBvb(y$J5gu>8!A!I@X-!ON`eX{x~w4fh^65%Yv`luV`lw-eaw&scM$15{aLiHB>WH87iEG zV3OofI&5ykVJF`6t& z7_3HSMaJo&V*k9ONHW48AdM47Jt35$?>mnDiEe25zcKcjvDd`$9AKLCU*%pct|;>b&Uhx*g9j%)=j#=55Hso# zCFem5f?(z}3g$qY%?5yeXc>o|-EK!9B>)$T3qlqBmFRr6;&?bwjYmWGJveK*xV#`<#EjL*=~R=1 z2`UX~tA;pBUKAOi{)g)FYOX*`u-oncSg%%d(k#VlgL95v5A%m!AcRB+Nti0aFvdE| zw%zh3x#siTzj?F1Mz&2&S2z6n%V(H8wQh)ugt$o1Ruf;P3|-G*e?$d}Sj1#y#^^_k zG=wrFPYUX)0^oXi&Ex)oH|uLGb|yJaQ*LfP^6Tw?2m(dl4PmLcz80ndZ>`*an4OyAdK6F@V(N?2)QH&CVQV~WmpFVz^?}A{a;bL`xl=CpfD#93x zNF-g~^2=|ZiBiS-eC8zb5mi?cI>k6nbVElND4g+pd3q#Mv&EE2iOh?fGB0Vzp0=CK z&SG(+!AzPW%DH~~o-$bgP&XB!P(*3O^LQYTlJqPgNCes%F4h}vx3~0qUQ}vm=YyU! zaTt;&bM`P(ST}E}OeRdjM3!ZId;Us0bgYXtC}{d-w)3-uBu?m?md*JkNt6(rdt{VG zWLZI|6lt01V0jCBq>TH1Q< zhI-x}{!jDXJG>NhZO3`JB$El9>j`YY=IjzFC7(Y0j2$#}I|n&5&;0QE-8@I$gM+qh z`S#x(@_WtWX2VMJgmIeaYzMu^Nk@=`Ojcuu*`LBNpzk}*ixt*cj;Cj|HV6bkI9rJP zUh`5Z1dwM1*&?5l6)7HvpDcA<(T^`}%OVzwlD?mLTrJjEt3e8^gl_DS-)kN~qIlYE z>8ci?1w%h@cYhB`vfiv1#vbD|Q#bG9G))=W4($zf-7slQ;O6c2C;Zpof>Ecm)c^nh M07*qoM6N<$f-SgSJOBUy literal 0 HcmV?d00001 diff --git a/desktop/graphics/glfw_mgmt.cpp b/desktop/graphics/glfw_mgmt.cpp new file mode 100644 index 0000000..2a60b7f --- /dev/null +++ b/desktop/graphics/glfw_mgmt.cpp @@ -0,0 +1,69 @@ +#include "glfw_mgmt_details.h" + +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include "vulkan_mgmt.h" + +namespace progressia { +namespace desktop { + +static GLFWwindow *window = nullptr; + +static void onGlfwError(int errorCode, const char *description); +static void onWindowGeometryChange(GLFWwindow *window, int width, int height); + +void initializeGlfw() { + std::cout << "Beginning GLFW init" << std::endl; + + glfwSetErrorCallback(onGlfwError); + + if (!glfwInit()) { + std::cout << "glfwInit() failed" << std::endl; + // REPORT_ERROR + exit(1); + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + + window = glfwCreateWindow(800, 800, "Progressia", nullptr, nullptr); + + glfwSetWindowSizeCallback(window, onWindowGeometryChange); + + std::cout << "GLFW init complete" << std::endl; +} + +void showWindow() { + glfwShowWindow(window); + std::cout << "Window now visible" << std::endl; +} + +bool shouldRun() { return !glfwWindowShouldClose(window); } + +void doGlfwRoutine() { glfwPollEvents(); } + +void shutdownGlfw() { glfwTerminate(); } + +void onGlfwError(int errorCode, const char *description) { + std::cout << "[GLFW] " << description << " (" << errorCode << ")" + << std::endl; + // REPORT_ERROR + exit(1); +} + +void onWindowGeometryChange(GLFWwindow *window, [[maybe_unused]] int width, + [[maybe_unused]] int height) { + if (window != progressia::desktop::window) { + return; + } + + resizeVulkanSurface(); +} + +GLFWwindow *getGLFWWindowHandle() { return window; } + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/glfw_mgmt.h b/desktop/graphics/glfw_mgmt.h new file mode 100644 index 0000000..16499ee --- /dev/null +++ b/desktop/graphics/glfw_mgmt.h @@ -0,0 +1,13 @@ +#pragma once + +namespace progressia { +namespace desktop { + +void initializeGlfw(); +void showWindow(); +void shutdownGlfw(); +bool shouldRun(); +void doGlfwRoutine(); + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/glfw_mgmt_details.h b/desktop/graphics/glfw_mgmt_details.h new file mode 100644 index 0000000..83f8266 --- /dev/null +++ b/desktop/graphics/glfw_mgmt_details.h @@ -0,0 +1,14 @@ +#pragma once + +#include "glfw_mgmt.h" + +#define GLFW_INCLUDE_VULKAN +#include + +namespace progressia { +namespace desktop { + +GLFWwindow *getGLFWWindowHandle(); + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/shaders/shader.frag b/desktop/graphics/shaders/shader.frag new file mode 100644 index 0000000..33d7bdb --- /dev/null +++ b/desktop/graphics/shaders/shader.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(set = 1, binding = 0) uniform sampler2D texSampler; + +layout(location = 0) in vec4 fragColor; +layout(location = 2) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = fragColor * texture(texSampler, fragTexCoord); +} diff --git a/desktop/graphics/shaders/shader.vert b/desktop/graphics/shaders/shader.vert new file mode 100644 index 0000000..cc03adc --- /dev/null +++ b/desktop/graphics/shaders/shader.vert @@ -0,0 +1,62 @@ +#version 450 + +layout(set = 0, binding = 0) uniform Projection { + mat4 m; +} projection; +layout(set = 0, binding = 1) uniform View { + mat4 m; +} view; + +layout(push_constant) uniform PushContants { + layout(offset = 0) mat3x4 model; +} push; + +layout(set = 2, binding = 0) uniform Light { + vec4 color; + vec4 from; + float contrast; + float softness; +} light; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec4 inColor; +layout(location = 2) in vec3 inNormal; +layout(location = 3) in vec2 inTexCoord; + +layout(location = 0) out vec4 fragColor; +layout(location = 2) out vec2 fragTexCoord; + +void main() { + mat4 model = mat4(push.model); + + gl_Position = projection.m * view.m * model * vec4(inPosition, 1); + + fragColor.a = inColor.a; + + float exposure = dot(light.from.xyz, (model * vec4(inNormal, 1)).xyz); + if (exposure < -light.softness) { + fragColor.rgb = inColor.rgb * ( + (exposure + 1) * ((0.5 - light.contrast) / (1 - light.softness)) + ); + } else if (exposure < light.softness) { + // FIXME + fragColor.rgb = + inColor.rgb + * ( + 0.5 + exposure * light.contrast / light.softness + ) + * ( + (+exposure / light.contrast + 1) / 2 * light.color.rgb + + (-exposure / light.contrast + 1) / 2 * vec3(1, 1, 1) + ); + } else { + fragColor.rgb = + inColor.rgb + * ( + 0.5 + light.contrast + (exposure - light.softness) * ((0.5 - light.contrast) / (1 - light.softness)) + ) + * light.color.rgb; + } + + fragTexCoord = inTexCoord; +} diff --git a/desktop/graphics/vulkan_adapter.cpp b/desktop/graphics/vulkan_adapter.cpp new file mode 100644 index 0000000..48d4573 --- /dev/null +++ b/desktop/graphics/vulkan_adapter.cpp @@ -0,0 +1,336 @@ +#include "vulkan_adapter.h" + +#include "vulkan_common.h" + +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +#include +#include + +#include "../../main/rendering.h" +#include "vulkan_buffer.h" +#include "vulkan_frame.h" +#include "vulkan_pipeline.h" +#include "vulkan_swap_chain.h" +#include "vulkan_texture_descriptors.h" + +#include + +namespace progressia { +namespace desktop { + +using progressia::main::Vertex; + +namespace { + +struct FieldProperties { + uint32_t offset; + VkFormat format; +}; + +auto getVertexFieldProperties() { + return std::array{ + FieldProperties{offsetof(Vertex, position), VK_FORMAT_R32G32B32_SFLOAT}, + FieldProperties{offsetof(Vertex, color), VK_FORMAT_R32G32B32A32_SFLOAT}, + FieldProperties{offsetof(Vertex, normal), VK_FORMAT_R32G32B32_SFLOAT}, + FieldProperties{offsetof(Vertex, texCoord), VK_FORMAT_R32G32_SFLOAT}, + }; +} + +} // namespace + +namespace { +std::vector tmp_readFile(const std::string &path) { + auto resource = __embedded_resources::getEmbeddedResource(path.c_str()); + + if (resource.data == nullptr) { + // REPORT_ERROR + std::cerr << "Could not find resource \"" << path << "\"" << std::endl; + exit(1); + } + + return std::vector(resource.data, resource.data + resource.length); +} +} // namespace + +Adapter::Adapter(Vulkan &vulkan) + : vulkan(vulkan), viewUniform(0, vulkan), lightUniform(2, vulkan) { + + attachments.push_back( + {"Depth buffer", + + vulkan.findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, + VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT), + + VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ATTACHMENT_LOAD_OP_CLEAR, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + + {1.0f, 0}, + + nullptr}); +} + +Adapter::~Adapter() { + // Do nothing +} + +std::vector &Adapter::getAttachments() { return attachments; } + +std::vector Adapter::loadVertexShader() { + return tmp_readFile("shader.vert.spv"); +} + +std::vector Adapter::loadFragmentShader() { + return tmp_readFile("shader.frag.spv"); +} + +VkVertexInputBindingDescription Adapter::getVertexInputBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; +} + +std::vector +Adapter::getVertexInputAttributeDescriptions() { + std::vector attributeDescriptions; + + uint32_t i = 0; + for (auto props : getVertexFieldProperties()) { + attributeDescriptions.push_back({}); + + attributeDescriptions[i].binding = 0; + attributeDescriptions[i].location = i; + attributeDescriptions[i].format = props.format; + attributeDescriptions[i].offset = props.offset; + + i++; + } + + return attributeDescriptions; +} + +std::vector Adapter::getUsedDSLayouts() const { + return {viewUniform.getLayout(), vulkan.getTextureDescriptors().getLayout(), + lightUniform.getLayout()}; +} + +Adapter::ViewUniform::State Adapter::createView() { + return viewUniform.addState(); +} + +Adapter::LightUniform::State Adapter::createLight() { + return lightUniform.addState(); +} + +void Adapter::onPreFrame() { + viewUniform.doUpdates(); + lightUniform.doUpdates(); +} + +/* + * graphics_interface implementation + */ + +} // namespace desktop +namespace main { + +using namespace progressia::desktop; + +namespace { +struct DrawRequest { + progressia::desktop::Texture *texture; + IndexedBuffer *vertices; + glm::mat4 modelTransform; +}; + +std::vector pendingDrawCommands; +glm::mat4 currentModelTransform; +} // namespace + +progressia::main::Texture::Texture(Backend backend) : backend(backend) {} + +progressia::main::Texture::~Texture() { + delete static_cast(this->backend); +} + +namespace { +struct PrimitiveBackend { + + IndexedBuffer buf; + progressia::main::Texture *tex; +}; +} // namespace + +Primitive::Primitive(Backend backend) : backend(backend) {} + +Primitive::~Primitive() { + delete static_cast(this->backend); +} + +void Primitive::draw() { + auto backend = static_cast(this->backend); + + if (pendingDrawCommands.size() > 100000) { + backend->buf.getVulkan().getGint().flush(); + } + + pendingDrawCommands.push_back( + {static_cast(backend->tex->backend), + &backend->buf, currentModelTransform}); +} + +const progressia::main::Texture *Primitive::getTexture() const { + return static_cast(this->backend)->tex; +} + +View::View(Backend backend) : backend(backend) {} + +View::~View() { + delete static_cast(this->backend); +} + +void View::configure(const glm::mat4 &proj, const glm::mat4 &view) { + + static_cast(this->backend) + ->update(proj, view); +} + +void View::use() { + auto backend = static_cast(this->backend); + backend->uniform->getVulkan().getGint().flush(); + backend->bind(); +} + +Light::Light(Backend backend) : backend(backend) {} + +Light::~Light() { + delete static_cast(this->backend); +} + +void Light::configure(const glm::vec3 &color, const glm::vec3 &from, + float contrast, float softness) { + + static_cast(this->backend) + ->update(Adapter::Light{glm::vec4(color, 1.0f), + glm::vec4(glm::normalize(from), 1.0f), contrast, + softness}); +} + +void Light::use() { + auto backend = static_cast(this->backend); + backend->uniform->getVulkan().getGint().flush(); + backend->bind(); +} + +GraphicsInterface::GraphicsInterface(Backend backend) : backend(backend) {} + +GraphicsInterface::~GraphicsInterface() { + // Do nothing +} + +progressia::main::Texture * +GraphicsInterface::newTexture(const progressia::main::Image &src) { + auto backend = new progressia::desktop::Texture( + src, *static_cast(this->backend)); + + return new Texture(backend); +} + +Primitive * +GraphicsInterface::newPrimitive(const std::vector &vertices, + const std::vector &indices, + progressia::main::Texture *texture) { + + auto backend = new PrimitiveBackend{ + IndexedBuffer(vertices.size(), indices.size(), + *static_cast(this->backend)), + texture}; + + backend->buf.load(vertices.data(), indices.data()); + + return new Primitive(backend); +} + +View *GraphicsInterface::newView() { + return new View(new Adapter::ViewUniform::State( + static_cast(this->backend)->getAdapter().createView())); +} + +Light *GraphicsInterface::newLight() { + return new Light(new Adapter::LightUniform::State( + static_cast(this->backend)->getAdapter().createLight())); +} + +glm::vec2 GraphicsInterface::getViewport() const { + auto extent = + static_cast(this->backend)->getSwapChain().getExtent(); + return {extent.width, extent.height}; +} + +void GraphicsInterface::setModelTransform(const glm::mat4 &m) { + currentModelTransform = m; +} + +void GraphicsInterface::flush() { + + auto commandBuffer = static_cast(this->backend) + ->getCurrentFrame() + ->getCommandBuffer(); + auto pipelineLayout = + static_cast(this->backend)->getPipeline().getLayout(); + + progressia::desktop::Texture *lastTexture = nullptr; + + for (auto &cmd : pendingDrawCommands) { + if (cmd.texture != lastTexture) { + lastTexture = cmd.texture; + cmd.texture->bind(); + } + + auto &m = cmd.modelTransform; + // Evil transposition: column_major -> row_major + // clang-format off + std::remove_reference_t::value_type src[3*4] { + m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3] + }; + // clang-format on + + vkCmdPushConstants( + // REPORT_ERROR if getCurrentFrame() == nullptr + commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, + sizeof(src), &src); + + cmd.vertices->draw(commandBuffer); + } + + pendingDrawCommands.clear(); +} + +float GraphicsInterface::tmp_getTime() { return glfwGetTime(); } + +uint64_t GraphicsInterface::getLastStartedFrame() { + return static_cast(this->backend)->getLastStartedFrame(); +} + +} // namespace main +} // namespace progressia diff --git a/desktop/graphics/vulkan_adapter.h b/desktop/graphics/vulkan_adapter.h new file mode 100644 index 0000000..15446b4 --- /dev/null +++ b/desktop/graphics/vulkan_adapter.h @@ -0,0 +1,72 @@ +#pragma once + +#include "boost/core/noncopyable.hpp" +#include "vulkan_common.h" +#include "vulkan_descriptor_set.h" +#include "vulkan_image.h" +#include "vulkan_uniform.h" + +namespace progressia { +namespace desktop { + +class Attachment { + public: + const char *name; + + VkFormat format; + VkImageAspectFlags aspect; + VkImageUsageFlags usage; + + VkImageLayout workLayout; + VkImageLayout finalLayout; + VkAttachmentLoadOp loadOp; + VkAttachmentStoreOp storeOp; + + VkClearValue clearValue; + + std::unique_ptr image; +}; + +class Adapter : public VkObjectWrapper { + public: + using ViewUniform = Uniform; + + struct Light { + glm::vec4 color; + glm::vec4 from; + float contrast; + float softness; + }; + + using LightUniform = Uniform; + + private: + Vulkan &vulkan; + + ViewUniform viewUniform; + LightUniform lightUniform; + + std::vector attachments; + + public: + Adapter(Vulkan &); + ~Adapter(); + + std::vector &getAttachments(); + + VkVertexInputBindingDescription getVertexInputBindingDescription(); + std::vector + getVertexInputAttributeDescriptions(); + + std::vector loadVertexShader(); + std::vector loadFragmentShader(); + + ViewUniform::State createView(); + LightUniform::State createLight(); + + std::vector getUsedDSLayouts() const; + void onPreFrame(); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_buffer.h b/desktop/graphics/vulkan_buffer.h new file mode 100644 index 0000000..352bc71 --- /dev/null +++ b/desktop/graphics/vulkan_buffer.h @@ -0,0 +1,196 @@ +#pragma once + +#include +#include + +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +/* + * A single buffer with a chunk of allocated memory. + */ +template class Buffer : public VkObjectWrapper { + + private: + std::size_t itemCount; + + public: + VkBuffer buffer; + VkDeviceMemory memory; + + Vulkan &vulkan; + + Buffer(std::size_t itemCount, VkBufferUsageFlags usage, + VkMemoryPropertyFlags properties, Vulkan &vulkan) + : + + itemCount(itemCount), vulkan(vulkan) { + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = getSize(); + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + vulkan.handleVkResult( + "Could not create a buffer", + vkCreateBuffer(vulkan.getDevice(), &bufferInfo, nullptr, &buffer)); + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(vulkan.getDevice(), buffer, + &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = + vulkan.findMemoryType(memRequirements.memoryTypeBits, properties); + + vulkan.handleVkResult( + "Could not allocate memory for a buffer", + vkAllocateMemory(vulkan.getDevice(), &allocInfo, nullptr, &memory)); + + vkBindBufferMemory(vulkan.getDevice(), buffer, memory, 0); + } + + ~Buffer() { + if (buffer != VK_NULL_HANDLE) { + vkDestroyBuffer(vulkan.getDevice(), buffer, nullptr); + } + + if (memory != VK_NULL_HANDLE) { + vkFreeMemory(vulkan.getDevice(), memory, nullptr); + } + } + + std::size_t getItemCount() const { return itemCount; } + + std::size_t getSize() const { return sizeof(Item) * itemCount; } + + void *map() { + void *dst; + vkMapMemory(vulkan.getDevice(), memory, 0, getSize(), 0, &dst); + return dst; + } + + void unmap() { vkUnmapMemory(vulkan.getDevice(), memory); } +}; + +/* + * A buffer that is optimized for reading by the device. This buffer uses a + * secondary staging buffer. + */ +template class FastReadBuffer : public VkObjectWrapper { + private: + VkCommandBuffer commandBuffer; + Vulkan &vulkan; + + public: + Buffer stagingBuffer; + Buffer remoteBuffer; + + FastReadBuffer(std::size_t itemCount, VkBufferUsageFlags usage, + Vulkan &vulkan) + : + + vulkan(vulkan), + + stagingBuffer(itemCount, usage | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vulkan), + + remoteBuffer(itemCount, usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vulkan) { + recordCopyCommands(); + } + + ~FastReadBuffer() { vulkan.getCommandPool().freeMultiUse(commandBuffer); } + + private: + void recordCopyCommands() { + commandBuffer = vulkan.getCommandPool().beginMultiUse(); + + VkBufferCopy copyRegion{}; + copyRegion.srcOffset = 0; + copyRegion.dstOffset = 0; + copyRegion.size = getSize(); + vkCmdCopyBuffer(commandBuffer, stagingBuffer.buffer, + remoteBuffer.buffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + } + + public: + void flush() const { + vulkan.getCommandPool().submitMultiUse(commandBuffer, true); + } + + void load(const Item *data) const { + void *dst; + vkMapMemory(vulkan.getDevice(), stagingBuffer.memory, 0, getSize(), 0, + &dst); + memcpy(dst, data, getSize()); + vkUnmapMemory(vulkan.getDevice(), stagingBuffer.memory); + + flush(); + } + + std::size_t getItemCount() const { return stagingBuffer.getItemCount(); } + + std::size_t getSize() const { return stagingBuffer.getSize(); } + + Vulkan &getVulkan() { return vulkan; } + + const Vulkan &getVulkan() const { return vulkan; } +}; + +/* + * A pair of a vertex buffer and an index buffer. + */ +template +class IndexedBufferBase : public VkObjectWrapper { + + private: + FastReadBuffer vertexBuffer; + FastReadBuffer indexBuffer; + + public: + IndexedBufferBase(std::size_t vertexCount, std::size_t indexCount, + Vulkan &vulkan) + : + + vertexBuffer(vertexCount, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vulkan), + indexBuffer(indexCount, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, vulkan) { + + // Do nothing + } + + void load(const Vertex *vertices, const Index *indices) const { + vertexBuffer.load(vertices); + indexBuffer.load(indices); + } + + void draw(VkCommandBuffer commandBuffer) { + VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, + &vertexBuffer.remoteBuffer.buffer, &offset); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer.remoteBuffer.buffer, 0, + INDEX_TYPE); + vkCmdDrawIndexed(commandBuffer, + static_cast(indexBuffer.getItemCount()), 1, + 0, 0, 0); + } + + Vulkan &getVulkan() { return vertexBuffer.getVulkan(); } + + const Vulkan &getVulkan() const { return vertexBuffer.getVulkan(); } +}; + +template +using IndexedBuffer = IndexedBufferBase; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_common.cpp b/desktop/graphics/vulkan_common.cpp new file mode 100644 index 0000000..f28f56c --- /dev/null +++ b/desktop/graphics/vulkan_common.cpp @@ -0,0 +1,776 @@ +#include "vulkan_common.h" + +#include "vulkan_adapter.h" +#include "vulkan_frame.h" +#include "vulkan_pick_device.h" +#include "vulkan_pipeline.h" +#include "vulkan_render_pass.h" +#include "vulkan_swap_chain.h" +#include "vulkan_texture_descriptors.h" + +#include "../../main/meta.h" +#include "glfw_mgmt_details.h" + +namespace progressia { +namespace desktop { + +/* + * Vulkan + */ + +Vulkan::Vulkan(std::vector instanceExtensions, + std::vector deviceExtensions, + std::vector validationLayers) + : + + frames(MAX_FRAMES_IN_FLIGHT), isRenderingFrame(false), + lastStartedFrame(0) { + + /* + * Create error handler + */ + errorHandler = std::make_unique(*this); + + /* + * Create instance + */ + { + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + + // Set application data + + using namespace progressia::main::meta; + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = NAME; + appInfo.applicationVersion = + VK_MAKE_VERSION(VERSION.major, VERSION.minor, VERSION.patch); + appInfo.pEngineName = nullptr; + appInfo.engineVersion = 0; + appInfo.apiVersion = VK_API_VERSION_1_0; + createInfo.pApplicationInfo = &appInfo; + + // Enable extensions + { + uint32_t extensionCount; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, + nullptr); + std::vector available(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, + available.data()); + + CstrUtils::CstrHashSet toFind(instanceExtensions.cbegin(), + instanceExtensions.cend()); + + for (const auto &extensionProperties : available) { + toFind.erase(extensionProperties.extensionName); + } + + if (!toFind.empty()) { + std::cout << "Could not locate following requested Vulkan " + "extensions:"; + for (const auto &extension : toFind) { + std::cout << "\n\t- " << extension; + } + std::cout << std::endl; + // REPORT_ERROR + exit(1); + } + } + + createInfo.enabledExtensionCount = + static_cast(instanceExtensions.size()); + createInfo.ppEnabledExtensionNames = instanceExtensions.data(); + + // Enable validation layers + { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + std::vector available(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, available.data()); + + CstrUtils::CstrHashSet toFind(validationLayers.cbegin(), + validationLayers.cend()); + + for (const auto &layerProperties : available) { + toFind.erase(layerProperties.layerName); + } + + if (!toFind.empty()) { + std::cout << "Could not locate following requested Vulkan " + "validation layers:"; + for (const auto &layer : toFind) { + std::cout << "\n\t- " << layer; + } + std::cout << std::endl; + // REPORT_ERROR + exit(1); + } + } + + createInfo.enabledLayerCount = + static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + // Setup one-use debug listener if necessary + + // cppcheck-suppress unreadVariable; bug in cppcheck <2.9 + auto debugProbe = errorHandler->attachDebugProbe(createInfo); + + // Create instance + + handleVkResult("Could not create VkInstance", + vkCreateInstance(&createInfo, nullptr, &instance)); + } + + /* + * Setup debug + */ + errorHandler->onInstanceReady(); + + /* + * Create surface + */ + surface = std::make_unique(*this); + + /* + * Pick physical device + */ + { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + std::cout << "No GPUs with Vulkan support found" << std::endl; + // REPORT_ERROR + exit(1); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + std::vector choices; + + for (const auto &device : devices) { + PhysicalDeviceData data = {}; + data.device = device; + + vkGetPhysicalDeviceProperties(device, &data.properties); + vkGetPhysicalDeviceFeatures(device, &data.features); + + choices.push_back(data); + } + + const auto &result = + pickPhysicalDevice(choices, *this, deviceExtensions); + physicalDevice = result.device; + } + + /* + * Setup queues + */ + + queues = std::make_unique(physicalDevice, *this); + + /* + * Create logical device + */ + { + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + // Specify queues + + // cppcheck-suppress unreadVariable; bug in cppcheck <2.9 + auto queueRequests = queues->requestCreation(createInfo); + + // Specify features + + VkPhysicalDeviceFeatures deviceFeatures{}; + createInfo.pEnabledFeatures = &deviceFeatures; + + // Specify device extensions + + createInfo.enabledExtensionCount = + static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + // Provide a copy of instance validation layers + + createInfo.enabledLayerCount = + static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + // Create logical device + + handleVkResult( + "Could not create logical device", + vkCreateDevice(physicalDevice, &createInfo, nullptr, &device)); + + // Store queue handles + + queues->storeHandles(device); + } + + /* + * Create command pool + */ + commandPool = + std::make_unique(*this, queues->getGraphicsQueue()); + + /* + * Create texture descriptor manager + */ + + textureDescriptors = std::make_unique(*this); + + /* + * Initialize adapter + */ + adapter = std::make_unique(*this); + + /* + * Initialize swap chain + */ + swapChain = std::make_unique(*this); + + /* + * Create render pass + */ + renderPass = std::make_unique(*this); + + /* + * Create pipeline + */ + pipeline = std::make_unique(*this); + + /* + * Create swap chain + */ + swapChain->recreate(); + + /* + * Create frames + */ + for (auto &container : frames) { + container.emplace(*this); + } + currentFrame = 0; + + gint = std::make_unique(this); +} + +Vulkan::~Vulkan() { + gint.reset(); + frames.clear(); + swapChain.reset(); + pipeline.reset(); + renderPass.reset(); + adapter.reset(); + textureDescriptors.reset(); + commandPool.reset(); + vkDestroyDevice(device, nullptr); + surface.reset(); + errorHandler.reset(); + vkDestroyInstance(instance, nullptr); +} + +VkInstance Vulkan::getInstance() const { return instance; } + +VkPhysicalDevice Vulkan::getPhysicalDevice() const { return physicalDevice; } + +VkDevice Vulkan::getDevice() const { return device; } + +Surface &Vulkan::getSurface() { return *surface; } + +const Surface &Vulkan::getSurface() const { return *surface; } + +Queues &Vulkan::getQueues() { return *queues; } + +const Queues &Vulkan::getQueues() const { return *queues; } + +CommandPool &Vulkan::getCommandPool() { return *commandPool; } + +const CommandPool &Vulkan::getCommandPool() const { return *commandPool; } + +RenderPass &Vulkan::getRenderPass() { return *renderPass; } + +const RenderPass &Vulkan::getRenderPass() const { return *renderPass; } + +Pipeline &Vulkan::getPipeline() { return *pipeline; } + +const Pipeline &Vulkan::getPipeline() const { return *pipeline; } + +SwapChain &Vulkan::getSwapChain() { return *swapChain; } + +const SwapChain &Vulkan::getSwapChain() const { return *swapChain; } + +TextureDescriptors &Vulkan::getTextureDescriptors() { + return *textureDescriptors; +} + +const TextureDescriptors &Vulkan::getTextureDescriptors() const { + return *textureDescriptors; +} + +Adapter &Vulkan::getAdapter() { return *adapter; } + +const Adapter &Vulkan::getAdapter() const { return *adapter; } + +progressia::main::GraphicsInterface &Vulkan::getGint() { return *gint; } + +const progressia::main::GraphicsInterface &Vulkan::getGint() const { + return *gint; +} + +VkFormat Vulkan::findSupportedFormat(const std::vector &candidates, + VkImageTiling tiling, + VkFormatFeatureFlags features) { + + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && + (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && + (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + std::cout << "Could not find a suitable format" << std::endl; + // REPORT_ERROR + exit(1); +} + +uint32_t Vulkan::findMemoryType(uint32_t allowedByDevice, + VkMemoryPropertyFlags desiredProperties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if (((1 << i) & allowedByDevice) == 0) { + continue; + } + if ((memProperties.memoryTypes[i].propertyFlags & desiredProperties) != + desiredProperties) { + continue; + } + + return i; + } + + std::cout << "Could not find suitable memory type" << std::endl; + // REPORT_ERROR + exit(1); + return -1; +} + +void Vulkan::handleVkResult(const char *errorMessage, VkResult result) { + errorHandler->handleVkResult(errorMessage, result); +} + +Frame *Vulkan::getCurrentFrame() { + if (isRenderingFrame) { + return &*frames.at(currentFrame); + } + return nullptr; +} + +uint64_t Vulkan::getLastStartedFrame() { return lastStartedFrame; } + +std::size_t Vulkan::getFrameInFlightIndex() { return currentFrame; } + +bool Vulkan::startRender() { + if (currentFrame >= MAX_FRAMES_IN_FLIGHT - 1) { + currentFrame = 0; + } else { + currentFrame++; + } + + bool shouldContinue = frames.at(currentFrame)->startRender(); + if (!shouldContinue) { + return false; + } + + isRenderingFrame = true; + lastStartedFrame++; + + return true; +} + +void Vulkan::endRender() { + gint->flush(); + isRenderingFrame = false; + frames.at(currentFrame)->endRender(); +} + +void Vulkan::waitIdle() { + if (device != VK_NULL_HANDLE) { + vkDeviceWaitIdle(device); + } +} + +/* + * VulkanErrorHandler + */ + +VulkanErrorHandler::VulkanErrorHandler(Vulkan &vulkan) : vulkan(vulkan) { + // do nothing +} + +VulkanErrorHandler::~VulkanErrorHandler() { +#ifdef VULKAN_ERROR_CHECKING + vulkan.callVoid("vkDestroyDebugUtilsMessengerEXT", + (VkDebugUtilsMessengerEXT)debugMessenger, nullptr); +#endif +} + +#ifdef VULKAN_ERROR_CHECKING +namespace { + +VKAPI_ATTR VkBool32 VKAPI_CALL +debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, + void *pUserData) { + + if (messageSeverity < VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + return VK_FALSE; + } + + [[maybe_unused]] auto &vk = *reinterpret_cast(pUserData); + + const char *severityStr = + messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT + ? "\x1B[1;91m\x1B[40mERROR\x1B[0m" + : messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + ? "\x1B[1;93m\x1B[40mWARNING\x1B[0m" + : messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + ? "info" + : "verbose"; + + const char *typeStr; + switch (messageType) { + case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: + typeStr = "general"; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: + typeStr = "violation"; + break; + default: + typeStr = "performance"; + break; + } + + std::cout << "[Vulkan] [" << typeStr << " / " << severityStr << "]\t" + << pCallbackData->pMessage << std::endl; + // REPORT_ERROR + return VK_FALSE; +} + +void populateDebugMessengerCreateInfo( + VkDebugUtilsMessengerCreateInfoEXT &createInfo, Vulkan &vulkan) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + + createInfo.messageSeverity = + 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; + + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + + createInfo.pfnUserCallback = debugCallback; + createInfo.pUserData = &vulkan; +} + +} // namespace +#endif + +std::unique_ptr +VulkanErrorHandler::attachDebugProbe(VkInstanceCreateInfo &createInfo) { +#ifdef VULKAN_ERROR_CHECKING + + std::unique_ptr result = + std::make_unique(); + + populateDebugMessengerCreateInfo(*result, vulkan); + + result->pNext = createInfo.pNext; + createInfo.pNext = &*result; + + return result; + +#else + + (void)createInfo; + return std::unique_ptr(); + +#endif +} + +void VulkanErrorHandler::onInstanceReady() { +#ifdef VULKAN_ERROR_CHECKING + std::cout << "Registering debug callback" << std::endl; + + VkDebugUtilsMessengerCreateInfoEXT createInfo{}; + populateDebugMessengerCreateInfo(createInfo, vulkan); + + handleVkResult("Could not register debug messanger", + vulkan.call("vkCreateDebugUtilsMessengerEXT", &createInfo, + nullptr, &debugMessenger)); + +#endif +} + +void VulkanErrorHandler::handleVkResult(const char *errorMessage, + VkResult result) { + if (result == VK_SUCCESS) { + return; + } + + std::cout << "Vulkan error (" << result << "): " << errorMessage + << std::endl; + // REPORT_ERROR + exit(1); +} + +/* + * Surface + */ + +Surface::Surface(Vulkan &vulkan) : vulkan(vulkan) { + vulkan.handleVkResult("Could not create window surface (what?)", + glfwCreateWindowSurface(vulkan.getInstance(), + getGLFWWindowHandle(), + nullptr, &vk)); +} + +Surface::~Surface() { vkDestroySurfaceKHR(vulkan.getInstance(), vk, nullptr); } + +VkSurfaceKHR Surface::getVk() { return vk; } + +/* + * Queue + */ + +Queue::Queue(Test test) : test(test) { + // do nothing +} + +bool Queue::isSuitable(VkPhysicalDevice physicalDevice, uint32_t familyIndex, + Vulkan &vulkan, + const VkQueueFamilyProperties &properties) const { + + return test(physicalDevice, familyIndex, vulkan, properties); +} + +VkQueue Queue::getVk() const { return vk; } + +uint32_t Queue::getFamilyIndex() const { return *familyIndex; } + +void Queue::waitIdle() const { vkQueueWaitIdle(vk); } + +/* + * Queues + */ + +namespace { + +bool graphicsQueueTest(VkPhysicalDevice, uint32_t, Vulkan &, + const VkQueueFamilyProperties &properties) { + + return properties.queueFlags & VK_QUEUE_GRAPHICS_BIT; +} + +bool presentQueueTest(VkPhysicalDevice physicalDevice, uint32_t familyIndex, + Vulkan &vulkan, const VkQueueFamilyProperties &) { + + VkBool32 presentSupport = false; + + vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, familyIndex, + vulkan.getSurface().getVk(), + &presentSupport); + + return presentSupport; +} + +} // namespace + +Queues::Queues(VkPhysicalDevice physicalDevice, Vulkan &vulkan) + : graphicsQueue(graphicsQueueTest), presentQueue(presentQueueTest) { + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, + nullptr); + + std::vector properties(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, + properties.data()); + + for (std::size_t index = 0; index < queueFamilyCount; index++) { + + for (auto queue : {&graphicsQueue, &presentQueue}) { + if (!queue->isSuitable(physicalDevice, index, vulkan, + properties[index])) { + continue; + } + + queue->familyIndex = index; + } + + if (isComplete()) { + break; + } + } +} + +Queues::~Queues() { + // do nothing +} + +void Queues::storeHandles(VkDevice device) { + for (auto queue : {&graphicsQueue, &presentQueue}) { + vkGetDeviceQueue(device, queue->getFamilyIndex(), 0, &queue->vk); + } +} + +std::unique_ptr +Queues::requestCreation(VkDeviceCreateInfo &createInfo) const { + + std::unique_ptr result = std::make_unique(); + result->priority = 1.0f; + + std::unordered_set uniqueQueues; + for (const auto *queue : {&graphicsQueue, &presentQueue}) { + uniqueQueues.insert(queue->getFamilyIndex()); + } + + for (const auto &index : uniqueQueues) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = index; + queueCreateInfo.pQueuePriorities = &result->priority; + queueCreateInfo.queueCount = 1; + + result->queueCreateInfos.push_back(queueCreateInfo); + } + + createInfo.pQueueCreateInfos = result->queueCreateInfos.data(); + createInfo.queueCreateInfoCount = + static_cast(result->queueCreateInfos.size()); + + return result; +} + +bool Queues::isComplete() const { + for (auto queue : {&graphicsQueue, &presentQueue}) { + if (!queue->familyIndex.has_value()) { + return false; + } + } + + return true; +} + +const Queue &Queues::getGraphicsQueue() const { return graphicsQueue; } + +const Queue &Queues::getPresentQueue() const { return presentQueue; } + +/* + * CommandPool + */ + +CommandPool::CommandPool(Vulkan &vulkan, const Queue &queue) + : queue(queue), vulkan(vulkan) { + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queue.getFamilyIndex(); + + vulkan.handleVkResult( + "Could not create CommandPool", + vkCreateCommandPool(vulkan.getDevice(), &poolInfo, nullptr, &pool)); +} + +CommandPool::~CommandPool() { + vkDestroyCommandPool(vulkan.getDevice(), pool, nullptr); +} + +VkCommandBuffer CommandPool::allocateCommandBuffer() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = pool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(vulkan.getDevice(), &allocInfo, &commandBuffer); + + return commandBuffer; +} + +void CommandPool::beginCommandBuffer(VkCommandBuffer commandBuffer, + VkCommandBufferUsageFlags usage) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = usage; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); +} + +VkCommandBuffer CommandPool::beginSingleUse() { + VkCommandBuffer buffer = allocateCommandBuffer(); + beginCommandBuffer(buffer, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + return buffer; +} + +void CommandPool::runSingleUse(VkCommandBuffer buffer, bool waitIdle) { + vkEndCommandBuffer(buffer); + submitMultiUse(buffer, false); + + if (waitIdle) { + queue.waitIdle(); + } + + freeMultiUse(buffer); +} + +VkCommandBuffer CommandPool::allocateMultiUse() { + return allocateCommandBuffer(); +} + +VkCommandBuffer CommandPool::beginMultiUse() { + VkCommandBuffer buffer = allocateMultiUse(); + beginCommandBuffer(buffer, 0); + return buffer; +} + +void CommandPool::submitMultiUse(VkCommandBuffer buffer, bool waitIdle) { + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &buffer; + + vkQueueSubmit(queue.getVk(), 1, &submitInfo, VK_NULL_HANDLE); + + if (waitIdle) { + queue.waitIdle(); + } +} + +void CommandPool::freeMultiUse(VkCommandBuffer buffer) { + vkFreeCommandBuffers(vulkan.getDevice(), pool, 1, &buffer); +} + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_common.h b/desktop/graphics/vulkan_common.h new file mode 100644 index 0000000..03f7823 --- /dev/null +++ b/desktop/graphics/vulkan_common.h @@ -0,0 +1,289 @@ +#pragma once + +#define GLFW_INCLUDE_VULKAN +#include +#include +#include +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +#include + +#include + +#include "../../main/rendering/graphics_interface.h" + +namespace progressia { +namespace desktop { + +namespace CstrUtils { +struct CstrHash { + std::size_t operator()(const char *s) const noexcept { + std::size_t acc = 0; + + while (*s != 0) { + acc = acc * 31 + *s; + s++; + } + + return acc; + } +}; + +struct CstrEqual { + bool operator()(const char *lhs, const char *rhs) const noexcept { + return strcmp(lhs, rhs) == 0; + } +}; + +struct CstrCompare { + bool operator()(const char *lhs, const char *rhs) const noexcept { + return strcmp(lhs, rhs) < 0; + } +}; + +using CstrHashSet = std::unordered_set; +} // namespace CstrUtils + +class VkObjectWrapper : private boost::noncopyable { + // empty +}; + +constexpr std::size_t MAX_FRAMES_IN_FLIGHT = 2; + +class VulkanErrorHandler; +class Surface; +class Queue; +class Queues; +class CommandPool; +class RenderPass; +class Pipeline; +class SwapChain; +class TextureDescriptors; +class Adapter; +class Frame; + +class Vulkan : public VkObjectWrapper { + private: + VkInstance instance = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + + std::unique_ptr errorHandler; + std::unique_ptr surface; + std::unique_ptr queues; + std::unique_ptr commandPool; + std::unique_ptr renderPass; + std::unique_ptr pipeline; + std::unique_ptr swapChain; + std::unique_ptr textureDescriptors; + std::unique_ptr adapter; + + std::unique_ptr gint; + + std::vector> frames; + std::size_t currentFrame; + bool isRenderingFrame; + uint64_t lastStartedFrame; + + public: + Vulkan(std::vector instanceExtensions, + std::vector deviceExtensions, + std::vector validationLayers); + + ~Vulkan(); + + VkInstance getInstance() const; + VkPhysicalDevice getPhysicalDevice() const; + VkDevice getDevice() const; + + Surface &getSurface(); + const Surface &getSurface() const; + Queues &getQueues(); + const Queues &getQueues() const; + SwapChain &getSwapChain(); + const SwapChain &getSwapChain() const; + CommandPool &getCommandPool(); + const CommandPool &getCommandPool() const; + RenderPass &getRenderPass(); + const RenderPass &getRenderPass() const; + Pipeline &getPipeline(); + const Pipeline &getPipeline() const; + TextureDescriptors &getTextureDescriptors(); + const TextureDescriptors &getTextureDescriptors() const; + Adapter &getAdapter(); + const Adapter &getAdapter() const; + + Frame *getCurrentFrame(); + const Frame *getCurrentFrame() const; + + progressia::main::GraphicsInterface &getGint(); + const progressia::main::GraphicsInterface &getGint() const; + + /* + * Returns false when the frame should be skipped + */ + bool startRender(); + void endRender(); + + uint64_t getLastStartedFrame(); + std::size_t getFrameInFlightIndex(); + + void waitIdle(); + + VkFormat findSupportedFormat(const std::vector &, VkImageTiling, + VkFormatFeatureFlags); + uint32_t findMemoryType(uint32_t allowedByDevice, + VkMemoryPropertyFlags desiredProperties); + + template + VkResult call(const char *functionName, Args &&...args) { + + using FunctionSignature = VkResult(VkInstance, Args...); + + auto func = reinterpret_cast( + vkGetInstanceProcAddr(instance, functionName)); + if (func != nullptr) { + return func(instance, std::forward(args)...); + } else { + std::cout << "[Vulkan] [dynVkCall / VkResult]\tFunction not found " + "for name \"" + << functionName << "\"" << std::endl; + // REPORT_ERROR + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + + template + VkResult callVoid(const char *functionName, Args &&...args) { + + using FunctionSignature = void(VkInstance, Args...); + + auto func = reinterpret_cast( + vkGetInstanceProcAddr(instance, functionName)); + if (func != nullptr) { + func(instance, std::forward(args)...); + return VK_SUCCESS; + } else { + std::cout + << "[Vulkan] [dynVkCall / void]\tFunction not found for name \"" + << functionName << "\"" << std::endl; + // REPORT_ERROR + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + + void handleVkResult(const char *errorMessage, VkResult); +}; + +class VulkanErrorHandler : public VkObjectWrapper { + private: + VkDebugUtilsMessengerEXT debugMessenger; + Vulkan &vulkan; + + public: + VulkanErrorHandler(Vulkan &); + + std::unique_ptr + attachDebugProbe(VkInstanceCreateInfo &); + void onInstanceReady(); + + ~VulkanErrorHandler(); + + void handleVkResult(const char *errorMessage, VkResult result); +}; + +class Surface : public VkObjectWrapper { + private: + VkSurfaceKHR vk; + Vulkan &vulkan; + + public: + Surface(Vulkan &); + ~Surface(); + + VkSurfaceKHR getVk(); +}; + +class Queue { + private: + using Test = std::function; + + Test test; + std::optional familyIndex; + VkQueue vk; + + friend class Queues; + + Queue(Test); + + public: + bool isSuitable(VkPhysicalDevice, uint32_t familyIndex, Vulkan &, + const VkQueueFamilyProperties &) const; + + VkQueue getVk() const; + uint32_t getFamilyIndex() const; + + void waitIdle() const; +}; + +class Queues { + private: + Queue graphicsQueue; + Queue presentQueue; + + public: + Queues(VkPhysicalDevice physicalDevice, Vulkan &vulkan); + ~Queues(); + + // cppcheck-suppress functionConst; this method modifies the Queue fields + void storeHandles(VkDevice device); + + bool isComplete() const; + + struct CreationRequest { + float priority; + std::vector queueCreateInfos; + }; + std::unique_ptr + requestCreation(VkDeviceCreateInfo &) const; + + const Queue &getGraphicsQueue() const; + const Queue &getPresentQueue() const; +}; + +class CommandPool : public VkObjectWrapper { + + private: + VkCommandPool pool; + const Queue &queue; + Vulkan &vulkan; + + VkCommandBuffer allocateCommandBuffer(); + void beginCommandBuffer(VkCommandBuffer commandBuffer, + VkCommandBufferUsageFlags usage); + + public: + CommandPool(Vulkan &, const Queue &); + ~CommandPool(); + + VkCommandBuffer beginSingleUse(); + void runSingleUse(VkCommandBuffer, bool waitIdle = false); + + VkCommandBuffer allocateMultiUse(); + VkCommandBuffer beginMultiUse(); + void submitMultiUse(VkCommandBuffer, bool waitIdle = false); + void freeMultiUse(VkCommandBuffer); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_descriptor_set.cpp b/desktop/graphics/vulkan_descriptor_set.cpp new file mode 100644 index 0000000..74fd420 --- /dev/null +++ b/desktop/graphics/vulkan_descriptor_set.cpp @@ -0,0 +1,19 @@ +#include "vulkan_descriptor_set.h" + +namespace progressia { +namespace desktop { + +DescriptorSetInterface::DescriptorSetInterface(uint32_t setNumber, + Vulkan &vulkan) + : setNumber(setNumber), vulkan(vulkan) {} + +VkDescriptorSetLayout DescriptorSetInterface::getLayout() const { + return layout; +} + +uint32_t DescriptorSetInterface::getSetNumber() const { return setNumber; } + +Vulkan &DescriptorSetInterface::getVulkan() { return vulkan; } + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_descriptor_set.h b/desktop/graphics/vulkan_descriptor_set.h new file mode 100644 index 0000000..1f23fc8 --- /dev/null +++ b/desktop/graphics/vulkan_descriptor_set.h @@ -0,0 +1,23 @@ +#pragma once + +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +class DescriptorSetInterface : public VkObjectWrapper { + protected: + VkDescriptorSetLayout layout; + uint32_t setNumber; + Vulkan &vulkan; + + DescriptorSetInterface(uint32_t setNumber, Vulkan &); + + public: + VkDescriptorSetLayout getLayout() const; + uint32_t getSetNumber() const; + Vulkan &getVulkan(); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_frame.cpp b/desktop/graphics/vulkan_frame.cpp new file mode 100644 index 0000000..b5c3a10 --- /dev/null +++ b/desktop/graphics/vulkan_frame.cpp @@ -0,0 +1,171 @@ +#include "vulkan_frame.h" + +#include + +#include "vulkan_adapter.h" +#include "vulkan_common.h" +#include "vulkan_pipeline.h" +#include "vulkan_render_pass.h" +#include "vulkan_swap_chain.h" + +namespace progressia { +namespace desktop { + +Frame::Frame(Vulkan &vulkan) + : vulkan(vulkan), + commandBuffer(vulkan.getCommandPool().allocateMultiUse()) { + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + vulkan.handleVkResult("Could not create imageAvailableSemaphore", + vkCreateSemaphore(vulkan.getDevice(), &semaphoreInfo, + nullptr, &imageAvailableSemaphore)); + vulkan.handleVkResult("Could not create renderFinishedSemaphore", + vkCreateSemaphore(vulkan.getDevice(), &semaphoreInfo, + nullptr, &renderFinishedSemaphore)); + vulkan.handleVkResult( + "Could not create inFlightFence", + vkCreateFence(vulkan.getDevice(), &fenceInfo, nullptr, &inFlightFence)); + + for (const auto &attachment : vulkan.getAdapter().getAttachments()) { + clearValues.push_back(attachment.clearValue); + } +} + +Frame::~Frame() { + vulkan.waitIdle(); + vkDestroySemaphore(vulkan.getDevice(), imageAvailableSemaphore, nullptr); + vkDestroySemaphore(vulkan.getDevice(), renderFinishedSemaphore, nullptr); + vkDestroyFence(vulkan.getDevice(), inFlightFence, nullptr); +} + +bool Frame::startRender() { + // Wait for frame + vkWaitForFences(vulkan.getDevice(), 1, &inFlightFence, VK_TRUE, UINT64_MAX); + + // Acquire an image + VkResult result = vkAcquireNextImageKHR( + vulkan.getDevice(), vulkan.getSwapChain().getVk(), UINT64_MAX, + imageAvailableSemaphore, VK_NULL_HANDLE, &*imageIndexInFlight); + + switch (result) { + case VK_ERROR_OUT_OF_DATE_KHR: + vulkan.getSwapChain().recreate(); + // Skip this frame, try again later + return false; + case VK_SUBOPTIMAL_KHR: + // Continue as normal + break; + default: + vulkan.handleVkResult("Could not acquire next image", result); + break; + } + + vulkan.getAdapter().onPreFrame(); + + // Reset command buffer + vkResetCommandBuffer(commandBuffer, 0); + + // Setup command buffer + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + vulkan.handleVkResult("Could not begin recording command buffer", + vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + auto extent = vulkan.getSwapChain().getExtent(); + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = vulkan.getRenderPass().getVk(); + renderPassInfo.framebuffer = + vulkan.getSwapChain().getFramebuffer(*imageIndexInFlight); + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = extent; + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + vulkan.getPipeline().getVk()); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)extent.width; + viewport.height = (float)extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = extent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + return true; +} + +void Frame::endRender() { + // End command buffer + vkCmdEndRenderPass(commandBuffer); + + vulkan.handleVkResult("Could not end recording command buffer", + vkEndCommandBuffer(commandBuffer)); + + // Submit command buffer + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkPipelineStageFlags waitStages[] = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(vulkan.getDevice(), 1, &inFlightFence); + vulkan.handleVkResult( + "Could not submit draw command buffer", + vkQueueSubmit(vulkan.getQueues().getGraphicsQueue().getVk(), 1, + &submitInfo, inFlightFence)); + + // Present result + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {vulkan.getSwapChain().getVk()}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &*imageIndexInFlight; + + VkResult result = vkQueuePresentKHR( + vulkan.getQueues().getPresentQueue().getVk(), &presentInfo); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + // We're at the end of this frame already, no need to skip + vulkan.getSwapChain().recreate(); + } else { + vulkan.handleVkResult("Could not present", result); + } +} + +VkCommandBuffer Frame::getCommandBuffer() { return commandBuffer; } + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_frame.h b/desktop/graphics/vulkan_frame.h new file mode 100644 index 0000000..b5ddaa3 --- /dev/null +++ b/desktop/graphics/vulkan_frame.h @@ -0,0 +1,36 @@ +#pragma once + +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +class Frame : public VkObjectWrapper { + private: + Vulkan &vulkan; + + VkCommandBuffer commandBuffer; + + VkSemaphore imageAvailableSemaphore; + VkSemaphore renderFinishedSemaphore; + VkFence inFlightFence; + + std::vector clearValues; + + std::optional imageIndexInFlight; + + public: + Frame(Vulkan &vulkan); + ~Frame(); + + /* + * Returns false when the frame should be skipped + */ + bool startRender(); + void endRender(); + + VkCommandBuffer getCommandBuffer(); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_image.cpp b/desktop/graphics/vulkan_image.cpp new file mode 100644 index 0000000..7517df4 --- /dev/null +++ b/desktop/graphics/vulkan_image.cpp @@ -0,0 +1,247 @@ +#include "vulkan_image.h" + +#include +#include + +#include "vulkan_buffer.h" +#include "vulkan_common.h" +#include "vulkan_frame.h" +#include "vulkan_pipeline.h" +#include "vulkan_texture_descriptors.h" + +namespace progressia { +namespace desktop { + +/* + * Image + */ + +Image::Image(VkImage vk, VkImageView view, VkFormat format) + : vk(vk), view(view), format(format) { + // do nothing +} + +Image::~Image() { + // do nothing +} + +/* + * ManagedImage + */ + +ManagedImage::ManagedImage(std::size_t width, std::size_t height, + VkFormat format, VkImageAspectFlags aspect, + VkImageUsageFlags usage, Vulkan &vulkan) + : + + Image(VK_NULL_HANDLE, VK_NULL_HANDLE, format), vulkan(vulkan), + + state{VK_IMAGE_LAYOUT_UNDEFINED, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT} { + + /* + * Create VkImage + */ + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = static_cast(width); + imageInfo.extent.height = static_cast(height); + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; // Optional + + vulkan.handleVkResult( + "Could not create an image", + vkCreateImage(vulkan.getDevice(), &imageInfo, nullptr, &vk)); + + /* + * Allocate memory + */ + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(vulkan.getDevice(), vk, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = vulkan.findMemoryType( + memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + vulkan.handleVkResult( + "Could not allocate memory for image", + vkAllocateMemory(vulkan.getDevice(), &allocInfo, nullptr, &memory)); + + /* + * Bind memory to image + */ + + vkBindImageMemory(vulkan.getDevice(), vk, memory, 0); + + /* + * Create image view + */ + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = vk; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspect; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + vulkan.handleVkResult( + "Could not create image view", + vkCreateImageView(vulkan.getDevice(), &viewInfo, nullptr, &view)); +} + +ManagedImage::~ManagedImage() { + vkDestroyImageView(vulkan.getDevice(), view, nullptr); + vkDestroyImage(vulkan.getDevice(), vk, nullptr); + vkFreeMemory(vulkan.getDevice(), memory, nullptr); +} + +void ManagedImage::transition(State newState) { + VkCommandBuffer commandBuffer = vulkan.getCommandPool().beginSingleUse(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = state.layout; + barrier.newLayout = newState.layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = vk; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = state.accessMask; + barrier.dstAccessMask = newState.accessMask; + + vkCmdPipelineBarrier(commandBuffer, state.stageMask, newState.stageMask, 0, + 0, nullptr, 0, nullptr, 1, &barrier); + + vulkan.getCommandPool().runSingleUse(commandBuffer, true); + + state = newState; +} + +/* + * Texture + */ + +Texture::Texture(const progressia::main::Image &src, Vulkan &vulkan) + : + + ManagedImage(src.width, src.height, VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + vulkan) { + + /* + * Create a staging buffer + */ + + Buffer stagingBuffer( + src.getSize(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vulkan); + + /* + * Transfer pixels to staging buffer + */ + + void *dst = stagingBuffer.map(); + memcpy(dst, src.getData(), src.getSize()); + stagingBuffer.unmap(); + + /* + * Transfer pixels from staging buffer to image + */ + + transition({VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT}); + + VkCommandBuffer commandBuffer = vulkan.getCommandPool().beginSingleUse(); + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = {static_cast(src.width), + static_cast(src.height), 1}; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer.buffer, vk, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + vulkan.getCommandPool().runSingleUse(commandBuffer, true); + + transition({VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT}); + + /* + * Create a sampler + */ + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_NEAREST; + samplerInfo.minFilter = VK_FILTER_NEAREST; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 0; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + vulkan.handleVkResult( + "Could not create texture sampler", + vkCreateSampler(vulkan.getDevice(), &samplerInfo, nullptr, &sampler)); + + /* + * Create descriptor set + */ + + descriptorSet = vulkan.getTextureDescriptors().addTexture(view, sampler); +} + +Texture::~Texture() { + vkDestroySampler(vulkan.getDevice(), sampler, nullptr); + // TODO free descriptorSet +} + +void Texture::bind() { + // REPORT_ERROR if getCurrentFrame() == nullptr + auto commandBuffer = vulkan.getCurrentFrame()->getCommandBuffer(); + auto pipelineLayout = vulkan.getPipeline().getLayout(); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout, + vulkan.getTextureDescriptors().getSetNumber(), 1, + &descriptorSet, 0, nullptr); +} + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_image.h b/desktop/graphics/vulkan_image.h new file mode 100644 index 0000000..07accb6 --- /dev/null +++ b/desktop/graphics/vulkan_image.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include "vulkan_buffer.h" +#include "vulkan_common.h" + +#include "../../main/rendering/image.h" + +namespace progressia { +namespace desktop { + +class Image : public VkObjectWrapper { + public: + VkImage vk; + VkImageView view; + VkFormat format; + + Image(VkImage, VkImageView, VkFormat); + virtual ~Image(); +}; + +class ManagedImage : public Image { + + public: + VkDeviceMemory memory; + Vulkan &vulkan; + + struct State { + VkImageLayout layout; + VkAccessFlags accessMask; + VkPipelineStageFlags stageMask; + }; + + private: + State state; + + public: + ManagedImage(std::size_t width, std::size_t height, VkFormat, + VkImageAspectFlags, VkImageUsageFlags, Vulkan &); + ~ManagedImage(); + + void transition(State); +}; + +class Texture : public ManagedImage { + + public: + VkSampler sampler; + VkDescriptorSet descriptorSet; + + Texture(const progressia::main::Image &, Vulkan &vulkan); + ~Texture(); + + void bind(); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_mgmt.cpp b/desktop/graphics/vulkan_mgmt.cpp new file mode 100644 index 0000000..563f573 --- /dev/null +++ b/desktop/graphics/vulkan_mgmt.cpp @@ -0,0 +1,68 @@ +#include "vulkan_mgmt.h" + +#include "vulkan_common.h" +#include "vulkan_swap_chain.h" + +namespace progressia { +namespace desktop { + +Vulkan *vulkan; + +void initializeVulkan() { + std::cout << "Vulkan initializing" << std::endl; + + // Instance extensions + + std::vector instanceExtensions; + { + uint32_t glfwExtensionCount; + const char **glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + for (std::size_t i = 0; i < glfwExtensionCount; i++) { + instanceExtensions.push_back(glfwExtensions[i]); + } + +#ifdef VULKAN_ERROR_CHECKING + instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); +#endif + } + + // Device extensions + + std::vector deviceExtensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + // Validation layers + + std::vector validationLayers{ +#ifdef VULKAN_ERROR_CHECKING + "VK_LAYER_KHRONOS_validation" +#endif + }; + + vulkan = new Vulkan(instanceExtensions, deviceExtensions, validationLayers); + + std::cout << "Vulkan initialized" << std::endl; +} + +Vulkan *getVulkan() { return vulkan; } + +bool startRender() { return vulkan->startRender(); } + +void endRender() { return vulkan->endRender(); } + +void resizeVulkanSurface() { vulkan->getSwapChain().recreate(); } + +void shutdownVulkan() { + std::cout << "Vulkan terminating" << std::endl; + + if (vulkan != nullptr) { + delete vulkan; + vulkan = nullptr; + } + + std::cout << "Vulkan terminated" << std::endl; +} + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_mgmt.h b/desktop/graphics/vulkan_mgmt.h new file mode 100644 index 0000000..f0e76d9 --- /dev/null +++ b/desktop/graphics/vulkan_mgmt.h @@ -0,0 +1,23 @@ +#pragma once + +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +void initializeVulkan(); + +Vulkan *getVulkan(); + +void resizeVulkanSurface(); + +/* + * Returns false when the frame should be skipped + */ +bool startRender(); +void endRender(); + +void shutdownVulkan(); + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_pick_device.cpp b/desktop/graphics/vulkan_pick_device.cpp new file mode 100644 index 0000000..51eee93 --- /dev/null +++ b/desktop/graphics/vulkan_pick_device.cpp @@ -0,0 +1,98 @@ +#include "vulkan_pick_device.h" + +#include "vulkan_swap_chain.h" + +namespace progressia { +namespace desktop { + +namespace { + +bool checkDeviceExtensions(VkPhysicalDevice device, + const std::vector &deviceExtensions) { + CstrUtils::CstrHashSet toFind(deviceExtensions.cbegin(), + deviceExtensions.cend()); + + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, + nullptr); + + std::vector available(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, + available.data()); + + for (const auto &extension : available) { + toFind.erase(extension.extensionName); + } + + return toFind.empty(); +} + +bool isDeviceSuitable(const PhysicalDeviceData &data, Vulkan &vulkan, + const std::vector &deviceExtensions) { + + if (!Queues(data.device, vulkan).isComplete()) { + return false; + } + + if (!checkDeviceExtensions(data.device, deviceExtensions)) { + return false; + } + + // Check requires that the swap chain extension is present + if (!SwapChain::isSwapChainSuitable( + SwapChain::querySwapChainSupport(data.device, vulkan))) { + return false; + } + + return true; +} + +} // namespace + +const PhysicalDeviceData & +pickPhysicalDevice(std::vector &choices, Vulkan &vulkan, + const std::vector &deviceExtensions) { + + // Remove unsuitable devices + auto it = std::remove_if(choices.begin(), choices.end(), [&](auto x) { + return !isDeviceSuitable(x, vulkan, deviceExtensions); + }); + choices.erase(it, choices.end()); + + if (choices.empty()) { + std::cout << "No suitable GPUs found" << std::endl; + // REPORT_ERROR + exit(1); + } + + const auto *pick = &choices.front(); + + std::cout << "Suitable devices:"; + for (const auto &option : choices) { + + struct { + const char *description; + int value; + } opinions[] = {{"", 0}, + {"Integrated GPU", 0}, + {"Discrete GPU", +1}, + {"Virtual GPU", +1}, + {"CPU", -1}}; + + auto type = option.properties.deviceType; + std::cout << "\n\t- " << opinions[type].description << " " + << option.properties.deviceName; + + if (opinions[pick->properties.deviceType].value < + opinions[type].value) { + pick = &option; + } + } + std::cout << std::endl; + + std::cout << "Picked device " << pick->properties.deviceName << std::endl; + return *pick; +} + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_pick_device.h b/desktop/graphics/vulkan_pick_device.h new file mode 100644 index 0000000..2e7998f --- /dev/null +++ b/desktop/graphics/vulkan_pick_device.h @@ -0,0 +1,21 @@ +#pragma once + +#include "vulkan_common.h" + +#include + +namespace progressia { +namespace desktop { + +struct PhysicalDeviceData { + VkPhysicalDevice device; + VkPhysicalDeviceProperties properties; + VkPhysicalDeviceFeatures features; +}; + +const PhysicalDeviceData & +pickPhysicalDevice(std::vector &, Vulkan &, + const std::vector &deviceExtensions); + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_pipeline.cpp b/desktop/graphics/vulkan_pipeline.cpp new file mode 100644 index 0000000..f2ef8c9 --- /dev/null +++ b/desktop/graphics/vulkan_pipeline.cpp @@ -0,0 +1,223 @@ +#include "vulkan_pipeline.h" + +#include "vulkan_adapter.h" +#include "vulkan_common.h" +#include "vulkan_descriptor_set.h" +#include "vulkan_render_pass.h" + +namespace progressia { +namespace desktop { + +Pipeline::Pipeline(Vulkan &vulkan) : vulkan(vulkan) { + + auto &adapter = vulkan.getAdapter(); + + // Shaders + + auto vertShader = createShaderModule(adapter.loadVertexShader()); + auto fragShader = createShaderModule(adapter.loadFragmentShader()); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShader; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShader; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, + fragShaderStageInfo}; + + // Dynamic states + + std::vector dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR}; + + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = + static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + auto bindingDescription = adapter.getVertexInputBindingDescription(); + auto attributeDescriptions = adapter.getVertexInputAttributeDescriptions(); + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.vertexAttributeDescriptionCount = + static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + // Input assembly + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + // Viewport & scissor + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + // Rasterizer + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; // Optional + rasterizer.depthBiasClamp = 0.0f; // Optional + rasterizer.depthBiasSlopeFactor = 0.0f; // Optional + + // Multisampling (disabled) + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; // Optional + multisampling.pSampleMask = nullptr; // Optional + multisampling.alphaToCoverageEnable = VK_FALSE; // Optional + multisampling.alphaToOneEnable = VK_FALSE; // Optional + + // Depth testing + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + // Stencil testing (disabled) + + // do nothing + + // Color blending + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; // Optional + colorBlending.blendConstants[1] = 0.0f; // Optional + colorBlending.blendConstants[2] = 0.0f; // Optional + colorBlending.blendConstants[3] = 0.0f; // Optional + + // Pipeline + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + + auto layouts = vulkan.getAdapter().getUsedDSLayouts(); + pipelineLayoutInfo.setLayoutCount = layouts.size(); + pipelineLayoutInfo.pSetLayouts = layouts.data(); + + VkPushConstantRange pushConstantRange{}; + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(glm::mat3x4); + + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; + + vulkan.handleVkResult("Could not create PipelineLayout", + vkCreatePipelineLayout(vulkan.getDevice(), + &pipelineLayoutInfo, nullptr, + &layout)); + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = layout; + pipelineInfo.renderPass = vulkan.getRenderPass().getVk(); + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional + pipelineInfo.basePipelineIndex = -1; // Optional + + vulkan.handleVkResult( + "Could not create Pipeline", + vkCreateGraphicsPipelines(vulkan.getDevice(), VK_NULL_HANDLE, 1, + &pipelineInfo, nullptr, &vk)); + + // Cleanup + + vkDestroyShaderModule(vulkan.getDevice(), fragShader, nullptr); + vkDestroyShaderModule(vulkan.getDevice(), vertShader, nullptr); +} + +VkShaderModule Pipeline::createShaderModule(const std::vector &bytecode) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = bytecode.size(); + + // Important - the buffer must be aligned properly. std::vector does that. + createInfo.pCode = reinterpret_cast(bytecode.data()); + + VkShaderModule shaderModule; + vulkan.handleVkResult("Could not load shader", + vkCreateShaderModule(vulkan.getDevice(), &createInfo, + nullptr, &shaderModule)); + + return shaderModule; +} + +Pipeline::~Pipeline() { + vkDestroyPipeline(vulkan.getDevice(), vk, nullptr); + vkDestroyPipelineLayout(vulkan.getDevice(), layout, nullptr); +} + +VkPipeline Pipeline::getVk() { return vk; } + +VkPipelineLayout Pipeline::getLayout() { return layout; } + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_pipeline.h b/desktop/graphics/vulkan_pipeline.h new file mode 100644 index 0000000..00b70b9 --- /dev/null +++ b/desktop/graphics/vulkan_pipeline.h @@ -0,0 +1,27 @@ +#pragma once + +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +class Pipeline : public VkObjectWrapper { + + private: + VkPipelineLayout layout; + VkPipeline vk; + + Vulkan &vulkan; + + VkShaderModule createShaderModule(const std::vector &bytecode); + + public: + Pipeline(Vulkan &); + ~Pipeline(); + + VkPipeline getVk(); + VkPipelineLayout getLayout(); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_render_pass.cpp b/desktop/graphics/vulkan_render_pass.cpp new file mode 100644 index 0000000..b7f7930 --- /dev/null +++ b/desktop/graphics/vulkan_render_pass.cpp @@ -0,0 +1,83 @@ +#include "vulkan_render_pass.h" + +#include "vulkan_adapter.h" +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +RenderPass::RenderPass(Vulkan &vulkan) : vulkan(vulkan) { + + std::vector attachmentDescriptions; + std::vector attachmentReferences; + VkAttachmentReference depthAttachmentRef{}; + const auto &attachments = vulkan.getAdapter().getAttachments(); + + for (std::size_t i = 0; i < attachments.size(); i++) { + const auto &attachment = attachments[i]; + VkAttachmentDescription *desc; + VkAttachmentReference *ref; + + attachmentDescriptions.push_back({}); + desc = &attachmentDescriptions.back(); + if (attachment.aspect == VK_IMAGE_ASPECT_DEPTH_BIT) { + ref = &depthAttachmentRef; + } else { + attachmentReferences.push_back({}); + ref = &attachmentReferences.back(); + } + + desc->format = attachment.image == nullptr ? attachment.format + : attachment.image->format; + desc->samples = VK_SAMPLE_COUNT_1_BIT; + desc->loadOp = attachment.loadOp; + desc->storeOp = attachment.storeOp; + desc->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + desc->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + desc->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + desc->finalLayout = attachment.finalLayout; + + ref->attachment = i; + ref->layout = attachment.workLayout; + } + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = attachmentReferences.size(); + subpass.pColorAttachments = attachmentReferences.data(); + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = + static_cast(attachmentDescriptions.size()); + renderPassInfo.pAttachments = attachmentDescriptions.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + vulkan.handleVkResult( + "Could not create render pass", + vkCreateRenderPass(vulkan.getDevice(), &renderPassInfo, nullptr, &vk)); +} + +RenderPass::~RenderPass() { + vkDestroyRenderPass(vulkan.getDevice(), vk, nullptr); +} + +VkRenderPass RenderPass::getVk() { return vk; } + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_render_pass.h b/desktop/graphics/vulkan_render_pass.h new file mode 100644 index 0000000..46df7f0 --- /dev/null +++ b/desktop/graphics/vulkan_render_pass.h @@ -0,0 +1,23 @@ +#pragma once + +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +class RenderPass : public VkObjectWrapper { + + private: + VkRenderPass vk; + + Vulkan &vulkan; + + public: + RenderPass(Vulkan &); + ~RenderPass(); + + VkRenderPass getVk(); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_swap_chain.cpp b/desktop/graphics/vulkan_swap_chain.cpp new file mode 100644 index 0000000..fb9e22c --- /dev/null +++ b/desktop/graphics/vulkan_swap_chain.cpp @@ -0,0 +1,328 @@ +#include "vulkan_swap_chain.h" + +#include +#include +#include + +#include "glfw_mgmt_details.h" +#include "vulkan_adapter.h" +#include "vulkan_common.h" +#include "vulkan_render_pass.h" + +namespace progressia { +namespace desktop { + +SwapChain::SupportDetails +SwapChain::querySwapChainSupport(VkPhysicalDevice device, Vulkan &vulkan) { + SupportDetails details; + auto surface = vulkan.getSurface().getVk(); + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, + &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, + nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, + details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, + &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR( + device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; +} + +bool SwapChain::isSwapChainSuitable(const SupportDetails &details) { + return !details.formats.empty() && !details.presentModes.empty(); +} + +void SwapChain::create() { + auto details = querySwapChainSupport(vulkan.getPhysicalDevice(), vulkan); + auto surfaceFormat = chooseSurfaceFormat(details.formats); + auto presentMode = choosePresentMode(details.presentModes, true); + this->extent = chooseExtent(details.capabilities); + + uint32_t imageCount = details.capabilities.minImageCount + 1; + uint32_t maxImageCount = details.capabilities.maxImageCount; + if (maxImageCount > 0 && imageCount > maxImageCount) { + imageCount = maxImageCount; + } + + // Fill out the createInfo + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = vulkan.getSurface().getVk(); + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + createInfo.preTransform = details.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = + VK_NULL_HANDLE; // TODO Figure out if this should be used + + // Specify queues + + uint32_t queueFamilyIndices[] = { + vulkan.getQueues().getGraphicsQueue().getFamilyIndex(), + vulkan.getQueues().getPresentQueue().getFamilyIndex()}; + + if (queueFamilyIndices[0] != queueFamilyIndices[1]) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + // Create swap chain object + + vulkan.handleVkResult( + "Could not create swap chain", + vkCreateSwapchainKHR(vulkan.getDevice(), &createInfo, nullptr, &vk)); + + // Store color buffers + + std::vector colorBufferImages; + vkGetSwapchainImagesKHR(vulkan.getDevice(), vk, &imageCount, nullptr); + colorBufferImages.resize(imageCount); + vkGetSwapchainImagesKHR(vulkan.getDevice(), vk, &imageCount, + colorBufferImages.data()); + + colorBufferViews.resize(colorBufferImages.size()); + for (size_t i = 0; i < colorBufferImages.size(); i++) { + VkImageViewCreateInfo viewCreateInfo{}; + viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewCreateInfo.image = colorBufferImages[i]; + viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewCreateInfo.format = surfaceFormat.format; + viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewCreateInfo.subresourceRange.baseMipLevel = 0; + viewCreateInfo.subresourceRange.levelCount = 1; + viewCreateInfo.subresourceRange.baseArrayLayer = 0; + viewCreateInfo.subresourceRange.layerCount = 1; + + vulkan.handleVkResult("Cound not create ImageView", + vkCreateImageView(vulkan.getDevice(), + &viewCreateInfo, nullptr, + &colorBufferViews[i])); + } + + // Create attachment images + + for (auto &attachment : vulkan.getAdapter().getAttachments()) { + if (attachment.format == VK_FORMAT_UNDEFINED) { + if (!attachment.image) { + std::cout << "Attachment " << attachment.name + << " format is VK_FORMAT_UNDEFINED but it does not " + "have an image" + << std::endl; + // REPORT_ERROR + exit(1); + } + continue; + } + + attachment.image = std::make_unique( + extent.width, extent.height, attachment.format, attachment.aspect, + attachment.usage, vulkan); + } + + // Create framebuffer + + framebuffers.resize(colorBufferViews.size()); + for (size_t i = 0; i < framebuffers.size(); i++) { + std::vector attachmentViews; + for (const auto &attachment : vulkan.getAdapter().getAttachments()) { + if (&attachment == colorBuffer) { + attachmentViews.push_back(colorBufferViews[i]); + } else if (attachment.image) { + attachmentViews.push_back(attachment.image->view); + } else { + std::cout << "Attachment " << attachment.name + << " is not colorBuffer but it does not have an image" + << std::endl; + // REPORT_ERROR + exit(1); + } + } + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = vulkan.getRenderPass().getVk(); + framebufferInfo.attachmentCount = + static_cast(attachmentViews.size()); + framebufferInfo.pAttachments = attachmentViews.data(); + framebufferInfo.width = extent.width; + framebufferInfo.height = extent.height; + framebufferInfo.layers = 1; + + vulkan.handleVkResult("Could not create Framebuffer", + vkCreateFramebuffer(vulkan.getDevice(), + &framebufferInfo, nullptr, + &framebuffers[i])); + } +} + +VkSurfaceFormatKHR SwapChain::chooseSurfaceFormat( + const std::vector &supported) { + for (const auto &option : supported) { + if (option.format == VK_FORMAT_B8G8R8A8_SRGB && + option.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return option; + } + } + + std::cout << "No suitable formats available" << std::endl; + // REPORT_ERROR + exit(1); +} + +bool SwapChain::isTripleBufferingSupported( + const std::vector &supported) { + return std::find(supported.begin(), supported.end(), + VK_PRESENT_MODE_MAILBOX_KHR) != supported.end(); +} + +VkPresentModeKHR +SwapChain::choosePresentMode(const std::vector &supported, + bool avoidVsync) { + if (avoidVsync && isTripleBufferingSupported(supported)) { + return VK_PRESENT_MODE_MAILBOX_KHR; + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D +SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR &capabilities) { + if (capabilities.currentExtent.width != + std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(getGLFWWindowHandle(), &width, &height); + + VkExtent2D actualExtent = {static_cast(width), + static_cast(height)}; + + actualExtent.width = + std::clamp(actualExtent.width, capabilities.minImageExtent.width, + capabilities.maxImageExtent.width); + actualExtent.height = + std::clamp(actualExtent.height, capabilities.minImageExtent.height, + capabilities.maxImageExtent.height); + + return actualExtent; +} + +void SwapChain::destroy() { + for (auto framebuffer : framebuffers) { + vkDestroyFramebuffer(vulkan.getDevice(), framebuffer, nullptr); + } + framebuffers.clear(); + + if (depthBuffer != nullptr) { + delete depthBuffer; + depthBuffer = nullptr; + } + + auto &attachments = vulkan.getAdapter().getAttachments(); + for (auto &attachment : attachments) { + if (attachment.format != VK_FORMAT_UNDEFINED) { + attachment.image.reset(); + } + } + + for (auto colorBufferView : colorBufferViews) { + vkDestroyImageView(vulkan.getDevice(), colorBufferView, nullptr); + } + colorBufferViews.clear(); + + if (vk != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(vulkan.getDevice(), vk, nullptr); + vk = VK_NULL_HANDLE; + } +} + +SwapChain::SwapChain(Vulkan &vulkan) + : vk(VK_NULL_HANDLE), colorBuffer(nullptr), + colorBufferViews(), extent{0, 0}, depthBuffer(nullptr), framebuffers(), + vulkan(vulkan) { + auto details = querySwapChainSupport(vulkan.getPhysicalDevice(), vulkan); + auto surfaceFormat = chooseSurfaceFormat(details.formats); + + vulkan.getAdapter().getAttachments().push_back( + {"Color buffer", + + VK_FORMAT_UNDEFINED, + 0, + 0, + + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_ATTACHMENT_LOAD_OP_CLEAR, + VK_ATTACHMENT_STORE_OP_STORE, + + {{{0.0f, 0.0f, 0.0f, 1.0f}}}, + + std::make_unique(static_cast(VK_NULL_HANDLE), + static_cast(VK_NULL_HANDLE), + surfaceFormat.format)}); + + colorBuffer = &vulkan.getAdapter().getAttachments().back(); +} + +SwapChain::~SwapChain() { + destroy(); + + auto &attachments = vulkan.getAdapter().getAttachments(); + for (auto it = attachments.begin(); it != attachments.end(); it++) { + if (&(*it) == colorBuffer) { + attachments.erase(it); + colorBuffer = nullptr; + break; + } + } +} + +void SwapChain::recreate() { + vulkan.waitIdle(); + destroy(); + create(); +} + +VkSwapchainKHR SwapChain::getVk() const { return vk; } + +VkFramebuffer SwapChain::getFramebuffer(std::size_t index) const { + return framebuffers.at(index); +} + +VkExtent2D SwapChain::getExtent() const { return extent; } + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_swap_chain.h b/desktop/graphics/vulkan_swap_chain.h new file mode 100644 index 0000000..f938afc --- /dev/null +++ b/desktop/graphics/vulkan_swap_chain.h @@ -0,0 +1,58 @@ +#pragma once + +#include "vulkan_adapter.h" +#include "vulkan_common.h" + +namespace progressia { +namespace desktop { + +class SwapChain : public VkObjectWrapper { + + public: + struct SupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + static SupportDetails querySwapChainSupport(VkPhysicalDevice device, + Vulkan &vulkan); + static bool isSwapChainSuitable(const SupportDetails &details); + + private: + VkSwapchainKHR vk; + + Attachment *colorBuffer; + std::vector colorBufferViews; + + VkExtent2D extent; + + Image *depthBuffer; + + std::vector framebuffers; + + Vulkan &vulkan; + + void create(); + void destroy(); + + VkSurfaceFormatKHR + chooseSurfaceFormat(const std::vector &); + bool isTripleBufferingSupported(const std::vector &); + VkPresentModeKHR choosePresentMode(const std::vector &, + bool avoidVsync); + VkExtent2D chooseExtent(const VkSurfaceCapabilitiesKHR &); + + public: + SwapChain(Vulkan &); + ~SwapChain(); + + void recreate(); + + VkSwapchainKHR getVk() const; + VkFramebuffer getFramebuffer(std::size_t index) const; + VkExtent2D getExtent() const; +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_texture_descriptors.cpp b/desktop/graphics/vulkan_texture_descriptors.cpp new file mode 100644 index 0000000..d1c81a9 --- /dev/null +++ b/desktop/graphics/vulkan_texture_descriptors.cpp @@ -0,0 +1,106 @@ +#include "vulkan_texture_descriptors.h" + +namespace progressia { +namespace desktop { + +void TextureDescriptors::allocatePool() { + pools.resize(pools.size() + 1); + + VkDescriptorPoolSize poolSize = {}; + poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSize.descriptorCount = POOL_SIZE; + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + poolInfo.maxSets = POOL_SIZE; + + auto output = &pools[pools.size() - 1]; + vulkan.handleVkResult( + "Could not create texture descriptor pool", + vkCreateDescriptorPool(vulkan.getDevice(), &poolInfo, nullptr, output)); + + lastPoolCapacity = POOL_SIZE; +} + +TextureDescriptors::TextureDescriptors(Vulkan &vulkan) + : DescriptorSetInterface(SET_NUMBER, vulkan) { + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + + VkDescriptorSetLayoutBinding binding = {}; + binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + binding.descriptorCount = 1; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + binding.pImmutableSamplers = nullptr; + binding.binding = 0; + + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &binding; + + vulkan.handleVkResult("Could not create texture descriptor set layout", + vkCreateDescriptorSetLayout(vulkan.getDevice(), + &layoutInfo, nullptr, + &layout)); + + allocatePool(); +} + +TextureDescriptors::~TextureDescriptors() { + for (auto pool : pools) { + vkDestroyDescriptorPool(vulkan.getDevice(), pool, nullptr); + } + + vkDestroyDescriptorSetLayout(vulkan.getDevice(), layout, nullptr); +} + +VkDescriptorSet TextureDescriptors::addTexture(VkImageView view, + VkSampler sampler) { + + /* + * Allocate descriptor set + */ + + if (lastPoolCapacity == 0) { + allocatePool(); + } + + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = pools.back(); + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &layout; + + VkDescriptorSet descriptorSet; + vulkan.handleVkResult("Could not create texture descriptor set", + vkAllocateDescriptorSets(vulkan.getDevice(), + &allocInfo, &descriptorSet)); + + lastPoolCapacity--; + + /* + * Write to descriptor set + */ + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = view; + imageInfo.sampler = sampler; + + VkWriteDescriptorSet write = {}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = 0; + write.dstArrayElement = 0; + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.descriptorCount = 1; + write.pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(vulkan.getDevice(), 1, &write, 0, nullptr); + + return descriptorSet; +} + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_texture_descriptors.h b/desktop/graphics/vulkan_texture_descriptors.h new file mode 100644 index 0000000..9a82462 --- /dev/null +++ b/desktop/graphics/vulkan_texture_descriptors.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "vulkan_common.h" +#include "vulkan_descriptor_set.h" + +namespace progressia { +namespace desktop { + +class TextureDescriptors : public DescriptorSetInterface { + private: + constexpr static uint32_t POOL_SIZE = 64; + constexpr static uint32_t SET_NUMBER = 1; + + std::vector pools; + uint32_t lastPoolCapacity; + + void allocatePool(); + + public: + TextureDescriptors(Vulkan &); + ~TextureDescriptors(); + + VkDescriptorSet addTexture(VkImageView, VkSampler); +}; + +} // namespace desktop +} // namespace progressia diff --git a/desktop/graphics/vulkan_uniform.h b/desktop/graphics/vulkan_uniform.h new file mode 100644 index 0000000..91a8f8f --- /dev/null +++ b/desktop/graphics/vulkan_uniform.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#include "vulkan_buffer.h" +#include "vulkan_common.h" +#include "vulkan_descriptor_set.h" + +namespace progressia { +namespace desktop { + +template class Uniform : public DescriptorSetInterface { + + private: + constexpr static uint32_t POOL_SIZE = 64; + + std::vector pools; + + struct StateImpl { + struct Set { + VkDescriptorSet vk; + Buffer contents; + + Set(VkDescriptorSet, Vulkan &); + }; + + std::array, MAX_FRAMES_IN_FLIGHT> sets; + + std::array newContents; + uint64_t setsToUpdate; + + StateImpl(const std::array &vks, + Vulkan &); + }; + + std::vector> states; + + uint32_t lastPoolCapacity; + + void allocatePool(); + + public: + class State { + + private: + std::size_t id; + + public: + Uniform *uniform; + + private: + friend class Uniform; + State(std::size_t id, Uniform *); + + void doUpdate(); + + public: + State(); + + void update(const Entries &...entries); + void bind(); + }; + + Uniform(uint32_t setNumber, Vulkan &); + ~Uniform(); + + State addState(); + + void doUpdates(); +}; + +} // namespace desktop +} // namespace progressia + +#include "vulkan_uniform.inl" diff --git a/desktop/graphics/vulkan_uniform.inl b/desktop/graphics/vulkan_uniform.inl new file mode 100644 index 0000000..79e99b6 --- /dev/null +++ b/desktop/graphics/vulkan_uniform.inl @@ -0,0 +1,194 @@ +#pragma once + +#include + +#include "../../main/util.h" +#include "vulkan_frame.h" +#include "vulkan_pipeline.h" + +namespace progressia { +namespace desktop { + +template +Uniform::StateImpl::Set::Set(VkDescriptorSet vk, Vulkan &vulkan) + : vk(vk), + contents((sizeof(Entries) + ...), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vulkan) {} + +template +Uniform::StateImpl::StateImpl( + const std::array &vks, + Vulkan &vulkan) + : setsToUpdate(0) { + constexpr std::size_t COUNT = sizeof...(Entries) * MAX_FRAMES_IN_FLIGHT; + + std::array bufferInfos; + std::array writes; + std::size_t index = 0; + + for (std::size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + auto &set = sets.at(i); + set.emplace(vks.at(i), vulkan); + + std::size_t offset = 0; + FOR_PACK_S(Entries, Entry, { + bufferInfos[index] = {}; + bufferInfos[index].buffer = set->contents.buffer; + bufferInfos[index].offset = offset; + bufferInfos[index].range = sizeof(Entry); + + writes[index] = {}; + writes[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[index].dstSet = set->vk; + writes[index].dstBinding = index % sizeof...(Entries); + writes[index].dstArrayElement = 0; + writes[index].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writes[index].descriptorCount = 1; + writes[index].pBufferInfo = &bufferInfos[index]; + + offset += sizeof(Entry); + index++; + }) + } + + vkUpdateDescriptorSets(vulkan.getDevice(), writes.size(), writes.data(), 0, + nullptr); +} + +template +Uniform::State::State(std::size_t id, Uniform *uniform) + : id(id), uniform(uniform) {} + +template +Uniform::State::State() : id(-1), uniform(nullptr) {} + +template +void Uniform::State::update(const Entries &...entries) { + auto &state = *uniform->states.at(id); + + auto *dst = state.newContents.data(); + FOR_PACK(Entries, entries, e, { + std::memcpy(dst, &e, sizeof(e)); + dst += sizeof(e); + }) + state.setsToUpdate = state.sets.size(); +} + +template void Uniform::State::bind() { + auto &state = *uniform->states.at(id); + auto &set = *state.sets.at(uniform->vulkan.getFrameInFlightIndex()); + + // REPORT_ERROR if getCurrentFrame() == nullptr + auto commandBuffer = uniform->vulkan.getCurrentFrame()->getCommandBuffer(); + auto pipelineLayout = uniform->vulkan.getPipeline().getLayout(); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout, uniform->getSetNumber(), 1, &set.vk, + 0, nullptr); +} + +template +Uniform::Uniform(uint32_t setNumber, Vulkan &vulkan) + : DescriptorSetInterface(setNumber, vulkan) { + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + + std::array bindings; + for (std::size_t i = 0; i < bindings.size(); i++) { + bindings[i] = {}; + bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[i].descriptorCount = 1; + bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | + VK_SHADER_STAGE_FRAGMENT_BIT; // TODO optimize? + bindings[i].pImmutableSamplers = nullptr; + bindings[i].binding = i; + } + + layoutInfo.bindingCount = bindings.size(); + layoutInfo.pBindings = bindings.data(); + + vulkan.handleVkResult("Could not create uniform descriptor set layout", + vkCreateDescriptorSetLayout(vulkan.getDevice(), + &layoutInfo, nullptr, + &layout)); + + allocatePool(); +} + +template Uniform::~Uniform() { + for (auto pool : pools) { + vkDestroyDescriptorPool(vulkan.getDevice(), pool, nullptr); + } + + vkDestroyDescriptorSetLayout(vulkan.getDevice(), layout, nullptr); +} + +template void Uniform::allocatePool() { + pools.resize(pools.size() + 1); + + std::array poolSizes{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = sizeof...(Entries) * POOL_SIZE; + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = poolSizes.size(); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = POOL_SIZE; + + auto output = &pools[pools.size() - 1]; + vulkan.handleVkResult( + "Could not create uniform descriptor pool", + vkCreateDescriptorPool(vulkan.getDevice(), &poolInfo, nullptr, output)); + + lastPoolCapacity = POOL_SIZE; +} + +template +typename Uniform::State Uniform::addState() { + if (lastPoolCapacity < MAX_FRAMES_IN_FLIGHT) { + allocatePool(); + } + + std::array vks; + + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = pools.back(); + allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; + + std::array layouts; + layouts.fill(layout); + + allocInfo.pSetLayouts = layouts.data(); + + vulkan.handleVkResult( + "Could not create descriptor set", + vkAllocateDescriptorSets(vulkan.getDevice(), &allocInfo, vks.data())); + + lastPoolCapacity -= MAX_FRAMES_IN_FLIGHT; + + states.push_back(std::make_unique(vks, vulkan)); + + return State(states.size() - 1, this); +} + +template void Uniform::doUpdates() { + for (auto &state : states) { + auto &buffer = state->sets.at(vulkan.getFrameInFlightIndex())->contents; + auto &src = state->newContents; + + if (state->setsToUpdate > 0) { + auto *dst = buffer.map(); + std::memcpy(dst, src.data(), src.size()); + buffer.unmap(); + + state->setsToUpdate--; + } + } +} + +} // namespace desktop +} // namespace progressia diff --git a/desktop/main.cpp b/desktop/main.cpp new file mode 100644 index 0000000..0735a37 --- /dev/null +++ b/desktop/main.cpp @@ -0,0 +1,35 @@ +#include + +#include "../main/game.h" +#include "graphics/glfw_mgmt.h" +#include "graphics/vulkan_mgmt.h" + +int main() { + + using namespace progressia; + + desktop::initializeGlfw(); + desktop::initializeVulkan(); + desktop::showWindow(); + + main::initialize(desktop::getVulkan()->getGint()); + + while (desktop::shouldRun()) { + bool abortFrame = !desktop::startRender(); + if (abortFrame) { + continue; + } + + main::renderTick(); + + desktop::endRender(); + desktop::doGlfwRoutine(); + } + + desktop::getVulkan()->waitIdle(); + main::shutdown(); + desktop::shutdownVulkan(); + desktop::shutdownGlfw(); + + return 0; +} diff --git a/docs/BuildingGuide.md b/docs/BuildingGuide.md new file mode 100644 index 0000000..4129c39 --- /dev/null +++ b/docs/BuildingGuide.md @@ -0,0 +1,88 @@ +# Building guide + +At this time, building is only supported in GNU/Linux targeting GNU/Linux X11. +See also [Development Setup Guide](DevelopmentSetupGuide.md) if you want to make +git commits. + +## Prerequisites + +Install the following software: + - a C++ compiler (GCC or clang preferably), + - CMake, + - pkg-config, + - Python 3, + - glslc. + +Install the following libraries with headers: + - Vulkan (loader library and headers), + - GLFW3, + - GLM, + - STB, + - Boost (only core library required). + +### Debian + +On Debian, you can run the following commands as root to install almost all +required software: +```bash +apt-get install \ + g++ \ + cmake \ + pkg-config \ + python3 && +apt-get install --no-install-recommends \ + libvulkan-dev \ + libglfw3-dev \ + libglm-dev \ + libstb-dev \ + libboost-dev +``` + +However, glslc, the shader compiler, is not available as a Debian package at the +moment. You can install it manually from official sources or use the download it +from windcorp.ru by running these commands as root: +```bash +apt-get install wget && +mkdir -p /opt/glslc && +wget --output-file=/opt/glslc/glslc \ + 'https://windcorp.ru/other/glslc-v2022.1-6-ga0a247d-static' && +chmod +x /opt/glslc/glslc +``` + +Alternatively, packages provided by LunarG are available for Ubuntu. Follow the +instructions on [LunarG.com](https://vulkan.lunarg.com/sdk/home) to install +`vulkan-sdk`. + +## Setup + +```bash +git clone +cd Progressia +chmod +x tools/setup.sh +tools/setup.sh +``` + +`tools/setup.sh` will check the availability of all required commands and +libraries. + +Build tools use enviroment variables `PATH`, `PKG_CONFIG_PATH` and +`CMAKE_MODULE_PATH`; you can edit these variables system-wide or use +`tools/private.sh` to make project-specific changes. +(Your changes to `tools/private.sh` are ignored by git.) +For example, of you ran the script to download glslc on Debian, you will need to +add the following line to `tools/private.sh`: +```bash +PATH="$PATH:/opt/glslc" +``` + +## Building + +```bash +tools/build.sh +``` + +## Running + +```bash +tools/build.sh -R +``` diff --git a/docs/DevelopmentSetupGuide.md b/docs/DevelopmentSetupGuide.md new file mode 100644 index 0000000..c96db26 --- /dev/null +++ b/docs/DevelopmentSetupGuide.md @@ -0,0 +1,63 @@ +# Development setup guide + +To make development easier, contributors should be using a few tools. Included +with the project are configurations and scripts for these tools: + - [cppcheck](http://cppcheck.net/) – performs static code analysis for C++ + - [clang-format](https://clang.llvm.org/docs/ClangFormat.html) – automatically + formats C++ source code + - [memcheck](https://valgrind.org/docs/manual/mc-manual.html) + (part of [valgrind](https://valgrind.org/)) – performs runtime memory + error detection + +Additionally, git hooks prevent committing code that is formatted incorrectly, +does not compile or produces warnings. You can bypass this check using + `git commit --no-verify` +in case of dire need. + +## Prerequisites + +Perform the setup described in the [Building Guide](BuildingGuide.md) first. + +Install the following software: + - cppcheck, + - clang-format (version 13 is recommended) + - valgrind + +### Debian + +On Debian, you can run the following commands as root to install all required +software: +```bash +apt-get install \ + cppcheck \ + clang-format-13 \ + valgrind +``` + +## Setup + +```bash +tools/setup.sh --for-development +``` + +With `--for-development` flag, `tools/setup.sh` will check the development tools +and install git pre-commit hook in addition to its normal duties. + +## Notes + +Developers will find it useful to read through the following help pages: +```bash +tools/build.sh --help +tools/cppcheck/use-cppcheck.sh --help +tools/clang-format/use-clang-format.sh --help +``` + +LunarG validation layers are extremely useful when writing and debugging Vulkan. +The official +[Vulkan tutorial](https://vulkan-tutorial.com/Development_environment) +has detailed instructions for all platforms. + +In particular, Debian users can run the following command as root: +```bash +apt-get install vulkan-validationlayers-dev +``` diff --git a/main/game.cpp b/main/game.cpp new file mode 100644 index 0000000..cfe2989 --- /dev/null +++ b/main/game.cpp @@ -0,0 +1,178 @@ +#include "game.h" + +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +#include +#include + +#include "rendering.h" + +namespace progressia { +namespace main { + +std::unique_ptr cube1, cube2; +std::unique_ptr texture1, texture2; +std::unique_ptr perspective; +std::unique_ptr light; + +GraphicsInterface *gint; + +void addRect(glm::vec3 origin, glm::vec3 width, glm::vec3 height, + glm::vec4 color, std::vector &vertices, + std::vector &indices) { + + Vertex::Index offset = vertices.size(); + + vertices.push_back({origin, color, {}, {0, 0}}); + vertices.push_back({origin + width, color, {}, {0, 1}}); + vertices.push_back({origin + width + height, color, {}, {1, 1}}); + vertices.push_back({origin + height, color, {}, {1, 0}}); + + indices.push_back(offset + 0); + indices.push_back(offset + 1); + indices.push_back(offset + 2); + + indices.push_back(offset + 0); + indices.push_back(offset + 2); + indices.push_back(offset + 3); +} + +void addBox(glm::vec3 origin, glm::vec3 length, glm::vec3 height, + glm::vec3 depth, std::array colors, + std::vector &vertices, + std::vector &indices) { + addRect(origin, height, length, colors[0], vertices, indices); + addRect(origin, length, depth, colors[1], vertices, indices); + addRect(origin, depth, height, colors[2], vertices, indices); + addRect(origin + height, depth, length, colors[3], vertices, indices); + addRect(origin + length, height, depth, colors[4], vertices, indices); + addRect(origin + depth, length, height, colors[5], vertices, indices); +} + +void initialize(GraphicsInterface &gintp) { + + std::cout << "game init begin" << std::endl; + gint = &gintp; + + texture1.reset( + gint->newTexture(progressia::main::loadImage(u"../assets/texture.png"))); + texture2.reset( + gint->newTexture(progressia::main::loadImage(u"../assets/texture2.png"))); + + // Cube 1 + { + std::vector vertices; + std::vector indices; + auto white = glm::vec4(1, 1, 1, 1); + + addBox({-0.5, -0.5, -0.5}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, + {white, white, white, white, white, white}, vertices, indices); + + for (std::size_t i = 0; i < indices.size(); i += 3) { + Vertex &a = vertices[indices[i + 0]]; + Vertex &b = vertices[indices[i + 1]]; + Vertex &c = vertices[indices[i + 2]]; + + glm::vec3 x = b.position - a.position; + glm::vec3 y = c.position - a.position; + + glm::vec3 normal = glm::normalize(glm::cross(x, y)); + + a.normal = normal; + b.normal = normal; + c.normal = normal; + } + + cube1.reset(gint->newPrimitive(vertices, indices, &*texture1)); + } + + // Cube 2 + { + std::vector vertices; + std::vector indices; + auto white = glm::vec4(1, 1, 1, 1); + + addBox({-0.5, -2.5, -0.5}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, + {white, white, white, white, white, white}, vertices, indices); + + for (std::size_t i = 0; i < indices.size(); i += 3) { + Vertex &a = vertices[indices[i + 0]]; + Vertex &b = vertices[indices[i + 1]]; + Vertex &c = vertices[indices[i + 2]]; + + glm::vec3 x = b.position - a.position; + glm::vec3 y = c.position - a.position; + + glm::vec3 normal = glm::normalize(glm::cross(x, y)); + + a.normal = normal; + b.normal = normal; + c.normal = normal; + } + + cube2.reset(gint->newPrimitive(vertices, indices, &*texture2)); + } + + perspective.reset(gint->newView()); + light.reset(gint->newLight()); + + std::cout << "game init complete" << std::endl; +} + +void renderTick() { + + { + float fov = 70.0f; + + auto extent = gint->getViewport(); + auto proj = glm::perspective(glm::radians(fov), + extent.x / (float)extent.y, 0.1f, 10.0f); + proj[1][1] *= -1; + + auto view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 1.0f)); + + perspective->configure(proj, view); + } + + perspective->use(); + + float contrast = glm::sin(gint->tmp_getTime() / 3) * 0.18f + 0.18f; + glm::vec3 color0(0.60f, 0.60f, 0.70f); + glm::vec3 color1(1.10f, 1.05f, 0.70f); + + float m = glm::sin(gint->tmp_getTime() / 3) * 0.5 + 0.5; + glm::vec3 color = m * color1 + (1 - m) * color0; + + light->configure(color, glm::vec3(1.0f, -2.0f, 1.0f), contrast, 0.1f); + light->use(); + + auto model = glm::eulerAngleYXZ(0.0f, 0.0f, gint->tmp_getTime() * 0.1f); + + gint->setModelTransform(model); + cube1->draw(); + cube2->draw(); +} + +void shutdown() { + std::cout << "game shutdown begin" << std::endl; + + cube1.reset(); + cube2.reset(); + texture1.reset(); + texture2.reset(); + + light.reset(); + perspective.reset(); + + std::cout << "game shutdown complete" << std::endl; +} + +} // namespace main +} // namespace progressia diff --git a/main/game.h b/main/game.h new file mode 100644 index 0000000..8a1b65e --- /dev/null +++ b/main/game.h @@ -0,0 +1,13 @@ +#pragma once + +#include "rendering.h" + +namespace progressia { +namespace main { + +void initialize(GraphicsInterface &); +void renderTick(); +void shutdown(); + +} // namespace main +} // namespace progressia diff --git a/main/meta.h b/main/meta.h new file mode 100644 index 0000000..239a8bc --- /dev/null +++ b/main/meta.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace progressia { +namespace main { +namespace meta { + +constexpr const char *NAME = "Progressia"; + +#ifndef _MAJOR +#warning Version number (_MAJOR _MINOR _PATCH _BUILD) not set, using 0.0.0+1 +#define _MAJOR 0 +#define _MINOR 0 +#define _PATCH 0 +#define _BUILD 1 +#endif + +using VersionUnit = uint8_t; +using VersionInt = uint32_t; + +constexpr struct { + + VersionUnit major, minor, patch, build; + + VersionInt number; + + bool isRelease; + +} VERSION{_MAJOR, + _MINOR, + _PATCH, + _BUILD, + + (static_cast(_MAJOR) << 24) | + (static_cast(_MINOR) << 16) | + (static_cast(_PATCH) << 8) | + (static_cast(_BUILD) << 0), + + _BUILD == 0}; + +} // namespace meta +} // namespace main +} // namespace progressia diff --git a/main/rendering.h b/main/rendering.h new file mode 100644 index 0000000..0348577 --- /dev/null +++ b/main/rendering.h @@ -0,0 +1,8 @@ +#pragma once + +#include "rendering/graphics_interface.h" +#include "rendering/image.h" + +namespace progressia { +namespace main {} // namespace main +} // namespace progressia diff --git a/main/rendering/graphics_interface.h b/main/rendering/graphics_interface.h new file mode 100644 index 0000000..8b44b9e --- /dev/null +++ b/main/rendering/graphics_interface.h @@ -0,0 +1,123 @@ +#pragma once + +#include "boost/core/noncopyable.hpp" +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +#include + +#include "image.h" + +namespace progressia { +namespace main { + +struct Vertex { + + using Index = uint16_t; + + glm::vec3 position; + glm::vec4 color; + glm::vec3 normal; + glm::vec2 texCoord; +}; + +class Texture : private boost::noncopyable { + public: + using Backend = void *; + + private: + Backend backend; + + friend class Primitive; + + public: + Texture(Backend); + ~Texture(); +}; + +class Primitive : private boost::noncopyable { + public: + using Backend = void *; + + private: + Backend backend; + + friend class GraphicsInterface; + + public: + Primitive(Backend); + ~Primitive(); + + void draw(); + + const Texture *getTexture() const; +}; + +class View : private boost::noncopyable { + public: + using Backend = void *; + + private: + Backend backend; + + public: + View(Backend); + ~View(); + + void configure(const glm::mat4 &proj, const glm::mat4 &view); + void use(); +}; + +class Light : private boost::noncopyable { + public: + using Backend = void *; + + private: + Backend backend; + + public: + Light(Backend); + ~Light(); + + void configure(const glm::vec3 &color, const glm::vec3 &from, + float contrast, float softness); + void use(); +}; + +class GraphicsInterface : private boost::noncopyable { + public: + using Backend = void *; + + private: + Backend backend; + + public: + GraphicsInterface(Backend); + ~GraphicsInterface(); + + Texture *newTexture(const Image &); + + Primitive *newPrimitive(const std::vector &, + const std::vector &, + Texture *texture); + + glm::vec2 getViewport() const; + + void setModelTransform(const glm::mat4 &); + + View *newView(); + Light *newLight(); + + void flush(); + void startNextLayer(); + + float tmp_getTime(); + uint64_t getLastStartedFrame(); +}; + +} // namespace main +} // namespace progressia diff --git a/main/rendering/image.cpp b/main/rendering/image.cpp new file mode 100644 index 0000000..d3c228d --- /dev/null +++ b/main/rendering/image.cpp @@ -0,0 +1,64 @@ +#include "image.h" + +#include +#include +#include +#include +#include + +#include "stb/stb_image.h" + +namespace progressia { +namespace main { + +std::size_t Image::getSize() const { return data.size(); } + +const Image::Byte *Image::getData() const { return data.data(); } + +Image::Byte *Image::getData() { return data.data(); } + +Image loadImage(const std::filesystem::path &path) { + + std::ifstream file(path, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + std::cout << "Could not read a PNG image from file " << path + << std::endl; + // REPORT_ERROR + exit(1); + } + + std::size_t fileSize = static_cast(file.tellg()); + std::vector png(fileSize); + + file.seekg(0); + file.read(reinterpret_cast(png.data()), fileSize); + + file.close(); + + int width; + int height; + int channelsInFile; + + Image::Byte *stbAllocatedData = + stbi_load_from_memory(png.data(), png.size(), &width, &height, + &channelsInFile, STBI_rgb_alpha); + + if (stbAllocatedData == NULL) { + std::cout << "Could not load a PNG image from file " << path + << std::endl; + // REPORT_ERROR + exit(1); + } + + std::vector data(width * height * STBI_rgb_alpha); + memcpy(data.data(), stbAllocatedData, data.size()); + + stbi_image_free(stbAllocatedData); + + return {static_cast(width), static_cast(height), + data}; +} + +} // namespace main +} // namespace progressia diff --git a/main/rendering/image.h b/main/rendering/image.h new file mode 100644 index 0000000..a49e4b7 --- /dev/null +++ b/main/rendering/image.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace progressia { +namespace main { + +class Image { + public: + using Byte = unsigned char; + + std::size_t width; + std::size_t height; + std::vector data; + + std::size_t getSize() const; + const Byte *getData() const; + Byte *getData(); +}; + +Image loadImage(const std::filesystem::path &); + +} // namespace main +} // namespace progressia diff --git a/main/stb_image.c b/main/stb_image.c new file mode 100644 index 0000000..2a9e7fd --- /dev/null +++ b/main/stb_image.c @@ -0,0 +1,4 @@ +#define STBI_ONLY_PNG + +#define STB_IMAGE_IMPLEMENTATION +#include diff --git a/main/util.h b/main/util.h new file mode 100644 index 0000000..34f4ccc --- /dev/null +++ b/main/util.h @@ -0,0 +1,30 @@ +#pragma once + +// clang-format off +#define FOR_PACK(PACK_TYPE, PACK_NAME, VAR, CODE) \ +{ \ + [[maybe_unused]] int dummy[] { \ + ( \ + [&](PACK_TYPE VAR) { \ + CODE; \ + return 0; \ + } \ + )(PACK_NAME)... \ + }; \ +} +// clang-format on + +// clang-format off +#define FOR_PACK_S(PACK_TYPE, VAR_TYPE, CODE) \ +{ \ + [[maybe_unused]] int dummy[] { \ + ( \ + [&]() { \ + using VAR_TYPE = PACK_TYPE; \ + CODE; \ + return 0; \ + } \ + )()... \ + }; \ +} +// clang-format on diff --git a/tools/bashlib.sh b/tools/bashlib.sh new file mode 100644 index 0000000..2c75f2e --- /dev/null +++ b/tools/bashlib.sh @@ -0,0 +1,66 @@ +#!/bin/false + +# Writes a message to stderr. +# Parameters: +# $@ - the message to display +function error() { + echo >&2 "`basename "$0"`: $@" +} + +# Writes a message to stderr and exits with code 1. +# Parameters: +# $@ - the message to display +function fail() { + error "$@" + exit 1; +} + +# Ensures that a variable with name $1 has a valid executable. If it does not, +# this function attempts to find an executable with a name suggested in $2...$n. +# In either way, if the variable does not end up naming an executable, fail() is +# called. +# Parameters: +# $1 - name of the variable to check and modify +# $2...$n - suggested executables (at least one) +# $FAIL_SILENTLY - if set, don't call exit and don't print anything on failure +function find_cmd() { + declare -n target="$1" + + if [ -z "${target+x}" ]; then + for candidate in "${@:2}"; do + if command -v "$candidate" >/dev/null; then + target="$candidate" + break + fi + done + fi + + if ! command -v "$target" >/dev/null; then + [ -n "${FAIL_SILENTLY+x}" ] && return 1 + fail "Command $2 is not available. Check \$PATH or set \$$1." + fi + + unset -n target + return 0 +} + +# Displays the command and then runs it. +# Parameters: +# $@ - the command to run +function echo_and_run() { + echo " > $*" + command "$@" +} + +root_dir="$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}")")")" +source_dir="$root_dir" +build_dir="$root_dir/build" +tools_dir="$root_dir/tools" + +# Load private.sh +private_sh="$tools_dir/private.sh" +if [ -f "$private_sh" ]; then + [ -x "$private_sh" ] \ + || fail 'tools/private.sh exists but it is not executable' + source "$private_sh" +fi diff --git a/tools/build.sh b/tools/build.sh new file mode 100755 index 0000000..4856124 --- /dev/null +++ b/tools/build.sh @@ -0,0 +1,170 @@ +#!/bin/bash + +usage=\ +"Usage: build.sh [OPTIONS...] [TOOL-ARGUMENT...] +Build and run the game. + +Options: + --debug make a debug build (default) + --release make a release build + --dont-generate don't generate build instructions; use existing + configuration if building + --dont-build don't build; run existing binaries or generate build + instructions only + --debug-vulkan enable Vulkan validation layers from LunarG + -R, --run run the game after building + --memcheck[=ARGS] run the game using valgrind's memcheck dynamic memory + analysis tool. Implies -R. ARGS is the ;-separated + list of arguments to pass to valgrind/memcheck. + + -h, --help display this help and exit + +Environment variables: + PARALLELISM threads to use, default is 1 + + CMAKE cmake executable + VALGRIND valgrind executable + +See also: tools/cppcheck/use-cppcheck.sh --help + tools/clang-format/use-clang-format.sh --help + tools/setup.sh --help" + +rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +source "$rsrc/bashlib.sh" + + + +# Parse arguments + +build_type=Debug +do_generate=true +do_build=true + +run_type=Normal +do_run='' + +debug_vulkan='' +memcheck_args=() + +for arg in "$@"; do + + if [ $is_cmake_arg ]; then + cmake_args+=("$arg") + else + case "$arg" in + -h | --help ) + echo "$usage" + exit + ;; + --debug ) + build_type=Debug + ;; + --release ) + build_type=Release + ;; + --debug-vulkan ) + debug_vulkan=true + ;; + -R | --run ) + do_run=true + ;; + --memcheck ) + do_run=true + run_type=memcheck + ;; + --memcheck=* ) + do_run=true + run_type=memcheck + readarray -t -d ';' new_memcheck_args <<<"${arg#*=};" + unset new_memcheck_args[-1] + memcheck_args+=("${new_memcheck_args[@]}") + unset new_memcheck_args + ;; + --dont-generate ) + do_generate='' + ;; + --dont-build ) + do_build='' + ;; + * ) + fail "Unknown option '$arg'" + ;; + esac + fi +done + +if [ -z "$do_build" -a -z "$do_generate" -a ${#cmake_args[@]} != 0 ]; then + fail "CMake arguments are set, but no build is requested. Aborting" +fi + +if [ -z "$do_build" -a -z "$do_generate" -a -z "$do_run" ]; then + fail "Nothing to do" +fi + + + +# Generate build files + +find_cmd CMAKE cmake +if [ $do_generate ]; then + echo_and_run "$CMAKE" \ + -B "$build_dir" \ + -S "$source_dir" \ + -DCMAKE_BUILD_TYPE=$build_type \ + -DVULKAN_ERROR_CHECKING=`[ $debug_vulkan ] && echo ON || echo OFF` \ + "${cmake_args[@]}" \ + || fail "Could not generate build files" +fi + + + +# Build + +find_cmd CMAKE cmake +if [ $do_build ]; then + options=() + + [ -n "${PARALLELISM+x}" ] && options+=(-j "$PARALLELISM") + + echo_and_run "$CMAKE" \ + --build "$build_dir" \ + "${options[@]}" \ + || fail "Build failed" + + unset options +fi + + + +# Run + +if [ $do_run ]; then + + run_command=() + + if [ $run_type == memcheck ]; then + find_cmd VALGRIND valgrind + + run_command+=( + "$VALGRIND" + --tool=memcheck + --suppressions="$tools_dir"/memcheck/suppressions.supp + "${memcheck_args[@]}" + -- + ) + fi + + run_command+=( + "$build_dir/progressia" + ) + + run_dir="$root_dir/run" + mkdir -p "$run_dir" + + ( + cd "$run_dir" + echo_and_run "${run_command[@]}" + echo "Process exited with code $?" + ) + +fi diff --git a/tools/clang-format/clang-format.yml b/tools/clang-format/clang-format.yml new file mode 100644 index 0000000..6d3cedb --- /dev/null +++ b/tools/clang-format/clang-format.yml @@ -0,0 +1,4 @@ +BasedOnStyle: LLVM + +# Use larger indentation +IndentWidth: 4 diff --git a/tools/clang-format/use-clang-format.sh b/tools/clang-format/use-clang-format.sh new file mode 100755 index 0000000..cb5274f --- /dev/null +++ b/tools/clang-format/use-clang-format.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +usage=\ +"Usage: use-clang-format.sh git + or: use-clang-format.sh files FILES... + or: use-clang-format.sh raw ARGUMENTS... +In the 1st form, format all files that have changed since last git commit. +In the 2nd form, format all FILES, treating directories recursively. +In the 3rd form, run \`clang-format --style=