mirror of
https://gitea.windcorp.ru/Wind-Corporation/Progressia.git
synced 2025-04-21 08:00: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