mirror of
https://gitea.windcorp.ru/Wind-Corporation/Progressia.git
synced 2025-04-20 21:40:46 +03:00
Initial commit
This commit is contained in:
commit
da10f7c5cd
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -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
|
74
CMakeLists.txt
Normal file
74
CMakeLists.txt
Normal file
@ -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})
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
34
README.md
Normal file
34
README.md
Normal file
@ -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
|
BIN
assets/texture.png
Normal file
BIN
assets/texture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 706 B |
BIN
assets/texture2.png
Normal file
BIN
assets/texture2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
69
desktop/graphics/glfw_mgmt.cpp
Normal file
69
desktop/graphics/glfw_mgmt.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include "glfw_mgmt_details.h"
|
||||
|
||||
#define GLFW_INCLUDE_VULKAN
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#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
|
13
desktop/graphics/glfw_mgmt.h
Normal file
13
desktop/graphics/glfw_mgmt.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace progressia {
|
||||
namespace desktop {
|
||||
|
||||
void initializeGlfw();
|
||||
void showWindow();
|
||||
void shutdownGlfw();
|
||||
bool shouldRun();
|
||||
void doGlfwRoutine();
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
14
desktop/graphics/glfw_mgmt_details.h
Normal file
14
desktop/graphics/glfw_mgmt_details.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "glfw_mgmt.h"
|
||||
|
||||
#define GLFW_INCLUDE_VULKAN
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
namespace progressia {
|
||||
namespace desktop {
|
||||
|
||||
GLFWwindow *getGLFWWindowHandle();
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
12
desktop/graphics/shaders/shader.frag
Normal file
12
desktop/graphics/shaders/shader.frag
Normal file
@ -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);
|
||||
}
|
62
desktop/graphics/shaders/shader.vert
Normal file
62
desktop/graphics/shaders/shader.vert
Normal file
@ -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;
|
||||
}
|
336
desktop/graphics/vulkan_adapter.cpp
Normal file
336
desktop/graphics/vulkan_adapter.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
#include "vulkan_adapter.h"
|
||||
|
||||
#include "vulkan_common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/gtx/euler_angles.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#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 <embedded_resources.h>
|
||||
|
||||
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<char> 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<char>(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<Attachment> &Adapter::getAttachments() { return attachments; }
|
||||
|
||||
std::vector<char> Adapter::loadVertexShader() {
|
||||
return tmp_readFile("shader.vert.spv");
|
||||
}
|
||||
|
||||
std::vector<char> 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<VkVertexInputAttributeDescription>
|
||||
Adapter::getVertexInputAttributeDescriptions() {
|
||||
std::vector<VkVertexInputAttributeDescription> 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<VkDescriptorSetLayout> 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<Vertex> *vertices;
|
||||
glm::mat4 modelTransform;
|
||||
};
|
||||
|
||||
std::vector<DrawRequest> pendingDrawCommands;
|
||||
glm::mat4 currentModelTransform;
|
||||
} // namespace
|
||||
|
||||
progressia::main::Texture::Texture(Backend backend) : backend(backend) {}
|
||||
|
||||
progressia::main::Texture::~Texture() {
|
||||
delete static_cast<progressia::desktop::Texture *>(this->backend);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct PrimitiveBackend {
|
||||
|
||||
IndexedBuffer<Vertex> buf;
|
||||
progressia::main::Texture *tex;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Primitive::Primitive(Backend backend) : backend(backend) {}
|
||||
|
||||
Primitive::~Primitive() {
|
||||
delete static_cast<PrimitiveBackend *>(this->backend);
|
||||
}
|
||||
|
||||
void Primitive::draw() {
|
||||
auto backend = static_cast<PrimitiveBackend *>(this->backend);
|
||||
|
||||
if (pendingDrawCommands.size() > 100000) {
|
||||
backend->buf.getVulkan().getGint().flush();
|
||||
}
|
||||
|
||||
pendingDrawCommands.push_back(
|
||||
{static_cast<progressia::desktop::Texture *>(backend->tex->backend),
|
||||
&backend->buf, currentModelTransform});
|
||||
}
|
||||
|
||||
const progressia::main::Texture *Primitive::getTexture() const {
|
||||
return static_cast<PrimitiveBackend *>(this->backend)->tex;
|
||||
}
|
||||
|
||||
View::View(Backend backend) : backend(backend) {}
|
||||
|
||||
View::~View() {
|
||||
delete static_cast<Adapter::ViewUniform::State *>(this->backend);
|
||||
}
|
||||
|
||||
void View::configure(const glm::mat4 &proj, const glm::mat4 &view) {
|
||||
|
||||
static_cast<Adapter::ViewUniform::State *>(this->backend)
|
||||
->update(proj, view);
|
||||
}
|
||||
|
||||
void View::use() {
|
||||
auto backend = static_cast<Adapter::ViewUniform::State *>(this->backend);
|
||||
backend->uniform->getVulkan().getGint().flush();
|
||||
backend->bind();
|
||||
}
|
||||
|
||||
Light::Light(Backend backend) : backend(backend) {}
|
||||
|
||||
Light::~Light() {
|
||||
delete static_cast<Adapter::LightUniform::State *>(this->backend);
|
||||
}
|
||||
|
||||
void Light::configure(const glm::vec3 &color, const glm::vec3 &from,
|
||||
float contrast, float softness) {
|
||||
|
||||
static_cast<Adapter::LightUniform::State *>(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<Adapter::LightUniform::State *>(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<Vulkan *>(this->backend));
|
||||
|
||||
return new Texture(backend);
|
||||
}
|
||||
|
||||
Primitive *
|
||||
GraphicsInterface::newPrimitive(const std::vector<Vertex> &vertices,
|
||||
const std::vector<Vertex::Index> &indices,
|
||||
progressia::main::Texture *texture) {
|
||||
|
||||
auto backend = new PrimitiveBackend{
|
||||
IndexedBuffer<Vertex>(vertices.size(), indices.size(),
|
||||
*static_cast<Vulkan *>(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<Vulkan *>(this->backend)->getAdapter().createView()));
|
||||
}
|
||||
|
||||
Light *GraphicsInterface::newLight() {
|
||||
return new Light(new Adapter::LightUniform::State(
|
||||
static_cast<Vulkan *>(this->backend)->getAdapter().createLight()));
|
||||
}
|
||||
|
||||
glm::vec2 GraphicsInterface::getViewport() const {
|
||||
auto extent =
|
||||
static_cast<const Vulkan *>(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<Vulkan *>(this->backend)
|
||||
->getCurrentFrame()
|
||||
->getCommandBuffer();
|
||||
auto pipelineLayout =
|
||||
static_cast<Vulkan *>(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<decltype(m)>::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<Vulkan *>(this->backend)->getLastStartedFrame();
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace progressia
|
72
desktop/graphics/vulkan_adapter.h
Normal file
72
desktop/graphics/vulkan_adapter.h
Normal file
@ -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> image;
|
||||
};
|
||||
|
||||
class Adapter : public VkObjectWrapper {
|
||||
public:
|
||||
using ViewUniform = Uniform<glm::mat4, glm::mat4>;
|
||||
|
||||
struct Light {
|
||||
glm::vec4 color;
|
||||
glm::vec4 from;
|
||||
float contrast;
|
||||
float softness;
|
||||
};
|
||||
|
||||
using LightUniform = Uniform<Light>;
|
||||
|
||||
private:
|
||||
Vulkan &vulkan;
|
||||
|
||||
ViewUniform viewUniform;
|
||||
LightUniform lightUniform;
|
||||
|
||||
std::vector<Attachment> attachments;
|
||||
|
||||
public:
|
||||
Adapter(Vulkan &);
|
||||
~Adapter();
|
||||
|
||||
std::vector<Attachment> &getAttachments();
|
||||
|
||||
VkVertexInputBindingDescription getVertexInputBindingDescription();
|
||||
std::vector<VkVertexInputAttributeDescription>
|
||||
getVertexInputAttributeDescriptions();
|
||||
|
||||
std::vector<char> loadVertexShader();
|
||||
std::vector<char> loadFragmentShader();
|
||||
|
||||
ViewUniform::State createView();
|
||||
LightUniform::State createLight();
|
||||
|
||||
std::vector<VkDescriptorSetLayout> getUsedDSLayouts() const;
|
||||
void onPreFrame();
|
||||
};
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
196
desktop/graphics/vulkan_buffer.h
Normal file
196
desktop/graphics/vulkan_buffer.h
Normal file
@ -0,0 +1,196 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "vulkan_common.h"
|
||||
|
||||
namespace progressia {
|
||||
namespace desktop {
|
||||
|
||||
/*
|
||||
* A single buffer with a chunk of allocated memory.
|
||||
*/
|
||||
template <typename Item> 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 <typename Item> class FastReadBuffer : public VkObjectWrapper {
|
||||
private:
|
||||
VkCommandBuffer commandBuffer;
|
||||
Vulkan &vulkan;
|
||||
|
||||
public:
|
||||
Buffer<Item> stagingBuffer;
|
||||
Buffer<Item> 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 <typename Vertex, typename Index, VkIndexType INDEX_TYPE>
|
||||
class IndexedBufferBase : public VkObjectWrapper {
|
||||
|
||||
private:
|
||||
FastReadBuffer<Vertex> vertexBuffer;
|
||||
FastReadBuffer<Index> 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<uint32_t>(indexBuffer.getItemCount()), 1,
|
||||
0, 0, 0);
|
||||
}
|
||||
|
||||
Vulkan &getVulkan() { return vertexBuffer.getVulkan(); }
|
||||
|
||||
const Vulkan &getVulkan() const { return vertexBuffer.getVulkan(); }
|
||||
};
|
||||
|
||||
template <typename Vertex>
|
||||
using IndexedBuffer = IndexedBufferBase<Vertex, uint16_t, VK_INDEX_TYPE_UINT16>;
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
776
desktop/graphics/vulkan_common.cpp
Normal file
776
desktop/graphics/vulkan_common.cpp
Normal file
@ -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<const char *> instanceExtensions,
|
||||
std::vector<const char *> deviceExtensions,
|
||||
std::vector<const char *> validationLayers)
|
||||
:
|
||||
|
||||
frames(MAX_FRAMES_IN_FLIGHT), isRenderingFrame(false),
|
||||
lastStartedFrame(0) {
|
||||
|
||||
/*
|
||||
* Create error handler
|
||||
*/
|
||||
errorHandler = std::make_unique<VulkanErrorHandler>(*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<VkExtensionProperties> 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<uint32_t>(instanceExtensions.size());
|
||||
createInfo.ppEnabledExtensionNames = instanceExtensions.data();
|
||||
|
||||
// Enable validation layers
|
||||
{
|
||||
uint32_t layerCount;
|
||||
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
|
||||
std::vector<VkLayerProperties> 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<uint32_t>(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<Surface>(*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<VkPhysicalDevice> devices(deviceCount);
|
||||
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
|
||||
|
||||
std::vector<PhysicalDeviceData> 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<Queues>(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<uint32_t>(deviceExtensions.size());
|
||||
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
|
||||
// Provide a copy of instance validation layers
|
||||
|
||||
createInfo.enabledLayerCount =
|
||||
static_cast<uint32_t>(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<CommandPool>(*this, queues->getGraphicsQueue());
|
||||
|
||||
/*
|
||||
* Create texture descriptor manager
|
||||
*/
|
||||
|
||||
textureDescriptors = std::make_unique<TextureDescriptors>(*this);
|
||||
|
||||
/*
|
||||
* Initialize adapter
|
||||
*/
|
||||
adapter = std::make_unique<Adapter>(*this);
|
||||
|
||||
/*
|
||||
* Initialize swap chain
|
||||
*/
|
||||
swapChain = std::make_unique<SwapChain>(*this);
|
||||
|
||||
/*
|
||||
* Create render pass
|
||||
*/
|
||||
renderPass = std::make_unique<RenderPass>(*this);
|
||||
|
||||
/*
|
||||
* Create pipeline
|
||||
*/
|
||||
pipeline = std::make_unique<Pipeline>(*this);
|
||||
|
||||
/*
|
||||
* Create swap chain
|
||||
*/
|
||||
swapChain->recreate();
|
||||
|
||||
/*
|
||||
* Create frames
|
||||
*/
|
||||
for (auto &container : frames) {
|
||||
container.emplace(*this);
|
||||
}
|
||||
currentFrame = 0;
|
||||
|
||||
gint = std::make_unique<progressia::main::GraphicsInterface>(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<VkFormat> &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<const Vulkan *>(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<VkDebugUtilsMessengerCreateInfoEXT>
|
||||
VulkanErrorHandler::attachDebugProbe(VkInstanceCreateInfo &createInfo) {
|
||||
#ifdef VULKAN_ERROR_CHECKING
|
||||
|
||||
std::unique_ptr result =
|
||||
std::make_unique<VkDebugUtilsMessengerCreateInfoEXT>();
|
||||
|
||||
populateDebugMessengerCreateInfo(*result, vulkan);
|
||||
|
||||
result->pNext = createInfo.pNext;
|
||||
createInfo.pNext = &*result;
|
||||
|
||||
return result;
|
||||
|
||||
#else
|
||||
|
||||
(void)createInfo;
|
||||
return std::unique_ptr<VkDebugUtilsMessengerCreateInfoEXT>();
|
||||
|
||||
#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<VkQueueFamilyProperties> 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::CreationRequest>
|
||||
Queues::requestCreation(VkDeviceCreateInfo &createInfo) const {
|
||||
|
||||
std::unique_ptr result = std::make_unique<CreationRequest>();
|
||||
result->priority = 1.0f;
|
||||
|
||||
std::unordered_set<uint32_t> 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<uint32_t>(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
|
289
desktop/graphics/vulkan_common.h
Normal file
289
desktop/graphics/vulkan_common.h
Normal file
@ -0,0 +1,289 @@
|
||||
#pragma once
|
||||
|
||||
#define GLFW_INCLUDE_VULKAN
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
|
||||
#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<const char *, CstrHash, CstrEqual>;
|
||||
} // 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<VulkanErrorHandler> errorHandler;
|
||||
std::unique_ptr<Surface> surface;
|
||||
std::unique_ptr<Queues> queues;
|
||||
std::unique_ptr<CommandPool> commandPool;
|
||||
std::unique_ptr<RenderPass> renderPass;
|
||||
std::unique_ptr<Pipeline> pipeline;
|
||||
std::unique_ptr<SwapChain> swapChain;
|
||||
std::unique_ptr<TextureDescriptors> textureDescriptors;
|
||||
std::unique_ptr<Adapter> adapter;
|
||||
|
||||
std::unique_ptr<progressia::main::GraphicsInterface> gint;
|
||||
|
||||
std::vector<std::optional<Frame>> frames;
|
||||
std::size_t currentFrame;
|
||||
bool isRenderingFrame;
|
||||
uint64_t lastStartedFrame;
|
||||
|
||||
public:
|
||||
Vulkan(std::vector<const char *> instanceExtensions,
|
||||
std::vector<const char *> deviceExtensions,
|
||||
std::vector<const char *> 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<VkFormat> &, VkImageTiling,
|
||||
VkFormatFeatureFlags);
|
||||
uint32_t findMemoryType(uint32_t allowedByDevice,
|
||||
VkMemoryPropertyFlags desiredProperties);
|
||||
|
||||
template <typename... Args>
|
||||
VkResult call(const char *functionName, Args &&...args) {
|
||||
|
||||
using FunctionSignature = VkResult(VkInstance, Args...);
|
||||
|
||||
auto func = reinterpret_cast<FunctionSignature *>(
|
||||
vkGetInstanceProcAddr(instance, functionName));
|
||||
if (func != nullptr) {
|
||||
return func(instance, std::forward<Args>(args)...);
|
||||
} else {
|
||||
std::cout << "[Vulkan] [dynVkCall / VkResult]\tFunction not found "
|
||||
"for name \""
|
||||
<< functionName << "\"" << std::endl;
|
||||
// REPORT_ERROR
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
VkResult callVoid(const char *functionName, Args &&...args) {
|
||||
|
||||
using FunctionSignature = void(VkInstance, Args...);
|
||||
|
||||
auto func = reinterpret_cast<FunctionSignature *>(
|
||||
vkGetInstanceProcAddr(instance, functionName));
|
||||
if (func != nullptr) {
|
||||
func(instance, std::forward<Args>(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<VkDebugUtilsMessengerCreateInfoEXT>
|
||||
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<bool(VkPhysicalDevice, uint32_t, Vulkan &,
|
||||
const VkQueueFamilyProperties &)>;
|
||||
|
||||
Test test;
|
||||
std::optional<uint32_t> 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<VkDeviceQueueCreateInfo> queueCreateInfos;
|
||||
};
|
||||
std::unique_ptr<CreationRequest>
|
||||
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
|
19
desktop/graphics/vulkan_descriptor_set.cpp
Normal file
19
desktop/graphics/vulkan_descriptor_set.cpp
Normal file
@ -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
|
23
desktop/graphics/vulkan_descriptor_set.h
Normal file
23
desktop/graphics/vulkan_descriptor_set.h
Normal file
@ -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
|
171
desktop/graphics/vulkan_frame.cpp
Normal file
171
desktop/graphics/vulkan_frame.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
#include "vulkan_frame.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#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<uint32_t>(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
|
36
desktop/graphics/vulkan_frame.h
Normal file
36
desktop/graphics/vulkan_frame.h
Normal file
@ -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<VkClearValue> clearValues;
|
||||
|
||||
std::optional<uint32_t> imageIndexInFlight;
|
||||
|
||||
public:
|
||||
Frame(Vulkan &vulkan);
|
||||
~Frame();
|
||||
|
||||
/*
|
||||
* Returns false when the frame should be skipped
|
||||
*/
|
||||
bool startRender();
|
||||
void endRender();
|
||||
|
||||
VkCommandBuffer getCommandBuffer();
|
||||
};
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
247
desktop/graphics/vulkan_image.cpp
Normal file
247
desktop/graphics/vulkan_image.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
#include "vulkan_image.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#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<uint32_t>(width);
|
||||
imageInfo.extent.height = static_cast<uint32_t>(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<progressia::main::Image::Byte> 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<uint32_t>(src.width),
|
||||
static_cast<uint32_t>(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
|
60
desktop/graphics/vulkan_image.h
Normal file
60
desktop/graphics/vulkan_image.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
#include <vector>
|
||||
|
||||
#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
|
68
desktop/graphics/vulkan_mgmt.cpp
Normal file
68
desktop/graphics/vulkan_mgmt.cpp
Normal file
@ -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<const char *> 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<const char *> deviceExtensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||
|
||||
// Validation layers
|
||||
|
||||
std::vector<const char *> 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
|
23
desktop/graphics/vulkan_mgmt.h
Normal file
23
desktop/graphics/vulkan_mgmt.h
Normal file
@ -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
|
98
desktop/graphics/vulkan_pick_device.cpp
Normal file
98
desktop/graphics/vulkan_pick_device.cpp
Normal file
@ -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<const char *> &deviceExtensions) {
|
||||
CstrUtils::CstrHashSet toFind(deviceExtensions.cbegin(),
|
||||
deviceExtensions.cend());
|
||||
|
||||
uint32_t extensionCount;
|
||||
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
|
||||
nullptr);
|
||||
|
||||
std::vector<VkExtensionProperties> 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<const char *> &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<PhysicalDeviceData> &choices, Vulkan &vulkan,
|
||||
const std::vector<const char *> &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[] = {{"<unknown>", 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
|
21
desktop/graphics/vulkan_pick_device.h
Normal file
21
desktop/graphics/vulkan_pick_device.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "vulkan_common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace progressia {
|
||||
namespace desktop {
|
||||
|
||||
struct PhysicalDeviceData {
|
||||
VkPhysicalDevice device;
|
||||
VkPhysicalDeviceProperties properties;
|
||||
VkPhysicalDeviceFeatures features;
|
||||
};
|
||||
|
||||
const PhysicalDeviceData &
|
||||
pickPhysicalDevice(std::vector<PhysicalDeviceData> &, Vulkan &,
|
||||
const std::vector<const char *> &deviceExtensions);
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
223
desktop/graphics/vulkan_pipeline.cpp
Normal file
223
desktop/graphics/vulkan_pipeline.cpp
Normal file
@ -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<VkDynamicState> 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<uint32_t>(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<uint32_t>(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<char> &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<const uint32_t *>(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
|
27
desktop/graphics/vulkan_pipeline.h
Normal file
27
desktop/graphics/vulkan_pipeline.h
Normal file
@ -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<char> &bytecode);
|
||||
|
||||
public:
|
||||
Pipeline(Vulkan &);
|
||||
~Pipeline();
|
||||
|
||||
VkPipeline getVk();
|
||||
VkPipelineLayout getLayout();
|
||||
};
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
83
desktop/graphics/vulkan_render_pass.cpp
Normal file
83
desktop/graphics/vulkan_render_pass.cpp
Normal file
@ -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<VkAttachmentDescription> attachmentDescriptions;
|
||||
std::vector<VkAttachmentReference> 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<uint32_t>(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
|
23
desktop/graphics/vulkan_render_pass.h
Normal file
23
desktop/graphics/vulkan_render_pass.h
Normal file
@ -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
|
328
desktop/graphics/vulkan_swap_chain.cpp
Normal file
328
desktop/graphics/vulkan_swap_chain.cpp
Normal file
@ -0,0 +1,328 @@
|
||||
#include "vulkan_swap_chain.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#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<VkImage> 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<ManagedImage>(
|
||||
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<VkImageView> 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<uint32_t>(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<VkSurfaceFormatKHR> &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<VkPresentModeKHR> &supported) {
|
||||
return std::find(supported.begin(), supported.end(),
|
||||
VK_PRESENT_MODE_MAILBOX_KHR) != supported.end();
|
||||
}
|
||||
|
||||
VkPresentModeKHR
|
||||
SwapChain::choosePresentMode(const std::vector<VkPresentModeKHR> &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<uint32_t>::max()) {
|
||||
return capabilities.currentExtent;
|
||||
}
|
||||
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(getGLFWWindowHandle(), &width, &height);
|
||||
|
||||
VkExtent2D actualExtent = {static_cast<uint32_t>(width),
|
||||
static_cast<uint32_t>(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<Image>(static_cast<VkImage>(VK_NULL_HANDLE),
|
||||
static_cast<VkImageView>(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
|
58
desktop/graphics/vulkan_swap_chain.h
Normal file
58
desktop/graphics/vulkan_swap_chain.h
Normal file
@ -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<VkSurfaceFormatKHR> formats;
|
||||
std::vector<VkPresentModeKHR> presentModes;
|
||||
};
|
||||
|
||||
static SupportDetails querySwapChainSupport(VkPhysicalDevice device,
|
||||
Vulkan &vulkan);
|
||||
static bool isSwapChainSuitable(const SupportDetails &details);
|
||||
|
||||
private:
|
||||
VkSwapchainKHR vk;
|
||||
|
||||
Attachment *colorBuffer;
|
||||
std::vector<VkImageView> colorBufferViews;
|
||||
|
||||
VkExtent2D extent;
|
||||
|
||||
Image *depthBuffer;
|
||||
|
||||
std::vector<VkFramebuffer> framebuffers;
|
||||
|
||||
Vulkan &vulkan;
|
||||
|
||||
void create();
|
||||
void destroy();
|
||||
|
||||
VkSurfaceFormatKHR
|
||||
chooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR> &);
|
||||
bool isTripleBufferingSupported(const std::vector<VkPresentModeKHR> &);
|
||||
VkPresentModeKHR choosePresentMode(const std::vector<VkPresentModeKHR> &,
|
||||
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
|
106
desktop/graphics/vulkan_texture_descriptors.cpp
Normal file
106
desktop/graphics/vulkan_texture_descriptors.cpp
Normal file
@ -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
|
29
desktop/graphics/vulkan_texture_descriptors.h
Normal file
29
desktop/graphics/vulkan_texture_descriptors.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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<VkDescriptorPool> pools;
|
||||
uint32_t lastPoolCapacity;
|
||||
|
||||
void allocatePool();
|
||||
|
||||
public:
|
||||
TextureDescriptors(Vulkan &);
|
||||
~TextureDescriptors();
|
||||
|
||||
VkDescriptorSet addTexture(VkImageView, VkSampler);
|
||||
};
|
||||
|
||||
} // namespace desktop
|
||||
} // namespace progressia
|
76
desktop/graphics/vulkan_uniform.h
Normal file
76
desktop/graphics/vulkan_uniform.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "vulkan_buffer.h"
|
||||
#include "vulkan_common.h"
|
||||
#include "vulkan_descriptor_set.h"
|
||||
|
||||
namespace progressia {
|
||||
namespace desktop {
|
||||
|
||||
template <typename... Entries> class Uniform : public DescriptorSetInterface {
|
||||
|
||||
private:
|
||||
constexpr static uint32_t POOL_SIZE = 64;
|
||||
|
||||
std::vector<VkDescriptorPool> pools;
|
||||
|
||||
struct StateImpl {
|
||||
struct Set {
|
||||
VkDescriptorSet vk;
|
||||
Buffer<unsigned char> contents;
|
||||
|
||||
Set(VkDescriptorSet, Vulkan &);
|
||||
};
|
||||
|
||||
std::array<std::optional<Set>, MAX_FRAMES_IN_FLIGHT> sets;
|
||||
|
||||
std::array<unsigned char, (sizeof(Entries) + ...)> newContents;
|
||||
uint64_t setsToUpdate;
|
||||
|
||||
StateImpl(const std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT> &vks,
|
||||
Vulkan &);
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<StateImpl>> states;
|
||||
|
||||
uint32_t lastPoolCapacity;
|
||||
|
||||
void allocatePool();
|
||||
|
||||
public:
|
||||
class State {
|
||||
|
||||
private:
|
||||
std::size_t id;
|
||||
|
||||
public:
|
||||
Uniform<Entries...> *uniform;
|
||||
|
||||
private:
|
||||
friend class Uniform<Entries...>;
|
||||
State(std::size_t id, Uniform<Entries...> *);
|
||||
|
||||
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"
|
194
desktop/graphics/vulkan_uniform.inl
Normal file
194
desktop/graphics/vulkan_uniform.inl
Normal file
@ -0,0 +1,194 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../../main/util.h"
|
||||
#include "vulkan_frame.h"
|
||||
#include "vulkan_pipeline.h"
|
||||
|
||||
namespace progressia {
|
||||
namespace desktop {
|
||||
|
||||
template <typename... Entries>
|
||||
Uniform<Entries...>::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 <typename... Entries>
|
||||
Uniform<Entries...>::StateImpl::StateImpl(
|
||||
const std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT> &vks,
|
||||
Vulkan &vulkan)
|
||||
: setsToUpdate(0) {
|
||||
constexpr std::size_t COUNT = sizeof...(Entries) * MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
std::array<VkDescriptorBufferInfo, COUNT> bufferInfos;
|
||||
std::array<VkWriteDescriptorSet, COUNT> 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 <typename... Entries>
|
||||
Uniform<Entries...>::State::State(std::size_t id, Uniform *uniform)
|
||||
: id(id), uniform(uniform) {}
|
||||
|
||||
template <typename... Entries>
|
||||
Uniform<Entries...>::State::State() : id(-1), uniform(nullptr) {}
|
||||
|
||||
template <typename... Entries>
|
||||
void Uniform<Entries...>::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 <typename... Entries> void Uniform<Entries...>::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 <typename... Entries>
|
||||
Uniform<Entries...>::Uniform(uint32_t setNumber, Vulkan &vulkan)
|
||||
: DescriptorSetInterface(setNumber, vulkan) {
|
||||
VkDescriptorSetLayoutCreateInfo layoutInfo{};
|
||||
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
|
||||
std::array<VkDescriptorSetLayoutBinding, sizeof...(Entries)> 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 <typename... Entries> Uniform<Entries...>::~Uniform() {
|
||||
for (auto pool : pools) {
|
||||
vkDestroyDescriptorPool(vulkan.getDevice(), pool, nullptr);
|
||||
}
|
||||
|
||||
vkDestroyDescriptorSetLayout(vulkan.getDevice(), layout, nullptr);
|
||||
}
|
||||
|
||||
template <typename... Entries> void Uniform<Entries...>::allocatePool() {
|
||||
pools.resize(pools.size() + 1);
|
||||
|
||||
std::array<VkDescriptorPoolSize, 1> 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... Entries>
|
||||
typename Uniform<Entries...>::State Uniform<Entries...>::addState() {
|
||||
if (lastPoolCapacity < MAX_FRAMES_IN_FLIGHT) {
|
||||
allocatePool();
|
||||
}
|
||||
|
||||
std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT> vks;
|
||||
|
||||
VkDescriptorSetAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
allocInfo.descriptorPool = pools.back();
|
||||
allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> 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<StateImpl>(vks, vulkan));
|
||||
|
||||
return State(states.size() - 1, this);
|
||||
}
|
||||
|
||||
template <typename... Entries> void Uniform<Entries...>::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
|
35
desktop/main.cpp
Normal file
35
desktop/main.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include <iostream>
|
||||
|
||||
#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;
|
||||
}
|
88
docs/BuildingGuide.md
Normal file
88
docs/BuildingGuide.md
Normal file
@ -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 <clone url>
|
||||
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
|
||||
```
|
63
docs/DevelopmentSetupGuide.md
Normal file
63
docs/DevelopmentSetupGuide.md
Normal file
@ -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
|
||||
```
|
178
main/game.cpp
Normal file
178
main/game.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
#include "game.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/gtx/euler_angles.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#include "rendering.h"
|
||||
|
||||
namespace progressia {
|
||||
namespace main {
|
||||
|
||||
std::unique_ptr<Primitive> cube1, cube2;
|
||||
std::unique_ptr<Texture> texture1, texture2;
|
||||
std::unique_ptr<View> perspective;
|
||||
std::unique_ptr<Light> light;
|
||||
|
||||
GraphicsInterface *gint;
|
||||
|
||||
void addRect(glm::vec3 origin, glm::vec3 width, glm::vec3 height,
|
||||
glm::vec4 color, std::vector<Vertex> &vertices,
|
||||
std::vector<Vertex::Index> &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<glm::vec4, 6> colors,
|
||||
std::vector<Vertex> &vertices,
|
||||
std::vector<Vertex::Index> &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<Vertex> vertices;
|
||||
std::vector<Vertex::Index> 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<Vertex> vertices;
|
||||
std::vector<Vertex::Index> 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
|
13
main/game.h
Normal file
13
main/game.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "rendering.h"
|
||||
|
||||
namespace progressia {
|
||||
namespace main {
|
||||
|
||||
void initialize(GraphicsInterface &);
|
||||
void renderTick();
|
||||
void shutdown();
|
||||
|
||||
} // namespace main
|
||||
} // namespace progressia
|
44
main/meta.h
Normal file
44
main/meta.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
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<VersionInt>(_MAJOR) << 24) |
|
||||
(static_cast<VersionInt>(_MINOR) << 16) |
|
||||
(static_cast<VersionInt>(_PATCH) << 8) |
|
||||
(static_cast<VersionInt>(_BUILD) << 0),
|
||||
|
||||
_BUILD == 0};
|
||||
|
||||
} // namespace meta
|
||||
} // namespace main
|
||||
} // namespace progressia
|
8
main/rendering.h
Normal file
8
main/rendering.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "rendering/graphics_interface.h"
|
||||
#include "rendering/image.h"
|
||||
|
||||
namespace progressia {
|
||||
namespace main {} // namespace main
|
||||
} // namespace progressia
|
123
main/rendering/graphics_interface.h
Normal file
123
main/rendering/graphics_interface.h
Normal file
@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "boost/core/noncopyable.hpp"
|
||||
#include <vector>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#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<Vertex> &,
|
||||
const std::vector<Vertex::Index> &,
|
||||
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
|
64
main/rendering/image.cpp
Normal file
64
main/rendering/image.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "image.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#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<std::size_t>(file.tellg());
|
||||
std::vector<Image::Byte> png(fileSize);
|
||||
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char *>(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<Image::Byte> data(width * height * STBI_rgb_alpha);
|
||||
memcpy(data.data(), stbAllocatedData, data.size());
|
||||
|
||||
stbi_image_free(stbAllocatedData);
|
||||
|
||||
return {static_cast<std::size_t>(width), static_cast<std::size_t>(height),
|
||||
data};
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace progressia
|
25
main/rendering/image.h
Normal file
25
main/rendering/image.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace progressia {
|
||||
namespace main {
|
||||
|
||||
class Image {
|
||||
public:
|
||||
using Byte = unsigned char;
|
||||
|
||||
std::size_t width;
|
||||
std::size_t height;
|
||||
std::vector<Byte> data;
|
||||
|
||||
std::size_t getSize() const;
|
||||
const Byte *getData() const;
|
||||
Byte *getData();
|
||||
};
|
||||
|
||||
Image loadImage(const std::filesystem::path &);
|
||||
|
||||
} // namespace main
|
||||
} // namespace progressia
|
4
main/stb_image.c
Normal file
4
main/stb_image.c
Normal file
@ -0,0 +1,4 @@
|
||||
#define STBI_ONLY_PNG
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
30
main/util.h
Normal file
30
main/util.h
Normal file
@ -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
|
66
tools/bashlib.sh
Normal file
66
tools/bashlib.sh
Normal file
@ -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
|
170
tools/build.sh
Executable file
170
tools/build.sh
Executable file
@ -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
|
4
tools/clang-format/clang-format.yml
Normal file
4
tools/clang-format/clang-format.yml
Normal file
@ -0,0 +1,4 @@
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
# Use larger indentation
|
||||
IndentWidth: 4
|
104
tools/clang-format/use-clang-format.sh
Executable file
104
tools/clang-format/use-clang-format.sh
Executable file
@ -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=<style> ARGUMENTS...\`.
|
||||
|
||||
Environment variables:
|
||||
CLANG_FORMAT clang-format executable
|
||||
CLANG_FORMAT_DIFF clang-format-diff script"
|
||||
|
||||
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
|
||||
source "$rsrc/../bashlib.sh"
|
||||
|
||||
case "$1" in
|
||||
git )
|
||||
find_cmd CLANG_FORMAT_DIFF \
|
||||
clang-format-diff-13 \
|
||||
clang-format-diff \
|
||||
clang-format-diff.py
|
||||
;;
|
||||
files | raw )
|
||||
find_cmd CLANG_FORMAT \
|
||||
clang-format-13 \
|
||||
clang-format
|
||||
;;
|
||||
-h | --help | '' )
|
||||
echo "$usage"
|
||||
exit
|
||||
;;
|
||||
* )
|
||||
fail "Unknown option '$1'"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Generate style argument
|
||||
style=''
|
||||
while IFS='' read line; do
|
||||
[ -z "$line" ] && continue
|
||||
[ "${line:0:1}" = '#' ] && continue
|
||||
[ -n "$style" ] && style+=', '
|
||||
style+="$line"
|
||||
done < "$rsrc/clang-format.yml"
|
||||
style="{$style}" # Not typo
|
||||
|
||||
case "$1" in
|
||||
git )
|
||||
unstaged_changes="`git diff --name-only`"
|
||||
if [ -n "$unstaged_changes" ]; then
|
||||
fail "Refusing to operate in git repository with unstaged changes:
|
||||
$unstaged_changes"
|
||||
fi
|
||||
|
||||
git diff -U0 --no-color --relative HEAD \
|
||||
'*.cpp' \
|
||||
'*.h' \
|
||||
'*.inl' \
|
||||
| command "$CLANG_FORMAT_DIFF" -p1 -style="$style" -i --verbose
|
||||
exit_code="$?"
|
||||
git add "$root_dir"
|
||||
exit "$exit_code"
|
||||
;;
|
||||
|
||||
raw )
|
||||
command "$CLANG_FORMAT" -style="$style" "$@"
|
||||
;;
|
||||
|
||||
files )
|
||||
files=()
|
||||
|
||||
for input in "${@:2}"; do
|
||||
if [ -d "$input" ]; then
|
||||
readarray -d '' current_files < <(
|
||||
find "$input" \
|
||||
\( -name '*.cpp' -o -name '*.h' -o -name '*.inl' \) \
|
||||
-type f \
|
||||
-print0 \
|
||||
)
|
||||
|
||||
[ "${#current_files[@]}" -eq 0 ] \
|
||||
&& fail "No suitable files found in directory $input"
|
||||
|
||||
files+=("${current_files[@]}")
|
||||
else
|
||||
case "$input" in
|
||||
*.cpp | *.h | *.inl )
|
||||
files+=("$input")
|
||||
;;
|
||||
* )
|
||||
error "Refusing to format file '$input': `
|
||||
`only .cpp, .h and .inl supported"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
[ "${#files[@]}" -eq 0 ] && fail "No files to format"
|
||||
|
||||
command "$CLANG_FORMAT" -style="$style" -i --verbose "${files[@]}"
|
||||
;;
|
||||
esac
|
56
tools/cmake/embed.cmake
Normal file
56
tools/cmake/embed.cmake
Normal file
@ -0,0 +1,56 @@
|
||||
# Global variables. Yikes. FIXME
|
||||
set(tools ${PROJECT_SOURCE_DIR}/tools)
|
||||
set(generated ${PROJECT_BINARY_DIR}/generated)
|
||||
set(assets_to_embed "")
|
||||
set(assets_to_embed_args "")
|
||||
|
||||
file(MAKE_DIRECTORY ${generated})
|
||||
|
||||
find_package(Vulkan COMPONENTS glslc REQUIRED)
|
||||
find_program(glslc_executable NAMES glslc HINTS Vulkan::glslc)
|
||||
set(shaders ${generated}/shaders)
|
||||
file(MAKE_DIRECTORY ${shaders})
|
||||
|
||||
# Shedules compilation of shaders
|
||||
# Adapted from https://stackoverflow.com/a/60472877/4463352
|
||||
macro(compile_shader)
|
||||
foreach(source ${ARGV})
|
||||
get_filename_component(source_basename ${source} NAME)
|
||||
set(tmp "${shaders}/${source_basename}.spv")
|
||||
add_custom_command(
|
||||
OUTPUT ${tmp}
|
||||
DEPENDS ${source}
|
||||
COMMAND ${glslc_executable}
|
||||
-o ${tmp}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/${source}
|
||||
COMMENT "Compiling shader ${source}"
|
||||
)
|
||||
list(APPEND assets_to_embed_args "${tmp};as;${source_basename}.spv")
|
||||
list(APPEND assets_to_embed "${tmp}")
|
||||
unset(tmp)
|
||||
unset(source_basename)
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
compile_shader(
|
||||
desktop/graphics/shaders/shader.frag
|
||||
desktop/graphics/shaders/shader.vert
|
||||
)
|
||||
|
||||
# Generate embed files
|
||||
add_custom_command(
|
||||
OUTPUT ${generated}/embedded_resources.cpp
|
||||
${generated}/embedded_resources.h
|
||||
|
||||
COMMAND ${tools}/embed/embed.py
|
||||
--cpp ${generated}/embedded_resources.cpp
|
||||
--header ${generated}/embedded_resources.h
|
||||
--
|
||||
${assets_to_embed_args}
|
||||
|
||||
DEPENDS "${assets_to_embed}"
|
||||
${tools}/embed/embed.py
|
||||
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMENT "Embedding assets"
|
||||
)
|
28
tools/cppcheck/options.txt
Normal file
28
tools/cppcheck/options.txt
Normal file
@ -0,0 +1,28 @@
|
||||
# CppCheck command line arguments
|
||||
# Each line is treated as one argument, unless it is empty or it starts with #.
|
||||
#
|
||||
# Available variables:
|
||||
# ${CMAKE_SOURCE_DIR} project root
|
||||
# ${CMAKE_BINARY_DIR} CMake build directory
|
||||
|
||||
--enable=warning,style,information
|
||||
#--enable=unusedFunction # Unused functions are often OK since they are intended
|
||||
# # to be used later
|
||||
#--enable=missingInclude # Very prone to false positives; system-dependent
|
||||
--inconclusive
|
||||
|
||||
# SUPPRESSIONS
|
||||
# Warnings that are suppressed on a case-by-case basis should be suppressed
|
||||
# using inline suppressions.
|
||||
# Warnings that were decided to be generally inapplicable should be suppressed
|
||||
# using suppressions.txt.
|
||||
# Warnings that result from the way cppcheck is invoked should be suppressed
|
||||
# using this file.
|
||||
|
||||
--inline-suppr
|
||||
--suppressions-list=${CMAKE_SOURCE_DIR}/tools/cppcheck/suppressions.txt
|
||||
|
||||
# N.B.: this path is also mentioned in use scripts
|
||||
--cppcheck-build-dir=${CMAKE_BINARY_DIR}/cppcheck
|
||||
|
||||
--error-exitcode=2
|
15
tools/cppcheck/suppressions.txt
Normal file
15
tools/cppcheck/suppressions.txt
Normal file
@ -0,0 +1,15 @@
|
||||
# CppCheck global suppressions
|
||||
# Do not use this file for suppressions that could easily be declared inline.
|
||||
|
||||
# Allow the use of implicit constructors.
|
||||
noExplicitConstructor:*
|
||||
|
||||
# In most cases using STL algorithm functions causes unnecessary code bloat.
|
||||
useStlAlgorithm:*
|
||||
|
||||
# cppcheck trips on #include <embedded_resources.h> and there's no way to
|
||||
# suppress that exlusively
|
||||
missingInclude:*
|
||||
|
||||
# Shut up. Just shut up.
|
||||
unmatchedSuppression:*
|
65
tools/cppcheck/use-cppcheck.sh
Executable file
65
tools/cppcheck/use-cppcheck.sh
Executable file
@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
usage=\
|
||||
"Usage: use-cppcheck.sh
|
||||
Run cppcheck with correct options.
|
||||
|
||||
Environment variables:
|
||||
PARALLELISM threads to use, default is 1
|
||||
|
||||
CPPCHECK cppcheck executable
|
||||
CMAKE cmake executable"
|
||||
|
||||
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
|
||||
source "$rsrc/../bashlib.sh"
|
||||
|
||||
find_cmd CPPCHECK cppcheck
|
||||
find_cmd CMAKE cmake
|
||||
|
||||
case "$1" in
|
||||
-h | --help )
|
||||
echo "$usage"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# Generate compile database for CppCheck
|
||||
command "$CMAKE" \
|
||||
-B "$build_dir" \
|
||||
-S "$source_dir" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||
|
||||
compile_database="$build_dir/compile_commands.json"
|
||||
|
||||
mkdir -p "$build_dir/cppcheck"
|
||||
|
||||
options=()
|
||||
|
||||
while IFS='' read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
[ "${line:0:1}" = '#' ] && continue
|
||||
|
||||
option="$(
|
||||
CMAKE_SOURCE_DIR="$source_dir" \
|
||||
CMAKE_BINARY_DIR="$build_dir" \
|
||||
envsubst <<<"$line"
|
||||
)"
|
||||
|
||||
options+=("$option")
|
||||
done < "$tools_dir/cppcheck/options.txt"
|
||||
|
||||
[ -n "${PARALLELISM+x}" ] && options+=(-j "$PARALLELISM")
|
||||
|
||||
errors="`
|
||||
echo_and_run "$CPPCHECK" \
|
||||
--project="$compile_database" \
|
||||
-D__CPPCHECK__ \
|
||||
"${options[@]}" \
|
||||
2>&1 >/dev/fd/0 # Store stderr into variable, pass stdout to our stdout
|
||||
`"
|
||||
|
||||
exit_code="$?"
|
||||
if [ "$exit_code" -eq 2 ]; then
|
||||
less - <<<"$errors"
|
||||
exit "$exit_code"
|
||||
fi
|
298
tools/embed/embed.py
Executable file
298
tools/embed/embed.py
Executable file
@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
usage = \
|
||||
'''Usage: embed.py --cpp OUT_CPP --header OUT_H [--] [INPUT as PATH]...
|
||||
Generate C++ source code that includes binary contents of INPUT files.
|
||||
|
||||
Each file in INPUT is stored as a resource: a static array of unsigned char.
|
||||
It is identified by a PATH. If PATH is "auto", resource path is the path of
|
||||
the file relative to this script's working directory with forward slash '/'
|
||||
as separator.
|
||||
|
||||
Use -- to make sure the following one INPUT is not interpreted as an option.
|
||||
|
||||
This script generates two files:
|
||||
|
||||
OUT_CPP is a C++ implementation file that includes the contents of INPUT.
|
||||
OUT_H is a C++ header file that declares several methods of access to the data
|
||||
in OUT_CPP. It should be located in the same directory as OUT_H at compile
|
||||
time.
|
||||
|
||||
OUT_H declares the following symbols:
|
||||
|
||||
namespace __embedded_resources {
|
||||
struct EmbeddedResource {
|
||||
const unsigned char *data;
|
||||
std::size_t length;
|
||||
};
|
||||
EmbeddedResource getEmbeddedResource(const char *path);
|
||||
}
|
||||
|
||||
getEmbeddedResource(const char *path) returns an EmbeddedResource structure that
|
||||
contains the pointer to the beginning of the requested resource and its
|
||||
length, or {nullptr, 0} if the resource does not exist.'''
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from types import SimpleNamespace
|
||||
from json import dumps as json_dumps
|
||||
|
||||
def fail(*args):
|
||||
my_name = os.path.basename(sys.argv[0])
|
||||
print(my_name + ':', *args, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
# Parse arguments
|
||||
|
||||
out_cpp_path = None
|
||||
out_h_path = None
|
||||
inputs = []
|
||||
|
||||
argi = 1
|
||||
considerOptions = True
|
||||
while argi < len(sys.argv):
|
||||
arg = sys.argv[argi]
|
||||
|
||||
if considerOptions and arg.startswith('--cpp'):
|
||||
if arg == '--cpp':
|
||||
argi += 1
|
||||
if argi == len(sys.argv):
|
||||
fail('Missing argument for --cpp')
|
||||
out_cpp_path = sys.argv[argi]
|
||||
elif arg.startswith('--impl='):
|
||||
out_cpp_path = arg.removeprefix('--cpp=')
|
||||
else:
|
||||
fail(f"Unknown option '{arg}'")
|
||||
|
||||
elif considerOptions and arg.startswith('--header'):
|
||||
if arg == '--header':
|
||||
argi += 1
|
||||
if argi == len(sys.argv):
|
||||
fail('Missing argument for --header')
|
||||
out_h_path = sys.argv[argi]
|
||||
elif arg.startswith('--header='):
|
||||
out_h_path = arg.removeprefix('--header=')
|
||||
else:
|
||||
fail(f"Unknown option '{arg}'")
|
||||
|
||||
elif considerOptions and (arg == '-h' or arg == '--help'):
|
||||
sys.exit(0)
|
||||
|
||||
elif considerOptions and arg == '--':
|
||||
considerOptions = False
|
||||
|
||||
elif considerOptions and arg.startswith('-'):
|
||||
fail(f"Unknown option '{arg}'")
|
||||
|
||||
else:
|
||||
if argi + 2 >= len(sys.argv):
|
||||
fail(f'Invalid input declaration {sys.argv[argi:]}: '
|
||||
'expected "INPUT as PATH"')
|
||||
if sys.argv[argi + 1] != 'as':
|
||||
fail(f'Invalid input declaration {sys.argv[argi:argi+3]}: '
|
||||
'expected "INPUT as PATH"')
|
||||
|
||||
the_input = arg
|
||||
argi += 2
|
||||
name = sys.argv[argi]
|
||||
|
||||
if name == 'auto':
|
||||
name = os.path.relpath(the_input).replace(os.sep, '/')
|
||||
|
||||
inputs.append((the_input, name))
|
||||
|
||||
argi += 1
|
||||
|
||||
if out_cpp_path == None:
|
||||
fail('--impl not set')
|
||||
|
||||
if out_h_path == None:
|
||||
fail('--header not set')
|
||||
|
||||
if len(inputs) == 0:
|
||||
fail('No inputs')
|
||||
|
||||
generate_impl(out_cpp_path, out_h_path, inputs)
|
||||
generate_header(out_h_path)
|
||||
|
||||
|
||||
def generate_impl(out_cpp_path, out_h_path, inputs):
|
||||
|
||||
try:
|
||||
with open(out_cpp_path, 'w', encoding="utf-8") as output:
|
||||
|
||||
output.write(impl.start %
|
||||
{'header_name': os.path.basename(out_h_path)})
|
||||
|
||||
variables = {}
|
||||
|
||||
# Open each input
|
||||
for number, (input_path, resource_path) in enumerate(inputs):
|
||||
|
||||
variable_name = make_variable_name(resource_path, number)
|
||||
|
||||
if resource_path in variables:
|
||||
fail('Inputs resolve to duplicate resource paths: ' +
|
||||
resource_path)
|
||||
variables[resource_path] = variable_name
|
||||
|
||||
try:
|
||||
with open(input_path, 'rb') as input_file:
|
||||
write_bytes(output, input_file, variable_name)
|
||||
|
||||
if number == len(inputs) - 1:
|
||||
output.write(";\n")
|
||||
else:
|
||||
output.write(",\n")
|
||||
|
||||
except FileNotFoundError as e:
|
||||
fail(f"Input file '{input_path}' does not exist")
|
||||
except (PermissionError, OSError) as e:
|
||||
fail(f"Could not read input '{input_path}': {e}")
|
||||
|
||||
output.write(impl.mid)
|
||||
|
||||
# Add EmbeddedResources to lookup table
|
||||
|
||||
for number, (resource, variable) in enumerate(variables.items()):
|
||||
output.write(impl.mapping % {
|
||||
'resource_path_quoted': json_dumps(resource),
|
||||
'variable_name': variable})
|
||||
|
||||
if number == len(variables) - 1:
|
||||
output.write("\n")
|
||||
else:
|
||||
output.write(",\n")
|
||||
|
||||
output.write(impl.end)
|
||||
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
fail(f"Could not write to '{out_cpp_path}': {e}")
|
||||
|
||||
|
||||
def make_variable_name(resource_path, number):
|
||||
max_variable_name_length = 255 # very conservative
|
||||
max_path_length = max_variable_name_length - \
|
||||
len(impl.variable_name.format(number, ''))
|
||||
|
||||
return impl.variable_name % (number,
|
||||
re.sub(r'\W', '_', resource_path[-max_path_length:]).upper())
|
||||
|
||||
|
||||
def write_bytes(out_file, in_file, variable_name):
|
||||
|
||||
out_file.write(impl.declar_start % variable_name)
|
||||
|
||||
max_line_length = 79
|
||||
line = impl.declar_mid_prefix
|
||||
|
||||
# Process contents in chunks
|
||||
while True:
|
||||
chunk = in_file.read1(-1)
|
||||
if len(chunk) == 0:
|
||||
break
|
||||
|
||||
for byte in chunk:
|
||||
|
||||
byte_str = str(byte)
|
||||
if len(line) + 1 + len(byte_str) > max_line_length:
|
||||
out_file.write(line + '\n')
|
||||
line = impl.declar_mid_prefix
|
||||
|
||||
line += byte_str + ','
|
||||
|
||||
out_file.write(line[:-1] + '\n')
|
||||
out_file.write(impl.declar_end)
|
||||
|
||||
|
||||
def generate_header(out_h_path):
|
||||
try:
|
||||
with open(out_h_path, 'w', encoding="utf-8") as output:
|
||||
output.write(header)
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
fail(f"Could not write to '{out_h_path}': {e}")
|
||||
|
||||
|
||||
# Templates
|
||||
|
||||
impl = SimpleNamespace(
|
||||
|
||||
start=\
|
||||
'''/*
|
||||
* This file is autogenerated by tools/embed/embed.py. Do not edit directly.
|
||||
* Add this file as a compilation unit.
|
||||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include "%(header_name)s"
|
||||
|
||||
namespace {
|
||||
const unsigned char
|
||||
''',
|
||||
|
||||
mid=\
|
||||
'''
|
||||
std::unordered_map<std::string,
|
||||
__embedded_resources::EmbeddedResource>
|
||||
EMBEDDED_RESOURCES =
|
||||
{
|
||||
''',
|
||||
|
||||
end=\
|
||||
''' };
|
||||
}
|
||||
|
||||
namespace __embedded_resources {
|
||||
|
||||
EmbeddedResource getEmbeddedResource(const std::string &path) {
|
||||
auto result = EMBEDDED_RESOURCES.find(path);
|
||||
if (result == EMBEDDED_RESOURCES.end()) {
|
||||
return EmbeddedResource{nullptr, 0};
|
||||
}
|
||||
return result->second;
|
||||
}
|
||||
|
||||
}
|
||||
''',
|
||||
|
||||
mapping=\
|
||||
''' {%(resource_path_quoted)s, {
|
||||
%(variable_name)s,
|
||||
sizeof(%(variable_name)s)
|
||||
}}''',
|
||||
|
||||
declar_start= " %s[] = {\n",
|
||||
declar_mid_prefix= ' ',
|
||||
declar_end= ' }',
|
||||
|
||||
variable_name='EMBED_%s_%s'
|
||||
)
|
||||
|
||||
header = '''/*
|
||||
* This file is autogenerated by tools/embed/embed.py. Do not edit directly.
|
||||
* Include this header as necessary.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace __embedded_resources {
|
||||
|
||||
struct EmbeddedResource {
|
||||
const unsigned char *data;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
EmbeddedResource getEmbeddedResource(const std::string &path);
|
||||
|
||||
}
|
||||
'''
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
51
tools/git/hook_pre_commit.sh
Executable file
51
tools/git/hook_pre_commit.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
me="$(realpath "${BASH_SOURCE[0]}")"
|
||||
if [ "$(basename "$me")" = 'pre-commit' ]; then
|
||||
# i write good shell scripts - Javapony 2022-10-07
|
||||
root_dir="$(realpath "$(dirname "$me")/../../")"
|
||||
|
||||
hook_source="$root_dir/tools/git/hook_pre_commit.sh"
|
||||
if [ "$hook_source" -nt "$me" ]; then
|
||||
if [ -n "${ALREADY_UPDATED+x}" ]; then
|
||||
echo >&2 "git pre-commit hook: Attempted recursive hook update. `
|
||||
`Something is very wrong."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ''
|
||||
echo "===== tools/git/hook_pre_commit.sh updated; `
|
||||
`replacing pre-commit hook ====="
|
||||
echo ''
|
||||
|
||||
cp "$hook_source" "$me" &&
|
||||
chmod +x "$me" \
|
||||
|| fail 'Update failed'
|
||||
|
||||
ALREADY_UPDATED=true "$me"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
source "$root_dir/tools/bashlib.sh"
|
||||
else
|
||||
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
|
||||
source "$rsrc/../bashlib.sh"
|
||||
fi
|
||||
|
||||
unstaged_changes="`git diff --name-only`"
|
||||
if [ -n "$unstaged_changes" ]; then
|
||||
fail "Please stage all stash all unstaged changes in the following files:
|
||||
$unstaged_changes"
|
||||
fi
|
||||
|
||||
echo_and_run "$tools_dir/cppcheck/use-cppcheck.sh" \
|
||||
|| fail "Cppcheck has generated warnings, aborting commit"
|
||||
|
||||
echo_and_run "$tools_dir/clang-format/use-clang-format.sh" git \
|
||||
|| fail "clang-format has failed, aborting commit"
|
||||
|
||||
echo_and_run "$tools_dir/build.sh" --dont-generate \
|
||||
|| fail "Could not build project, aborting commit"
|
||||
|
||||
echo 'All checks passed'
|
||||
|
28
tools/memcheck/suppressions.supp
Normal file
28
tools/memcheck/suppressions.supp
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
Known X library leak (1)
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
obj:/usr/lib/x86_64-linux-gnu/libxcb.so.1.1.0
|
||||
...
|
||||
fun:vkEnumeratePhysicalDevices
|
||||
}
|
||||
{
|
||||
Known X library leak (2)
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:calloc
|
||||
fun:_XimOpenIM
|
||||
fun:_XimRegisterIMInstantiateCallback
|
||||
fun:XRegisterIMInstantiateCallback
|
||||
fun:_glfwPlatformInit
|
||||
fun:glfwInit
|
||||
}
|
||||
{
|
||||
Ignore errors in DL loading
|
||||
Memcheck:Addr8
|
||||
...
|
||||
fun:decompose_rpath
|
||||
...
|
||||
fun:dl_open_worker
|
||||
}
|
147
tools/setup.sh
Executable file
147
tools/setup.sh
Executable file
@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
|
||||
usage=\
|
||||
"Usage: setup.sh [--for-development]
|
||||
Set up the development environment after \`git clone\`
|
||||
|
||||
Options:
|
||||
--for-development perform additional setup only necessary for developers
|
||||
|
||||
-h, --help display this help and exit"
|
||||
|
||||
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
|
||||
source "$rsrc/bashlib.sh" || {
|
||||
echo >&2 'Could not load bashlib'
|
||||
exit 1
|
||||
}
|
||||
cd "$root_dir"
|
||||
|
||||
|
||||
|
||||
# Parse arguments
|
||||
|
||||
for_development=''
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-h | --help )
|
||||
echo "$usage"
|
||||
exit
|
||||
;;
|
||||
--for-development )
|
||||
for_development=true
|
||||
;;
|
||||
* )
|
||||
fail "Unknown option '$arg'"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
|
||||
# Сreate private.sh
|
||||
|
||||
if [ ! -e "$private_sh" ]; then
|
||||
echo '#!/bin/bash
|
||||
|
||||
# This file is ignored by git. Use it to configure shell scripts in tools/
|
||||
# for your development environment.
|
||||
|
||||
PARALLELISM=1
|
||||
#PATH="$PATH:/opt/whatever"
|
||||
' >"$private_sh" &&
|
||||
chmod +x "$private_sh" ||
|
||||
fail "tools/private.sh was not found; could not create it"
|
||||
|
||||
echo "Created tools/private.sh"
|
||||
else
|
||||
echo "Found and loaded private.sh"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Check available commands
|
||||
|
||||
failed=()
|
||||
|
||||
function check_cmd() {
|
||||
if FAIL_SILENTLY=true find_cmd found "$@"; then
|
||||
echo "Found command $found"
|
||||
else
|
||||
failed+=("command $1")
|
||||
echo "Could not find command $1"
|
||||
fi
|
||||
unset found
|
||||
}
|
||||
|
||||
check_cmd pkg-config
|
||||
check_cmd cmake
|
||||
check_cmd python3
|
||||
check_cmd glslc
|
||||
|
||||
if [ $for_development ]; then
|
||||
check_cmd git
|
||||
check_cmd cppcheck
|
||||
check_cmd clang-format-13 clang-format
|
||||
check_cmd clang-format-diff-13 clang-format-diff clang-format-diff.py
|
||||
check_cmd valgrind
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Try generating build files
|
||||
|
||||
if FAIL_SILENTLY=true find_cmd found_cmake cmake; then
|
||||
if CMAKE="$found_cmake" "$tools_dir/build.sh" --dont-build; then
|
||||
echo 'CMake did not encounter any problems'
|
||||
else
|
||||
echo 'Could not generate build files; libraries are probably missing'
|
||||
failed+=('some libraries, probably (see CMake messages for details)')
|
||||
fi
|
||||
else
|
||||
echo 'Skipping CMake test because cmake was not found'
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Display accumulated errors
|
||||
|
||||
[ ${#failed[@]} -ne 0 ] &&
|
||||
fail "Could not find the following required commands or libraries:
|
||||
|
||||
`for f in "${failed[@]}"; do echo " $f"; done`
|
||||
|
||||
You can resolve these errors in the following ways:
|
||||
1. Install required software packages. See README for specific instructions.
|
||||
2. Edit PATH, PKG_CONFIG_PATH or CMAKE_MODULE_PATH environment variables in
|
||||
tools/private.sh to include your installation directories.
|
||||
"
|
||||
|
||||
|
||||
|
||||
# Set executable flags
|
||||
|
||||
chmod -v +x tools/build.sh \
|
||||
tools/embed/embed.py \
|
||||
|| fail 'Could not make scripts executable'
|
||||
|
||||
if [ $for_development ]; then
|
||||
chmod -v +x tools/clang-format/use-clang-format.sh \
|
||||
tools/cppcheck/use-cppcheck.sh \
|
||||
|| fail 'Could not make developer scripts executable'
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Set git hook
|
||||
|
||||
if [ $for_development ]; then
|
||||
mkdir -vp .git/hooks &&
|
||||
cp -v tools/git/hook_pre_commit.sh .git/hooks/pre-commit &&
|
||||
chmod -v +x .git/hooks/pre-commit \
|
||||
|| fail 'Could not setup git pre-commit hook'
|
||||
fi
|
||||
|
||||
|
||||
|
||||
echo 'Setup complete'
|
Loading…
x
Reference in New Issue
Block a user