Compare commits
No commits in common. "main" and "marching-cubes" have entirely different histories.
main
...
marching-c
|
@ -1,2 +1 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
|
|
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
37
Cargo.toml
|
@ -1,39 +1,16 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"editor"
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "cyborg"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "^1.0", features = ["derive"] }
|
||||
bytemuck = { version="1.7", features=["derive"] }
|
||||
glam = "0.20"
|
||||
multimap = "0.8"
|
||||
noise = "^0.7"
|
||||
notify = "^4"
|
||||
parking_lot = "^0.11"
|
||||
gltf = { version="1.0", features=["utils"] }
|
||||
image = "0.24"
|
||||
pollster = "0.2"
|
||||
puffin = "^0.13"
|
||||
rand = "^0.8"
|
||||
rayon = "1"
|
||||
slab = "^0.4"
|
||||
smallmap = "^1.0"
|
||||
smallvec = "^1.0"
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
wgpu = "^0.13"
|
||||
slab = "0.4"
|
||||
tobj = "3.0"
|
||||
wgpu = "0.12"
|
||||
winit = "0.26"
|
||||
|
||||
[dependencies.legion]
|
||||
version = "^0.4"
|
||||
optional = true
|
||||
|
||||
[dependencies.naga]
|
||||
version = "0.9"
|
||||
features = ["wgsl-in", "glsl-in", "wgsl-out", "serialize", "deserialize"]
|
||||
|
||||
[[bin]]
|
||||
name = "cyborg"
|
||||
required-features = ["legion"]
|
||||
naga = { version = "0.8.5", features = ["wgsl-in", "glsl-in", "wgsl-out", "serialize", "deserialize"] }
|
||||
|
|
|
@ -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>.
|
39
README.md
39
README.md
|
@ -1,38 +1 @@
|
|||
# Cyborg
|
||||
|
||||
> `Cyborg` is an absolute nightmare.
|
||||
> A twisted factory.
|
||||
> A disgusting Enmanglement.
|
||||
> A perfect melding of terrifying machinic heartlessness and disappointing human fallacy.
|
||||
> A repulsive reaper of all of human consciousness -- but mostly SIGGRAPH presentations.
|
||||
> H.R Giger's irreconcilable reality... if H.R. Giger was a computer programmer.
|
||||
|
||||
Cyborg is an experimental, GPU-driven rendering engine using Rust and wgpu.
|
||||
It's a test bed for all sorts of modern rendering technology. Our goal is to take
|
||||
techniques and features from modern game engines and reimplement them on our own, for
|
||||
the sake of education, performance, and reusability. We wanna *make shit work.*
|
||||
|
||||
We also want to give artists a playground for generating all sorts of unique
|
||||
3D visuals -- custom shaders, procedurally generated meshes and textures,
|
||||
and a focus on *interesting* visuals.
|
||||
Realism is a non-goal!
|
||||
Go wild.
|
||||
|
||||
Modularity and reusability are important. We want to be able to unplug certain
|
||||
parts of the rendering pipeline, upgrade them, fix them, document them, commit
|
||||
them, tag them, etc., then stick them back in in a different place to end up
|
||||
with a different resulting image.
|
||||
|
||||
Cyborg is licensed under the GPL-3.0. Yes, this does mean that any software using
|
||||
Cyborg will be required to make its source code public. But it also means that
|
||||
new visuals added to Cyborg will become available to all other developers and
|
||||
artists to use. Hopefully, this will expand the collective knowledge of how different
|
||||
rendering effects work, and decrease the pressure on graphics programmers to
|
||||
reimplement the exact same graphical effects in different games over and over and
|
||||
over again. In the future, Cyborg will have a vast variety of visuals in its toolbox,
|
||||
gathered from countless programmers and individual projects.
|
||||
|
||||
> We will add your biological and technological distinctiveness to our own.
|
||||
> Resistance is futile.
|
||||
>
|
||||
> -- The Borg
|
||||
# cyborg
|
|
@ -1,29 +0,0 @@
|
|||
[package]
|
||||
name = "cyborg_editor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "^1.0"
|
||||
crossbeam-channel = "^0.5"
|
||||
cyborg = { path = "../", features = ["legion"] }
|
||||
egui = "0.18"
|
||||
egui-winit = "0.18.0"
|
||||
egui_wgpu_backend = "0.18.0"
|
||||
glam = { version = "0.20", features = ["serde"] }
|
||||
gltf = { version = "1.0", features = ["utils"] }
|
||||
legion = "^0.4"
|
||||
parking_lot = "^0.11"
|
||||
pollster = "0.2"
|
||||
puffin = "^0.13"
|
||||
puffin_egui = "0.16.0"
|
||||
rfd = "^0.8"
|
||||
stl = "0.2.1"
|
||||
|
||||
[dependencies.mlua]
|
||||
version = "^0.8"
|
||||
features = ["vendored", "luajit52", "serialize"]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
features = ["derive"]
|
|
@ -1,142 +0,0 @@
|
|||
use crate::model;
|
||||
|
||||
pub fn load_stl(path: std::path::PathBuf) -> model::Model {
|
||||
let name = path.file_name().map(|v| v.to_string_lossy().to_string());
|
||||
|
||||
let mut file = std::fs::File::open(path).unwrap();
|
||||
let stl = stl::read_stl(&mut file).unwrap();
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
for tri in stl.triangles.iter() {
|
||||
indices.push(vertices.len() as u32);
|
||||
vertices.push(model::BasicVertex {
|
||||
position: tri.v1.into(),
|
||||
});
|
||||
indices.push(vertices.len() as u32);
|
||||
vertices.push(model::BasicVertex {
|
||||
position: tri.v2.into(),
|
||||
});
|
||||
indices.push(vertices.len() as u32);
|
||||
vertices.push(model::BasicVertex {
|
||||
position: tri.v3.into(),
|
||||
});
|
||||
}
|
||||
|
||||
model::Model {
|
||||
name: name.clone(),
|
||||
objects: vec![model::Object {
|
||||
name,
|
||||
transform: Default::default(),
|
||||
meshes: vec![model::Mesh { vertices, indices }],
|
||||
children: vec![],
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_gltf(path: std::path::PathBuf) -> model::Model {
|
||||
use gltf::*;
|
||||
|
||||
let name = path.file_name().map(|v| v.to_string_lossy().to_string());
|
||||
let mut file = std::fs::File::open(path).unwrap();
|
||||
let model = Gltf::from_reader(&mut file).unwrap();
|
||||
|
||||
GltfLoader::load(name, &model)
|
||||
}
|
||||
|
||||
pub struct GltfLoader<'a> {
|
||||
pub model: &'a gltf::Gltf,
|
||||
pub buffers: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<'a> GltfLoader<'a> {
|
||||
pub fn load(name: Option<String>, model: &'a gltf::Gltf) -> model::Model {
|
||||
let buffers = Self::load_buffers(model);
|
||||
let mut loader = Self { model, buffers };
|
||||
|
||||
let mut model = model::Model {
|
||||
name,
|
||||
objects: vec![],
|
||||
};
|
||||
|
||||
for scene in loader.model.scenes() {
|
||||
for node in scene.nodes() {
|
||||
model.objects.push(loader.load_node(node));
|
||||
}
|
||||
}
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
pub fn load_buffers(model: &gltf::Gltf) -> Vec<Vec<u8>> {
|
||||
let mut buffer_data = Vec::<Vec<u8>>::new();
|
||||
for buffer in model.buffers() {
|
||||
match buffer.source() {
|
||||
gltf::buffer::Source::Bin => {
|
||||
buffer_data.push(model.blob.as_deref().unwrap().into());
|
||||
}
|
||||
_ => panic!("URI buffer sources are unsupported"),
|
||||
}
|
||||
}
|
||||
buffer_data
|
||||
}
|
||||
|
||||
pub fn load_node(&mut self, node: gltf::Node) -> model::Object {
|
||||
let transform = model::Transform::default();
|
||||
|
||||
let mut object = model::Object {
|
||||
name: node.name().map(str::to_string),
|
||||
transform,
|
||||
meshes: vec![],
|
||||
children: vec![],
|
||||
};
|
||||
|
||||
if let Some(mesh) = node.mesh() {
|
||||
for primitive in mesh.primitives() {
|
||||
object.meshes.push(self.load_primitive_mesh(primitive));
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
object.children.push(self.load_node(child));
|
||||
}
|
||||
|
||||
object
|
||||
}
|
||||
|
||||
pub fn load_primitive_mesh(&mut self, primitive: gltf::Primitive) -> model::Mesh {
|
||||
use gltf::mesh::util::{ReadIndices, ReadTexCoords};
|
||||
if primitive.mode() != gltf::mesh::Mode::Triangles {
|
||||
panic!("glTF primitive must be triangle list");
|
||||
}
|
||||
|
||||
let reader = primitive.reader(|buffer| Some(&self.buffers[buffer.index()]));
|
||||
let positions = reader.read_positions().unwrap();
|
||||
let mut normals = reader.read_normals().unwrap();
|
||||
|
||||
let tex_coords = reader.read_tex_coords(0).unwrap();
|
||||
let mut tex_coords = if let ReadTexCoords::F32(tex_coords) = tex_coords {
|
||||
tex_coords
|
||||
} else {
|
||||
panic!("only f32 texture coordinates are supported")
|
||||
};
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
for position in positions {
|
||||
let normal = normals.next().unwrap();
|
||||
let tex_coords = tex_coords.next().unwrap();
|
||||
vertices.push(model::BasicVertex {
|
||||
position: position.into(),
|
||||
});
|
||||
}
|
||||
|
||||
let indices = match reader.read_indices().unwrap() {
|
||||
ReadIndices::U32(indices) => indices.collect(),
|
||||
ReadIndices::U16(indices) => indices.map(|i| i as u32).collect(),
|
||||
ReadIndices::U8(indices) => indices.map(|i| i as u32).collect(),
|
||||
};
|
||||
|
||||
model::Mesh { vertices, indices }
|
||||
}
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
use egui_wgpu_backend::wgpu;
|
||||
use egui_wgpu_backend::RenderPass as EguiRenderPass;
|
||||
use egui_winit::winit::{
|
||||
self,
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use legion::EntityStore;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod import;
|
||||
mod model;
|
||||
mod render;
|
||||
mod script;
|
||||
mod ui;
|
||||
|
||||
struct Application {
|
||||
window: winit::window::Window,
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
surface: wgpu::Surface,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
egui_state: egui_winit::State,
|
||||
egui_ctx: egui::Context,
|
||||
egui_rp: EguiRenderPass,
|
||||
ui: ui::UserInterface,
|
||||
viewport: ui::ViewportWidget,
|
||||
render_state: Arc<RwLock<render::RenderState>>,
|
||||
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
|
||||
objects: Vec<ui::ObjectWidget>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub async fn new(window: winit::window::Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::LowPower,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
label: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: *surface.get_supported_formats(&adapter).first().unwrap(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let device = Arc::new(device);
|
||||
let queue = Arc::new(queue);
|
||||
|
||||
let egui_state = egui_winit::State::new(4096, &window);
|
||||
let egui_ctx = egui::Context::default();
|
||||
let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1);
|
||||
let render_state =
|
||||
render::RenderState::new(device.clone(), queue.clone(), config.format, &mut egui_rp);
|
||||
let viewport_texture = render_state
|
||||
.resources
|
||||
.get::<render::ViewportStore>()
|
||||
.unwrap()
|
||||
.viewport
|
||||
.egui_texture;
|
||||
|
||||
let (file_sender, file_receiver) = crossbeam_channel::unbounded();
|
||||
|
||||
Self {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
size,
|
||||
surface,
|
||||
config,
|
||||
egui_state,
|
||||
egui_ctx,
|
||||
egui_rp,
|
||||
ui: ui::UserInterface::new(file_sender),
|
||||
viewport: ui::ViewportWidget::new(viewport_texture),
|
||||
render_state: Arc::new(RwLock::new(render_state)),
|
||||
file_receiver,
|
||||
objects: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.window.request_redraw();
|
||||
|
||||
loop {
|
||||
match self.file_receiver.try_recv() {
|
||||
Ok(ui::FileEvent::Save) => println!("Saving!"),
|
||||
Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path),
|
||||
Ok(ui::FileEvent::Import(kind, path)) => {
|
||||
let model = match kind {
|
||||
ui::ImportKind::Stl => import::load_stl(path),
|
||||
ui::ImportKind::Gltf => import::load_gltf(path),
|
||||
};
|
||||
|
||||
let mut render_state = self.render_state.write();
|
||||
let loaded = render_state.load_model(&model);
|
||||
let transform = glam::Mat4::IDENTITY;
|
||||
let widgets = render_state.spawn_model(&loaded, &transform);
|
||||
self.objects.extend(widgets.into_iter());
|
||||
},
|
||||
Ok(ui::FileEvent::LoadScript(path)) => {
|
||||
script::Script::new(&path, self.render_state.to_owned());
|
||||
}
|
||||
Err(crossbeam_channel::TryRecvError::Empty) => break,
|
||||
Err(crossbeam_channel::TryRecvError::Disconnected) => {
|
||||
panic!("File event sender hung up!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut render_state = self.render_state.write();
|
||||
for object in self.objects.iter_mut() {
|
||||
object.flush_dirty(|object| {
|
||||
let transform = object.transform.to_mat4();
|
||||
for entity in object.entities.iter() {
|
||||
render_state
|
||||
.world
|
||||
.entry_mut(*entity)
|
||||
.unwrap()
|
||||
.get_component_mut::<cyborg::scene::Transform>()
|
||||
.unwrap()
|
||||
.transform = transform;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self) {
|
||||
match self.surface.get_current_texture() {
|
||||
Err(wgpu::SurfaceError::Lost) => self.on_resize(self.size),
|
||||
Err(e) => panic!("Surface error: {:?}", e),
|
||||
Ok(surface_texture) => {
|
||||
puffin::GlobalProfiler::lock().new_frame();
|
||||
|
||||
let output = {
|
||||
puffin::profile_scope!("Draw egui");
|
||||
let raw_input = self.egui_state.take_egui_input(&self.window);
|
||||
self.egui_ctx.run(raw_input, |ctx| {
|
||||
self.ui.run(ctx, &mut self.viewport, &mut self.objects)
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
puffin::profile_scope!("Main render");
|
||||
let mut render_state = self.render_state.write();
|
||||
render_state.update_viewport(&mut self.egui_rp, &mut self.viewport);
|
||||
render_state.render();
|
||||
}
|
||||
|
||||
puffin::profile_scope!("Render egui");
|
||||
|
||||
self.egui_state.handle_platform_output(
|
||||
&self.window,
|
||||
&self.egui_ctx,
|
||||
output.platform_output,
|
||||
);
|
||||
|
||||
let meshes = self.egui_ctx.tessellate(output.shapes);
|
||||
|
||||
let screen_desc = egui_wgpu_backend::ScreenDescriptor {
|
||||
physical_width: self.config.width,
|
||||
physical_height: self.config.height,
|
||||
scale_factor: self.egui_ctx.pixels_per_point(),
|
||||
};
|
||||
|
||||
self.egui_rp
|
||||
.update_buffers(&self.device, &self.queue, &meshes, &screen_desc);
|
||||
self.egui_rp
|
||||
.add_textures(&self.device, &self.queue, &output.textures_delta)
|
||||
.unwrap();
|
||||
|
||||
let output_view = surface_texture.texture.create_view(&Default::default());
|
||||
let mut cmds = self.device.create_command_encoder(&Default::default());
|
||||
self.egui_rp
|
||||
.execute(&mut cmds, &output_view, &meshes, &screen_desc, None)
|
||||
.unwrap();
|
||||
self.queue.submit(std::iter::once(cmds.finish()));
|
||||
surface_texture.present();
|
||||
self.egui_rp.remove_textures(output.textures_delta).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Cyborg Editor")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut app = pollster::block_on(Application::new(window));
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::RedrawRequested(window_id) if window_id == app.window.id() => {
|
||||
app.render();
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
app.update();
|
||||
|
||||
if app.ui.should_quit() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event, window_id } if window_id == app.window.id() => {
|
||||
match &event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
app.on_resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
app.on_resize(**new_inner_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
app.egui_state.on_event(&app.egui_ctx, &event);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
use cyborg::storage::mesh::MeshHandle;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Model {
|
||||
pub name: Option<String>,
|
||||
pub objects: Vec<Object>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Object {
|
||||
pub name: Option<String>,
|
||||
pub transform: Transform,
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub children: Vec<Object>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Mesh {
|
||||
pub vertices: Vec<BasicVertex>,
|
||||
pub indices: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BasicVertex {
|
||||
pub position: glam::Vec3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LoadedModel {
|
||||
pub name: Option<String>,
|
||||
pub objects: Vec<LoadedObject>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LoadedObject {
|
||||
pub name: Option<String>,
|
||||
pub transform: Transform,
|
||||
pub meshes: Vec<MeshHandle>,
|
||||
pub children: Vec<LoadedObject>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Transform {
|
||||
pub position: glam::Vec3,
|
||||
pub rotation: glam::Vec3, // TODO support glam::Quat too
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
pub fn to_mat4(&self) -> glam::Mat4 {
|
||||
let translation = self.position;
|
||||
let rotation = glam::Quat::from_euler(
|
||||
glam::EulerRot::XYZ,
|
||||
self.rotation[0],
|
||||
self.rotation[1],
|
||||
self.rotation[2],
|
||||
);
|
||||
let scale = glam::Vec3::splat(self.scale);
|
||||
glam::Mat4::from_scale_rotation_translation(scale, rotation, translation)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Transform {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: glam::Vec3::ZERO,
|
||||
rotation: glam::Vec3::ZERO,
|
||||
scale: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,387 +0,0 @@
|
|||
use crate::model;
|
||||
use crate::ui::{ObjectWidget, ViewportWidget};
|
||||
use crate::wgpu;
|
||||
use cyborg::camera::Camera;
|
||||
use cyborg::storage::mesh::MeshHandle;
|
||||
use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct OffscreenTextures {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub output_texture: wgpu::Texture,
|
||||
pub output_view: wgpu::TextureView,
|
||||
pub depth_texture: wgpu::Texture,
|
||||
pub depth_view: wgpu::TextureView,
|
||||
}
|
||||
|
||||
impl OffscreenTextures {
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
width: u32,
|
||||
height: u32,
|
||||
output_format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let mut tex_desc = wgpu::TextureDescriptor {
|
||||
label: None,
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: output_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
};
|
||||
|
||||
tex_desc.label = Some("Offscreen Output Texture");
|
||||
let output_texture = device.create_texture(&tex_desc);
|
||||
|
||||
tex_desc.label = Some("Offscreen Depth Texture");
|
||||
tex_desc.format = Self::DEPTH_FORMAT;
|
||||
let depth_texture = device.create_texture(&tex_desc);
|
||||
|
||||
let view_desc = wgpu::TextureViewDescriptor::default();
|
||||
let output_view = output_texture.create_view(&view_desc);
|
||||
let depth_view = depth_texture.create_view(&view_desc);
|
||||
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
output_texture,
|
||||
output_view,
|
||||
depth_texture,
|
||||
depth_view,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OffscreenViewport {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub output_format: wgpu::TextureFormat,
|
||||
pub textures: OffscreenTextures,
|
||||
pub egui_texture: egui::TextureId,
|
||||
pub camera: Camera,
|
||||
}
|
||||
|
||||
impl OffscreenViewport {
|
||||
pub fn new(
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
output_format: wgpu::TextureFormat,
|
||||
render_pass: &mut egui_wgpu_backend::RenderPass,
|
||||
) -> Self {
|
||||
let textures = OffscreenTextures::new(&device, 640, 480, output_format);
|
||||
|
||||
let egui_texture = render_pass.egui_texture_from_wgpu_texture(
|
||||
&device,
|
||||
&textures.output_view,
|
||||
wgpu::FilterMode::Nearest,
|
||||
);
|
||||
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
output_format,
|
||||
textures,
|
||||
egui_texture,
|
||||
camera: Camera {
|
||||
eye: [1.0, 1.0, 1.0, 0.0],
|
||||
vp: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_camera(&self) -> Camera {
|
||||
self.camera.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Viewport for OffscreenViewport {
|
||||
fn get_info(&self) -> ViewportInfo {
|
||||
ViewportInfo {
|
||||
output_format: self.output_format,
|
||||
depth_format: OffscreenTextures::DEPTH_FORMAT,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_queue(&self) -> &wgpu::Queue {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
fn get_views(&self) -> ViewportViews {
|
||||
ViewportViews {
|
||||
output: &self.textures.output_view,
|
||||
depth: &self.textures.depth_view,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewportStore {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub output_format: wgpu::TextureFormat,
|
||||
pub viewport: OffscreenViewport,
|
||||
}
|
||||
|
||||
impl ViewportStore {
|
||||
pub fn new(
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
output_format: wgpu::TextureFormat,
|
||||
render_pass: &mut egui_wgpu_backend::RenderPass,
|
||||
) -> Self {
|
||||
Self {
|
||||
device: device.clone(),
|
||||
output_format,
|
||||
viewport: OffscreenViewport::new(device, queue, output_format, render_pass),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl cyborg::legion::RenderCallbacks for ViewportStore {
|
||||
fn get_viewports(&mut self) -> Vec<(&dyn Viewport, Camera)> {
|
||||
vec![(&self.viewport, self.viewport.get_camera())]
|
||||
}
|
||||
|
||||
fn present(&mut self) {}
|
||||
}
|
||||
|
||||
pub struct RenderState {
|
||||
pub world: legion::World,
|
||||
pub resources: legion::Resources,
|
||||
pub render_schedule: legion::Schedule,
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
pub fn new(
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
output_format: wgpu::TextureFormat,
|
||||
render_pass: &mut egui_wgpu_backend::RenderPass,
|
||||
) -> Self {
|
||||
use cyborg::scene::{DebugDrawList, DebugVertex};
|
||||
use cyborg::shader::{ShaderStore, ShaderWatcher};
|
||||
|
||||
let mut world = legion::World::default();
|
||||
let mut resources = legion::Resources::default();
|
||||
let viewport_store =
|
||||
ViewportStore::new(device.clone(), queue.clone(), output_format, render_pass);
|
||||
|
||||
let renderer = cyborg::Renderer::new(device.clone(), queue.clone());
|
||||
resources.insert(renderer);
|
||||
|
||||
let viewport_info = ViewportInfo {
|
||||
output_format,
|
||||
depth_format: OffscreenTextures::DEPTH_FORMAT,
|
||||
};
|
||||
resources.insert(viewport_info);
|
||||
|
||||
let shader_store = Arc::new(ShaderStore::new(device.clone()));
|
||||
let shaders_dir = std::env::current_dir().unwrap();
|
||||
let shaders_dir = shaders_dir.join("shaders/");
|
||||
let shader_watcher = ShaderWatcher::new(shader_store.to_owned(), shaders_dir).unwrap();
|
||||
|
||||
let mesh_forward = shader_watcher.add_file("mesh_forward.wgsl").unwrap();
|
||||
let mesh_skinning = shader_watcher.add_file("mesh_skinning.wgsl").unwrap();
|
||||
|
||||
let mesh_shaders = cyborg::pass::mesh::ShaderInfo {
|
||||
store: shader_store.clone(),
|
||||
forward: mesh_forward,
|
||||
skinning: mesh_skinning,
|
||||
};
|
||||
|
||||
resources.insert(mesh_shaders);
|
||||
|
||||
let mut render_schedule = legion::Schedule::builder();
|
||||
cyborg::legion::build_renderer(viewport_store, &mut resources, &mut render_schedule);
|
||||
let render_schedule = render_schedule.build();
|
||||
|
||||
// make debug draw grid
|
||||
let grid_size = 100;
|
||||
let grid_color = [0.0, 1.0, 0.0];
|
||||
let mut grid_vertices = Vec::new();
|
||||
|
||||
// draw horizontal lines
|
||||
for x in -grid_size..=grid_size {
|
||||
grid_vertices.push(DebugVertex {
|
||||
position: [x as f32, 0.0, -grid_size as f32],
|
||||
color: grid_color.clone(),
|
||||
});
|
||||
grid_vertices.push(DebugVertex {
|
||||
position: [x as f32, 0.0, grid_size as f32],
|
||||
color: grid_color.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// draw vertical lines
|
||||
for z in -grid_size..=grid_size {
|
||||
grid_vertices.push(DebugVertex {
|
||||
position: [-grid_size as f32, 0.0, z as f32],
|
||||
color: grid_color.clone(),
|
||||
});
|
||||
grid_vertices.push(DebugVertex {
|
||||
position: [grid_size as f32, 0.0, z as f32],
|
||||
color: grid_color.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
world.push((
|
||||
cyborg::scene::Transform {
|
||||
transform: Default::default(),
|
||||
},
|
||||
DebugDrawList {
|
||||
indices: (0..(grid_vertices.len() as u32)).into_iter().collect(),
|
||||
vertices: grid_vertices,
|
||||
},
|
||||
));
|
||||
|
||||
Self {
|
||||
world,
|
||||
resources,
|
||||
render_schedule,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_viewport(
|
||||
&mut self,
|
||||
egui_rp: &mut egui_wgpu_backend::RenderPass,
|
||||
widget: &mut ViewportWidget,
|
||||
) {
|
||||
let mut store = self.resources.get_mut::<ViewportStore>().unwrap();
|
||||
let mut viewport = &mut store.viewport;
|
||||
viewport.camera = widget.flycam.get_camera();
|
||||
|
||||
if viewport.textures.width != widget.width || viewport.textures.height != widget.height {
|
||||
viewport.textures = OffscreenTextures::new(
|
||||
&viewport.device,
|
||||
widget.width,
|
||||
widget.height,
|
||||
viewport.output_format,
|
||||
);
|
||||
|
||||
egui_rp
|
||||
.update_egui_texture_from_wgpu_texture(
|
||||
&viewport.device,
|
||||
&viewport.textures.output_view,
|
||||
wgpu::FilterMode::Nearest,
|
||||
viewport.egui_texture,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self) {
|
||||
self.render_schedule
|
||||
.execute(&mut self.world, &mut self.resources);
|
||||
}
|
||||
|
||||
pub fn load_model(&mut self, model: &model::Model) -> model::LoadedModel {
|
||||
model::LoadedModel {
|
||||
name: model.name.clone(),
|
||||
objects: model.objects.iter().map(|o| self.load_object(o)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_object(&mut self, object: &model::Object) -> model::LoadedObject {
|
||||
model::LoadedObject {
|
||||
name: object.name.clone(),
|
||||
transform: object.transform.clone(),
|
||||
meshes: object.meshes.iter().map(|m| self.load_mesh(m)).collect(),
|
||||
children: object
|
||||
.children
|
||||
.iter()
|
||||
.map(|o| self.load_object(o))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_mesh(&mut self, data: &model::Mesh) -> MeshHandle {
|
||||
use cyborg::pass::{
|
||||
mesh::{MeshPass, Vertex},
|
||||
RenderPassBox,
|
||||
};
|
||||
use cyborg::storage::mesh::{AttrBuffer, MeshBuffer};
|
||||
|
||||
let mesh_pass = self.resources.get::<RenderPassBox<MeshPass>>().unwrap();
|
||||
let attributes = mesh_pass.get_attributes();
|
||||
|
||||
let vertices: Vec<_> = data
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|v| Vertex {
|
||||
position: v.position.to_array(),
|
||||
tan_frame: 0, // TODO encode tangents
|
||||
})
|
||||
.collect();
|
||||
|
||||
let vertices = AttrBuffer {
|
||||
id: attributes.vertex,
|
||||
count: vertices.len(),
|
||||
data: bytemuck::cast_slice(&vertices).to_vec(),
|
||||
};
|
||||
|
||||
let indices = AttrBuffer {
|
||||
id: attributes.index,
|
||||
count: data.indices.len(),
|
||||
data: bytemuck::cast_slice(&data.indices).to_vec(),
|
||||
};
|
||||
|
||||
let mut mesh = MeshBuffer::default();
|
||||
mesh.attributes.push(vertices);
|
||||
mesh.attributes.push(indices);
|
||||
|
||||
mesh_pass.get_mesh_pool().load(mesh).unwrap()
|
||||
}
|
||||
|
||||
pub fn spawn_model(
|
||||
&mut self,
|
||||
model: &model::LoadedModel,
|
||||
transform: &glam::Mat4,
|
||||
) -> Vec<ObjectWidget> {
|
||||
// TODO use model name?
|
||||
let mut objects = Vec::new();
|
||||
for object in model.objects.iter() {
|
||||
objects.push(self.spawn_object(object, transform));
|
||||
}
|
||||
|
||||
objects
|
||||
}
|
||||
|
||||
pub fn spawn_object(
|
||||
&mut self,
|
||||
object: &model::LoadedObject,
|
||||
root_transform: &glam::Mat4,
|
||||
) -> ObjectWidget {
|
||||
let transform = cyborg::scene::Transform {
|
||||
transform: *root_transform * object.transform.to_mat4(),
|
||||
};
|
||||
|
||||
let mut entities = Vec::new();
|
||||
for mesh in object.meshes.iter() {
|
||||
let mesh = cyborg::scene::Mesh { mesh: *mesh };
|
||||
let entity = self.world.push((mesh, transform.clone()));
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
let mut children = Vec::new();
|
||||
for child in object.children.iter() {
|
||||
children.push(self.spawn_object(child, root_transform));
|
||||
}
|
||||
|
||||
ObjectWidget {
|
||||
name: object.name.clone(),
|
||||
transform: object.transform.clone(),
|
||||
entities,
|
||||
children,
|
||||
dirty: false,
|
||||
children_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
use crate::model::LoadedModel;
|
||||
use crate::render::RenderState;
|
||||
use mlua::{Lua, LuaSerdeExt, Result, Value};
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
impl mlua::UserData for LoadedModel {}
|
||||
|
||||
pub struct ScriptData {
|
||||
root_path: PathBuf,
|
||||
render_state: Arc<RwLock<RenderState>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Transform {
|
||||
#[serde(default)]
|
||||
pub position: glam::Vec3,
|
||||
|
||||
#[serde(default)]
|
||||
pub orientation: glam::Quat,
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
pub fn to_mat4(&self) -> glam::Mat4 {
|
||||
glam::Mat4::from_rotation_translation(self.orientation, self.position)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_gltf(lua: &Lua, filename: String) -> Result<LoadedModel> {
|
||||
let data = lua.app_data_ref::<ScriptData>().unwrap();
|
||||
let model = crate::import::load_gltf(data.root_path.join(filename).into());
|
||||
let loaded = data.render_state.write().load_model(&model);
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
fn spawn_model(lua: &Lua, model: (LoadedModel, Value)) -> Result<()> {
|
||||
let data = lua.app_data_ref::<ScriptData>().unwrap();
|
||||
let transform: Transform = lua.from_value(model.1)?;
|
||||
let transform = transform.to_mat4();
|
||||
data.render_state.write().spawn_model(&model.0, &transform);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Script {
|
||||
lua: Lua,
|
||||
}
|
||||
|
||||
impl Script {
|
||||
pub fn new(file: &Path, render_state: Arc<RwLock<RenderState>>) -> Self {
|
||||
let lua = Lua::new();
|
||||
|
||||
let data = ScriptData {
|
||||
root_path: file.parent().unwrap().into(),
|
||||
render_state,
|
||||
};
|
||||
|
||||
lua.set_app_data(data);
|
||||
|
||||
Self::load_cyborg_lib(&lua).unwrap();
|
||||
let src = std::fs::read_to_string(file).unwrap();
|
||||
lua.load(&src).eval::<mlua::MultiValue>().unwrap();
|
||||
|
||||
Self { lua }
|
||||
}
|
||||
|
||||
pub fn load_cyborg_lib(lua: &Lua) -> Result<()> {
|
||||
let lib = lua.create_table()?;
|
||||
lib.set("load_gltf", lua.create_function(load_gltf)?)?;
|
||||
lib.set("spawn_model", lua.create_function(spawn_model)?)?;
|
||||
lua.globals().set("cyborg", lib)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
449
editor/src/ui.rs
449
editor/src/ui.rs
|
@ -1,449 +0,0 @@
|
|||
use crate::winit;
|
||||
use crossbeam_channel::Sender;
|
||||
use cyborg::camera::Flycam;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FileEvent {
|
||||
Save,
|
||||
SaveAs(PathBuf),
|
||||
Import(ImportKind, PathBuf),
|
||||
LoadScript(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ImportKind {
|
||||
Stl,
|
||||
Gltf,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Workspace {
|
||||
Scene,
|
||||
NodeEditor,
|
||||
}
|
||||
|
||||
pub struct UserInterface {
|
||||
file_sender: Sender<FileEvent>,
|
||||
developer_mode: bool,
|
||||
show_profiler: bool,
|
||||
quit: bool,
|
||||
show_about: bool,
|
||||
show_log: bool,
|
||||
log_contents: String,
|
||||
workspace: Workspace,
|
||||
}
|
||||
|
||||
impl UserInterface {
|
||||
pub fn new(file_sender: Sender<FileEvent>) -> Self {
|
||||
Self {
|
||||
file_sender,
|
||||
developer_mode: true,
|
||||
show_profiler: false,
|
||||
show_log: false,
|
||||
quit: false,
|
||||
show_about: false,
|
||||
log_contents: "Hello logging!\n".to_string(),
|
||||
workspace: Workspace::Scene,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_quit(&self) -> bool {
|
||||
self.quit
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
viewport: &mut ViewportWidget,
|
||||
objects: &mut [ObjectWidget],
|
||||
) {
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| self.ui_menu_bar(ui));
|
||||
});
|
||||
|
||||
if self.show_profiler {
|
||||
self.show_profiler = puffin_egui::profiler_window(ctx);
|
||||
}
|
||||
|
||||
if self.show_about {
|
||||
egui::Window::new("About")
|
||||
.open(&mut self.show_about)
|
||||
.resizable(false)
|
||||
.collapsible(false)
|
||||
.show(ctx, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("Cyborg Editor");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if self.show_log {
|
||||
egui::TopBottomPanel::bottom("info_panel")
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
ui.heading("Log Output");
|
||||
|
||||
egui::containers::ScrollArea::vertical()
|
||||
.auto_shrink([false, false])
|
||||
.max_width(f32::INFINITY)
|
||||
.max_height(f32::INFINITY)
|
||||
.show(ui, |ui| {
|
||||
let text_edit = egui::TextEdit::multiline(&mut self.log_contents)
|
||||
.desired_width(f32::INFINITY)
|
||||
.frame(false);
|
||||
ui.add(text_edit);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
match self.workspace {
|
||||
Workspace::Scene => self.ui_scene(ctx, viewport, objects),
|
||||
Workspace::NodeEditor => {
|
||||
egui::CentralPanel::default().show(ctx, |ui| self.ui_node_editor(ui));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui_menu_bar(&mut self, ui: &mut egui::Ui) {
|
||||
ui.menu_button("File", |ui| {
|
||||
if self.developer_mode {
|
||||
if ui.button("fuck").clicked() {
|
||||
println!("fuck");
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
ui.close_menu();
|
||||
self.file_sender.send(FileEvent::Save).unwrap();
|
||||
}
|
||||
|
||||
if ui.button("Save as...").clicked() {
|
||||
ui.close_menu();
|
||||
|
||||
let file_sender = self.file_sender.to_owned();
|
||||
std::thread::spawn(move || {
|
||||
if let Some(path) = rfd::FileDialog::new().save_file() {
|
||||
file_sender.send(FileEvent::SaveAs(path)).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui.menu_button("Import...", |ui| {
|
||||
if ui.button("STL").clicked() {
|
||||
ui.close_menu();
|
||||
self.on_import(ImportKind::Stl);
|
||||
}
|
||||
|
||||
if ui.button("glTF").clicked() {
|
||||
ui.close_menu();
|
||||
self.on_import(ImportKind::Gltf);
|
||||
}
|
||||
});
|
||||
|
||||
if ui.button("Load script...").clicked() {
|
||||
ui.close_menu();
|
||||
self.on_load_script();
|
||||
}
|
||||
|
||||
if ui.button("Open...").clicked() {
|
||||
println!("Opening!");
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui.button("Quit").clicked() {
|
||||
println!("Quitting!");
|
||||
ui.close_menu();
|
||||
self.quit = true;
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Edit", |ui| {
|
||||
if ui.button("Undo").clicked() {
|
||||
println!("Undoing!");
|
||||
}
|
||||
|
||||
if ui.button("Redo").clicked() {
|
||||
println!("Redoing!");
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("View", |ui| {
|
||||
ui.checkbox(&mut self.show_log, "Log");
|
||||
ui.checkbox(&mut self.developer_mode, "Developer mode");
|
||||
|
||||
if ui.checkbox(&mut self.show_profiler, "Profiler").changed() {
|
||||
puffin_egui::puffin::set_scopes_on(self.show_profiler);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("About").clicked() {
|
||||
self.show_about = true;
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.ui_select_workspace(ui);
|
||||
}
|
||||
|
||||
pub fn ui_select_workspace(&mut self, ui: &mut egui::Ui) {
|
||||
ui.selectable_value(&mut self.workspace, Workspace::Scene, "Scene");
|
||||
ui.selectable_value(&mut self.workspace, Workspace::NodeEditor, "Node Editor");
|
||||
}
|
||||
|
||||
pub fn ui_scene(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
viewport: &mut ViewportWidget,
|
||||
objects: &mut [ObjectWidget],
|
||||
) {
|
||||
egui::SidePanel::left("objects_panel")
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
for (index, object) in objects.iter_mut().enumerate() {
|
||||
object.ui(index, ui);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.add(viewport);
|
||||
ui.heading("Viewport");
|
||||
});
|
||||
}
|
||||
|
||||
pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label("Node editor goes here!");
|
||||
}
|
||||
|
||||
pub fn on_import(&self, kind: ImportKind) {
|
||||
let kind_name = match kind {
|
||||
ImportKind::Stl => "STL",
|
||||
ImportKind::Gltf => "glTF",
|
||||
};
|
||||
|
||||
let extensions: &[&str] = match kind {
|
||||
ImportKind::Stl => &["stl"],
|
||||
ImportKind::Gltf => &["gltf", "glb", "vrm"],
|
||||
};
|
||||
|
||||
let file_sender = self.file_sender.to_owned();
|
||||
std::thread::spawn(move || {
|
||||
let dialog = rfd::FileDialog::new().add_filter(kind_name, extensions);
|
||||
if let Some(paths) = dialog.pick_files() {
|
||||
for path in paths.iter() {
|
||||
let event = FileEvent::Import(kind, path.into());
|
||||
file_sender.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn on_load_script(&self) {
|
||||
let file_sender = self.file_sender.to_owned();
|
||||
std::thread::spawn(move || {
|
||||
let dialog = rfd::FileDialog::new().add_filter("Lua script", &["lua"]);
|
||||
if let Some(path) = dialog.pick_file() {
|
||||
let event = FileEvent::LoadScript(path.into());
|
||||
file_sender.send(event).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewportWidget {
|
||||
pub texture: egui::TextureId,
|
||||
pub flycam: Flycam,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl ViewportWidget {
|
||||
pub fn new(texture: egui::TextureId) -> Self {
|
||||
Self {
|
||||
texture,
|
||||
flycam: Flycam::new(0.002, 10.0, 0.25),
|
||||
width: 640,
|
||||
height: 480,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl egui::Widget for &mut ViewportWidget {
|
||||
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||
ui.style_mut().spacing.window_margin = egui::style::Margin::same(0.0);
|
||||
let rect = ui.max_rect();
|
||||
let id = egui::Id::new("viewport_widget");
|
||||
let sense = egui::Sense::click_and_drag();
|
||||
let response = ui.interact(rect, id, sense);
|
||||
|
||||
self.width = rect.width().round() as u32;
|
||||
self.height = rect.height().round() as u32;
|
||||
|
||||
use egui::{pos2, Color32, Mesh, Rect, Shape};
|
||||
let mut mesh = Mesh::with_texture(self.texture);
|
||||
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
||||
let tint = Color32::WHITE;
|
||||
mesh.add_rect_with_uv(rect, uv, tint);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
|
||||
if response.dragged() {
|
||||
let delta = response.drag_delta();
|
||||
self.flycam.process_mouse(delta.x as f64, delta.y as f64);
|
||||
|
||||
for event in ui.input().events.iter() {
|
||||
match event {
|
||||
egui::Event::Key { key, pressed, .. } => {
|
||||
use winit::event::{ElementState, VirtualKeyCode};
|
||||
|
||||
let key = match key {
|
||||
egui::Key::W => Some(VirtualKeyCode::W),
|
||||
egui::Key::A => Some(VirtualKeyCode::A),
|
||||
egui::Key::S => Some(VirtualKeyCode::S),
|
||||
egui::Key::D => Some(VirtualKeyCode::D),
|
||||
// TODO remap from shift key somehow?
|
||||
egui::Key::E => Some(VirtualKeyCode::E),
|
||||
egui::Key::Q => Some(VirtualKeyCode::Q),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let state = if *pressed {
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
ElementState::Released
|
||||
};
|
||||
|
||||
if let Some(key) = key {
|
||||
self.flycam.process_keyboard(key, state);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else if response.drag_released() {
|
||||
self.flycam.defocus();
|
||||
}
|
||||
|
||||
self.flycam.resize(self.width, self.height);
|
||||
self.flycam.update();
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjectWidget {
|
||||
pub name: Option<String>,
|
||||
pub transform: crate::model::Transform,
|
||||
pub entities: Vec<legion::Entity>,
|
||||
pub children: Vec<ObjectWidget>,
|
||||
pub dirty: bool,
|
||||
pub children_dirty: bool,
|
||||
}
|
||||
|
||||
impl ObjectWidget {
|
||||
pub fn ui(&mut self, index: usize, ui: &mut egui::Ui) {
|
||||
egui::CollapsingHeader::new(self.name.as_ref().unwrap_or(&"<unnamed>".into()))
|
||||
.id_source(format!("child_{}", index))
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
self.ui_self(ui);
|
||||
|
||||
for (index, child) in self.children.iter_mut().enumerate() {
|
||||
child.ui(index, ui);
|
||||
if child.dirty || child.children_dirty {
|
||||
self.children_dirty = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn ui_self(&mut self, ui: &mut egui::Ui) {
|
||||
egui::Grid::new("root_object")
|
||||
.num_columns(4)
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Position: ");
|
||||
self.ui_position(ui);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Rotation: ");
|
||||
self.ui_rotation(ui);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Scale: ");
|
||||
self.ui_scale(ui);
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn ui_position(&mut self, ui: &mut egui::Ui) {
|
||||
let speed = 0.1 * self.transform.scale;
|
||||
if Self::ui_vec3(ui, speed, &mut self.transform.position) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui_rotation(&mut self, ui: &mut egui::Ui) {
|
||||
let axes = &mut self.transform.rotation;
|
||||
|
||||
if ui.drag_angle(&mut axes.x).changed() {
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
if ui.drag_angle(&mut axes.y).changed() {
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
if ui.drag_angle(&mut axes.z).changed() {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool {
|
||||
let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed);
|
||||
let dirty = ui.add(x_drag).changed();
|
||||
|
||||
let dirty = dirty || {
|
||||
// For some reason, Rust complains if this isn't in a block
|
||||
let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed);
|
||||
ui.add(y_drag).changed()
|
||||
};
|
||||
|
||||
let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed);
|
||||
let dirty = dirty || ui.add(z_drag).changed();
|
||||
|
||||
dirty
|
||||
}
|
||||
|
||||
pub fn ui_scale(&mut self, ui: &mut egui::Ui) {
|
||||
let scale_speed = self.transform.scale * 0.01;
|
||||
let drag = egui::DragValue::new(&mut self.transform.scale)
|
||||
.clamp_range(0.0..=f32::INFINITY)
|
||||
.speed(scale_speed);
|
||||
if ui.add(drag).changed() {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush_dirty(&mut self, mut f: impl FnMut(&mut Self)) {
|
||||
let mut stack = vec![self];
|
||||
while let Some(parent) = stack.pop() {
|
||||
if parent.dirty {
|
||||
parent.dirty = false;
|
||||
f(parent);
|
||||
}
|
||||
|
||||
if parent.children_dirty {
|
||||
parent.children_dirty = false;
|
||||
|
||||
for child in parent.children.iter_mut() {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
use cyborg::shader::{parse_wgsl, generate_wgsl, add_includes};
|
||||
use cyborg::logger::Timer;
|
||||
|
||||
fn main() {
|
||||
let comp_timer = Timer::start("Shader compilation");
|
||||
|
||||
// Generate a shader and preprocess it
|
||||
let mut source = std::fs::read_to_string("src/shader.wgsl").unwrap();
|
||||
source = add_includes(&source);
|
||||
|
||||
// Parse the WGSL into a usable module
|
||||
let module = parse_wgsl(&source);
|
||||
|
||||
// Generate a valid WGSL string from the module
|
||||
let gen_wgsl = generate_wgsl(&module);
|
||||
|
||||
println!("Generated WGSL:\n\n{}", gen_wgsl);
|
||||
|
||||
comp_timer.print_status();
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
use std::fs::read_to_string;
|
||||
use cyborg::shader::{parse_wgsl, generate_wgsl, add_includes};
|
||||
|
||||
fn main() {
|
||||
// Generate a shader and preprocess it
|
||||
let mut source = read_to_string("src/shader.wgsl").unwrap();
|
||||
source = add_includes(&source);
|
||||
|
||||
// Parse the WGSL into a usable module
|
||||
let module = parse_wgsl(&source);
|
||||
|
||||
// Generate a valid WGSL string from the module
|
||||
let gen_wgsl = generate_wgsl(&module);
|
||||
|
||||
println!("{:?}", module);
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
// example shader for when the time is right
|
|
@ -1,46 +0,0 @@
|
|||
struct CameraUniform {
|
||||
eye: vec4<f32>,
|
||||
vp: mat4x4<f32>,
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tan_frame: u32,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) color: vec3<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
fn random(seed: u32, salt: f32) -> f32 {
|
||||
return abs(sin((f32(seed & u32(0x11111)) * 0.7071 + salt) * 78.233));
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(instance_index) mesh_idx: u32,
|
||||
@builtin(vertex_index) vertex_idx: u32,
|
||||
vertex: VertexInput,
|
||||
) -> VertexOutput {
|
||||
let world_pos = vertex.position;
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = camera.vp * vec4<f32>(world_pos, 1.0);
|
||||
out.position = world_pos;
|
||||
out.color.r = random(vertex_idx, f32(1.0) + world_pos.x);
|
||||
out.color.g = random(vertex_idx, f32(2.0) + world_pos.y);
|
||||
out.color.b = random(vertex_idx, f32(3.0) + world_pos.z);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
frag: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(frag.color, 1.0);
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
#include oct_encoding.wgsl
|
||||
#include skin.wgsl
|
||||
|
||||
struct SkinningUniform {
|
||||
transform: mat4x4<f32>,
|
||||
src_offset: u32,
|
||||
dst_offset: u32,
|
||||
count: u32,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<storage,read> skinning_ubo: SkinningUniform;
|
||||
|
||||
@group(0) @binding(1)
|
||||
var<storage,write> dst_vertices: SkinnedVertexArray;
|
||||
|
||||
@group(0) @binding(2)
|
||||
var<storage,read> src_vertices: SkinnedVertexArray;
|
||||
|
||||
@compute
|
||||
@workgroup_size(64)
|
||||
fn cs_main(
|
||||
@builtin(global_invocation_id) global_invocation_id: vec3<u32>,
|
||||
) {
|
||||
let vertex_index = global_invocation_id.x;
|
||||
if (vertex_index >= skinning_ubo.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
let src_index = skinning_ubo.src_offset + vertex_index;
|
||||
let dst_index = skinning_ubo.dst_offset + vertex_index;
|
||||
|
||||
let ptf = src_vertices.data[src_index].ptf;
|
||||
let position = ptf.xyz;
|
||||
let tan_frame = bitcast<u32>(ptf.w);
|
||||
|
||||
let transform = skinning_ubo.transform;
|
||||
let position = transform * vec4<f32>(position, 1.0);
|
||||
let tan_frame = tan_frame_transform(tan_frame, transform);
|
||||
|
||||
let dst_ptf = vec4<f32>(position.xyz, bitcast<f32>(tan_frame));
|
||||
dst_vertices.data[dst_index].ptf = dst_ptf;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
struct TangentFrame {
|
||||
normal: vec3<f32>,
|
||||
tangent: vec3<f32>
|
||||
};
|
||||
|
||||
// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/?like_comment=12
|
||||
fn oct_norm_wrap(v: vec2<f32>) -> vec2<f32> {
|
||||
return (1.0 - abs(v.yx)) * select(vec2<f32>(-1.0), vec2<f32>(1.0), v.xy >= vec2<f32>(0.0));
|
||||
}
|
||||
|
||||
fn oct_norm_encode(n: vec3<f32>) -> vec2<f32> {
|
||||
let ml = (abs(n.x) + abs(n.y) + abs(n.z)); // ml = manhattan length
|
||||
let n = n / ml;
|
||||
let v = select(oct_norm_wrap(n.xy), n.xy, n.z >= 0.0);
|
||||
return fma(v, vec2<f32>(0.5), vec2<f32>(0.5));
|
||||
}
|
||||
|
||||
fn oct_norm_decode(f: vec2<f32>) -> vec3<f32> {
|
||||
let f = fma(f, vec2<f32>(2.0), vec2<f32>(-1.0));
|
||||
let n = vec3<f32>(f.xy, 1.0 - abs(f.x) - abs(f.y));
|
||||
let t = vec2<f32>(clamp(-n.z, 0.0, 1.0));
|
||||
let nxy = n.xy + select(t, -t, n.xy >= vec2<f32>(0.0));
|
||||
return normalize(vec3<f32>(nxy, n.z));
|
||||
}
|
||||
|
||||
fn tan_frame_encode(tf: TangentFrame) -> u32 {
|
||||
return u32(0); // TODO
|
||||
}
|
||||
|
||||
fn tan_frame_decode(i: u32) -> TangentFrame {
|
||||
let n = i & u32(0xffff);
|
||||
let nx = n >> u32(8);
|
||||
let ny = n & u32(0xff);
|
||||
let n = vec2<f32>(f32(nx), f32(ny)) / 255.0;
|
||||
|
||||
let t = i >> u32(16);
|
||||
let tx = t >> u32(8);
|
||||
let ty = t & u32(0xff);
|
||||
let t = vec2<f32>(f32(tx), f32(ty)) / 255.0;
|
||||
|
||||
var tf: TangentFrame;
|
||||
tf.normal = oct_norm_decode(n);
|
||||
tf.tangent = oct_norm_decode(n);
|
||||
return tf;
|
||||
}
|
||||
|
||||
fn tan_frame_transform(i: u32, transform: mat4x4<f32>) -> u32 {
|
||||
var tf = tan_frame_decode(i);
|
||||
tf.normal = (transform * vec4<f32>(tf.normal, 0.0)).xyz;
|
||||
tf.tangent = (transform * vec4<f32>(tf.tangent, 0.0)).xyz;
|
||||
return tan_frame_encode(tf);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
struct SkinnedVertex {
|
||||
ptf: vec4<f32>
|
||||
};
|
||||
|
||||
struct SkinnedVertexArray {
|
||||
data: array<SkinnedVertex>
|
||||
};
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 242 KiB |
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
Binary file not shown.
After Width: | Height: | Size: 940 KiB |
File diff suppressed because it is too large
Load Diff
195
src/camera.rs
195
src/camera.rs
|
@ -1,46 +1,30 @@
|
|||
use glam::{Mat4, Quat, Vec3};
|
||||
use std::f32::consts::LN_2;
|
||||
use glam::{Mat4, Quat, Vec2, Vec3};
|
||||
use std::time::Instant;
|
||||
use winit::event::{ElementState, VirtualKeyCode};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Camera {
|
||||
pub eye: [f32; 4],
|
||||
pub vp: [[f32; 4]; 4],
|
||||
pub trait Camera {
|
||||
fn get_eye(&self) -> [f32; 4];
|
||||
fn get_vp(&self) -> [[f32; 4]; 4];
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Flycam {
|
||||
// input
|
||||
// currently held keys
|
||||
is_world_up_pressed: bool,
|
||||
is_world_down_pressed: bool,
|
||||
is_cam_up_pressed: bool,
|
||||
is_cam_down_pressed: bool,
|
||||
is_up_pressed: bool,
|
||||
is_down_pressed: bool,
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
// accumulated mouse movement yet to be processed
|
||||
mouse_dx: f32,
|
||||
mouse_dy: f32,
|
||||
|
||||
// state
|
||||
// timestamp
|
||||
last_update: Instant,
|
||||
// camera orientation
|
||||
euler_x: f32,
|
||||
euler_y: f32,
|
||||
// camera movement state
|
||||
velocity: Vec3,
|
||||
pan: f32,
|
||||
tilt: f32,
|
||||
position: Vec3,
|
||||
|
||||
// constants
|
||||
// camera movement
|
||||
turn_sensitivity: f32, // coefficient for mouse_dx/dy -> euler_x/y
|
||||
thrust_mag: f32, // coefficient for thrust acceleration vector
|
||||
damping_coeff: f32, // coefficient for damping acceleration vector
|
||||
// camera frustum
|
||||
speed: f32,
|
||||
turn_speed: f32,
|
||||
aspect: f32,
|
||||
fovy: f32,
|
||||
znear: f32,
|
||||
|
@ -48,14 +32,10 @@ pub struct Flycam {
|
|||
}
|
||||
|
||||
impl Flycam {
|
||||
/// thrust_speed: top speed when using a single thruster, in units/second
|
||||
/// duration to halve difference between current and target velocity, in seconds
|
||||
pub fn new(turn_sensitivity: f32, thrust_speed: f32, damper_half_life: f32) -> Self {
|
||||
pub fn new(speed: f32, turn_speed: f32) -> Self {
|
||||
Self {
|
||||
is_world_up_pressed: false,
|
||||
is_world_down_pressed: false,
|
||||
is_cam_up_pressed: false,
|
||||
is_cam_down_pressed: false,
|
||||
is_up_pressed: false,
|
||||
is_down_pressed: false,
|
||||
is_forward_pressed: false,
|
||||
is_backward_pressed: false,
|
||||
is_left_pressed: false,
|
||||
|
@ -63,13 +43,11 @@ impl Flycam {
|
|||
mouse_dx: 0.0,
|
||||
mouse_dy: 0.0,
|
||||
last_update: Instant::now(),
|
||||
euler_x: 0.0,
|
||||
euler_y: 0.0,
|
||||
velocity: Vec3::new(0.0, 0.0, 0.0),
|
||||
pan: 0.0,
|
||||
tilt: 0.0,
|
||||
position: Vec3::new(0.0, 0.5, 1.0),
|
||||
turn_sensitivity,
|
||||
thrust_mag: thrust_speed / damper_half_life * LN_2,
|
||||
damping_coeff: LN_2 / damper_half_life,
|
||||
speed,
|
||||
turn_speed,
|
||||
aspect: 1.0, // TODO compute from size
|
||||
fovy: std::f32::consts::FRAC_PI_2,
|
||||
znear: 0.01,
|
||||
|
@ -79,21 +57,14 @@ impl Flycam {
|
|||
}
|
||||
|
||||
impl Flycam {
|
||||
/// update stored keyboard state for use in update()
|
||||
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) {
|
||||
let is_pressed = state == ElementState::Pressed;
|
||||
match key {
|
||||
VirtualKeyCode::Space => {
|
||||
self.is_world_up_pressed = is_pressed;
|
||||
self.is_up_pressed = is_pressed;
|
||||
}
|
||||
VirtualKeyCode::LShift => {
|
||||
self.is_world_down_pressed = is_pressed;
|
||||
}
|
||||
VirtualKeyCode::Q => {
|
||||
self.is_cam_down_pressed = is_pressed;
|
||||
}
|
||||
VirtualKeyCode::E => {
|
||||
self.is_cam_up_pressed = is_pressed;
|
||||
self.is_down_pressed = is_pressed;
|
||||
}
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.is_forward_pressed = is_pressed;
|
||||
|
@ -111,7 +82,6 @@ impl Flycam {
|
|||
}
|
||||
}
|
||||
|
||||
/// update accumulated mouse movement for use in update()
|
||||
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
|
||||
self.mouse_dx += mouse_dx as f32;
|
||||
self.mouse_dy += mouse_dy as f32;
|
||||
|
@ -121,128 +91,73 @@ impl Flycam {
|
|||
self.aspect = (width as f32) / (height as f32);
|
||||
}
|
||||
|
||||
/// disable all key presses
|
||||
pub fn defocus(&mut self) {
|
||||
self.is_world_down_pressed = false;
|
||||
self.is_world_up_pressed = false;
|
||||
self.is_cam_down_pressed = false;
|
||||
self.is_cam_up_pressed = false;
|
||||
self.is_forward_pressed = false;
|
||||
self.is_left_pressed = false;
|
||||
self.is_backward_pressed = false;
|
||||
self.is_right_pressed = false;
|
||||
}
|
||||
|
||||
/// apply input and update camera movement
|
||||
pub fn update(&mut self) {
|
||||
let dt = self.last_update.elapsed();
|
||||
self.last_update = Instant::now();
|
||||
let dt = dt.as_micros() as f32 / 1_000_000.0;
|
||||
|
||||
self.update_orientation(dt);
|
||||
self.update_kinematic(dt);
|
||||
}
|
||||
|
||||
fn update_orientation(&mut self, _dt: f32) {
|
||||
let t = self.turn_sensitivity;
|
||||
|
||||
self.euler_x -= t * self.mouse_dy; // mouse +y = 2D plane down = look down = 3d space -x
|
||||
self.euler_y -= t * self.mouse_dx; // mouse +x = 2D plane right = look to the right = 3d space -y
|
||||
|
||||
let t = self.turn_speed;
|
||||
self.pan += t * self.mouse_dx;
|
||||
self.tilt += t * self.mouse_dy;
|
||||
self.mouse_dx = 0.0;
|
||||
self.mouse_dy = 0.0;
|
||||
|
||||
// Clamp euler_x to [-pi/2, pi/2]
|
||||
let euler_x_limit = std::f32::consts::FRAC_PI_2;
|
||||
if self.euler_x < -euler_x_limit {
|
||||
self.euler_x = -euler_x_limit;
|
||||
} else if self.euler_x > euler_x_limit {
|
||||
self.euler_x = euler_x_limit;
|
||||
let tilt_limit = std::f32::consts::FRAC_PI_2;
|
||||
if self.tilt < -tilt_limit {
|
||||
self.tilt = -tilt_limit;
|
||||
} else if self.tilt > tilt_limit {
|
||||
self.tilt = tilt_limit;
|
||||
}
|
||||
}
|
||||
|
||||
/// update velocity and position from acceleration using forward differences
|
||||
fn update_kinematic(&mut self, dt: f32) {
|
||||
let net_acc = self.get_thrust_acc() + self.get_damping_acc();
|
||||
|
||||
let delta_vel = net_acc * dt;
|
||||
self.velocity += delta_vel;
|
||||
|
||||
let delta_pos = self.velocity * dt;
|
||||
self.position += delta_pos;
|
||||
}
|
||||
|
||||
/// use keyboard key pairs to trigger directional thrusters in camera and world coordinates
|
||||
/// thrust_speed is the max speed (under drag) with a single thruster, but combinations can
|
||||
/// produce higher speeds (e.g. forward and right, camera down and world down)
|
||||
fn get_thrust_acc(&self) -> glam::Vec3 {
|
||||
let s = dt * self.speed;
|
||||
let axis = Self::key_axis;
|
||||
|
||||
let thruster_cam_x = axis(self.is_left_pressed, self.is_right_pressed);
|
||||
let thruster_cam_y = axis(self.is_cam_down_pressed, self.is_cam_up_pressed);
|
||||
let thruster_cam_z = -axis(self.is_backward_pressed, self.is_forward_pressed); // forward is -z
|
||||
let thruster_world_y = axis(self.is_world_down_pressed, self.is_world_up_pressed);
|
||||
|
||||
let thrusters_cam = Vec3::new(thruster_cam_x, thruster_cam_y, thruster_cam_z);
|
||||
let thrusters_world = Vec3::new(0.0, thruster_world_y, 0.0);
|
||||
|
||||
let cam_to_world = self.get_orientation();
|
||||
let thrusters_total = thrusters_world + cam_to_world * thrusters_cam;
|
||||
|
||||
self.thrust_mag * thrusters_total
|
||||
let truck = s * axis(self.is_backward_pressed, self.is_forward_pressed);
|
||||
let dolly = s * axis(self.is_right_pressed, self.is_left_pressed);
|
||||
let boom = s * axis(self.is_down_pressed, self.is_up_pressed);
|
||||
self.move_position(truck, dolly, boom);
|
||||
}
|
||||
|
||||
/// calculate a damping force (proportional to velocity)
|
||||
/// the damping coefficient is calculated in the constructor, which is parameterized in terms
|
||||
/// of more physically meaningful values
|
||||
fn get_damping_acc(&self) -> glam::Vec3 {
|
||||
self.damping_coeff * -self.velocity
|
||||
}
|
||||
|
||||
/// a helper function to turn a pair of key states into a sign for thruster direction
|
||||
fn key_axis(negative: bool, positive: bool) -> f32 {
|
||||
if negative {
|
||||
if positive {
|
||||
0.0 // positive + negative cancel out
|
||||
0.0
|
||||
} else {
|
||||
-1.0 // negative only
|
||||
-1.0
|
||||
}
|
||||
} else {
|
||||
if positive {
|
||||
1.0 // positive only
|
||||
1.0
|
||||
} else {
|
||||
0.0 // neutral
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// the current camera orientation, which can be seen as a rotation (in quaternion form) from
|
||||
/// camera axes to world axes
|
||||
/// glam's YXZ ordering matches the standard roll-pitch-yaw Euler angles
|
||||
fn get_orientation(&self) -> glam::Quat {
|
||||
Quat::from_euler(glam::EulerRot::YXZ, self.euler_y, self.euler_x, 0.0)
|
||||
}
|
||||
fn move_position(&mut self, truck: f32, dolly: f32, boom: f32) {
|
||||
// truck direction from straight down
|
||||
let h = Vec2::new(self.pan.sin(), -self.pan.cos());
|
||||
// truck direction from the side
|
||||
let v = Vec2::new(self.tilt.cos(), -self.tilt.sin());
|
||||
// composite to get forward direction
|
||||
let truck_to = Vec3::new(h.x * v.x, v.y, h.y * v.x);
|
||||
|
||||
pub fn get_camera(&self) -> Camera {
|
||||
Camera {
|
||||
eye: self.get_eye(),
|
||||
vp: self.get_vp(),
|
||||
}
|
||||
}
|
||||
let dolly_to = Vec3::new(-self.pan.cos(), 0.0, -self.pan.sin());
|
||||
|
||||
pub fn get_eye(&self) -> [f32; 4] {
|
||||
self.position += (truck_to * truck) + (dolly_to * dolly);
|
||||
self.position.y += boom;
|
||||
}
|
||||
}
|
||||
|
||||
impl Camera for Flycam {
|
||||
fn get_eye(&self) -> [f32; 4] {
|
||||
self.position.extend(0.0).to_array()
|
||||
}
|
||||
|
||||
pub fn get_vp(&self) -> [[f32; 4]; 4] {
|
||||
// view matrix is inverted camera pose (world space to camera space)
|
||||
let rotation = Mat4::from_quat(self.get_orientation().inverse());
|
||||
let translation = Mat4::from_translation(-self.position);
|
||||
let view = rotation * translation;
|
||||
|
||||
// perspective projection
|
||||
fn get_vp(&self) -> [[f32; 4]; 4] {
|
||||
let orientation = Quat::from_euler(glam::EulerRot::XYZ, self.tilt, self.pan, 0.0);
|
||||
let rotation = Mat4::from_quat(orientation);
|
||||
let view = rotation * Mat4::from_translation(-self.position);
|
||||
let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar);
|
||||
|
||||
let vp = proj * view;
|
||||
vp.to_cols_array_2d()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
use super::scene::MeshInstance;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DrawState {
|
||||
group_id: usize,
|
||||
material_id: usize,
|
||||
sub_id: usize,
|
||||
instance_range: Range<u32>,
|
||||
}
|
||||
|
||||
pub enum Command {
|
||||
BindMeshGroup {
|
||||
group_id: usize,
|
||||
},
|
||||
BindMaterial {
|
||||
material_id: usize,
|
||||
},
|
||||
Draw {
|
||||
sub_id: usize,
|
||||
instance_range: Range<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct CommandIterator<'a> {
|
||||
command_set: &'a CommandSet,
|
||||
command_idx: usize, // TODO use iterator instead lol
|
||||
last_group_id: usize,
|
||||
last_material_id: usize,
|
||||
}
|
||||
|
||||
impl<'a> CommandIterator<'a> {
|
||||
pub fn new(command_set: &'a CommandSet) -> Self {
|
||||
Self {
|
||||
command_set,
|
||||
command_idx: 0,
|
||||
last_group_id: usize::MAX,
|
||||
last_material_id: usize::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CommandIterator<'a> {
|
||||
type Item = Command;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let DrawState {
|
||||
group_id,
|
||||
sub_id,
|
||||
material_id,
|
||||
instance_range,
|
||||
} = self.command_set.draws.get(self.command_idx)?.clone();
|
||||
|
||||
if group_id != self.last_group_id {
|
||||
self.last_group_id = group_id;
|
||||
Some(Command::BindMeshGroup { group_id })
|
||||
} else if material_id != self.last_material_id {
|
||||
self.last_material_id = material_id;
|
||||
Some(Command::BindMaterial { material_id })
|
||||
} else {
|
||||
self.command_idx += 1;
|
||||
Some(Command::Draw {
|
||||
sub_id,
|
||||
instance_range,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandSet {
|
||||
transforms: Vec<[f32; 16]>,
|
||||
draws: Vec<DrawState>,
|
||||
}
|
||||
|
||||
impl CommandSet {
|
||||
pub fn build(instances: &[MeshInstance]) -> Self {
|
||||
let mut sorted_meshes = HashMap::<(usize, usize), Vec<MeshInstance>>::new();
|
||||
for instance in instances.iter() {
|
||||
let group_id = instance.mesh.group_id;
|
||||
let material_id = instance.material.id;
|
||||
let key = (group_id, material_id);
|
||||
if let Some(by_state) = sorted_meshes.get_mut(&key) {
|
||||
by_state.push(*instance);
|
||||
} else {
|
||||
sorted_meshes.insert(key, vec![*instance]);
|
||||
}
|
||||
}
|
||||
|
||||
let mut transforms = Vec::new();
|
||||
let mut draws = Vec::new();
|
||||
|
||||
for ((group_id, material_id), mut meshes) in sorted_meshes.drain() {
|
||||
let mut sorted_subs = HashMap::<usize, Vec<MeshInstance>>::new();
|
||||
for mesh in meshes.drain(..) {
|
||||
let sub_id = mesh.mesh.sub_id;
|
||||
if let Some(by_sub) = sorted_subs.get_mut(&sub_id) {
|
||||
by_sub.push(mesh);
|
||||
} else {
|
||||
sorted_subs.insert(sub_id, vec![mesh]);
|
||||
}
|
||||
}
|
||||
|
||||
for (sub_id, meshes) in sorted_subs.drain() {
|
||||
let start_idx = transforms.len() as u32;
|
||||
let as_arrays = meshes.iter().map(|i| i.transform.to_cols_array());
|
||||
transforms.extend(as_arrays);
|
||||
let end_idx = transforms.len() as u32;
|
||||
let instance_range = start_idx..end_idx;
|
||||
draws.push(DrawState {
|
||||
group_id,
|
||||
material_id,
|
||||
sub_id,
|
||||
instance_range,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Self { transforms, draws }
|
||||
}
|
||||
|
||||
pub fn get_storage(&self) -> &[u8] {
|
||||
bytemuck::cast_slice(&self.transforms)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> CommandIterator {
|
||||
CommandIterator::new(self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub struct MeshHandle {
|
||||
pub group_id: usize,
|
||||
pub sub_id: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub struct TextureHandle {
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub struct MaterialHandle {
|
||||
pub id: usize,
|
||||
}
|
162
src/legion.rs
162
src/legion.rs
|
@ -1,162 +0,0 @@
|
|||
//! [Legion](https://github.com/amethyst/legion) integration.
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::pass::{self, debug, mesh};
|
||||
use crate::scene;
|
||||
use crate::viewport::{Viewport, ViewportInfo};
|
||||
use crate::Renderer;
|
||||
use legion::systems::Builder;
|
||||
use legion::world::SubWorld;
|
||||
use legion::*;
|
||||
use rayon::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Callbacks trait for acquiring renderer input dynamically.
|
||||
pub trait RenderCallbacks: 'static + Send + Sync {
|
||||
fn get_viewports(&mut self) -> Vec<(&dyn Viewport, Camera)>;
|
||||
fn present(&mut self);
|
||||
}
|
||||
|
||||
/// Initializes the Cyborg renderer within a Legion world.
|
||||
///
|
||||
/// Uses the provided [Builder] to run all rendering code.
|
||||
///
|
||||
/// Required pre-initialized resources:
|
||||
/// - [Renderer]
|
||||
/// - [ViewportInfo] (TODO: dynamic viewport targeting)
|
||||
/// - [mesh::ShaderInfo]
|
||||
pub fn build_renderer<T: RenderCallbacks>(
|
||||
callbacks: T,
|
||||
resources: &mut Resources,
|
||||
builder: &mut Builder,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
let renderer = resources.get_mut::<Renderer>().unwrap();
|
||||
let viewport_info = resources.get::<ViewportInfo>().unwrap();
|
||||
let mesh_shaders = resources.get::<mesh::ShaderInfo>().unwrap();
|
||||
|
||||
let device = renderer.get_device();
|
||||
let layouts = renderer.get_layouts();
|
||||
|
||||
let mesh_pass = mesh::MeshPass::new(
|
||||
device.to_owned(),
|
||||
layouts.to_owned(),
|
||||
viewport_info.to_owned(),
|
||||
mesh_shaders.to_owned(),
|
||||
);
|
||||
|
||||
let debug_pass = debug::DebugPass::new(
|
||||
device.to_owned(),
|
||||
layouts.to_owned(),
|
||||
viewport_info.to_owned(),
|
||||
);
|
||||
|
||||
let frames_in_flight = renderer.get_frames_in_flight();
|
||||
let mesh_pass = pass::RenderPassBox::new(Arc::new(mesh_pass), frames_in_flight);
|
||||
let debug_pass = pass::RenderPassBox::new(Arc::new(debug_pass), frames_in_flight);
|
||||
|
||||
// drop borrowed resources so that new ones can be inserted
|
||||
drop(renderer);
|
||||
drop(viewport_info);
|
||||
drop(mesh_shaders);
|
||||
|
||||
resources.insert(mesh_pass);
|
||||
resources.insert(debug_pass);
|
||||
|
||||
resources.insert(callbacks);
|
||||
|
||||
builder.add_system(draw_transformed_meshes_system());
|
||||
builder.add_system(draw_debug_system());
|
||||
builder.add_system(draw_debug_transformed_system());
|
||||
builder.add_system(render_system::<T>());
|
||||
builder.add_system(present_system::<T>());
|
||||
}
|
||||
|
||||
#[system]
|
||||
fn render<T: RenderCallbacks>(
|
||||
#[resource] callbacks: &mut T,
|
||||
#[resource] renderer: &mut Renderer,
|
||||
#[resource] mesh_pass: &mut pass::RenderPassBox<mesh::MeshPass>,
|
||||
#[resource] debug_pass: &mut pass::RenderPassBox<debug::DebugPass>,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
let viewports = {
|
||||
puffin::profile_scope!("RenderCallbacks::get_viewports()");
|
||||
callbacks.get_viewports()
|
||||
};
|
||||
|
||||
if viewports.len() > 1 {
|
||||
eprintln!("Cyborg does not currently support more than one viewport at a time!");
|
||||
return;
|
||||
} else if let Some((viewport, camera)) = viewports.get(0) {
|
||||
let mut passes: Vec<&mut dyn pass::RenderPassBoxTrait> = Vec::new();
|
||||
passes.push(mesh_pass);
|
||||
passes.push(debug_pass);
|
||||
renderer.render(passes.as_mut_slice(), *viewport, camera);
|
||||
}
|
||||
}
|
||||
|
||||
#[system]
|
||||
fn present<T: RenderCallbacks>(#[resource] callbacks: &mut T) {
|
||||
puffin::profile_function!();
|
||||
|
||||
callbacks.present();
|
||||
}
|
||||
|
||||
#[system]
|
||||
fn draw_transformed_meshes(
|
||||
#[resource] mesh_pass: &pass::RenderPassBox<mesh::MeshPass>,
|
||||
world: &SubWorld,
|
||||
query: &mut Query<(&scene::Transform, &scene::Mesh)>,
|
||||
) {
|
||||
query.par_iter_chunks(world).for_each(|mesh_chunk| {
|
||||
puffin::profile_function!();
|
||||
|
||||
let meshes: Vec<_> = mesh_chunk
|
||||
.into_iter()
|
||||
.map(|(transform, mesh)| mesh::TransformedMesh {
|
||||
transform: transform.transform,
|
||||
mesh: mesh.mesh,
|
||||
})
|
||||
.collect();
|
||||
|
||||
mesh_pass.add_transformed_meshes(&meshes);
|
||||
});
|
||||
}
|
||||
|
||||
#[system(for_each)]
|
||||
#[filter(!component::<scene::Transform>())]
|
||||
fn draw_debug(
|
||||
#[resource] debug_pass: &pass::RenderPassBox<debug::DebugPass>,
|
||||
draw_list: &scene::DebugDrawList,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
debug_pass.add_draw_list(draw_list);
|
||||
}
|
||||
|
||||
#[system]
|
||||
fn draw_debug_transformed(
|
||||
#[resource] debug_pass: &pass::RenderPassBox<debug::DebugPass>,
|
||||
world: &SubWorld,
|
||||
query: &mut Query<(&scene::DebugDrawList, &scene::Transform)>,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
query.par_iter_chunks(world).for_each(|draw_list_chunk| {
|
||||
let mut sub_list = scene::DebugDrawList::default();
|
||||
for (draw_list, transform) in draw_list_chunk {
|
||||
let vertices_start = sub_list.vertices.len();
|
||||
sub_list.merge(draw_list);
|
||||
for vertex in sub_list.vertices[vertices_start..].iter_mut() {
|
||||
let position = glam::Vec3::from_slice(&vertex.position);
|
||||
let transformed = transform.transform.transform_point3(position);
|
||||
vertex.position = transformed.to_array();
|
||||
}
|
||||
}
|
||||
|
||||
debug_pass.add_draw_list(&sub_list);
|
||||
});
|
||||
}
|
298
src/lib.rs
298
src/lib.rs
|
@ -1,296 +1,2 @@
|
|||
//! Cyborg is a high-performance, modern, experimental rendering engine written
|
||||
//! in Rust.
|
||||
//!
|
||||
//! # Features
|
||||
//!
|
||||
//! - `legion`: Enables [Legion](https://github.com/amethyst/legion) integration
|
||||
//! for Cyborg. Disabled by default.
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use rayon::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod camera;
|
||||
pub mod pass;
|
||||
pub mod phase;
|
||||
pub mod scene;
|
||||
pub mod shader;
|
||||
pub mod staging;
|
||||
pub mod storage;
|
||||
pub mod viewport;
|
||||
|
||||
#[cfg(feature = "legion")]
|
||||
pub mod legion;
|
||||
|
||||
use camera::Camera;
|
||||
use pass::*;
|
||||
use phase::*;
|
||||
use viewport::Viewport;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct ViewportUniform {
|
||||
pub eye: [f32; 4],
|
||||
pub vp: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl ViewportUniform {
|
||||
pub fn from_camera(camera: &Camera) -> Self {
|
||||
Self {
|
||||
eye: camera.eye,
|
||||
vp: camera.vp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewportUniform {
|
||||
pub fn binding_size() -> wgpu::BufferSize {
|
||||
let size = std::mem::size_of::<Self>() as u64;
|
||||
size.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderLayouts {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub bind_viewport: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
impl RenderLayouts {
|
||||
pub fn new_arc(device: Arc<wgpu::Device>) -> Arc<Self> {
|
||||
let bind_viewport = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("viewport"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(ViewportUniform::binding_size()),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
Arc::new(Self {
|
||||
device,
|
||||
bind_viewport,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrameData {
|
||||
pub frame_index: usize,
|
||||
pub bind_viewport: wgpu::BindGroup,
|
||||
pub viewport_uniform: wgpu::Buffer,
|
||||
}
|
||||
|
||||
impl FrameData {
|
||||
pub fn new(frame_index: usize, layouts: &RenderLayouts) -> Self {
|
||||
let viewport_uniform = layouts.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("viewport"),
|
||||
size: ViewportUniform::binding_size().into(),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bind_viewport = layouts
|
||||
.device
|
||||
.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("viewport"),
|
||||
layout: &layouts.bind_viewport,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &viewport_uniform,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
}],
|
||||
});
|
||||
|
||||
Self {
|
||||
frame_index,
|
||||
bind_viewport,
|
||||
viewport_uniform,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_phase_data<'a>(
|
||||
&'a self,
|
||||
phase: Phase,
|
||||
viewport_data: &'a ViewportData,
|
||||
) -> IndexedPhaseData<'a> {
|
||||
IndexedPhaseData {
|
||||
phase,
|
||||
frame_data: self.frame_index,
|
||||
viewport_data,
|
||||
bind_viewport: &self.bind_viewport,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Renderer {
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
layouts: Arc<RenderLayouts>,
|
||||
frames_in_flight: usize,
|
||||
frame_datas: Vec<FrameData>,
|
||||
frame_index: usize,
|
||||
render_passes: Vec<Box<dyn RenderPassBoxTrait>>,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
|
||||
let layouts = RenderLayouts::new_arc(device.clone());
|
||||
|
||||
let frames_in_flight = 1;
|
||||
|
||||
let mut frame_datas = Vec::with_capacity(frames_in_flight);
|
||||
for frame_index in 0..frames_in_flight {
|
||||
frame_datas.push(FrameData::new(frame_index, &layouts));
|
||||
}
|
||||
|
||||
Self {
|
||||
device,
|
||||
layouts,
|
||||
queue,
|
||||
frames_in_flight,
|
||||
frame_datas,
|
||||
frame_index: 0,
|
||||
render_passes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_device(&self) -> &Arc<wgpu::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
pub fn get_layouts(&self) -> &Arc<RenderLayouts> {
|
||||
&self.layouts
|
||||
}
|
||||
|
||||
pub fn get_frames_in_flight(&self) -> usize {
|
||||
self.frames_in_flight
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&mut self,
|
||||
passes: &mut [&mut dyn RenderPassBoxTrait],
|
||||
target: &dyn Viewport,
|
||||
camera: &Camera,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
self.frame_index += 1;
|
||||
if self.frame_index >= self.frame_datas.len() {
|
||||
self.frame_index = 0;
|
||||
}
|
||||
|
||||
let frame_index = self.frame_index;
|
||||
let frame_data = &self.frame_datas[frame_index];
|
||||
|
||||
let viewport_uniform = ViewportUniform::from_camera(camera);
|
||||
self.queue.write_buffer(
|
||||
&frame_data.viewport_uniform,
|
||||
0,
|
||||
bytemuck::cast_slice(&[viewport_uniform]),
|
||||
);
|
||||
|
||||
let phase_passes = multimap::MultiMap::<Phase, usize>::new();
|
||||
let phase_passes = std::sync::Mutex::new(phase_passes);
|
||||
|
||||
passes
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(pass_index, rp)| {
|
||||
let mut phases_buf = Vec::new();
|
||||
phases_buf.clear();
|
||||
rp.begin_frame(frame_index, &mut phases_buf, &self.queue);
|
||||
|
||||
let mut passes = phase_passes.lock().unwrap();
|
||||
for phase in phases_buf.into_iter() {
|
||||
passes.insert(phase, pass_index);
|
||||
}
|
||||
});
|
||||
|
||||
let phase_passes = phase_passes.into_inner().unwrap();
|
||||
let viewport = ViewportData;
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
if let Some(upload) = phase_passes.get_vec(&Phase::Upload) {
|
||||
upload.iter().for_each(|pass_index| {
|
||||
let phase_data = frame_data.make_phase_data(Phase::Upload, &viewport);
|
||||
let pass = &passes[*pass_index];
|
||||
pass.record_commands(phase_data, &mut encoder);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(skinning) = phase_passes.get_vec(&Phase::Skinning) {
|
||||
let mut cmds = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some("Skinning Phase"),
|
||||
});
|
||||
|
||||
skinning.iter().for_each(|pass_index| {
|
||||
let phase_data = frame_data.make_phase_data(Phase::Skinning, &viewport);
|
||||
let pass = &passes[*pass_index];
|
||||
pass.record_compute(phase_data, &mut cmds);
|
||||
})
|
||||
}
|
||||
|
||||
let record_render = |phase| {
|
||||
let cmds = Mutex::new(Vec::new());
|
||||
if let Some(phase_passes) = phase_passes.get_vec(&phase) {
|
||||
phase_passes.par_iter().for_each(|pass_index| {
|
||||
let phase_data = frame_data.make_phase_data(phase, &viewport);
|
||||
let pass = &passes[*pass_index];
|
||||
|
||||
if let Some(cmd) = pass.record_render(phase_data) {
|
||||
cmds.lock().push(cmd);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
cmds.into_inner()
|
||||
};
|
||||
|
||||
// TODO: parallelize each phase's record_render
|
||||
let depth_cmds = record_render(Phase::Depth);
|
||||
let opaque_cmds = record_render(Phase::Opaque);
|
||||
let overlay_cmds = record_render(Phase::Overlay);
|
||||
|
||||
{
|
||||
let target_views = target.get_views();
|
||||
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target_views.output,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: target_views.depth,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
|
||||
rp.execute_bundles(depth_cmds.iter());
|
||||
rp.execute_bundles(opaque_cmds.iter());
|
||||
rp.execute_bundles(overlay_cmds.iter());
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
pub mod logger;
|
||||
pub mod shader;
|
|
@ -0,0 +1,19 @@
|
|||
use std::time::{Instant};
|
||||
|
||||
pub struct Timer<'a> {
|
||||
name: &'a str,
|
||||
start_time: Instant
|
||||
}
|
||||
|
||||
impl<'a> Timer<'a> {
|
||||
pub fn start(name: &'a str) -> Timer {
|
||||
Timer {
|
||||
name: name,
|
||||
start_time: Instant::now()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_status(&self) {
|
||||
println!("{}: {}ms", self.name, Instant::now().duration_since(self.start_time).as_nanos() as f32 / 1_000_000.0)
|
||||
}
|
||||
}
|
645
src/main.rs
645
src/main.rs
|
@ -1,457 +1,338 @@
|
|||
use cyborg::camera::{Camera, Flycam};
|
||||
use cyborg::pass::{mesh, RenderPassBox};
|
||||
use cyborg::shader;
|
||||
use cyborg::storage::mesh::*;
|
||||
use cyborg::viewport::*;
|
||||
use cyborg::Renderer;
|
||||
use legion::*;
|
||||
use rand::prelude::*;
|
||||
use std::sync::Arc;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
struct DeltaTime {
|
||||
pub dt: f32,
|
||||
last_update: std::time::Instant,
|
||||
mod camera;
|
||||
mod commands;
|
||||
mod handle;
|
||||
mod mesh;
|
||||
mod model;
|
||||
mod pool;
|
||||
mod procgen;
|
||||
mod renderer;
|
||||
mod scene;
|
||||
mod shader;
|
||||
mod texture;
|
||||
|
||||
use camera::*;
|
||||
use model::*;
|
||||
use renderer::Renderer;
|
||||
use scene::*;
|
||||
use texture::*;
|
||||
|
||||
trait WorldState {
|
||||
fn update(&mut self);
|
||||
fn render(&self) -> Vec<MeshInstance>;
|
||||
}
|
||||
|
||||
impl Default for DeltaTime {
|
||||
fn default() -> Self {
|
||||
DeltaTime {
|
||||
dt: 0.0,
|
||||
last_update: std::time::Instant::now(),
|
||||
struct Grid {
|
||||
meshes: Vec<MeshInstance>,
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
fn new(ren: &mut Renderer) -> Self {
|
||||
let model = GltfModel::load(ren);
|
||||
let mut meshes = Vec::new();
|
||||
for x in -5..5 {
|
||||
for y in -5..5 {
|
||||
let translation = glam::Vec3::new(x as f32, 0.0, y as f32) * 3.0;
|
||||
let transform = glam::Mat4::from_translation(translation);
|
||||
model.draw(&mut meshes, transform);
|
||||
}
|
||||
}
|
||||
|
||||
Self { meshes }
|
||||
}
|
||||
}
|
||||
|
||||
struct SpawnDistributions {
|
||||
theta: rand::distributions::Uniform<f32>,
|
||||
radius: rand::distributions::Uniform<f32>,
|
||||
up: rand::distributions::Uniform<f32>,
|
||||
}
|
||||
impl WorldState for Grid {
|
||||
fn update(&mut self) {}
|
||||
|
||||
impl Default for SpawnDistributions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theta: rand::distributions::Uniform::new(0.0, std::f32::consts::TAU),
|
||||
radius: rand::distributions::Uniform::new(3.0, 4.0),
|
||||
up: rand::distributions::Uniform::new(8.0, 12.0),
|
||||
}
|
||||
fn render(&self) -> Vec<MeshInstance> {
|
||||
self.meshes.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct Particle {
|
||||
position: glam::Vec3A,
|
||||
mass: f32,
|
||||
struct Metaballs {
|
||||
mesh: MeshInstance,
|
||||
}
|
||||
|
||||
struct Velocity {
|
||||
linear: glam::Vec3A,
|
||||
}
|
||||
impl Metaballs {
|
||||
fn new(mut ren: &mut Renderer) -> Self {
|
||||
use procgen::marching_cubes::*;
|
||||
|
||||
#[system]
|
||||
fn update_dt(#[resource] dt: &mut DeltaTime) {
|
||||
dt.dt = dt.last_update.elapsed().as_secs_f32();
|
||||
dt.last_update = std::time::Instant::now();
|
||||
}
|
||||
let metaballs = vec![
|
||||
glam::Vec3A::new(-5., -5., 2.),
|
||||
glam::Vec3A::new(8., 0.0, -1.),
|
||||
glam::Vec3A::new(1., 5., -3.),
|
||||
];
|
||||
|
||||
#[system]
|
||||
fn update_app(#[resource] app: &mut Application) {
|
||||
app.update();
|
||||
}
|
||||
let field = |x: i32, y: i32, z: i32| {
|
||||
let c = glam::Vec3A::new(x as f32, y as f32, z as f32);
|
||||
let mut sum = 0.0;
|
||||
for ball in metaballs.iter() {
|
||||
sum += 1.0 / ball.distance(c);
|
||||
}
|
||||
|
||||
#[system(for_each)]
|
||||
fn apply_gravity(#[resource] dt: &DeltaTime, velocity: &mut Velocity) {
|
||||
const GRAVITY: f32 = 6.0;
|
||||
velocity.linear.y -= GRAVITY * dt.dt;
|
||||
}
|
||||
sum - 0.4
|
||||
};
|
||||
|
||||
#[system(for_each)]
|
||||
fn move_particles(#[resource] dt: &DeltaTime, particle: &mut Particle, velocity: &Velocity) {
|
||||
particle.position += velocity.linear * (dt.dt / particle.mass);
|
||||
// TODO angular velocity
|
||||
}
|
||||
let r = 20;
|
||||
let domain = MarchDomain {
|
||||
min: Vec3::new(-r, -r, -r),
|
||||
max: Vec3::new(r, r, r),
|
||||
};
|
||||
|
||||
#[system(for_each)]
|
||||
fn respawn_particles(
|
||||
#[resource] distributions: &SpawnDistributions,
|
||||
particle: &mut Particle,
|
||||
velocity: &mut Velocity,
|
||||
) {
|
||||
if particle.position.y < -10.0 {
|
||||
particle.position = glam::Vec3A::ZERO;
|
||||
let rng = &mut rand::thread_rng();
|
||||
velocity.linear.y = distributions.up.sample(rng);
|
||||
let vertices = marching_cubes(field, &domain);
|
||||
let vertices: Vec<mesh::Vertex> = vertices
|
||||
.iter()
|
||||
.map(|v| mesh::Vertex {
|
||||
position: v.position.into(),
|
||||
normal: v.normal.into(),
|
||||
tex_coords: [0.0; 2],
|
||||
})
|
||||
.collect();
|
||||
|
||||
let theta = distributions.theta.sample(rng);
|
||||
let radius = distributions.radius.sample(rng);
|
||||
velocity.linear.x = theta.sin() * radius;
|
||||
velocity.linear.z = theta.cos() * radius;
|
||||
let indices = (0..(vertices.len() as u32)).collect();
|
||||
|
||||
let mesh_data = mesh::MeshData {
|
||||
vertices, indices
|
||||
};
|
||||
|
||||
let zeroed_data = pool::TextureData {
|
||||
width: 8,
|
||||
height: 8,
|
||||
data: vec![0x00; 256],
|
||||
};
|
||||
|
||||
let albedo_raw = include_bytes!("assets/brick_moss_001_diff_1k.jpg");
|
||||
let albedo_data = load_texture_data(albedo_raw);
|
||||
|
||||
let metalrough_raw = include_bytes!("assets/brick_moss_001_metalrough_1k.jpg");
|
||||
let metalrough_data = load_texture_data(metalrough_raw);
|
||||
|
||||
let mesh = ren.load_mesh(&mesh_data);
|
||||
let albedo = ren.load_texture(&albedo_data);
|
||||
let metalrough = ren.load_texture(&metalrough_data);
|
||||
let material = ren.load_material(&pool::MaterialData { albedo, metallic_roughness: metalrough });
|
||||
|
||||
let mesh = scene::MeshInstance {
|
||||
mesh, material, transform: glam::Mat4::IDENTITY,
|
||||
};
|
||||
|
||||
Self { mesh }
|
||||
}
|
||||
}
|
||||
|
||||
#[system(for_each)]
|
||||
fn update_transforms(transform: &mut cyborg::scene::Transform, particle: &Particle) {
|
||||
transform.transform = glam::Mat4::from_translation(particle.position.into());
|
||||
impl WorldState for Metaballs {
|
||||
fn update(&mut self) {}
|
||||
|
||||
fn render(&self) -> Vec<MeshInstance> {
|
||||
vec![self.mesh]
|
||||
}
|
||||
}
|
||||
|
||||
#[system(for_each)]
|
||||
fn update_debug(draw_list: &mut cyborg::scene::DebugDrawList, velocity: &Velocity) {
|
||||
draw_list.clear();
|
||||
draw_list.indices.extend_from_slice(&[0, 1]);
|
||||
|
||||
let color = [1.0, 1.0, 1.0];
|
||||
|
||||
draw_list.vertices.push(cyborg::scene::DebugVertex {
|
||||
position: [0.0, 0.0, 0.0],
|
||||
color,
|
||||
});
|
||||
|
||||
draw_list.vertices.push(cyborg::scene::DebugVertex {
|
||||
position: velocity.linear.to_array(),
|
||||
color,
|
||||
});
|
||||
struct Planet {
|
||||
speed: f32,
|
||||
offset: f32,
|
||||
radius: f32,
|
||||
size: f32,
|
||||
}
|
||||
|
||||
fn build_update_schedule() -> Schedule {
|
||||
let mut builder = Schedule::builder();
|
||||
builder.add_system(update_dt_system());
|
||||
builder.add_system(update_app_system());
|
||||
builder.add_system(apply_gravity_system());
|
||||
builder.add_system(move_particles_system());
|
||||
builder.add_system(respawn_particles_system());
|
||||
builder.add_system(update_transforms_system());
|
||||
builder.add_system(update_debug_system());
|
||||
builder.build()
|
||||
struct Planets {
|
||||
start: std::time::Instant,
|
||||
planets: Vec<Planet>,
|
||||
model: GltfModel,
|
||||
}
|
||||
|
||||
fn make_terrain(attributes: &mesh::Attributes) -> MeshBuffer {
|
||||
const MOUNTAINS_RADIUS: f32 = 25.0;
|
||||
const MOUNTAINS_WIDTH: f32 = 10.0;
|
||||
const MOUNTAINS_HEIGHT: f32 = 5.0;
|
||||
const MOUNTAINS_FREQUENCY: f32 = 1.0;
|
||||
impl Planets {
|
||||
fn new(ren: &mut Renderer) -> Self {
|
||||
let start = std::time::Instant::now();
|
||||
let model = GltfModel::load(ren);
|
||||
|
||||
use noise::NoiseFn;
|
||||
let noise = noise::OpenSimplex::new();
|
||||
|
||||
let sample = |xy: glam::Vec2| -> f32 {
|
||||
let mountains_dist = (xy.length() - MOUNTAINS_RADIUS).abs() / MOUNTAINS_WIDTH;
|
||||
let mountains_scale = 1.0 - mountains_dist;
|
||||
if mountains_scale > 0.0 {
|
||||
let noise_input = (xy * MOUNTAINS_FREQUENCY).as_dvec2();
|
||||
let sampled = noise.get(noise_input.to_array()) as f32;
|
||||
((sampled + 0.5) / 0.5) * MOUNTAINS_HEIGHT * mountains_scale
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
const GRID_FREQUENCY: f32 = 0.2;
|
||||
const GRID_RADIUS: isize = ((MOUNTAINS_RADIUS + MOUNTAINS_WIDTH) / GRID_FREQUENCY) as isize;
|
||||
const GRID_WIDTH: isize = GRID_RADIUS * 2 + 1;
|
||||
|
||||
let tile_num = (GRID_RADIUS * GRID_RADIUS) as usize;
|
||||
let mut vertices = Vec::with_capacity(tile_num);
|
||||
let mut indices = Vec::with_capacity(tile_num * 6);
|
||||
|
||||
for x in -GRID_RADIUS..=GRID_RADIUS {
|
||||
for z in -GRID_RADIUS..=GRID_RADIUS {
|
||||
let x = x as f32 * GRID_FREQUENCY;
|
||||
let z = z as f32 * GRID_FREQUENCY;
|
||||
let y = sample(glam::Vec2::new(x, z));
|
||||
|
||||
vertices.push(mesh::Vertex {
|
||||
position: [x, y, z],
|
||||
tan_frame: 0,
|
||||
let mut planets = Vec::new();
|
||||
for i in 0..10 {
|
||||
let i = i as f32;
|
||||
planets.push(Planet {
|
||||
speed: 1.618 * 1.5 / i,
|
||||
offset: 0.0,
|
||||
radius: i * 2.0,
|
||||
size: 0.5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut cursor: mesh::Index = 0;
|
||||
for _x in -GRID_RADIUS..GRID_RADIUS {
|
||||
for _z in -GRID_RADIUS..GRID_RADIUS {
|
||||
let next_line = cursor + GRID_WIDTH as mesh::Index;
|
||||
indices.extend_from_slice(&[
|
||||
cursor,
|
||||
next_line,
|
||||
cursor + 1,
|
||||
next_line,
|
||||
cursor + 1,
|
||||
next_line + 1,
|
||||
]);
|
||||
cursor += 1;
|
||||
Self {
|
||||
start,
|
||||
planets,
|
||||
model,
|
||||
}
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let vertices = AttrBuffer {
|
||||
id: attributes.vertex,
|
||||
count: vertices.len(),
|
||||
data: bytemuck::cast_slice(&vertices).to_vec(),
|
||||
impl WorldState for Planets {
|
||||
fn update(&mut self) {}
|
||||
|
||||
fn render(&self) -> Vec<MeshInstance> {
|
||||
let elapsed = self.start.elapsed().as_secs_f32();
|
||||
|
||||
let mut meshes = Vec::new();
|
||||
for planet in self.planets.iter() {
|
||||
let translation = glam::Vec3::new(0.0, 0.0, planet.radius);
|
||||
let translation = glam::Mat4::from_translation(translation);
|
||||
let theta = planet.speed * elapsed + planet.offset;
|
||||
let rotation = glam::Mat4::from_rotation_y(theta);
|
||||
self.model.draw(&mut meshes, rotation * translation);
|
||||
}
|
||||
|
||||
meshes
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_window_renderer(window: &winit::window::Window) -> Renderer {
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let surface = unsafe { instance.create_surface(window) };
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::LowPower,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
label: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface.get_preferred_format(&adapter).unwrap(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let indices = AttrBuffer {
|
||||
id: attributes.index,
|
||||
count: indices.len(),
|
||||
data: bytemuck::cast_slice(&indices).to_vec(),
|
||||
};
|
||||
|
||||
let mut mesh = MeshBuffer::default();
|
||||
mesh.attributes.push(vertices);
|
||||
mesh.attributes.push(indices);
|
||||
mesh
|
||||
}
|
||||
|
||||
struct Application {
|
||||
window: winit::window::Window,
|
||||
viewport: WinitViewport,
|
||||
flycam: Flycam,
|
||||
is_grabbed: bool,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub fn update(&mut self) {
|
||||
self.flycam.update();
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
||||
pub fn on_mouse_motion(&mut self, x: f64, y: f64) {
|
||||
if self.is_grabbed {
|
||||
self.flycam.process_mouse(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_mouse_event(&mut self, button: MouseButton, state: ElementState) {
|
||||
if button == MouseButton::Left && state == ElementState::Pressed && !self.is_grabbed {
|
||||
self.window.set_cursor_grab(true).unwrap();
|
||||
self.window.set_cursor_visible(false);
|
||||
self.is_grabbed = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_key_event(&mut self, key: VirtualKeyCode, state: ElementState) {
|
||||
if key == VirtualKeyCode::Escape && state == ElementState::Pressed {
|
||||
if self.is_grabbed {
|
||||
self.window.set_cursor_grab(false).unwrap();
|
||||
self.window.set_cursor_visible(true);
|
||||
self.is_grabbed = false;
|
||||
}
|
||||
} else {
|
||||
self.flycam.process_keyboard(key, state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
|
||||
self.viewport.resize(new_size);
|
||||
self.flycam.resize(new_size.width, new_size.height);
|
||||
}
|
||||
|
||||
pub fn on_lost_focus(&mut self) {
|
||||
self.flycam.defocus();
|
||||
}
|
||||
|
||||
pub fn on_surface_lost(&mut self) {
|
||||
self.viewport.resize(self.viewport.size);
|
||||
}
|
||||
}
|
||||
|
||||
impl cyborg::legion::RenderCallbacks for Application {
|
||||
fn get_viewports(&mut self) -> Vec<(&dyn Viewport, Camera)> {
|
||||
vec![(&self.viewport as &dyn Viewport, self.flycam.get_camera())]
|
||||
}
|
||||
|
||||
fn present(&mut self) {
|
||||
self.viewport.present();
|
||||
}
|
||||
Renderer::new(size, surface, device, queue, config)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let viewport = pollster::block_on(WinitViewport::from_window(&window));
|
||||
let mut camera = Flycam::new(10.0, 0.002);
|
||||
let mut is_grabbed = false;
|
||||
let mut ren = pollster::block_on(make_window_renderer(&window));
|
||||
// let mut state: Box<dyn WorldState> = Box::new(Planets::new(&mut ren));
|
||||
// let mut state: Box<dyn WorldState> = Box::new(Grid::new(&mut ren));
|
||||
let mut state: Box<dyn WorldState> = Box::new(Metaballs::new(&mut ren));
|
||||
|
||||
let mut world = World::new(Default::default());
|
||||
let mut resources = Resources::default();
|
||||
|
||||
resources.insert::<DeltaTime>(Default::default());
|
||||
resources.insert::<SpawnDistributions>(Default::default());
|
||||
let mut update_schedule = build_update_schedule();
|
||||
|
||||
let renderer = Renderer::new(viewport.device.clone(), viewport.queue.clone());
|
||||
resources.insert(renderer);
|
||||
|
||||
resources.insert(viewport.get_info().to_owned());
|
||||
|
||||
let flycam = cyborg::camera::Flycam::new(0.002, 10.0, 0.25);
|
||||
|
||||
let shader_store = Arc::new(shader::ShaderStore::new(viewport.device.clone()));
|
||||
let shaders_dir = std::env::current_dir().unwrap();
|
||||
let shaders_dir = shaders_dir.join("shaders/");
|
||||
let shader_watcher = shader::ShaderWatcher::new(shader_store.to_owned(), shaders_dir).unwrap();
|
||||
|
||||
let mesh_forward = shader_watcher.add_file("mesh_forward.wgsl").unwrap();
|
||||
let mesh_skinning = shader_watcher.add_file("mesh_skinning.wgsl").unwrap();
|
||||
|
||||
let mesh_shaders = mesh::ShaderInfo {
|
||||
store: shader_store.clone(),
|
||||
forward: mesh_forward,
|
||||
skinning: mesh_skinning,
|
||||
};
|
||||
|
||||
resources.insert(mesh_shaders);
|
||||
|
||||
let application = Application {
|
||||
window,
|
||||
viewport,
|
||||
flycam,
|
||||
is_grabbed: false,
|
||||
};
|
||||
|
||||
let mut render_schedule = Schedule::builder();
|
||||
cyborg::legion::build_renderer(application, &mut resources, &mut render_schedule);
|
||||
let mut render_schedule = render_schedule.build();
|
||||
|
||||
let example_vertices = vec![
|
||||
mesh::Vertex {
|
||||
position: [-0.5, 0.0, 0.5],
|
||||
tan_frame: 0,
|
||||
let lights = vec![
|
||||
PointLight {
|
||||
center: glam::Vec3A::new(0.0, 5.0, 0.0),
|
||||
intensity: glam::Vec3A::new(100.0, 100.0, 100.0),
|
||||
},
|
||||
mesh::Vertex {
|
||||
position: [0.5, 0.0, 0.5],
|
||||
tan_frame: 0,
|
||||
PointLight {
|
||||
center: glam::Vec3A::new(-7.0, 5.0, 7.0),
|
||||
intensity: glam::Vec3A::new(100.0, 0.0, 0.0),
|
||||
},
|
||||
mesh::Vertex {
|
||||
position: [0.0, -0.5, -0.5],
|
||||
tan_frame: 0,
|
||||
PointLight {
|
||||
center: glam::Vec3A::new(7.0, 5.0, 7.0),
|
||||
intensity: glam::Vec3A::new(0.0, 100.0, 0.0),
|
||||
},
|
||||
mesh::Vertex {
|
||||
position: [0.0, 0.5, -0.5],
|
||||
tan_frame: 0,
|
||||
PointLight {
|
||||
center: glam::Vec3A::new(0.0, 5.0, -7.0),
|
||||
intensity: glam::Vec3A::new(0.0, 0.0, 100.0),
|
||||
},
|
||||
PointLight {
|
||||
center: glam::Vec3A::new(0.0, 50.0, 0.0),
|
||||
intensity: glam::Vec3A::new(1000.0, 1000.0, 1000.0),
|
||||
},
|
||||
];
|
||||
|
||||
let mesh_pass = resources.get::<RenderPassBox<mesh::MeshPass>>().unwrap();
|
||||
let attributes = mesh_pass.get_attributes();
|
||||
|
||||
let example_vertices = AttrBuffer {
|
||||
id: attributes.vertex,
|
||||
count: example_vertices.len(),
|
||||
data: bytemuck::cast_slice(&example_vertices).to_vec(),
|
||||
};
|
||||
|
||||
let example_indices: Vec<mesh::Index> = vec![0, 1, 2, 1, 2, 3, 2, 3, 0, 0, 1, 3];
|
||||
let example_indices = AttrBuffer {
|
||||
id: attributes.index,
|
||||
count: example_indices.len(),
|
||||
data: bytemuck::cast_slice(&example_indices).to_vec(),
|
||||
};
|
||||
|
||||
let mut example_mesh = MeshBuffer::default();
|
||||
example_mesh.attributes.push(example_vertices);
|
||||
example_mesh.attributes.push(example_indices);
|
||||
let example_mesh = mesh_pass.get_mesh_pool().load(example_mesh).unwrap();
|
||||
|
||||
world.push((
|
||||
cyborg::scene::Mesh {
|
||||
mesh: mesh_pass
|
||||
.get_mesh_pool()
|
||||
.load(make_terrain(attributes))
|
||||
.unwrap(),
|
||||
},
|
||||
cyborg::scene::Transform {
|
||||
transform: glam::Mat4::from_translation(glam::Vec3::new(0.0, -10.0, 0.0)),
|
||||
},
|
||||
));
|
||||
|
||||
drop(mesh_pass);
|
||||
|
||||
let r = 6;
|
||||
for x in -r..r {
|
||||
for y in -r..r {
|
||||
for z in -r..r {
|
||||
let translation = glam::Vec3::new(x as f32, y as f32, z as f32);
|
||||
let transform = glam::Mat4::from_translation(translation);
|
||||
world.push((
|
||||
cyborg::scene::Mesh {
|
||||
mesh: example_mesh.clone(),
|
||||
},
|
||||
cyborg::scene::Transform { transform },
|
||||
cyborg::scene::DebugDrawList::default(),
|
||||
Particle {
|
||||
position: translation.into(),
|
||||
mass: 1.0,
|
||||
},
|
||||
Velocity {
|
||||
linear: glam::Vec3A::ZERO,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::RedrawRequested(_) => {
|
||||
let mut application = resources.get_mut::<Application>().unwrap();
|
||||
match application.viewport.acquire() {
|
||||
Err(wgpu::SurfaceError::Lost) => application.on_surface_lost(),
|
||||
let scene = Scene {
|
||||
meshes: &state.render(),
|
||||
point_lights: &lights,
|
||||
};
|
||||
match ren.render(&camera, &scene) {
|
||||
Ok(_) => {}
|
||||
Err(wgpu::SurfaceError::Lost) => ren.resize(ren.size),
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||
Err(e) => eprintln!("error: {:?}", e),
|
||||
Ok(_) => {
|
||||
drop(application);
|
||||
render_schedule.execute(&mut world, &mut resources);
|
||||
}
|
||||
}
|
||||
Err(wgpu::SurfaceError::Timeout) => {}
|
||||
Err(e) => println!("error: {:?}", e),
|
||||
};
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
update_schedule.execute(&mut world, &mut resources);
|
||||
shader_watcher.watch();
|
||||
camera.update();
|
||||
state.update();
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::DeviceEvent { ref event, .. } => match event {
|
||||
DeviceEvent::MouseMotion { delta } => {
|
||||
let mut application = resources.get_mut::<Application>().unwrap();
|
||||
application.on_mouse_motion(delta.0, delta.1);
|
||||
if is_grabbed {
|
||||
camera.process_mouse(delta.0, delta.1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} => {
|
||||
let mut application = resources.get_mut::<Application>().unwrap();
|
||||
if window_id == application.window.id() {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state,
|
||||
..
|
||||
},
|
||||
} if window_id == window.id() => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state,
|
||||
..
|
||||
} => {
|
||||
application.on_key_event(*key, *state);
|
||||
},
|
||||
..
|
||||
} => {
|
||||
if *state == ElementState::Pressed && *key == VirtualKeyCode::Escape {
|
||||
if is_grabbed {
|
||||
window.set_cursor_grab(false).unwrap();
|
||||
window.set_cursor_visible(true);
|
||||
is_grabbed = false;
|
||||
}
|
||||
WindowEvent::MouseInput { button, state, .. } => {
|
||||
application.on_mouse_event(*button, *state);
|
||||
}
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
application.on_resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
application.on_resize(**new_inner_size);
|
||||
}
|
||||
WindowEvent::Focused(false) => {
|
||||
application.on_lost_focus();
|
||||
}
|
||||
_ => {}
|
||||
} else {
|
||||
camera.process_keyboard(*key, *state);
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => {
|
||||
if !is_grabbed {
|
||||
window.set_cursor_grab(true).unwrap();
|
||||
window.set_cursor_visible(false);
|
||||
is_grabbed = true;
|
||||
}
|
||||
}
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
ren.resize(*physical_size);
|
||||
camera.resize(physical_size.width, physical_size.height);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
ren.resize(**new_inner_size);
|
||||
camera.resize(new_inner_size.width, new_inner_size.height);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Vertex {
|
||||
pub position: [f32; 3],
|
||||
pub normal: [f32; 3],
|
||||
pub tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Index = u32;
|
||||
|
||||
pub struct MeshData {
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub indices: Vec<Index>,
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
use crate::handle::*;
|
||||
use crate::mesh::*;
|
||||
use crate::pool::{MaterialData, TextureData};
|
||||
use crate::scene::MeshInstance;
|
||||
|
||||
pub trait OnLoad {
|
||||
fn load_mesh(&mut self, mesh_data: &MeshData) -> MeshHandle;
|
||||
fn load_texture(&mut self, texture_data: &TextureData) -> TextureHandle;
|
||||
fn load_material(&mut self, material_data: &MaterialData) -> MaterialHandle;
|
||||
}
|
||||
|
||||
pub struct BasicMesh {
|
||||
pub mesh_handle: MeshHandle,
|
||||
pub material_handle: MaterialHandle,
|
||||
}
|
||||
|
||||
impl BasicMesh {
|
||||
pub fn instantiate(&self, transform: glam::Mat4) -> MeshInstance {
|
||||
MeshInstance {
|
||||
mesh: self.mesh_handle,
|
||||
material: self.material_handle,
|
||||
transform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjModel {
|
||||
mesh: BasicMesh,
|
||||
}
|
||||
|
||||
impl ObjModel {
|
||||
pub fn load(mut on_load: impl OnLoad) -> Self {
|
||||
use tobj::*;
|
||||
|
||||
let model_data = include_bytes!("assets/viking_room/model.obj").to_vec();
|
||||
let model_data = &mut model_data.as_slice();
|
||||
|
||||
let load_options = LoadOptions {
|
||||
triangulate: true,
|
||||
single_index: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (models, _mats) =
|
||||
load_obj_buf(model_data, &load_options, |_| unimplemented!()).unwrap();
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
let m = models.first().unwrap();
|
||||
let index_base = vertices.len() as u32;
|
||||
for i in 0..m.mesh.positions.len() / 3 {
|
||||
let t = i * 3;
|
||||
vertices.push(Vertex {
|
||||
position: [
|
||||
m.mesh.positions[t],
|
||||
m.mesh.positions[t + 2],
|
||||
-m.mesh.positions[t + 1],
|
||||
],
|
||||
normal: [
|
||||
m.mesh.normals[t],
|
||||
m.mesh.normals[t + 2],
|
||||
-m.mesh.normals[t + 1],
|
||||
],
|
||||
tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]],
|
||||
});
|
||||
}
|
||||
|
||||
indices.extend(m.mesh.indices.iter().map(|i| i + index_base));
|
||||
|
||||
let mesh_data = MeshData { vertices, indices };
|
||||
let mesh_handle = on_load.load_mesh(&mesh_data);
|
||||
|
||||
let albedo_data = include_bytes!("assets/viking_room/albedo.png");
|
||||
let albedo = image::load_from_memory(albedo_data).unwrap();
|
||||
|
||||
use image::GenericImageView;
|
||||
let dimensions = albedo.dimensions();
|
||||
|
||||
let albedo_rgb = albedo.as_rgb8().unwrap().to_vec();
|
||||
let mut albedo_rgba = Vec::<u8>::new();
|
||||
for rgb in albedo_rgb.chunks(3) {
|
||||
albedo_rgba.extend_from_slice(rgb);
|
||||
albedo_rgba.push(0xff);
|
||||
}
|
||||
|
||||
let albedo_data = TextureData {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
data: albedo_rgba,
|
||||
};
|
||||
|
||||
let albedo = on_load.load_texture(&albedo_data);
|
||||
|
||||
let material_handle = on_load.load_material(&MaterialData {
|
||||
albedo,
|
||||
metallic_roughness: albedo, // TODO better placeholder MR texture
|
||||
});
|
||||
|
||||
let mesh = BasicMesh {
|
||||
mesh_handle,
|
||||
material_handle,
|
||||
};
|
||||
Self { mesh }
|
||||
}
|
||||
|
||||
pub fn draw(&self, meshes: &mut Vec<MeshInstance>, transform: glam::Mat4) {
|
||||
meshes.push(self.mesh.instantiate(transform));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GltfModel {
|
||||
meshes: Vec<BasicMesh>,
|
||||
}
|
||||
|
||||
impl GltfModel {
|
||||
pub fn load(on_load: impl OnLoad) -> Self {
|
||||
use gltf::*;
|
||||
|
||||
let model_data = include_bytes!("assets/DamagedHelmet.glb");
|
||||
let model = Gltf::from_slice(model_data.as_slice()).unwrap();
|
||||
let loader = GltfLoader::load(&model, on_load);
|
||||
|
||||
Self {
|
||||
meshes: loader.meshes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, meshes: &mut Vec<MeshInstance>, transform: glam::Mat4) {
|
||||
for mesh in self.meshes.iter() {
|
||||
meshes.push(mesh.instantiate(transform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GltfLoader<'a, T: OnLoad> {
|
||||
pub model: &'a gltf::Gltf,
|
||||
pub buffers: Vec<Vec<u8>>,
|
||||
pub on_load: T,
|
||||
pub meshes: Vec<BasicMesh>,
|
||||
}
|
||||
|
||||
impl<'a, T: OnLoad> GltfLoader<'a, T> {
|
||||
pub fn load(model: &'a gltf::Gltf, on_load: T) -> Self {
|
||||
let buffers = Self::load_buffers(model);
|
||||
|
||||
let mut loader = Self {
|
||||
model,
|
||||
buffers,
|
||||
on_load,
|
||||
meshes: Default::default(),
|
||||
};
|
||||
|
||||
for scene in loader.model.scenes() {
|
||||
for node in scene.nodes() {
|
||||
loader.load_node(node);
|
||||
}
|
||||
}
|
||||
|
||||
loader
|
||||
}
|
||||
|
||||
pub fn load_buffers(model: &gltf::Gltf) -> Vec<Vec<u8>> {
|
||||
let mut buffer_data = Vec::<Vec<u8>>::new();
|
||||
for buffer in model.buffers() {
|
||||
match buffer.source() {
|
||||
gltf::buffer::Source::Bin => {
|
||||
buffer_data.push(model.blob.as_deref().unwrap().into());
|
||||
}
|
||||
_ => panic!("URI buffer sources are unsupported"),
|
||||
}
|
||||
}
|
||||
|
||||
buffer_data
|
||||
}
|
||||
|
||||
pub fn load_node(&mut self, node: gltf::Node) {
|
||||
if let Some(mesh) = node.mesh() {
|
||||
for primitive in mesh.primitives() {
|
||||
let mesh = self.load_primitive(primitive);
|
||||
self.meshes.push(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
self.load_node(child);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_primitive(&mut self, primitive: gltf::Primitive) -> BasicMesh {
|
||||
let material_handle = self.load_primitive_material(primitive.clone());
|
||||
let mesh_handle = self.load_primitive_mesh(primitive);
|
||||
BasicMesh {
|
||||
material_handle,
|
||||
mesh_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_primitive_material(&mut self, primitive: gltf::Primitive) -> MaterialHandle {
|
||||
let pbr = primitive.material().pbr_metallic_roughness();
|
||||
|
||||
let base_color = pbr.base_color_texture().unwrap().texture();
|
||||
let albedo = self.load_texture(base_color);
|
||||
|
||||
let metallic_roughness = pbr.metallic_roughness_texture().unwrap().texture();
|
||||
let metallic_roughness = self.load_texture(metallic_roughness);
|
||||
|
||||
self.on_load.load_material(&MaterialData {
|
||||
albedo,
|
||||
metallic_roughness,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_texture(&mut self, texture: gltf::Texture) -> TextureHandle {
|
||||
let source = texture.source().source();
|
||||
let view = if let gltf::image::Source::View { view, .. } = source {
|
||||
view
|
||||
} else {
|
||||
panic!("texture must be embedded");
|
||||
};
|
||||
|
||||
let start = view.offset() as usize;
|
||||
let end = (view.offset() + view.length()) as usize;
|
||||
let src = &self.buffers[view.buffer().index()][start..end];
|
||||
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(src).unwrap();
|
||||
let dimensions = image.dimensions();
|
||||
|
||||
let rgba: Vec<u8> = if let Some(rgba) = image.as_rgba8() {
|
||||
rgba.to_vec()
|
||||
} else {
|
||||
let rgb = image.as_rgb8().unwrap().to_vec();
|
||||
let mut rgba = Vec::<u8>::new();
|
||||
for rgb in rgb.chunks(3) {
|
||||
rgba.extend_from_slice(rgb);
|
||||
rgba.push(0xff);
|
||||
}
|
||||
rgba
|
||||
};
|
||||
|
||||
self.on_load.load_texture(&TextureData {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
data: rgba,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_primitive_mesh(&mut self, primitive: gltf::Primitive) -> MeshHandle {
|
||||
use gltf::mesh::util::{ReadIndices, ReadTexCoords};
|
||||
|
||||
if primitive.mode() != gltf::mesh::Mode::Triangles {
|
||||
panic!("glTF primitive must be triangle list");
|
||||
}
|
||||
|
||||
let reader = primitive.reader(|buffer| Some(&self.buffers[buffer.index()]));
|
||||
let positions = reader.read_positions().unwrap();
|
||||
let mut normals = reader.read_normals().unwrap();
|
||||
let tex_coords = reader.read_tex_coords(0).unwrap();
|
||||
|
||||
let mut tex_coords = if let ReadTexCoords::F32(tex_coords) = tex_coords {
|
||||
tex_coords
|
||||
} else {
|
||||
panic!("only f32 texture coordinates are supported")
|
||||
};
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
for position in positions {
|
||||
let normal = normals.next().unwrap();
|
||||
let tex_coords = tex_coords.next().unwrap();
|
||||
|
||||
vertices.push(Vertex {
|
||||
position,
|
||||
normal,
|
||||
tex_coords,
|
||||
});
|
||||
}
|
||||
|
||||
let indices = match reader.read_indices().unwrap() {
|
||||
ReadIndices::U32(indices) => indices.collect(),
|
||||
ReadIndices::U16(indices) => indices.map(|i| i as u32).collect(),
|
||||
ReadIndices::U8(indices) => indices.map(|i| i as u32).collect(),
|
||||
};
|
||||
|
||||
self.on_load.load_mesh(&MeshData { vertices, indices })
|
||||
}
|
||||
}
|
185
src/pass.rs
185
src/pass.rs
|
@ -1,185 +0,0 @@
|
|||
//! Render pass data structures and interfaces.
|
||||
|
||||
use crate::phase::Phase;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod debug;
|
||||
pub mod mesh;
|
||||
|
||||
/// Viewport data shared by all passes, once the [ViewportPhase] group is
|
||||
/// entered.
|
||||
pub struct ViewportData;
|
||||
|
||||
/// Data passed to each render pass's recording phases.
|
||||
pub struct PhaseData<'a, T> {
|
||||
pub phase: Phase,
|
||||
pub frame_data: T,
|
||||
pub viewport_data: &'a ViewportData,
|
||||
pub bind_viewport: &'a wgpu::BindGroup,
|
||||
}
|
||||
|
||||
pub type IndexedPhaseData<'a> = PhaseData<'a, usize>;
|
||||
|
||||
/// The render pass interface trait.
|
||||
///
|
||||
/// Render passes are persistent structures that share GPU resources, user data,
|
||||
/// and more across both phases and frames.
|
||||
///
|
||||
/// Rendering functions record their GPU commands using the [gpu::RenderBundleEncoder]
|
||||
/// passed to them.
|
||||
pub trait RenderPass: Send + Sync {
|
||||
/// A structure that contains a pass's per-frame data.
|
||||
type FrameData: Send + Sync;
|
||||
|
||||
/// Gets a short name for this pass.
|
||||
fn get_name(&self) -> &str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
/// Sets up a new instance of [Self::FrameData].
|
||||
///
|
||||
/// Called when a render pass is added to [crate::Renderer].
|
||||
fn create_frame_data(&self) -> Self::FrameData;
|
||||
|
||||
/// Initializes this frame's [Self::FrameData], and queries the pass for
|
||||
/// which phases to execute.
|
||||
///
|
||||
/// This is the only opportunity the render pass has to mutate this frame's
|
||||
/// data this frame, so all setup for future phases should be done here.
|
||||
///
|
||||
/// The render pass is also given access to the [queue][wgpu::Queue] this
|
||||
/// frame will be submitted on, and can be used to transfer image or buffer
|
||||
/// data to the GPU.
|
||||
fn begin_frame(&self, data: &mut Self::FrameData, phases: &mut Vec<Phase>, queue: &wgpu::Queue);
|
||||
|
||||
fn record_commands(
|
||||
&self,
|
||||
_data: PhaseData<&Self::FrameData>,
|
||||
_cmds: &mut wgpu::CommandEncoder,
|
||||
) {
|
||||
}
|
||||
|
||||
fn record_compute<'a>(
|
||||
&'a self,
|
||||
_data: PhaseData<&'a Self::FrameData>,
|
||||
_cmds: &mut wgpu::ComputePass<'a>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn record_render(&self, _data: PhaseData<&Self::FrameData>) -> Option<wgpu::RenderBundle> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The interface trait for [RenderPassBox], allowing use of a statically-sized
|
||||
/// [Box] for storage.
|
||||
///
|
||||
/// Functions on this trait map one-to-one with [RenderPass], except that
|
||||
/// frame data is passed by index and not by reference.
|
||||
pub trait RenderPassBoxTrait: Send + Sync {
|
||||
fn begin_frame(&mut self, data_index: usize, phases: &mut Vec<Phase>, queue: &wgpu::Queue);
|
||||
|
||||
fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder);
|
||||
|
||||
fn record_compute<'a>(&'a self, data: IndexedPhaseData<'a>, cmds: &mut wgpu::ComputePass<'a>);
|
||||
|
||||
fn record_render(&self, data: IndexedPhaseData) -> Option<wgpu::RenderBundle>;
|
||||
}
|
||||
|
||||
/// A container for a reference-counted render pass instance and its frame data.
|
||||
pub struct RenderPassBox<T: RenderPass> {
|
||||
render_pass: Arc<T>,
|
||||
frame_data: Vec<T::FrameData>,
|
||||
}
|
||||
|
||||
impl<T: 'static + RenderPass> RenderPassBox<T> {
|
||||
/// Creates a new boxed render pass.
|
||||
///
|
||||
/// Calls to [RenderPassBoxTrait] functions with frame indices greater
|
||||
/// than or equal to `frame_num` are out-of-bounds and will panic.
|
||||
pub fn new(render_pass: Arc<T>, frame_num: usize) -> Self {
|
||||
let frame_data = {
|
||||
(0..frame_num)
|
||||
.map(|_| render_pass.create_frame_data())
|
||||
.collect()
|
||||
};
|
||||
|
||||
Self {
|
||||
render_pass,
|
||||
frame_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [Self::new], but creates a boxed [RenderPassBoxTrait] in a
|
||||
/// generic-friendly interface.
|
||||
pub fn new_boxed(render_pass: Arc<T>, frame_num: usize) -> Box<dyn RenderPassBoxTrait> {
|
||||
Box::new(Self::new(render_pass, frame_num))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderPass> RenderPassBox<T> {
|
||||
fn get_frame_data<'a>(
|
||||
&'a self,
|
||||
index: IndexedPhaseData<'a>,
|
||||
) -> PhaseData<'a, &'a T::FrameData> {
|
||||
let PhaseData {
|
||||
phase,
|
||||
frame_data,
|
||||
viewport_data,
|
||||
bind_viewport,
|
||||
} = index;
|
||||
|
||||
let frame_data = self.frame_data.get(frame_data).unwrap();
|
||||
|
||||
PhaseData {
|
||||
phase,
|
||||
frame_data,
|
||||
viewport_data,
|
||||
bind_viewport,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderPass> std::ops::Deref for RenderPassBox<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
self.render_pass.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderPass> RenderPassBoxTrait for RenderPassBox<T> {
|
||||
fn begin_frame(&mut self, data_index: usize, phases: &mut Vec<Phase>, queue: &wgpu::Queue) {
|
||||
let rp = &self.render_pass;
|
||||
let name = rp.get_name();
|
||||
puffin::profile_scope!("RenderPass::begin_frame(...)", name);
|
||||
let frame_data = &mut self.frame_data[data_index];
|
||||
rp.begin_frame(frame_data, phases, queue)
|
||||
}
|
||||
|
||||
fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder) {
|
||||
let rp = &self.render_pass;
|
||||
let name = rp.get_name();
|
||||
let scope = format!("{} (phase: {:?})", name, data.phase);
|
||||
puffin::profile_scope!("RenderPass::record_commands(...)", scope);
|
||||
let frame_data = self.get_frame_data(data);
|
||||
rp.record_commands(frame_data, cmds)
|
||||
}
|
||||
|
||||
fn record_compute<'a>(&'a self, data: IndexedPhaseData<'a>, cmds: &mut wgpu::ComputePass<'a>) {
|
||||
let rp = &self.render_pass;
|
||||
let name = rp.get_name();
|
||||
let scope = format!("{} (phase: {:?})", name, data.phase);
|
||||
puffin::profile_scope!("RenderPass::record_compute(...)", scope);
|
||||
let frame_data = self.get_frame_data(data);
|
||||
rp.record_compute(frame_data, cmds)
|
||||
}
|
||||
|
||||
fn record_render(&self, data: IndexedPhaseData) -> Option<wgpu::RenderBundle> {
|
||||
let rp = &self.render_pass;
|
||||
let name = rp.get_name();
|
||||
let scope = format!("{} (phase: {:?})", name, data.phase);
|
||||
puffin::profile_scope!("RenderPass::record_render(...)", scope);
|
||||
let frame_data = self.get_frame_data(data);
|
||||
rp.record_render(frame_data)
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
use super::*;
|
||||
use crate::scene::{DebugDrawList, DebugIndex as Index, DebugVertex as Vertex};
|
||||
use crate::storage::GpuVec;
|
||||
use crate::viewport::ViewportInfo;
|
||||
use crate::RenderLayouts;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub struct FrameData {
|
||||
vertices: GpuVec<Vertex>,
|
||||
indices: GpuVec<Index>,
|
||||
}
|
||||
|
||||
pub struct DebugPass {
|
||||
device: Arc<wgpu::Device>,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
target_info: ViewportInfo,
|
||||
draw_list: RwLock<DebugDrawList>,
|
||||
}
|
||||
|
||||
impl DebugPass {
|
||||
pub fn new(
|
||||
device: Arc<wgpu::Device>,
|
||||
layouts: Arc<RenderLayouts>,
|
||||
target_info: ViewportInfo,
|
||||
) -> Self {
|
||||
// TODO hook into ShaderStore system
|
||||
let shader = device.create_shader_module(wgpu::include_wgsl!("debug_shader.wgsl"));
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("DebugPass Pipeline Layout"),
|
||||
bind_group_layouts: &[&layouts.bind_viewport],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("DebugPass Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[Vertex::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: target_info.output_format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::LineList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: target_info.depth_format,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
device,
|
||||
pipeline,
|
||||
target_info,
|
||||
draw_list: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_draw_list(&self, draw_list: &DebugDrawList) {
|
||||
self.draw_list.write().merge(draw_list);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderPass for DebugPass {
|
||||
type FrameData = FrameData;
|
||||
|
||||
fn create_frame_data(&self) -> FrameData {
|
||||
FrameData {
|
||||
vertices: GpuVec::new(
|
||||
self.device.clone(),
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
1024 * 1024,
|
||||
Some("Debug Vertex Buffer".to_string()),
|
||||
false,
|
||||
),
|
||||
indices: GpuVec::new(
|
||||
self.device.clone(),
|
||||
wgpu::BufferUsages::INDEX,
|
||||
1024 * 1024,
|
||||
Some("Debug Index Buffer".to_string()),
|
||||
false,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_frame(&self, data: &mut FrameData, phases: &mut Vec<Phase>, queue: &wgpu::Queue) {
|
||||
phases.push(Phase::Overlay);
|
||||
|
||||
use std::mem::replace;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
let mut draw_list_lock = self.draw_list.write();
|
||||
let vertices = replace(&mut draw_list_lock.vertices, Default::default());
|
||||
let indices = replace(&mut draw_list_lock.indices, Default::default());
|
||||
|
||||
let _ = replace(data.vertices.deref_mut(), vertices);
|
||||
let _ = replace(data.indices.deref_mut(), indices);
|
||||
|
||||
data.vertices.write(queue);
|
||||
data.indices.write(queue);
|
||||
}
|
||||
|
||||
fn record_render(&self, data: PhaseData<&FrameData>) -> Option<wgpu::RenderBundle> {
|
||||
let mut cmds =
|
||||
self.device
|
||||
.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
|
||||
label: Some("DebugPass Render Bundle"),
|
||||
color_formats: &[Some(self.target_info.output_format)],
|
||||
depth_stencil: Some(wgpu::RenderBundleDepthStencil {
|
||||
format: self.target_info.depth_format,
|
||||
depth_read_only: false, // TODO optimize?
|
||||
stencil_read_only: true,
|
||||
}),
|
||||
sample_count: 1,
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let vertices = &data.frame_data.vertices;
|
||||
let indices = &data.frame_data.indices;
|
||||
|
||||
cmds.set_pipeline(&self.pipeline);
|
||||
cmds.set_bind_group(0, data.bind_viewport, &[]);
|
||||
cmds.set_vertex_buffer(0, vertices.as_ref().slice(..));
|
||||
cmds.set_index_buffer(indices.as_ref().slice(..), wgpu::IndexFormat::Uint32);
|
||||
|
||||
let index_range = 0..(indices.len() as u32);
|
||||
cmds.draw_indexed(index_range, 0, 0..1);
|
||||
|
||||
Some(cmds.finish(&wgpu::RenderBundleDescriptor::default()))
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
struct CameraUniform {
|
||||
eye: vec4<f32>,
|
||||
vp: mat4x4<f32>,
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) color: vec3<f32>
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) color: vec3<f32>
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(instance_index) mesh_idx: u32,
|
||||
@builtin(vertex_index) vertex_idx: u32,
|
||||
vertex: VertexInput,
|
||||
) -> VertexOutput {
|
||||
let world_pos = vertex.position;
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = camera.vp * vec4<f32>(world_pos, 1.0);
|
||||
out.position = world_pos;
|
||||
out.color = vertex.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(
|
||||
frag: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(frag.color, 1.0);
|
||||
}
|
514
src/pass/mesh.rs
514
src/pass/mesh.rs
|
@ -1,514 +0,0 @@
|
|||
use super::*;
|
||||
use crate::shader::{ShaderHandle, ShaderStore};
|
||||
use crate::storage::mesh::*;
|
||||
use crate::storage::GpuVec;
|
||||
use crate::viewport::ViewportInfo;
|
||||
use crate::RenderLayouts;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ShaderInfo {
|
||||
pub store: Arc<ShaderStore>,
|
||||
pub forward: ShaderHandle,
|
||||
pub skinning: ShaderHandle,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Vertex {
|
||||
pub position: [f32; 3],
|
||||
pub tan_frame: u32,
|
||||
}
|
||||
|
||||
impl Attribute for Vertex {
|
||||
fn get_usages() -> wgpu::BufferUsages {
|
||||
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::VERTEX
|
||||
}
|
||||
}
|
||||
|
||||
const VERTEX_ATTRS: &[wgpu::VertexAttribute] =
|
||||
&wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32];
|
||||
|
||||
impl Vertex {
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: VERTEX_ATTRS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Index = u32;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Attributes {
|
||||
pub vertex: AttrId,
|
||||
pub index: AttrId,
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
pub fn new(attr_store: impl AsRef<AttrStore>) -> Self {
|
||||
Self {
|
||||
vertex: attr_store.as_ref().get_type::<Vertex>(),
|
||||
index: attr_store.as_ref().add(AttrInfo {
|
||||
layout: AttrLayout {
|
||||
size: std::mem::size_of::<Index>(),
|
||||
},
|
||||
usages: wgpu::BufferUsages::INDEX,
|
||||
default_pool_size: 1_000_000,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TransformedMesh {
|
||||
pub mesh: MeshHandle,
|
||||
pub transform: glam::Mat4,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct SkinningUniform {
|
||||
transform: [[f32; 4]; 4],
|
||||
src_offset: u32,
|
||||
dst_offset: u32,
|
||||
count: u32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MeshCommand {
|
||||
vertex_offset: usize,
|
||||
vertex_count: usize,
|
||||
skinned_offset: usize,
|
||||
index_offset: usize,
|
||||
index_count: usize,
|
||||
skinning_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MeshGroupCommands {
|
||||
binding_indices: MeshLayoutBindingIndices,
|
||||
bind_group: wgpu::BindGroup,
|
||||
meshes: Vec<MeshCommand>,
|
||||
}
|
||||
|
||||
pub struct FrameData {
|
||||
skinned_vertices: GpuVec<Vertex>,
|
||||
skinning_uniforms: GpuVec<SkinningUniform>,
|
||||
groups: Vec<MeshGroupCommands>,
|
||||
}
|
||||
|
||||
pub struct MeshPass {
|
||||
device: Arc<wgpu::Device>,
|
||||
layouts: Arc<RenderLayouts>,
|
||||
attr_store: Arc<AttrStore>,
|
||||
shader_info: ShaderInfo,
|
||||
mesh_pool: Arc<MeshPool>,
|
||||
attributes: Attributes,
|
||||
mesh_layout_id: MeshLayoutId,
|
||||
skinning_bind_group_layout: wgpu::BindGroupLayout,
|
||||
skinning_pipeline: wgpu::ComputePipeline,
|
||||
depth_pipeline: wgpu::RenderPipeline,
|
||||
opaque_pipeline: wgpu::RenderPipeline,
|
||||
target_info: ViewportInfo,
|
||||
transformed_meshes: RwLock<Vec<TransformedMesh>>,
|
||||
}
|
||||
|
||||
impl MeshPass {
|
||||
pub fn new(
|
||||
device: Arc<wgpu::Device>,
|
||||
layouts: Arc<RenderLayouts>,
|
||||
target_info: ViewportInfo,
|
||||
shader_info: ShaderInfo,
|
||||
) -> Self {
|
||||
let attr_store = AttrStore::new();
|
||||
let mesh_pool = MeshPool::new(device.clone(), attr_store.to_owned());
|
||||
let attributes = Attributes::new(&attr_store);
|
||||
|
||||
let mut mesh_layout = MeshLayoutDesc::new();
|
||||
mesh_layout.insert(attributes.vertex, ());
|
||||
mesh_layout.insert(attributes.index, ());
|
||||
let mesh_layout_id = mesh_pool.add_layout(mesh_layout).unwrap();
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("MeshPass Pipeline Layout"),
|
||||
bind_group_layouts: &[&layouts.bind_viewport],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let shader = shader_info.store.get(&shader_info.forward).unwrap();
|
||||
|
||||
let targets = &[Some(wgpu::ColorTargetState {
|
||||
format: target_info.output_format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})];
|
||||
|
||||
let mut pipeline_desc = wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Opaque MeshPass Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: shader.as_ref(),
|
||||
entry_point: "vs_main",
|
||||
buffers: &[Vertex::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: shader.as_ref(),
|
||||
entry_point: "fs_main",
|
||||
targets,
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None, // Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: target_info.depth_format,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
};
|
||||
|
||||
let depth_pipeline = device.create_render_pipeline(&pipeline_desc);
|
||||
|
||||
pipeline_desc.depth_stencil = Some(wgpu::DepthStencilState {
|
||||
format: target_info.depth_format,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: wgpu::CompareFunction::Equal,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
});
|
||||
|
||||
let opaque_pipeline = device.create_render_pipeline(&pipeline_desc);
|
||||
|
||||
drop(shader);
|
||||
|
||||
let skinning_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Skinning Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: None, // TODO ???
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let skinning_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Skinning Pipeline Layout"),
|
||||
bind_group_layouts: &[&skinning_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let skinning_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some("Skinning Pipeline"),
|
||||
layout: Some(&skinning_pipeline_layout),
|
||||
module: shader_info
|
||||
.store
|
||||
.get(&shader_info.skinning)
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
entry_point: "cs_main",
|
||||
});
|
||||
|
||||
Self {
|
||||
device,
|
||||
layouts,
|
||||
attr_store,
|
||||
shader_info,
|
||||
mesh_pool,
|
||||
attributes,
|
||||
mesh_layout_id,
|
||||
skinning_bind_group_layout,
|
||||
skinning_pipeline,
|
||||
depth_pipeline,
|
||||
opaque_pipeline,
|
||||
target_info,
|
||||
transformed_meshes: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mesh_pool(&self) -> &MeshPool {
|
||||
&self.mesh_pool
|
||||
}
|
||||
|
||||
pub fn get_attributes(&self) -> &Attributes {
|
||||
&self.attributes
|
||||
}
|
||||
|
||||
pub fn add_transformed_meshes(&self, meshes: &[TransformedMesh]) {
|
||||
self.transformed_meshes.write().extend_from_slice(meshes);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderPass for MeshPass {
|
||||
type FrameData = FrameData;
|
||||
|
||||
fn create_frame_data(&self) -> FrameData {
|
||||
FrameData {
|
||||
skinned_vertices: GpuVec::new(
|
||||
self.device.clone(),
|
||||
Vertex::get_usages(),
|
||||
1024 * 128,
|
||||
Some("Skinned Vertices".to_string()),
|
||||
false,
|
||||
),
|
||||
skinning_uniforms: GpuVec::new(
|
||||
self.device.clone(),
|
||||
wgpu::BufferUsages::STORAGE,
|
||||
1024 * 128,
|
||||
Some("Skinning Uniforms".to_string()),
|
||||
true,
|
||||
),
|
||||
groups: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_frame(&self, data: &mut FrameData, phases: &mut Vec<Phase>, queue: &wgpu::Queue) {
|
||||
phases.push(Phase::Upload);
|
||||
phases.push(Phase::Skinning);
|
||||
phases.push(Phase::Depth);
|
||||
phases.push(Phase::Opaque);
|
||||
phases.push(Phase::Transparent);
|
||||
|
||||
data.groups.clear();
|
||||
data.skinning_uniforms.clear();
|
||||
|
||||
let mut transformed_meshes_lock = self.transformed_meshes.write();
|
||||
let mesh_bindings = self
|
||||
.mesh_pool
|
||||
.iter_meshes(self.mesh_layout_id, transformed_meshes_lock.iter(), |i| {
|
||||
&i.mesh
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut skinned_cursor = 0;
|
||||
|
||||
for MeshLayoutInstances {
|
||||
bindings,
|
||||
instances,
|
||||
} in mesh_bindings.iter()
|
||||
{
|
||||
let pools = self.mesh_pool.get_bindings(bindings.clone());
|
||||
let vertices_pool = pools.get(self.attributes.vertex).unwrap();
|
||||
|
||||
// TODO defer bind group creation into separate Vec after GpuVecs have been written
|
||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &self.skinning_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: data.skinning_uniforms.as_ref(),
|
||||
offset: 0,
|
||||
// TODO ugly!
|
||||
size: Some(
|
||||
std::num::NonZeroU64::new(
|
||||
std::mem::size_of::<SkinningUniform>() as u64
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: data.skinned_vertices.as_ref(),
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: vertices_pool.get_buffer(),
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let mut group = MeshGroupCommands {
|
||||
binding_indices: bindings.clone(),
|
||||
meshes: Default::default(),
|
||||
bind_group,
|
||||
};
|
||||
|
||||
for (mesh, infos) in instances {
|
||||
let vertices = infos
|
||||
.iter()
|
||||
.find(|i| i.0 == self.attributes.vertex)
|
||||
.unwrap()
|
||||
.1;
|
||||
let indices = infos
|
||||
.iter()
|
||||
.find(|i| i.0 == self.attributes.index)
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
group.meshes.push(MeshCommand {
|
||||
vertex_offset: vertices.offset,
|
||||
vertex_count: vertices.count,
|
||||
skinned_offset: skinned_cursor,
|
||||
index_offset: indices.offset,
|
||||
index_count: indices.count,
|
||||
skinning_index: data.skinning_uniforms.len(),
|
||||
});
|
||||
|
||||
data.skinning_uniforms.push(SkinningUniform {
|
||||
transform: mesh.transform.to_cols_array_2d(),
|
||||
src_offset: vertices.offset as u32,
|
||||
dst_offset: skinned_cursor as u32,
|
||||
count: vertices.count as u32,
|
||||
_padding: 0,
|
||||
});
|
||||
|
||||
skinned_cursor += vertices.count;
|
||||
}
|
||||
|
||||
data.groups.push(group);
|
||||
}
|
||||
|
||||
data.skinned_vertices.reserve(skinned_cursor);
|
||||
data.skinned_vertices.write(&queue);
|
||||
data.skinning_uniforms.write(&queue);
|
||||
|
||||
transformed_meshes_lock.clear();
|
||||
}
|
||||
|
||||
fn record_commands(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::CommandEncoder) {
|
||||
match data.phase {
|
||||
Phase::Upload => self.mesh_pool.flush(cmds),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_compute<'a>(
|
||||
&'a self,
|
||||
data: PhaseData<&'a FrameData>,
|
||||
cmds: &mut wgpu::ComputePass<'a>,
|
||||
) {
|
||||
cmds.set_pipeline(&self.skinning_pipeline);
|
||||
for group in data.frame_data.groups.iter() {
|
||||
for mesh in group.meshes.iter() {
|
||||
let ubo_offset = data
|
||||
.frame_data
|
||||
.skinning_uniforms
|
||||
.buf_offset(mesh.skinning_index);
|
||||
cmds.set_bind_group(0, &group.bind_group, &[ubo_offset as u32]);
|
||||
|
||||
// TODO use div_ceil instead
|
||||
let workgroup_num = if mesh.vertex_count % 64 == 0 {
|
||||
mesh.vertex_count / 64
|
||||
} else {
|
||||
mesh.vertex_count / 64 + 1
|
||||
};
|
||||
|
||||
cmds.dispatch_workgroups(workgroup_num as u32, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_render(&self, data: PhaseData<&FrameData>) -> Option<wgpu::RenderBundle> {
|
||||
let pipeline = match data.phase {
|
||||
Phase::Depth => &self.depth_pipeline,
|
||||
Phase::Opaque => &self.opaque_pipeline,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mut cmds =
|
||||
self.device
|
||||
.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
|
||||
label: Some("Opaque Pass Render Bundle"),
|
||||
color_formats: &[Some(self.target_info.output_format)],
|
||||
depth_stencil: Some(wgpu::RenderBundleDepthStencil {
|
||||
format: self.target_info.depth_format,
|
||||
depth_read_only: false, // TODO optimize?
|
||||
stencil_read_only: true,
|
||||
}),
|
||||
sample_count: 1,
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
// yikes
|
||||
let mesh_bindings: Vec<_> = data
|
||||
.frame_data
|
||||
.groups
|
||||
.iter()
|
||||
.map(
|
||||
|MeshGroupCommands {
|
||||
binding_indices,
|
||||
meshes,
|
||||
..
|
||||
}| (self.mesh_pool.get_bindings(binding_indices.clone()), meshes),
|
||||
)
|
||||
.collect();
|
||||
|
||||
cmds.set_pipeline(pipeline);
|
||||
cmds.set_bind_group(0, data.bind_viewport, &[]);
|
||||
|
||||
for (bindings, meshes) in mesh_bindings.iter() {
|
||||
let indices_pool = bindings.get(self.attributes.index).unwrap();
|
||||
|
||||
cmds.set_vertex_buffer(0, data.frame_data.skinned_vertices.as_ref().slice(..));
|
||||
cmds.set_index_buffer(
|
||||
indices_pool.get_buffer().slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
|
||||
for mesh in meshes.iter() {
|
||||
let is_start = mesh.index_offset as u32;
|
||||
let is_end = is_start + mesh.index_count as u32;
|
||||
let vs_offset = mesh.skinned_offset as i32;
|
||||
cmds.draw_indexed(is_start..is_end, vs_offset, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
Some(cmds.finish(&wgpu::RenderBundleDescriptor::default()))
|
||||
}
|
||||
}
|
32
src/phase.rs
32
src/phase.rs
|
@ -1,32 +0,0 @@
|
|||
//! Render phase definitions.
|
||||
|
||||
/// The main render phase definitions.
|
||||
///
|
||||
/// These variants are temporary and for testing purposes.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, strum::EnumIter)]
|
||||
pub enum Phase {
|
||||
Upload,
|
||||
Skinning,
|
||||
Depth,
|
||||
Opaque,
|
||||
Transparent,
|
||||
Overlay,
|
||||
}
|
||||
|
||||
impl Phase {
|
||||
pub fn get_kind(&self) -> PhaseKind {
|
||||
use Phase::*;
|
||||
match self {
|
||||
Upload => PhaseKind::Command,
|
||||
Skinning => PhaseKind::Compute,
|
||||
Depth | Opaque | Transparent | Overlay => PhaseKind::Render,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The different kinds of phases.
|
||||
pub enum PhaseKind {
|
||||
Command,
|
||||
Compute,
|
||||
Render,
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
use super::handle::{MaterialHandle, MeshHandle, TextureHandle};
|
||||
use super::mesh::MeshData;
|
||||
use slab::Slab;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
pub struct MeshGroup {
|
||||
// TODO make these all private
|
||||
pub vertices: wgpu::Buffer,
|
||||
pub vertex_capacity: usize,
|
||||
pub indices: wgpu::Buffer,
|
||||
pub index_capacity: usize,
|
||||
}
|
||||
|
||||
impl MeshGroup {
|
||||
fn new(device: &wgpu::Device, data: &MeshData) -> Self {
|
||||
let vertex_capacity = data.vertices.len();
|
||||
let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&data.vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let index_capacity = data.indices.len();
|
||||
let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents: bytemuck::cast_slice(&data.indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
Self {
|
||||
vertex_capacity,
|
||||
vertices,
|
||||
index_capacity,
|
||||
indices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshPool {
|
||||
// TODO make this private
|
||||
pub groups: Slab<MeshGroup>,
|
||||
}
|
||||
|
||||
impl MeshPool {
|
||||
pub fn allocate(&mut self, device: &wgpu::Device, data: &MeshData) -> MeshHandle {
|
||||
let group = MeshGroup::new(device, data);
|
||||
let group_id = self.groups.insert(group);
|
||||
let sub_id = 0;
|
||||
MeshHandle { group_id, sub_id }
|
||||
}
|
||||
|
||||
pub fn get_group(&self, handle: &MeshHandle) -> Option<&MeshGroup> {
|
||||
self.groups.get(handle.group_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextureData {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct Texture {
|
||||
texture: wgpu::Texture,
|
||||
texture_view: wgpu::TextureView,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, data: &TextureData) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
label: None,
|
||||
});
|
||||
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&data.data,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: std::num::NonZeroU32::new(4 * size.width),
|
||||
rows_per_image: std::num::NonZeroU32::new(size.height),
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
Texture {
|
||||
texture,
|
||||
texture_view,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TexturePool {
|
||||
// TODO make this all private
|
||||
pub textures: Slab<Texture>,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl TexturePool {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let textures = Default::default();
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::Repeat,
|
||||
address_mode_v: wgpu::AddressMode::Repeat,
|
||||
address_mode_w: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Self { textures, sampler }
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
data: &TextureData,
|
||||
) -> TextureHandle {
|
||||
let texture = Texture::new(device, queue, data);
|
||||
let id = self.textures.insert(texture);
|
||||
TextureHandle { id }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MaterialData {
|
||||
pub albedo: TextureHandle,
|
||||
pub metallic_roughness: TextureHandle,
|
||||
}
|
||||
|
||||
pub struct Material {
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
pub struct MaterialPool {
|
||||
pub materials: Slab<Material>,
|
||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
impl MaterialPool {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let texture_entry = wgpu::BindGroupLayoutEntry {
|
||||
binding: u32::MAX,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
};
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
..texture_entry
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
..texture_entry
|
||||
},
|
||||
],
|
||||
label: Some("Texture Bind Group Layout"),
|
||||
});
|
||||
|
||||
Self {
|
||||
materials: Default::default(),
|
||||
bind_group_layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture_pool: &TexturePool,
|
||||
data: &MaterialData,
|
||||
) -> MaterialHandle {
|
||||
let albedo_view = &texture_pool.textures[data.albedo.id].texture_view;
|
||||
let mr_view = &texture_pool.textures[data.metallic_roughness.id].texture_view;
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &self.bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&texture_pool.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(albedo_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::TextureView(mr_view),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let material = Material { bind_group };
|
||||
let id = self.materials.insert(material);
|
||||
MaterialHandle { id }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,472 @@
|
|||
// Copyright (c) 2022 Marceline Cramer
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// largely based on http://paulbourke.net/geometry/polygonise/
|
||||
// TODO the Vec3 generic is pretty redundant, glam provides these types
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Vec3<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
}
|
||||
|
||||
impl<T: Copy> Vec3<T> {
|
||||
pub fn new(x: T, y: T, z: T) -> Self {
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
pub fn to_array(&self) -> [T; 3] {
|
||||
[self.x, self.y, self.z]
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec3<f32> {
|
||||
pub fn to_glam(self) -> glam::Vec3A {
|
||||
glam::Vec3A::new(self.x, self.y, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec3<i32>> for Vec3<f32> {
|
||||
fn from(other: Vec3<i32>) -> Self {
|
||||
Self {
|
||||
x: other.x as f32,
|
||||
y: other.y as f32,
|
||||
z: other.z as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> From<Vec3<T>> for [T; 3] {
|
||||
fn from(other: Vec3<T>) -> Self {
|
||||
other.to_array()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash)]
|
||||
pub struct MarchDomain {
|
||||
pub min: Vec3<i32>,
|
||||
pub max: Vec3<i32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Vertex {
|
||||
pub position: Vec3<f32>,
|
||||
pub normal: Vec3<f32>,
|
||||
}
|
||||
|
||||
// TODO support a set of march domains
|
||||
// TODO use index buffer
|
||||
// TODO cache shared vertices using HashMap<Edge, Vec3<f32>>
|
||||
pub fn marching_cubes(
|
||||
scalar_field: impl Fn(i32, i32, i32) -> f32,
|
||||
domain: &MarchDomain,
|
||||
) -> Vec<Vertex> {
|
||||
let samples = sample_corners(scalar_field, domain);
|
||||
|
||||
let test = |corner| samples[&corner] > 0.0;
|
||||
|
||||
// TODO this could be optimized using single-edge interpolation
|
||||
// TODO improve with glam vector math
|
||||
let interp = |cube: Vec3<i32>, edge_index| {
|
||||
let (c1, c2) = edge_coords(cube, edge_index);
|
||||
let v1 = samples[&c1];
|
||||
let v2 = samples[&c2];
|
||||
let p1: Vec3<f32> = c1.into();
|
||||
let p2: Vec3<f32> = c2.into();
|
||||
let mu = v1 / (v1 - v2);
|
||||
|
||||
Vec3 {
|
||||
x: p1.x + mu * (p2.x - p1.x),
|
||||
y: p1.y + mu * (p2.y - p1.y),
|
||||
z: p1.z + mu * (p2.z - p1.z),
|
||||
}
|
||||
};
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
for x in domain.min.x..domain.max.x {
|
||||
for y in domain.min.y..domain.max.y {
|
||||
for z in domain.min.z..domain.max.z {
|
||||
let cube = Vec3::new(x, y, z);
|
||||
let index = configuration_index(cube, test);
|
||||
let edges = CUBE_CONFIGURATIONS[index];
|
||||
|
||||
// TODO use Paul Bourke's edge table to share a cube's triangle vertices
|
||||
let mut ntriangle = 0;
|
||||
loop {
|
||||
let e1 = edges[ntriangle];
|
||||
if e1 < 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let p1 = interp(cube, e1);
|
||||
let p2 = interp(cube, edges[ntriangle + 1]);
|
||||
let p3 = interp(cube, edges[ntriangle + 2]);
|
||||
|
||||
let n = {
|
||||
let p1 = p1.to_glam();
|
||||
let p2 = p2.to_glam();
|
||||
let p3 = p3.to_glam();
|
||||
|
||||
let n = (p2 - p1).cross(p3 - p1).normalize();
|
||||
|
||||
Vec3 {
|
||||
x: n.x,
|
||||
y: n.y,
|
||||
z: n.z,
|
||||
}
|
||||
};
|
||||
|
||||
let n1 = n;
|
||||
let n2 = n;
|
||||
let n3 = n;
|
||||
|
||||
vertices.push(Vertex {
|
||||
position: p1,
|
||||
normal: n1,
|
||||
});
|
||||
vertices.push(Vertex {
|
||||
position: p2,
|
||||
normal: n2,
|
||||
});
|
||||
vertices.push(Vertex {
|
||||
position: p3,
|
||||
normal: n3,
|
||||
});
|
||||
|
||||
ntriangle += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vertices
|
||||
}
|
||||
|
||||
fn sample_corners(
|
||||
scalar_field: impl Fn(i32, i32, i32) -> f32,
|
||||
domain: &MarchDomain,
|
||||
) -> HashMap<Vec3<i32>, f32> {
|
||||
let mut samples = HashMap::new();
|
||||
|
||||
for x in domain.min.x..=domain.max.x {
|
||||
for y in domain.min.y..=domain.max.y {
|
||||
for z in domain.min.z..=domain.max.z {
|
||||
let s = scalar_field(x, y, z);
|
||||
let c = Vec3::new(x, y, z);
|
||||
samples.insert(c, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
samples
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn corner_coords(cube: Vec3<i32>, corner_index: i8) -> Vec3<i32> {
|
||||
match corner_index {
|
||||
0 => cube,
|
||||
1 => Vec3 { y: cube.y + 1, ..cube },
|
||||
2 => Vec3 { x: cube.x + 1, y: cube.y + 1, ..cube },
|
||||
3 => Vec3 { x: cube.x + 1, ..cube },
|
||||
4 => Vec3 { z: cube.z + 1, ..cube },
|
||||
5 => Vec3 { y: cube.y + 1, z: cube.z + 1, ..cube },
|
||||
6 => Vec3 { x: cube.x + 1, y: cube.y + 1, z: cube.z + 1 },
|
||||
7 => Vec3 { x: cube.x + 1, z: cube.z + 1, ..cube },
|
||||
index => unreachable!("invalid corner index {}", index),
|
||||
}
|
||||
}
|
||||
|
||||
fn edge_coords(cube: Vec3<i32>, edge_index: i8) -> (Vec3<i32>, Vec3<i32>) {
|
||||
let c = corner_coords;
|
||||
match edge_index {
|
||||
0 => (c(cube, 0), c(cube, 1)),
|
||||
1 => (c(cube, 1), c(cube, 2)),
|
||||
2 => (c(cube, 2), c(cube, 3)),
|
||||
3 => (c(cube, 3), c(cube, 0)),
|
||||
4 => (c(cube, 4), c(cube, 5)),
|
||||
5 => (c(cube, 5), c(cube, 6)),
|
||||
6 => (c(cube, 6), c(cube, 7)),
|
||||
7 => (c(cube, 7), c(cube, 4)),
|
||||
8 => (c(cube, 0), c(cube, 4)),
|
||||
9 => (c(cube, 1), c(cube, 5)),
|
||||
10 => (c(cube, 2), c(cube, 6)),
|
||||
11 => (c(cube, 3), c(cube, 7)),
|
||||
_ => unreachable!("invalid edge index"),
|
||||
}
|
||||
}
|
||||
|
||||
fn configuration_index(cube: Vec3<i32>, test: impl Fn(Vec3<i32>) -> bool) -> usize {
|
||||
let s = |cube, corner| test(corner_coords(cube, corner)) as usize;
|
||||
s(cube, 0)
|
||||
| (s(cube, 1) << 1)
|
||||
| (s(cube, 2) << 2)
|
||||
| (s(cube, 3) << 3)
|
||||
| (s(cube, 4) << 4)
|
||||
| (s(cube, 5) << 5)
|
||||
| (s(cube, 6) << 6)
|
||||
| (s(cube, 7) << 7)
|
||||
}
|
||||
|
||||
// source: http://paulbourke.net/geometry/polygonise/table2.txt
|
||||
#[rustfmt::skip]
|
||||
const CUBE_CONFIGURATIONS: [[i8; 13]; 256] = [
|
||||
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 3, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 0, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 3, 1, 8, 1, 9,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 1, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 3, 0, 1, 2,10,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 0, 2, 9, 2,10,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 2, 8, 2,10, 8, 8,10, 9,-1,-1,-1,-1],
|
||||
[11, 2, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 2, 0,11, 0, 8,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 2, 3, 0, 1, 9,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 2, 1,11, 1, 9,11,11, 9, 8,-1,-1,-1,-1],
|
||||
[10, 1, 3,10, 3,11,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 1, 0,10, 0, 8,10,10, 8,11,-1,-1,-1,-1],
|
||||
[ 0, 3, 9, 3,11, 9, 9,11,10,-1,-1,-1,-1],
|
||||
[ 8,10, 9, 8,11,10,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 4, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 0, 4, 3, 4, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 1, 9, 0, 8, 4, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 4, 1, 4, 7, 1, 1, 7, 3,-1,-1,-1,-1],
|
||||
[10, 1, 2, 8, 4, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 2,10, 1, 0, 4, 7, 0, 7, 3,-1,-1,-1,-1],
|
||||
[ 4, 7, 8, 0, 2,10, 0,10, 9,-1,-1,-1,-1],
|
||||
[ 2, 7, 3, 2, 9, 7, 7, 9, 4, 2,10, 9,-1],
|
||||
[ 2, 3,11, 7, 8, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 7,11, 4,11, 2, 4, 4, 2, 0,-1,-1,-1,-1],
|
||||
[ 3,11, 2, 4, 7, 8, 9, 0, 1,-1,-1,-1,-1],
|
||||
[ 2, 7,11, 2, 1, 7, 1, 4, 7, 1, 9, 4,-1],
|
||||
[ 8, 4, 7,11,10, 1,11, 1, 3,-1,-1,-1,-1],
|
||||
[11, 4, 7, 1, 4,11, 1,11,10, 1, 0, 4,-1],
|
||||
[ 3, 8, 0, 7,11, 4,11, 9, 4,11,10, 9,-1],
|
||||
[ 7,11, 4, 4,11, 9,11,10, 9,-1,-1,-1,-1],
|
||||
[ 9, 5, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 0, 8, 4, 9, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 5, 4, 0, 5, 0, 1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 4, 8, 5, 8, 3, 5, 5, 3, 1,-1,-1,-1,-1],
|
||||
[ 2,10, 1, 9, 5, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 8, 3, 5, 4, 9,10, 1, 2,-1,-1,-1,-1],
|
||||
[10, 5, 2, 5, 4, 2, 2, 4, 0,-1,-1,-1,-1],
|
||||
[ 3, 4, 8, 3, 2, 4, 2, 5, 4, 2,10, 5,-1],
|
||||
[11, 2, 3, 9, 5, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 5, 4, 8,11, 2, 8, 2, 0,-1,-1,-1,-1],
|
||||
[ 3,11, 2, 1, 5, 4, 1, 4, 0,-1,-1,-1,-1],
|
||||
[ 8, 5, 4, 2, 5, 8, 2, 8,11, 2, 1, 5,-1],
|
||||
[ 5, 4, 9, 1, 3,11, 1,11,10,-1,-1,-1,-1],
|
||||
[ 0, 9, 1, 4, 8, 5, 8,10, 5, 8,11,10,-1],
|
||||
[ 3, 4, 0, 3,10, 4, 4,10, 5, 3,11,10,-1],
|
||||
[ 4, 8, 5, 5, 8,10, 8,11,10,-1,-1,-1,-1],
|
||||
[ 9, 5, 7, 9, 7, 8,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 9, 3, 9, 5, 3, 3, 5, 7,-1,-1,-1,-1],
|
||||
[ 8, 0, 7, 0, 1, 7, 7, 1, 5,-1,-1,-1,-1],
|
||||
[ 1, 7, 3, 1, 5, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 1, 2,10, 5, 7, 8, 5, 8, 9,-1,-1,-1,-1],
|
||||
[ 9, 1, 0,10, 5, 2, 5, 3, 2, 5, 7, 3,-1],
|
||||
[ 5, 2,10, 8, 2, 5, 8, 5, 7, 8, 0, 2,-1],
|
||||
[10, 5, 2, 2, 5, 3, 5, 7, 3,-1,-1,-1,-1],
|
||||
[11, 2, 3, 8, 9, 5, 8, 5, 7,-1,-1,-1,-1],
|
||||
[ 9, 2, 0, 9, 7, 2, 2, 7,11, 9, 5, 7,-1],
|
||||
[ 0, 3, 8, 2, 1,11, 1, 7,11, 1, 5, 7,-1],
|
||||
[ 2, 1,11,11, 1, 7, 1, 5, 7,-1,-1,-1,-1],
|
||||
[ 3, 9, 1, 3, 8, 9, 7,11,10, 7,10, 5,-1],
|
||||
[ 9, 1, 0,10, 7,11,10, 5, 7,-1,-1,-1,-1],
|
||||
[ 3, 8, 0, 7,10, 5, 7,11,10,-1,-1,-1,-1],
|
||||
[11, 5, 7,11,10, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 6, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 3, 0,10, 6, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 1, 9, 5,10, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 6, 5, 9, 8, 3, 9, 3, 1,-1,-1,-1,-1],
|
||||
[ 1, 2, 6, 1, 6, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 8, 3, 2, 6, 5, 2, 5, 1,-1,-1,-1,-1],
|
||||
[ 5, 9, 6, 9, 0, 6, 6, 0, 2,-1,-1,-1,-1],
|
||||
[ 9, 6, 5, 3, 6, 9, 3, 9, 8, 3, 2, 6,-1],
|
||||
[ 3,11, 2,10, 6, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 6, 5,10, 2, 0, 8, 2, 8,11,-1,-1,-1,-1],
|
||||
[ 1, 9, 0, 6, 5,10,11, 2, 3,-1,-1,-1,-1],
|
||||
[ 1,10, 2, 5, 9, 6, 9,11, 6, 9, 8,11,-1],
|
||||
[11, 6, 3, 6, 5, 3, 3, 5, 1,-1,-1,-1,-1],
|
||||
[ 0, 5, 1, 0,11, 5, 5,11, 6, 0, 8,11,-1],
|
||||
[ 0, 5, 9, 0, 3, 5, 3, 6, 5, 3,11, 6,-1],
|
||||
[ 5, 9, 6, 6, 9,11, 9, 8,11,-1,-1,-1,-1],
|
||||
[10, 6, 5, 4, 7, 8,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 5,10, 6, 7, 3, 0, 7, 0, 4,-1,-1,-1,-1],
|
||||
[ 5,10, 6, 0, 1, 9, 8, 4, 7,-1,-1,-1,-1],
|
||||
[ 4, 5, 9, 6, 7,10, 7, 1,10, 7, 3, 1,-1],
|
||||
[ 7, 8, 4, 5, 1, 2, 5, 2, 6,-1,-1,-1,-1],
|
||||
[ 4, 1, 0, 4, 5, 1, 6, 7, 3, 6, 3, 2,-1],
|
||||
[ 9, 4, 5, 8, 0, 7, 0, 6, 7, 0, 2, 6,-1],
|
||||
[ 4, 5, 9, 6, 3, 2, 6, 7, 3,-1,-1,-1,-1],
|
||||
[ 7, 8, 4, 2, 3,11,10, 6, 5,-1,-1,-1,-1],
|
||||
[11, 6, 7,10, 2, 5, 2, 4, 5, 2, 0, 4,-1],
|
||||
[11, 6, 7, 8, 0, 3, 1,10, 2, 9, 4, 5,-1],
|
||||
[ 6, 7,11, 1,10, 2, 9, 4, 5,-1,-1,-1,-1],
|
||||
[ 6, 7,11, 4, 5, 8, 5, 3, 8, 5, 1, 3,-1],
|
||||
[ 6, 7,11, 4, 1, 0, 4, 5, 1,-1,-1,-1,-1],
|
||||
[ 4, 5, 9, 3, 8, 0,11, 6, 7,-1,-1,-1,-1],
|
||||
[ 9, 4, 5, 7,11, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 6, 4,10, 4, 9,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 3, 0, 9,10, 6, 9, 6, 4,-1,-1,-1,-1],
|
||||
[ 1,10, 0,10, 6, 0, 0, 6, 4,-1,-1,-1,-1],
|
||||
[ 8, 6, 4, 8, 1, 6, 6, 1,10, 8, 3, 1,-1],
|
||||
[ 9, 1, 4, 1, 2, 4, 4, 2, 6,-1,-1,-1,-1],
|
||||
[ 1, 0, 9, 3, 2, 8, 2, 4, 8, 2, 6, 4,-1],
|
||||
[ 2, 4, 0, 2, 6, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 2, 8, 8, 2, 4, 2, 6, 4,-1,-1,-1,-1],
|
||||
[ 2, 3,11, 6, 4, 9, 6, 9,10,-1,-1,-1,-1],
|
||||
[ 0,10, 2, 0, 9,10, 4, 8,11, 4,11, 6,-1],
|
||||
[10, 2, 1,11, 6, 3, 6, 0, 3, 6, 4, 0,-1],
|
||||
[10, 2, 1,11, 4, 8,11, 6, 4,-1,-1,-1,-1],
|
||||
[ 1, 4, 9,11, 4, 1,11, 1, 3,11, 6, 4,-1],
|
||||
[ 0, 9, 1, 4,11, 6, 4, 8,11,-1,-1,-1,-1],
|
||||
[11, 6, 3, 3, 6, 0, 6, 4, 0,-1,-1,-1,-1],
|
||||
[ 8, 6, 4, 8,11, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 6, 7,10, 7, 8,10,10, 8, 9,-1,-1,-1,-1],
|
||||
[ 9, 3, 0, 6, 3, 9, 6, 9,10, 6, 7, 3,-1],
|
||||
[ 6, 1,10, 6, 7, 1, 7, 0, 1, 7, 8, 0,-1],
|
||||
[ 6, 7,10,10, 7, 1, 7, 3, 1,-1,-1,-1,-1],
|
||||
[ 7, 2, 6, 7, 9, 2, 2, 9, 1, 7, 8, 9,-1],
|
||||
[ 1, 0, 9, 3, 6, 7, 3, 2, 6,-1,-1,-1,-1],
|
||||
[ 8, 0, 7, 7, 0, 6, 0, 2, 6,-1,-1,-1,-1],
|
||||
[ 2, 7, 3, 2, 6, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 7,11, 6, 3, 8, 2, 8,10, 2, 8, 9,10,-1],
|
||||
[11, 6, 7,10, 0, 9,10, 2, 0,-1,-1,-1,-1],
|
||||
[ 2, 1,10, 7,11, 6, 8, 0, 3,-1,-1,-1,-1],
|
||||
[ 1,10, 2, 6, 7,11,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 7,11, 6, 3, 9, 1, 3, 8, 9,-1,-1,-1,-1],
|
||||
[ 9, 1, 0,11, 6, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 3, 8,11, 6, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 6, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 7, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 8, 3,11, 7, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 0, 1,11, 7, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 7, 6,11, 3, 1, 9, 3, 9, 8,-1,-1,-1,-1],
|
||||
[ 1, 2,10, 6,11, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 2,10, 1, 7, 6,11, 8, 3, 0,-1,-1,-1,-1],
|
||||
[11, 7, 6,10, 9, 0,10, 0, 2,-1,-1,-1,-1],
|
||||
[ 7, 6,11, 3, 2, 8, 8, 2,10, 8,10, 9,-1],
|
||||
[ 2, 3, 7, 2, 7, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 7, 0, 7, 6, 0, 0, 6, 2,-1,-1,-1,-1],
|
||||
[ 1, 9, 0, 3, 7, 6, 3, 6, 2,-1,-1,-1,-1],
|
||||
[ 7, 6, 2, 7, 2, 9, 2, 1, 9, 7, 9, 8,-1],
|
||||
[ 6,10, 7,10, 1, 7, 7, 1, 3,-1,-1,-1,-1],
|
||||
[ 6,10, 1, 6, 1, 7, 7, 1, 0, 7, 0, 8,-1],
|
||||
[ 9, 0, 3, 6, 9, 3, 6,10, 9, 6, 3, 7,-1],
|
||||
[ 6,10, 7, 7,10, 8,10, 9, 8,-1,-1,-1,-1],
|
||||
[ 8, 4, 6, 8, 6,11,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 3, 6, 3, 0, 6, 6, 0, 4,-1,-1,-1,-1],
|
||||
[ 0, 1, 9, 4, 6,11, 4,11, 8,-1,-1,-1,-1],
|
||||
[ 1, 9, 4,11, 1, 4,11, 3, 1,11, 4, 6,-1],
|
||||
[10, 1, 2,11, 8, 4,11, 4, 6,-1,-1,-1,-1],
|
||||
[10, 1, 2,11, 3, 6, 6, 3, 0, 6, 0, 4,-1],
|
||||
[ 0, 2,10, 0,10, 9, 4,11, 8, 4, 6,11,-1],
|
||||
[ 2,11, 3, 6, 9, 4, 6,10, 9,-1,-1,-1,-1],
|
||||
[ 3, 8, 2, 8, 4, 2, 2, 4, 6,-1,-1,-1,-1],
|
||||
[ 2, 0, 4, 2, 4, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 1, 9, 0, 3, 8, 2, 2, 8, 4, 2, 4, 6,-1],
|
||||
[ 9, 4, 1, 1, 4, 2, 4, 6, 2,-1,-1,-1,-1],
|
||||
[ 8, 4, 6, 8, 6, 1, 6,10, 1, 8, 1, 3,-1],
|
||||
[ 1, 0,10,10, 0, 6, 0, 4, 6,-1,-1,-1,-1],
|
||||
[ 8, 0, 3, 9, 6,10, 9, 4, 6,-1,-1,-1,-1],
|
||||
[10, 4, 6,10, 9, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 5, 4, 7, 6,11,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 4, 9, 5, 3, 0, 8,11, 7, 6,-1,-1,-1,-1],
|
||||
[ 6,11, 7, 4, 0, 1, 4, 1, 5,-1,-1,-1,-1],
|
||||
[ 6,11, 7, 4, 8, 5, 5, 8, 3, 5, 3, 1,-1],
|
||||
[ 6,11, 7, 1, 2,10, 9, 5, 4,-1,-1,-1,-1],
|
||||
[11, 7, 6, 8, 3, 0, 1, 2,10, 9, 5, 4,-1],
|
||||
[11, 7, 6,10, 5, 2, 2, 5, 4, 2, 4, 0,-1],
|
||||
[ 7, 4, 8, 2,11, 3,10, 5, 6,-1,-1,-1,-1],
|
||||
[ 4, 9, 5, 6, 2, 3, 6, 3, 7,-1,-1,-1,-1],
|
||||
[ 9, 5, 4, 8, 7, 0, 0, 7, 6, 0, 6, 2,-1],
|
||||
[ 4, 0, 1, 4, 1, 5, 6, 3, 7, 6, 2, 3,-1],
|
||||
[ 7, 4, 8, 5, 2, 1, 5, 6, 2,-1,-1,-1,-1],
|
||||
[ 4, 9, 5, 6,10, 7, 7,10, 1, 7, 1, 3,-1],
|
||||
[ 5, 6,10, 0, 9, 1, 8, 7, 4,-1,-1,-1,-1],
|
||||
[ 5, 6,10, 7, 0, 3, 7, 4, 0,-1,-1,-1,-1],
|
||||
[10, 5, 6, 4, 8, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 5, 6, 9, 6,11, 9, 9,11, 8,-1,-1,-1,-1],
|
||||
[ 0, 9, 5, 0, 5, 3, 3, 5, 6, 3, 6,11,-1],
|
||||
[ 0, 1, 5, 0, 5,11, 5, 6,11, 0,11, 8,-1],
|
||||
[11, 3, 6, 6, 3, 5, 3, 1, 5,-1,-1,-1,-1],
|
||||
[ 1, 2,10, 5, 6, 9, 9, 6,11, 9,11, 8,-1],
|
||||
[ 1, 0, 9, 6,10, 5,11, 3, 2,-1,-1,-1,-1],
|
||||
[ 6,10, 5, 2, 8, 0, 2,11, 8,-1,-1,-1,-1],
|
||||
[ 3, 2,11,10, 5, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 5, 6, 3, 9, 6, 3, 8, 9, 3, 6, 2,-1],
|
||||
[ 5, 6, 9, 9, 6, 0, 6, 2, 0,-1,-1,-1,-1],
|
||||
[ 0, 3, 8, 2, 5, 6, 2, 1, 5,-1,-1,-1,-1],
|
||||
[ 1, 6, 2, 1, 5, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 5, 6, 9, 3, 8, 9, 1, 3,-1,-1,-1,-1],
|
||||
[ 0, 9, 1, 5, 6,10,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 0, 3,10, 5, 6,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 5, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 7, 5,11, 5,10,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 0, 8, 7, 5,10, 7,10,11,-1,-1,-1,-1],
|
||||
[ 9, 0, 1,10,11, 7,10, 7, 5,-1,-1,-1,-1],
|
||||
[ 3, 1, 9, 3, 9, 8, 7,10,11, 7, 5,10,-1],
|
||||
[ 2,11, 1,11, 7, 1, 1, 7, 5,-1,-1,-1,-1],
|
||||
[ 0, 8, 3, 2,11, 1, 1,11, 7, 1, 7, 5,-1],
|
||||
[ 9, 0, 2, 9, 2, 7, 2,11, 7, 9, 7, 5,-1],
|
||||
[11, 3, 2, 8, 5, 9, 8, 7, 5,-1,-1,-1,-1],
|
||||
[10, 2, 5, 2, 3, 5, 5, 3, 7,-1,-1,-1,-1],
|
||||
[ 5,10, 2, 8, 5, 2, 8, 7, 5, 8, 2, 0,-1],
|
||||
[ 9, 0, 1,10, 2, 5, 5, 2, 3, 5, 3, 7,-1],
|
||||
[ 1,10, 2, 5, 8, 7, 5, 9, 8,-1,-1,-1,-1],
|
||||
[ 1, 3, 7, 1, 7, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 7, 0, 0, 7, 1, 7, 5, 1,-1,-1,-1,-1],
|
||||
[ 0, 3, 9, 9, 3, 5, 3, 7, 5,-1,-1,-1,-1],
|
||||
[ 9, 7, 5, 9, 8, 7,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 4, 5, 8, 5,10, 8, 8,10,11,-1,-1,-1,-1],
|
||||
[ 3, 0, 4, 3, 4,10, 4, 5,10, 3,10,11,-1],
|
||||
[ 0, 1, 9, 4, 5, 8, 8, 5,10, 8,10,11,-1],
|
||||
[ 5, 9, 4, 1,11, 3, 1,10,11,-1,-1,-1,-1],
|
||||
[ 8, 4, 5, 2, 8, 5, 2,11, 8, 2, 5, 1,-1],
|
||||
[ 3, 2,11, 1, 4, 5, 1, 0, 4,-1,-1,-1,-1],
|
||||
[ 9, 4, 5, 8, 2,11, 8, 0, 2,-1,-1,-1,-1],
|
||||
[11, 3, 2, 9, 4, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 8, 4, 3, 4, 2, 2, 4, 5, 2, 5,10,-1],
|
||||
[10, 2, 5, 5, 2, 4, 2, 0, 4,-1,-1,-1,-1],
|
||||
[ 0, 3, 8, 5, 9, 4,10, 2, 1,-1,-1,-1,-1],
|
||||
[ 2, 1,10, 9, 4, 5,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 4, 5, 8, 8, 5, 3, 5, 1, 3,-1,-1,-1,-1],
|
||||
[ 5, 0, 4, 5, 1, 0,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 8, 0, 4, 5, 9,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 4, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 7, 4,11, 4, 9,11,11, 9,10,-1,-1,-1,-1],
|
||||
[ 3, 0, 8, 7, 4,11,11, 4, 9,11, 9,10,-1],
|
||||
[11, 7, 4, 1,11, 4, 1,10,11, 1, 4, 0,-1],
|
||||
[ 8, 7, 4,11, 1,10,11, 3, 1,-1,-1,-1,-1],
|
||||
[ 2,11, 7, 2, 7, 1, 1, 7, 4, 1, 4, 9,-1],
|
||||
[ 3, 2,11, 4, 8, 7, 9, 1, 0,-1,-1,-1,-1],
|
||||
[ 7, 4,11,11, 4, 2, 4, 0, 2,-1,-1,-1,-1],
|
||||
[ 2,11, 3, 7, 4, 8,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 2, 3, 7, 2, 7, 9, 7, 4, 9, 2, 9,10,-1],
|
||||
[ 4, 8, 7, 0,10, 2, 0, 9,10,-1,-1,-1,-1],
|
||||
[ 2, 1,10, 0, 7, 4, 0, 3, 7,-1,-1,-1,-1],
|
||||
[10, 2, 1, 8, 7, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 1, 4, 4, 1, 7, 1, 3, 7,-1,-1,-1,-1],
|
||||
[ 1, 0, 9, 8, 7, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 4, 0, 3, 7, 4,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 7, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 9,10, 8,10,11,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 0, 9, 3, 3, 9,11, 9,10,11,-1,-1,-1,-1],
|
||||
[ 1,10, 0, 0,10, 8,10,11, 8,-1,-1,-1,-1],
|
||||
[10, 3, 1,10,11, 3,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 2,11, 1, 1,11, 9,11, 8, 9,-1,-1,-1,-1],
|
||||
[11, 3, 2, 0, 9, 1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 0, 2,11, 8, 0,-1,-1,-1,-1,-1,-1,-1],
|
||||
[11, 3, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 3, 8, 2, 2, 8,10, 8, 9,10,-1,-1,-1,-1],
|
||||
[ 9, 2, 0, 9,10, 2,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 0, 3, 1,10, 2,-1,-1,-1,-1,-1,-1,-1],
|
||||
[10, 2, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 1, 3, 8, 9, 1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 9, 1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[ 8, 0, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
|
||||
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
|
||||
];
|
|
@ -0,0 +1 @@
|
|||
pub mod marching_cubes;
|
|
@ -0,0 +1,426 @@
|
|||
use super::camera::Camera;
|
||||
use super::commands::{Command, CommandSet};
|
||||
use super::mesh::{MeshData, Vertex};
|
||||
use super::pool::*;
|
||||
use super::scene::{PointLight, Scene};
|
||||
use super::shader::{parse_wgsl, generate_wgsl, add_includes};
|
||||
use crate::handle::*;
|
||||
use crate::model::OnLoad;
|
||||
use wgpu::util::DeviceExt;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
pub struct Renderer {
|
||||
pub device: wgpu::Device,
|
||||
pub queue: wgpu::Queue,
|
||||
pub mesh_pool: MeshPool,
|
||||
pub texture_pool: TexturePool,
|
||||
pub material_pool: MaterialPool,
|
||||
pub size: winit::dpi::PhysicalSize<u32>,
|
||||
surface: wgpu::Surface,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
depth_texture: wgpu::Texture,
|
||||
depth_texture_view: wgpu::TextureView,
|
||||
camera_uniform: CameraUniform,
|
||||
camera_buffer: wgpu::Buffer,
|
||||
point_lights_buffer: wgpu::Buffer,
|
||||
camera_bind_group: wgpu::BindGroup,
|
||||
meshes_buffer: wgpu::Buffer,
|
||||
meshes_bind_group: wgpu::BindGroup,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
) -> Self {
|
||||
let mesh_pool = MeshPool::default();
|
||||
let texture_pool = TexturePool::new(&device);
|
||||
let material_pool = MaterialPool::new(&device);
|
||||
|
||||
let (depth_texture, depth_texture_view) = Self::make_depth_texture(&device, &config);
|
||||
|
||||
let camera_uniform = CameraUniform::new();
|
||||
|
||||
let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Camera Buffer"),
|
||||
contents: bytemuck::cast_slice(&[camera_uniform]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let point_lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Point Lights Buffer"),
|
||||
size: 65536, // TODO buffer resizing
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let camera_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("Camera Bind Group Layout"),
|
||||
});
|
||||
|
||||
let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &camera_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: camera_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: point_lights_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: Some("Camera Bind Group"),
|
||||
});
|
||||
|
||||
let meshes_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Meshes Buffer"),
|
||||
size: 65536, // TODO resizable meshes buffer/gpu vectors
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let meshes_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("Meshes Bind Group Layout"),
|
||||
});
|
||||
|
||||
let meshes_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &meshes_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: meshes_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("Meshes Bind Group"),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[
|
||||
&camera_bind_group_layout,
|
||||
&meshes_bind_group_layout,
|
||||
&material_pool.bind_group_layout,
|
||||
],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
|
||||
// Generate a shader and preprocess it
|
||||
let mut source = read_to_string("src/shader.wgsl").unwrap();
|
||||
source = add_includes(&source);
|
||||
|
||||
// Parse the WGSL into a usable module
|
||||
let module = parse_wgsl(&source);
|
||||
|
||||
// Generate a valid WGSL string from the module
|
||||
let gen_wgsl = generate_wgsl(&module);
|
||||
|
||||
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||
label: Some("shader.wgsl"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
std::borrow::Cow::Owned(gen_wgsl),
|
||||
),
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[Vertex::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: Self::DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
size,
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
mesh_pool,
|
||||
texture_pool,
|
||||
material_pool,
|
||||
depth_texture,
|
||||
depth_texture_view,
|
||||
camera_uniform,
|
||||
camera_buffer,
|
||||
point_lights_buffer,
|
||||
camera_bind_group,
|
||||
meshes_buffer,
|
||||
meshes_bind_group,
|
||||
render_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
fn make_depth_texture(
|
||||
device: &wgpu::Device,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
) -> (wgpu::Texture, wgpu::TextureView) {
|
||||
let size = wgpu::Extent3d {
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some("Depth Texture"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&desc);
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
(texture, view)
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
let (depth_texture, depth_texture_view) =
|
||||
Self::make_depth_texture(&self.device, &self.config);
|
||||
self.depth_texture = depth_texture;
|
||||
self.depth_texture_view = depth_texture_view;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
camera: &impl Camera,
|
||||
scene: &Scene,
|
||||
) -> Result<(), wgpu::SurfaceError> {
|
||||
self.camera_uniform.update(camera);
|
||||
self.queue.write_buffer(
|
||||
&self.camera_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[self.camera_uniform]),
|
||||
);
|
||||
|
||||
let Scene {
|
||||
meshes,
|
||||
point_lights,
|
||||
} = scene;
|
||||
let mesh_commands = CommandSet::build(meshes);
|
||||
|
||||
// TODO persistent staging buffer (write_buffer creates a new one per call)
|
||||
self.queue
|
||||
.write_buffer(&self.meshes_buffer, 0, mesh_commands.get_storage());
|
||||
|
||||
let point_lights: Vec<PointLightUniform> = point_lights.iter().map(|p| p.into()).collect();
|
||||
|
||||
// TODO make a function to ease arranging header + array data (this is really ugly)
|
||||
// researching proper structure alignment will be necessary
|
||||
self.queue.write_buffer(
|
||||
&self.point_lights_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[point_lights.len() as u32]),
|
||||
);
|
||||
self.queue.write_buffer(
|
||||
&self.point_lights_buffer,
|
||||
16,
|
||||
bytemuck::cast_slice(&point_lights),
|
||||
);
|
||||
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_texture_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
|
||||
rp.set_pipeline(&self.render_pipeline);
|
||||
rp.set_bind_group(0, &self.camera_bind_group, &[]);
|
||||
rp.set_bind_group(1, &self.meshes_bind_group, &[]);
|
||||
|
||||
let mut group: Option<&MeshGroup> = None;
|
||||
for cmd in mesh_commands.iter() {
|
||||
match cmd {
|
||||
Command::BindMeshGroup { group_id } => {
|
||||
group = self.mesh_pool.groups.get(group_id);
|
||||
let group = group.unwrap();
|
||||
rp.set_vertex_buffer(0, group.vertices.slice(..));
|
||||
rp.set_index_buffer(group.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
}
|
||||
Command::BindMaterial { material_id } => {
|
||||
let material = self.material_pool.materials.get(material_id).unwrap();
|
||||
rp.set_bind_group(2, &material.bind_group, &[]);
|
||||
}
|
||||
Command::Draw {
|
||||
sub_id: _,
|
||||
instance_range,
|
||||
} => {
|
||||
// TODO use sub_id in mesh draw
|
||||
let indices = 0..(group.unwrap().index_capacity as u32);
|
||||
rp.draw_indexed(indices, 0, instance_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OnLoad for &mut Renderer {
|
||||
fn load_mesh(&mut self, mesh_data: &MeshData) -> MeshHandle {
|
||||
self.mesh_pool.allocate(&self.device, mesh_data)
|
||||
}
|
||||
|
||||
fn load_texture(&mut self, texture_data: &TextureData) -> TextureHandle {
|
||||
self.texture_pool
|
||||
.allocate(&self.device, &self.queue, texture_data)
|
||||
}
|
||||
|
||||
fn load_material(&mut self, material_data: &MaterialData) -> MaterialHandle {
|
||||
self.material_pool
|
||||
.allocate(&self.device, &self.texture_pool, material_data)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct CameraUniform {
|
||||
eye: [f32; 4],
|
||||
vp: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl CameraUniform {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
eye: [0.0; 4],
|
||||
vp: glam::Mat4::IDENTITY.to_cols_array_2d(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, camera: &impl Camera) {
|
||||
self.eye = camera.get_eye();
|
||||
self.vp = camera.get_vp();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct PointLightUniform {
|
||||
center: [f32; 4],
|
||||
intensity: [f32; 4],
|
||||
}
|
||||
|
||||
impl From<&PointLight> for PointLightUniform {
|
||||
fn from(p: &PointLight) -> Self {
|
||||
Self {
|
||||
center: p.center.extend(0.0).to_array(),
|
||||
intensity: p.intensity.extend(0.0).to_array(),
|
||||
}
|
||||
}
|
||||
}
|
70
src/scene.rs
70
src/scene.rs
|
@ -1,64 +1,20 @@
|
|||
//! Traits for describing Cyborg's scene representation.
|
||||
//!
|
||||
//! TODO this will all need to be replaced in favor of a way to represent
|
||||
//! querying component and resource data framework-agnostically
|
||||
use super::handle::*;
|
||||
|
||||
use crate::storage::mesh::MeshHandle;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Transform {
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct MeshInstance {
|
||||
pub mesh: MeshHandle,
|
||||
pub material: MaterialHandle,
|
||||
pub transform: glam::Mat4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Mesh {
|
||||
pub mesh: MeshHandle,
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct PointLight {
|
||||
pub center: glam::Vec3A,
|
||||
pub intensity: glam::Vec3A,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct DebugVertex {
|
||||
pub position: [f32; 3],
|
||||
pub color: [f32; 3],
|
||||
}
|
||||
|
||||
pub const DEBUG_VERTEX_ATTRS: &[wgpu::VertexAttribute] =
|
||||
&wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
|
||||
|
||||
impl DebugVertex {
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: DEBUG_VERTEX_ATTRS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DebugIndex = u32;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DebugDrawList {
|
||||
pub vertices: Vec<DebugVertex>,
|
||||
pub indices: Vec<DebugIndex>,
|
||||
}
|
||||
|
||||
impl DebugDrawList {
|
||||
pub fn clear(&mut self) {
|
||||
self.vertices.clear();
|
||||
self.indices.clear();
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
self.vertices.extend_from_slice(&other.vertices);
|
||||
|
||||
let is_offset = self.indices.len();
|
||||
self.indices.reserve(is_offset + other.indices.len());
|
||||
|
||||
for index in other.indices.iter() {
|
||||
if *index < (other.vertices.len() as DebugIndex) {
|
||||
self.indices.push(index + is_offset as DebugIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Scene<'a> {
|
||||
pub meshes: &'a [MeshInstance],
|
||||
pub point_lights: &'a [PointLight],
|
||||
}
|
||||
|
|
346
src/shader.rs
346
src/shader.rs
|
@ -1,258 +1,88 @@
|
|||
use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use slab::Slab;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::fs::read_to_string;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::{channel, Receiver, TryRecvError};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct ShaderHandle(usize);
|
||||
|
||||
pub struct ShaderStoreReadGuard<'a> {
|
||||
guard: RwLockReadGuard<'a, Slab<wgpu::ShaderModule>>,
|
||||
handle: ShaderHandle,
|
||||
}
|
||||
|
||||
impl<'a> AsRef<wgpu::ShaderModule> for ShaderStoreReadGuard<'a> {
|
||||
fn as_ref(&self) -> &wgpu::ShaderModule {
|
||||
self.guard.get(self.handle.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ShaderError {
|
||||
InvalidHandle,
|
||||
}
|
||||
|
||||
pub struct ShaderStore {
|
||||
device: Arc<wgpu::Device>,
|
||||
shaders: RwLock<Slab<wgpu::ShaderModule>>,
|
||||
}
|
||||
|
||||
impl ShaderStore {
|
||||
pub fn new(device: Arc<wgpu::Device>) -> Self {
|
||||
Self {
|
||||
device,
|
||||
shaders: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&self, module: &naga::Module) -> Result<ShaderHandle, ShaderError> {
|
||||
let source = self.generate_wgsl(module)?;
|
||||
let shader = self.load_wgsl(source)?;
|
||||
let index = self.shaders.write().insert(shader);
|
||||
Ok(ShaderHandle(index))
|
||||
}
|
||||
|
||||
pub fn reload(&self, handle: &ShaderHandle, module: &naga::Module) -> Result<(), ShaderError> {
|
||||
let mut write = self.shaders.write();
|
||||
match write.get_mut(handle.0) {
|
||||
Some(stored) => {
|
||||
let source = self.generate_wgsl(module)?;
|
||||
let shader = self.load_wgsl(source)?;
|
||||
let _old = std::mem::replace(stored, shader);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(ShaderError::InvalidHandle),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_wgsl(&self, module: &naga::Module) -> Result<String, ShaderError> {
|
||||
// TODO handle all the errors that can happen here
|
||||
use naga::back::wgsl::{Writer, WriterFlags};
|
||||
use naga::valid::{Capabilities, ValidationFlags, Validator};
|
||||
|
||||
let validation_flags = ValidationFlags::all();
|
||||
let capabilities = Capabilities::empty();
|
||||
let mut validator = Validator::new(validation_flags, capabilities);
|
||||
let module_info = validator.validate(&module).unwrap();
|
||||
|
||||
let wgsl_flags = WriterFlags::empty();
|
||||
let mut wgsl_buffer = String::new();
|
||||
let mut wgsl_writer = Writer::new(&mut wgsl_buffer, wgsl_flags);
|
||||
wgsl_writer
|
||||
.write(&module, &module_info)
|
||||
.expect("wgsl write failed");
|
||||
|
||||
Ok(wgsl_buffer)
|
||||
}
|
||||
|
||||
fn load_wgsl(&self, wgsl_source: String) -> Result<wgpu::ShaderModule, ShaderError> {
|
||||
let shader = self
|
||||
.device
|
||||
.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl_source)),
|
||||
});
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: &ShaderHandle) -> Option<ShaderStoreReadGuard> {
|
||||
let guard = self.shaders.read();
|
||||
if let Some(_) = guard.get(handle.0) {
|
||||
Some(ShaderStoreReadGuard {
|
||||
guard,
|
||||
handle: *handle,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShaderInfo {
|
||||
pub handle: ShaderHandle,
|
||||
pub dependencies: BTreeSet<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct ShaderLoader<'a> {
|
||||
store: &'a ShaderStore,
|
||||
source: String,
|
||||
root_path: PathBuf,
|
||||
old_handle: Option<ShaderHandle>,
|
||||
include_stack: Vec<PathBuf>,
|
||||
included: BTreeSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl<'a> ShaderLoader<'a> {
|
||||
pub fn new(
|
||||
store: &'a ShaderStore,
|
||||
root_path: impl AsRef<Path>,
|
||||
old_handle: Option<ShaderHandle>,
|
||||
) -> Self {
|
||||
Self {
|
||||
store,
|
||||
source: String::new(),
|
||||
root_path: root_path.as_ref().to_path_buf(),
|
||||
old_handle,
|
||||
include_stack: Default::default(),
|
||||
included: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn include_file(&mut self, path: &PathBuf) {
|
||||
let path = self.root_path.join(path);
|
||||
|
||||
if self.include_stack.contains(&path) {
|
||||
panic!("Circular include of {:?}", path);
|
||||
}
|
||||
|
||||
self.included.insert(path.clone());
|
||||
self.include_stack.push(path.clone());
|
||||
|
||||
let contents = read_to_string(path).unwrap();
|
||||
for line in contents.lines() {
|
||||
let words: Vec<&str> = line.split_whitespace().filter(|w| w.len() > 0).collect();
|
||||
|
||||
if words.get(0) == Some(&"#include") {
|
||||
let include_path = words[1];
|
||||
self.include_file(&include_path.into());
|
||||
} else {
|
||||
self.source.push_str(line);
|
||||
self.source.push_str("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
self.include_stack.pop();
|
||||
}
|
||||
|
||||
pub fn finish(self) -> ShaderInfo {
|
||||
let mut parser = naga::front::wgsl::Parser::new();
|
||||
let module = match parser.parse(&self.source) {
|
||||
Ok(module) => module,
|
||||
// TODO handle parsing errors
|
||||
Err(error) => panic!(
|
||||
"wgsl parsing error:\n{}",
|
||||
error.emit_to_string(&self.source)
|
||||
),
|
||||
};
|
||||
|
||||
let handle = if let Some(handle) = self.old_handle {
|
||||
self.store.reload(&handle, &module).unwrap();
|
||||
handle
|
||||
} else {
|
||||
self.store.load(&module).unwrap()
|
||||
};
|
||||
|
||||
ShaderInfo {
|
||||
handle,
|
||||
dependencies: self.included,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShaderWatcher {
|
||||
store: Arc<ShaderStore>,
|
||||
root_path: PathBuf,
|
||||
_watcher: RecommendedWatcher,
|
||||
notify_rx: Receiver<RawEvent>,
|
||||
file_infos: RwLock<HashMap<PathBuf, ShaderHandle>>,
|
||||
}
|
||||
|
||||
impl ShaderWatcher {
|
||||
pub fn new(
|
||||
store: Arc<ShaderStore>,
|
||||
root_path: impl AsRef<Path>,
|
||||
) -> Result<Self, notify::Error> {
|
||||
let root_path = root_path.as_ref().to_path_buf();
|
||||
|
||||
let (notify_tx, notify_rx) = channel();
|
||||
let mut watcher = raw_watcher(notify_tx)?;
|
||||
watcher.watch(root_path.clone(), RecursiveMode::Recursive)?;
|
||||
|
||||
Ok(Self {
|
||||
store,
|
||||
root_path,
|
||||
_watcher: watcher,
|
||||
notify_rx,
|
||||
file_infos: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn watch(&self) {
|
||||
loop {
|
||||
match self.notify_rx.try_recv() {
|
||||
Ok(event) => match event {
|
||||
RawEvent {
|
||||
path: Some(path),
|
||||
op: Ok(op),
|
||||
cookie: _, // TODO use cookie to disambiguate updates
|
||||
} => {
|
||||
if op.contains(notify::Op::CREATE) {
|
||||
self.on_changed(&path);
|
||||
}
|
||||
}
|
||||
other => panic!("unexpected shader loader watcher event: {:#?}", other),
|
||||
},
|
||||
Err(TryRecvError::Empty) => break,
|
||||
Err(TryRecvError::Disconnected) => panic!("shader loader watcher disconnected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_changed(&self, path: &Path) {
|
||||
let infos_read = self.file_infos.read();
|
||||
let path_buf = path.to_path_buf();
|
||||
match infos_read.get(&path_buf) {
|
||||
Some(handle) => {
|
||||
let mut loader = ShaderLoader::new(&self.store, &self.root_path, Some(*handle));
|
||||
loader.include_file(&path_buf);
|
||||
let _info = loader.finish(); // TODO update dependencies
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_file(&self, shader_path: impl AsRef<Path>) -> Result<ShaderHandle, ShaderError> {
|
||||
let shader_path_buf = shader_path.as_ref().to_path_buf();
|
||||
let mut loader = ShaderLoader::new(&self.store, &self.root_path, None);
|
||||
loader.include_file(&shader_path_buf);
|
||||
let info = loader.finish();
|
||||
let mut infos_write = self.file_infos.write();
|
||||
infos_write.insert(self.root_path.join(shader_path_buf), info.handle);
|
||||
Ok(info.handle)
|
||||
}
|
||||
}
|
||||
use std::fs::{File, read_to_string};
|
||||
|
||||
pub fn parse_wgsl(source: &String) -> naga::Module {
|
||||
// Create Parser
|
||||
let mut parser = naga::front::wgsl::Parser::new();
|
||||
|
||||
// Create empty Module
|
||||
let mut module = naga::Module::default();
|
||||
|
||||
// Attempt to parse the source code
|
||||
let module_result = parser.parse(source.as_str());
|
||||
match module_result {
|
||||
Ok(in_mod) => {
|
||||
//println!("{:?}", in_mod);
|
||||
module = in_mod;
|
||||
},
|
||||
Err(error) => { println!("Parsing error:\n{}", error) }
|
||||
}
|
||||
|
||||
// Return the Module
|
||||
module
|
||||
}
|
||||
|
||||
pub fn generate_wgsl(module: &naga::Module) -> String {
|
||||
// Create options for the Writer
|
||||
let wgsl_flags = naga::back::wgsl::WriterFlags::empty();
|
||||
|
||||
// Create a string buffer for the Writer to use
|
||||
let mut wgsl_buffer = String::new();
|
||||
|
||||
// Create validator (to check if module is OK)
|
||||
let mut validator = naga::valid::Validator::new(naga::valid::ValidationFlags::empty(), naga::valid::Capabilities::empty());
|
||||
|
||||
// Get ModuleInfo (produced by a syntax validator)
|
||||
let module_info = validator.validate(&module).unwrap();
|
||||
|
||||
// Create the Writer itself
|
||||
let mut wgsl_writer = naga::back::wgsl::Writer::new(&mut wgsl_buffer, wgsl_flags);
|
||||
|
||||
// Attempt to write
|
||||
wgsl_writer.write(&module, &module_info).expect("wgsl write failed");
|
||||
|
||||
wgsl_buffer
|
||||
}
|
||||
|
||||
pub fn add_includes(source: &String) -> String {
|
||||
let mut combined = String::new();
|
||||
|
||||
let source_lines = source.lines();
|
||||
for line in source_lines {
|
||||
// Get a vector of the words in the line
|
||||
let mut words: Vec<&str> = line.split(" ").collect();
|
||||
|
||||
// Check if this is an include statement
|
||||
if words[0] == "#include" {
|
||||
// Get the rest of the line
|
||||
words.remove(0);
|
||||
let file_path: String = words.join(" ");
|
||||
|
||||
// Get the code from the included file
|
||||
let included = read_to_string(file_path).unwrap();
|
||||
|
||||
combined = format!("{}\n{}", combined, included);
|
||||
} else {
|
||||
combined = format!("{}\n{}", combined, line);
|
||||
}
|
||||
}
|
||||
|
||||
combined
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn preprocess_file() {
|
||||
// Generate a shader and preprocess it
|
||||
let mut source = read_to_string("src/shader.wgsl").unwrap();
|
||||
source = add_includes(&source);
|
||||
|
||||
// Parse the WGSL into a usable module
|
||||
let module = parse_wgsl(&source);
|
||||
|
||||
// Generate a valid WGSL string from the module
|
||||
let gen_wgsl = generate_wgsl(&module);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#include src/shaders/basic_structs.wgsl
|
||||
#include src/shaders/basic_pbr.wgsl
|
||||
#include src/shaders/tri_sampler.wgsl
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
[[group(0), binding(1)]]
|
||||
var<storage,read> point_lights: PointLightData;
|
||||
|
||||
[[group(1), binding(0)]]
|
||||
var<storage,read> meshes: MeshData;
|
||||
|
||||
[[group(2), binding(0)]] var m_sampler: sampler;
|
||||
[[group(2), binding(1)]] var m_albedo: texture_2d<f32>;
|
||||
[[group(2), binding(2)]] var m_metallic_roughness: texture_2d<f32>;
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vs_main(
|
||||
[[builtin(instance_index)]] mesh_idx: u32,
|
||||
vertex: VertexInput,
|
||||
) -> VertexOutput {
|
||||
let transform = meshes.instances[mesh_idx].transform;
|
||||
let world_pos = transform * vec4<f32>(vertex.position, 1.0);
|
||||
let world_normal = transform * vec4<f32>(vertex.normal, 0.0);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = camera.vp * world_pos;
|
||||
out.position = world_pos.xyz;
|
||||
out.normal = world_normal.xyz;
|
||||
out.tex_coords = vertex.tex_coords;
|
||||
return out;
|
||||
}
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fs_main(
|
||||
frag: VertexOutput,
|
||||
) -> [[location(0)]] vec4<f32> {
|
||||
let normal = normalize(frag.normal);
|
||||
let view = normalize(camera.eye.xyz - frag.position);
|
||||
|
||||
//let albedo = textureSample(m_albedo, m_sampler, frag.tex_coords).xyz;
|
||||
let albedo = TriSampler(m_albedo, m_sampler, frag.position / 5.0, normal, 10.0);
|
||||
|
||||
//let metallic_roughness = textureSample(m_metallic_roughness, m_sampler, frag.tex_coords).bg;
|
||||
let metallic_roughness = TriSampler(m_metallic_roughness, m_sampler, frag.position / 5.0, normal, 10.0);
|
||||
let metallic = metallic_roughness.x;
|
||||
let roughness = metallic_roughness.y;
|
||||
|
||||
var lum = vec3<f32>(0.0);
|
||||
for(var i = 0; i < 4; i = i + 1) {
|
||||
let light = point_lights.lights[i];
|
||||
let light_position = light.center.xyz - frag.position;
|
||||
let light_intensity = light.intensity.rgb;
|
||||
let light_direction = normalize(light_position);
|
||||
|
||||
let radiance = light_intensity / dot(light_position, light_position);
|
||||
|
||||
let reflected = BRDF(
|
||||
light_direction, normal, view,
|
||||
albedo, metallic, roughness
|
||||
);
|
||||
|
||||
lum = lum + (radiance * reflected);
|
||||
}
|
||||
|
||||
return vec4<f32>(lum, 1.0);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
let PI: f32 = 3.141592;
|
||||
|
||||
fn D_GGX(NoH: f32, roughness: f32) -> f32 {
|
||||
let a = roughness * roughness;
|
||||
let a2 = a * a;
|
||||
let NoH2 = NoH * NoH;
|
||||
let f = NoH * (a2 - 1.0) + 1.0;
|
||||
return a2 / (PI * f * f);
|
||||
}
|
||||
|
||||
fn g1(NoV: f32, roughness: f32, k: f32) -> f32 {
|
||||
let denom = NoV * (1.0 - k) + k;
|
||||
return NoV / denom;
|
||||
}
|
||||
|
||||
fn G_SmithGGXCorrelated(NoV: f32, NoL: f32, roughness: f32) -> f32 {
|
||||
let r = roughness + 1.0;
|
||||
let k = (r * r) / 8.0;
|
||||
|
||||
let g1l = g1(NoV, roughness, k);
|
||||
let g1v = g1(NoL, roughness, k);
|
||||
|
||||
return g1l * g1v;
|
||||
}
|
||||
|
||||
fn F_Schlick(u: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||
let f = pow(1.0 - u, 5.0);
|
||||
return f + f0 * (1.0 - f);
|
||||
}
|
||||
|
||||
fn BRDF(
|
||||
l: vec3<f32>, // normalized light direction
|
||||
n: vec3<f32>, // normalized surface normal
|
||||
v: vec3<f32>, // normalized view direction
|
||||
albedo: vec3<f32>, // surface albedo
|
||||
metallic: f32, // surface metallic
|
||||
roughness: f32, // surface roughness
|
||||
) -> vec3<f32> {
|
||||
let h = normalize(v + l);
|
||||
let NoL = max(dot(n, l), 0.0);
|
||||
let NoV = max(dot(n, v), 0.0);
|
||||
let NoH = max(dot(n, h), 0.0);
|
||||
let LoH = max(dot(l, h), 0.0);
|
||||
|
||||
// calculate reflectance at surface incidence
|
||||
let f0 = mix(vec3<f32>(0.04), albedo, metallic);
|
||||
|
||||
// specular BRDF
|
||||
let D = D_GGX(NoH, roughness);
|
||||
let G = G_SmithGGXCorrelated(NoV, NoL, roughness);
|
||||
let F = F_Schlick(LoH, f0);
|
||||
|
||||
let numerator = (D * G) * F;
|
||||
let denominator = 4.0 * NoV * NoL;
|
||||
|
||||
let Fr = numerator / max(denominator, 0.01);
|
||||
|
||||
// diffuse BRDF
|
||||
let diffuse_fresnel =
|
||||
(vec3<f32>(1.0) - F_Schlick(NoL, f0)) *
|
||||
(vec3<f32>(1.0) - F_Schlick(NoV, f0));
|
||||
|
||||
let lambertian = albedo / PI;
|
||||
let Fd = diffuse_fresnel * lambertian;
|
||||
|
||||
// TODO multiple scattering
|
||||
return (Fr + Fd) * NoL;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
struct CameraUniform {
|
||||
eye: vec4<f32>;
|
||||
vp: mat4x4<f32>;
|
||||
};
|
||||
|
||||
struct MeshInstance {
|
||||
transform: mat4x4<f32>;
|
||||
};
|
||||
|
||||
struct MeshData {
|
||||
instances: array<MeshInstance>;
|
||||
};
|
||||
|
||||
struct PointLight {
|
||||
center: vec4<f32>;
|
||||
intensity: vec4<f32>;
|
||||
};
|
||||
|
||||
struct PointLightData {
|
||||
num: i32;
|
||||
lights: array<PointLight>;
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] normal: vec3<f32>;
|
||||
[[location(2)]] tex_coords: vec2<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] normal: vec3<f32>;
|
||||
[[location(2)]] tex_coords: vec2<f32>;
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
// Triplanar mapping sampler
|
||||
fn TriSampler(tex: texture_2d<f32>, tex_sampler: sampler, position: vec3<f32>, normal: vec3<f32>, sharpness: f32) -> vec3<f32> {
|
||||
let uv_x = position.yz;
|
||||
let uv_y = position.xz;
|
||||
let uv_z = position.xy;
|
||||
let tri_mask: vec3<f32> = normalize(pow(abs(normal), vec3<f32>(sharpness)));
|
||||
let sample_x = textureSample(tex, tex_sampler, uv_x).rgb;
|
||||
let sample_y = textureSample(tex, tex_sampler, uv_y).rgb;
|
||||
let sample_z = textureSample(tex, tex_sampler, uv_z).rgb;
|
||||
|
||||
return (sample_x * tri_mask.x) + (sample_y * tri_mask.y) + (sample_z * tri_mask.z);
|
||||
}
|
146
src/staging.rs
146
src/staging.rs
|
@ -1,146 +0,0 @@
|
|||
//! Intermediate CPU-mappable, GPU-visible storage for transferral to a GPU buffer.
|
||||
//!
|
||||
//! TODO: persistent mapping to bypass spillover
|
||||
//! TODO: use wgpu::util::StagingBelt?
|
||||
//! TODO: pass access to a wgpu::Queue for write_buffer, staging belt recall, or command encoding
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct StagingPool<T> {
|
||||
device: Arc<wgpu::Device>,
|
||||
stage_size: usize,
|
||||
spillover: Mutex<VecDeque<CopyBuffer<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> StagingPool<T> {
|
||||
pub fn new(device: Arc<wgpu::Device>, stage_size: usize) -> Self {
|
||||
Self {
|
||||
device,
|
||||
stage_size,
|
||||
spillover: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush<'a>(
|
||||
&self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
get_dst: impl Fn(&T) -> CopyDest<'a>,
|
||||
on_complete: impl Fn(T),
|
||||
) {
|
||||
let mut spillover = self.spillover.lock();
|
||||
if spillover.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let src = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("staging buffer"),
|
||||
size: self.stage_size as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_SRC,
|
||||
mapped_at_creation: true,
|
||||
});
|
||||
|
||||
let mut src_view = src.slice(..).get_mapped_range_mut();
|
||||
let mut src_offset = 0;
|
||||
while let Some(copy) = spillover.pop_back() {
|
||||
let (copy, next) = copy.eat(&mut src_view[src_offset..]);
|
||||
let dst = get_dst(©.target);
|
||||
let dst_offset = dst.offset + copy.offset;
|
||||
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&src,
|
||||
src_offset as wgpu::BufferAddress,
|
||||
dst.buffer,
|
||||
dst_offset as wgpu::BufferAddress,
|
||||
copy.size as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
src_offset += copy.size;
|
||||
|
||||
if let Some(next) = next {
|
||||
spillover.push_back(next);
|
||||
break;
|
||||
} else {
|
||||
on_complete(copy.target);
|
||||
}
|
||||
}
|
||||
|
||||
drop(src_view);
|
||||
src.unmap();
|
||||
}
|
||||
|
||||
pub fn queue_copies(&self, copies: Vec<CopyBuffer<T>>) {
|
||||
let mut spillover = self.spillover.lock();
|
||||
spillover.reserve(copies.len());
|
||||
spillover.extend(copies.into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CopyDest<'a> {
|
||||
/// The destination buffer.
|
||||
pub buffer: &'a wgpu::Buffer,
|
||||
|
||||
/// The destination offset *in bytes.*
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
pub struct CopyInfo<T> {
|
||||
/// The target of the copy.
|
||||
pub target: T,
|
||||
|
||||
/// The offset for the destination, including the [CopyDest] offset.
|
||||
pub offset: usize,
|
||||
|
||||
/// The copy size *in bytes.*
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
pub struct CopyBuffer<T> {
|
||||
/// The target of the copy.
|
||||
pub target: T,
|
||||
|
||||
/// The offset for the destination, including the [CopyDest] offset.
|
||||
pub offset: usize,
|
||||
|
||||
/// The CPU memory for the copy.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<T: Clone> CopyBuffer<T> {
|
||||
pub fn eat(self, dst: &mut [u8]) -> (CopyInfo<T>, Option<Self>) {
|
||||
let Self {
|
||||
target,
|
||||
offset,
|
||||
mut data,
|
||||
} = self;
|
||||
|
||||
let dst_size = dst.len();
|
||||
let size = data.len();
|
||||
|
||||
if dst_size >= size {
|
||||
dst[0..size].copy_from_slice(&data);
|
||||
let info = CopyInfo {
|
||||
target,
|
||||
offset,
|
||||
size,
|
||||
};
|
||||
(info, None)
|
||||
} else {
|
||||
let remainder = data.split_off(dst_size);
|
||||
dst.copy_from_slice(&data);
|
||||
let info = CopyInfo {
|
||||
target: target.clone(),
|
||||
offset,
|
||||
size: dst_size,
|
||||
};
|
||||
let offset = offset + dst_size;
|
||||
let next = Self {
|
||||
target,
|
||||
offset,
|
||||
data: remainder,
|
||||
};
|
||||
(info, Some(next))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,571 +0,0 @@
|
|||
use crate::staging::*;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use slab::Slab;
|
||||
use smallvec::SmallVec;
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// An error that can be returned when allocating a mesh.
|
||||
#[derive(Debug)]
|
||||
pub enum PoolError {
|
||||
TooBig,
|
||||
NoMoreRoom,
|
||||
InvalidIndex,
|
||||
AttrTaken,
|
||||
AttrUnregistered,
|
||||
LayoutUnregistered,
|
||||
MismatchedId,
|
||||
MismatchedLayout,
|
||||
}
|
||||
|
||||
/// An identifier for a mesh attribute.
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AttrId(usize);
|
||||
|
||||
/// A description of an attribute's layout in memory.
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AttrLayout {
|
||||
/// The size (in bytes) of this attribute.
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
/// Information about an [Attribute] registered in [AttrStore].
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AttrInfo {
|
||||
pub layout: AttrLayout,
|
||||
pub usages: wgpu::BufferUsages,
|
||||
pub default_pool_size: usize,
|
||||
}
|
||||
|
||||
/// The data single mesh attribute.
|
||||
#[derive(Clone)]
|
||||
pub struct AttrBuffer {
|
||||
pub id: AttrId,
|
||||
pub count: usize,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A compile-time attribute data type.
|
||||
pub trait Attribute: Sized {
|
||||
/// The memory layout of this data.
|
||||
fn get_layout() -> AttrLayout {
|
||||
AttrLayout {
|
||||
size: std::mem::size_of::<Self>(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The [wgpu::BufferUsages] of this data.
|
||||
fn get_usages() -> wgpu::BufferUsages;
|
||||
|
||||
/// The default size for new pools of this attribute.
|
||||
///
|
||||
/// Defaults to 1024 * 1024. (Around one million.)
|
||||
fn get_default_pool_size() -> usize {
|
||||
1024 * 1024
|
||||
}
|
||||
}
|
||||
|
||||
/// A store of [AttrIds][AttrId].
|
||||
pub struct AttrStore {
|
||||
attributes: RwLock<Slab<AttrInfo>>,
|
||||
types: RwLock<HashMap<TypeId, AttrId>>,
|
||||
}
|
||||
|
||||
impl AttrStore {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
attributes: Default::default(),
|
||||
types: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Dynamically creates a new [AttrId].
|
||||
pub fn add(&self, info: AttrInfo) -> AttrId {
|
||||
let index = self.attributes.write().insert(info);
|
||||
AttrId(index)
|
||||
}
|
||||
|
||||
/// Gets the [AttrId] for a type implementing [Attribute].
|
||||
///
|
||||
/// Creates a new [AttrId] for unrecognized types, otherwise reuses an
|
||||
/// existing [AttrId].
|
||||
pub fn get_type<T: 'static + Attribute>(&self) -> AttrId {
|
||||
let type_id = TypeId::of::<T>();
|
||||
let existing_id = self.types.read().get(&type_id).copied();
|
||||
|
||||
if let Some(id) = existing_id {
|
||||
id
|
||||
} else {
|
||||
let layout = T::get_layout();
|
||||
let usages = T::get_usages();
|
||||
let default_pool_size = T::get_default_pool_size();
|
||||
let info = AttrInfo {
|
||||
layout,
|
||||
usages,
|
||||
default_pool_size,
|
||||
};
|
||||
let id = self.add(info);
|
||||
self.types.write().insert(type_id, id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the [AttrInfo] for an [AttrId].
|
||||
pub fn get_info(&self, id: &AttrId) -> Option<AttrInfo> {
|
||||
self.attributes.read().get(id.0).copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Attribute pool allocation location.
|
||||
#[derive(Clone)]
|
||||
pub struct AttrAllocKey {
|
||||
/// The target attribute.
|
||||
pub attr: AttrId,
|
||||
|
||||
/// The index of the attribute pool.
|
||||
pub pool: usize,
|
||||
|
||||
/// The allocation within the attribute pool.
|
||||
pub alloc: usize,
|
||||
}
|
||||
|
||||
/// Info about an array of attributes that has been allocated in an [AttrPool].
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AttrAlloc {
|
||||
pub offset: usize,
|
||||
pub count: usize,
|
||||
pub offset_bytes: usize,
|
||||
pub count_bytes: usize,
|
||||
}
|
||||
|
||||
/// An unused space range in an [AttrPool].
|
||||
pub struct FreeSpace {
|
||||
offset: usize,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
/// A single GPU buffer containing linear arrays of attributes.
|
||||
pub struct AttrPool {
|
||||
buffer: wgpu::Buffer,
|
||||
id: AttrId,
|
||||
pool_id: usize,
|
||||
layout: AttrLayout,
|
||||
size: usize,
|
||||
allocs: Slab<AttrAlloc>,
|
||||
free_space: Vec<FreeSpace>,
|
||||
}
|
||||
|
||||
impl PartialEq for AttrPool {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.pool_id == other.pool_id && self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrPool {
|
||||
pub fn new(device: &wgpu::Device, id: AttrId, pool_id: usize, info: AttrInfo) -> Self {
|
||||
let layout = info.layout;
|
||||
let size = info.default_pool_size;
|
||||
|
||||
// TODO debug strings for attributes + pools + buffers
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
usage: wgpu::BufferUsages::COPY_DST | info.usages,
|
||||
mapped_at_creation: false,
|
||||
size: (size * layout.size) as wgpu::BufferAddress,
|
||||
});
|
||||
|
||||
Self {
|
||||
buffer,
|
||||
id,
|
||||
pool_id,
|
||||
layout,
|
||||
size,
|
||||
free_space: vec![FreeSpace {
|
||||
offset: 0,
|
||||
count: size,
|
||||
}],
|
||||
allocs: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads an [AttrBuffer].
|
||||
///
|
||||
/// Returns the new [AttrAllocKey].
|
||||
pub fn load(&mut self, buf: &AttrBuffer) -> Result<AttrAllocKey, PoolError> {
|
||||
let best_index = self.can_load(buf)?;
|
||||
self.alloc_at(best_index, buf.count)
|
||||
}
|
||||
|
||||
/// Allocates an array of attributes.
|
||||
///
|
||||
/// Returns the new [AttrAllocKey].
|
||||
pub fn alloc(&mut self, count: usize) -> Result<AttrAllocKey, PoolError> {
|
||||
let best_index = self.can_alloc(count)?;
|
||||
self.alloc_at(best_index, count)
|
||||
}
|
||||
|
||||
/// Tests if an [AttrBuffer] can be loaded.
|
||||
///
|
||||
/// Returns the result of [Self::best_fit].
|
||||
pub fn can_load(&self, buf: &AttrBuffer) -> Result<usize, PoolError> {
|
||||
if buf.id != self.id {
|
||||
Err(PoolError::MismatchedId)
|
||||
} else if buf.count * self.layout.size != buf.data.len() {
|
||||
Err(PoolError::MismatchedLayout)
|
||||
} else {
|
||||
self.can_alloc(buf.count)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests if an array of attributes can be allocated.
|
||||
///
|
||||
/// Returns the result of [Self::best_fit].
|
||||
pub fn can_alloc(&self, count: usize) -> Result<usize, PoolError> {
|
||||
if count > self.size {
|
||||
Err(PoolError::TooBig)
|
||||
} else if let Some(best_index) = self.best_fit(count) {
|
||||
Ok(best_index)
|
||||
} else {
|
||||
Err(PoolError::NoMoreRoom)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the index of the best-fit free space for an array of attributes.
|
||||
///
|
||||
/// TODO: use a binary tree to find best-fit free space in logarithmic time
|
||||
fn best_fit(&self, count: usize) -> Option<usize> {
|
||||
let mut best_index = None;
|
||||
let mut best_count = usize::MAX;
|
||||
for (index, space) in self.free_space.iter().enumerate() {
|
||||
if space.count >= count && space.count < best_count {
|
||||
best_index = Some(index);
|
||||
best_count = space.count;
|
||||
}
|
||||
}
|
||||
|
||||
best_index
|
||||
}
|
||||
|
||||
/// Allocates an array of attribute at a specific free space index.
|
||||
///
|
||||
/// Returns the new [AttrAllocKey].
|
||||
fn alloc_at(&mut self, index: usize, count: usize) -> Result<AttrAllocKey, PoolError> {
|
||||
let free_space = match self.free_space.get_mut(index) {
|
||||
Some(index) => index,
|
||||
None => return Err(PoolError::InvalidIndex),
|
||||
};
|
||||
|
||||
let offset = free_space.offset;
|
||||
|
||||
let alloc = AttrAlloc {
|
||||
offset,
|
||||
count,
|
||||
offset_bytes: offset * self.layout.size,
|
||||
count_bytes: count * self.layout.size,
|
||||
};
|
||||
|
||||
let index = self.allocs.insert(alloc);
|
||||
|
||||
let key = AttrAllocKey {
|
||||
attr: self.id,
|
||||
pool: self.pool_id,
|
||||
alloc: index,
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
match free_space.count.cmp(&count) {
|
||||
Ordering::Less => {
|
||||
return Err(PoolError::TooBig);
|
||||
}
|
||||
Ordering::Equal => {
|
||||
self.free_space.remove(index);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
free_space.count -= count;
|
||||
free_space.offset += count;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// Frees an allocation (by key) from the pool.
|
||||
pub fn free(&mut self, key: usize) -> Result<(), PoolError> {
|
||||
let alloc = self.allocs.try_remove(key).ok_or(PoolError::InvalidIndex)?;
|
||||
|
||||
let free_space = FreeSpace {
|
||||
offset: alloc.offset,
|
||||
count: alloc.count,
|
||||
};
|
||||
|
||||
// TODO merge free spaces
|
||||
self.free_space.push(free_space);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves an [AttrAlloc] by key.
|
||||
pub fn get(&self, key: usize) -> Option<AttrAlloc> {
|
||||
self.allocs.get(key).copied()
|
||||
}
|
||||
|
||||
/// Gets this pool's internal GPU buffer.
|
||||
pub fn get_buffer(&self) -> &wgpu::Buffer {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
/// Gets a [CopyDest] for an allocation, by key.
|
||||
pub fn get_copy_dest(&self, key: usize) -> Result<CopyDest, PoolError> {
|
||||
let offset = self.get(key).ok_or(PoolError::InvalidIndex)?.offset_bytes;
|
||||
Ok(CopyDest {
|
||||
buffer: self.get_buffer(),
|
||||
offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of attributes a mesh can have before they're moved to the heap.
|
||||
pub const MAX_MESH_INLINE_ATTRIBUTES: usize = 16;
|
||||
|
||||
/// A mesh and all of its attributes.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MeshBuffer {
|
||||
pub attributes: SmallVec<[AttrBuffer; MAX_MESH_INLINE_ATTRIBUTES]>,
|
||||
}
|
||||
|
||||
/// An allocated mesh.
|
||||
pub struct MeshAlloc {
|
||||
pub attributes: SmallVec<[AttrAllocKey; MAX_MESH_INLINE_ATTRIBUTES]>,
|
||||
}
|
||||
|
||||
/// A handle to an allocated mesh.
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MeshHandle(usize);
|
||||
|
||||
/// A reusable set of [AttrIds][AttrId], for use with querying compatible meshes.
|
||||
pub type MeshLayoutDesc = smallmap::Set<AttrId>;
|
||||
|
||||
/// The ID of a [MeshLayoutDesc] registered in a [MeshPool].
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MeshLayoutId(usize);
|
||||
|
||||
/// Mappings of the attributes in a [MeshLayout] to specific pool indices.
|
||||
pub type MeshLayoutBindingIndices = smallmap::Map<AttrId, usize>;
|
||||
|
||||
/// Mappings of the attributes in a [MeshLayout] to specific pools.
|
||||
pub struct MeshLayoutBindings<'a> {
|
||||
lock: RwLockReadGuard<'a, HashMap<AttrId, Vec<AttrPool>>>,
|
||||
indices: MeshLayoutBindingIndices,
|
||||
}
|
||||
|
||||
impl<'a> MeshLayoutBindings<'a> {
|
||||
pub fn get(&self, attr: AttrId) -> Option<&AttrPool> {
|
||||
let pool_id = self.indices.get(&attr)?;
|
||||
let pools = self.lock.get(&attr)?;
|
||||
pools.get(*pool_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mappings of the attributes in a [MeshAlloc] to specific pool offsets.
|
||||
pub type MeshAllocInfos = SmallVec<[(AttrId, AttrAlloc); MAX_MESH_INLINE_ATTRIBUTES]>;
|
||||
|
||||
/// A set of mesh instances fitting a common [MeshLayoutDesc].
|
||||
#[derive(Default)]
|
||||
pub struct MeshLayoutInstances<T> {
|
||||
pub bindings: MeshLayoutBindingIndices,
|
||||
pub instances: Vec<(T, MeshAllocInfos)>,
|
||||
}
|
||||
|
||||
/// A mesh data pool.
|
||||
pub struct MeshPool {
|
||||
device: Arc<wgpu::Device>,
|
||||
staging: StagingPool<AttrAllocKey>,
|
||||
attr_store: Arc<AttrStore>,
|
||||
allocs: RwLock<Slab<MeshAlloc>>,
|
||||
mesh_layouts: RwLock<Slab<MeshLayoutDesc>>,
|
||||
pools: RwLock<HashMap<AttrId, Vec<AttrPool>>>,
|
||||
}
|
||||
|
||||
impl MeshPool {
|
||||
pub fn new(device: Arc<wgpu::Device>, attr_store: Arc<AttrStore>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
device: device.clone(),
|
||||
staging: StagingPool::new(device, 1024 * 1024),
|
||||
attr_store,
|
||||
mesh_layouts: Default::default(),
|
||||
allocs: Default::default(),
|
||||
pools: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers a [MeshLayoutDesc] with this pool.
|
||||
///
|
||||
/// TODO: keep track of mesh allocations that fit each layout
|
||||
pub fn add_layout(&self, layout: MeshLayoutDesc) -> Result<MeshLayoutId, PoolError> {
|
||||
let idx = self.mesh_layouts.write().insert(layout);
|
||||
Ok(MeshLayoutId(idx))
|
||||
}
|
||||
|
||||
/// Loads a [MeshBuffer].
|
||||
pub fn load(&self, buf: MeshBuffer) -> Result<MeshHandle, PoolError> {
|
||||
let mut attrs = HashMap::new();
|
||||
|
||||
for attr in buf.attributes.into_iter() {
|
||||
if let Some(_) = attrs.insert(attr.id, attr) {
|
||||
// TODO: support for loading duplicate attribute IDs
|
||||
return Err(PoolError::AttrTaken);
|
||||
}
|
||||
}
|
||||
|
||||
let mut attr_allocs = SmallVec::new();
|
||||
let mut copies = Vec::new();
|
||||
for (id, buf) in attrs.drain() {
|
||||
let mut pools_write = self.pools.write();
|
||||
|
||||
let pools = match pools_write.get_mut(&id) {
|
||||
Some(pools) => pools,
|
||||
None => {
|
||||
let info = match self.attr_store.get_info(&id) {
|
||||
Some(info) => info,
|
||||
None => return Err(PoolError::AttrUnregistered),
|
||||
};
|
||||
|
||||
let pool = AttrPool::new(&self.device, id, 0, info);
|
||||
pools_write.insert(id, vec![pool]);
|
||||
pools_write.get_mut(&id).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
for pool in pools.iter_mut() {
|
||||
match pool.load(&buf) {
|
||||
Ok(alloc) => {
|
||||
let copy = CopyBuffer {
|
||||
target: alloc.clone(),
|
||||
offset: 0,
|
||||
data: buf.data,
|
||||
};
|
||||
|
||||
attr_allocs.push(alloc);
|
||||
copies.push(copy);
|
||||
break;
|
||||
}
|
||||
Err(PoolError::NoMoreRoom) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO create a new pool if no available pool was found
|
||||
}
|
||||
|
||||
self.staging.queue_copies(copies);
|
||||
|
||||
let alloc = MeshAlloc {
|
||||
attributes: attr_allocs,
|
||||
};
|
||||
let key = self.allocs.write().insert(alloc);
|
||||
let handle = MeshHandle(key);
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
pub fn flush(&self, commands: &mut wgpu::CommandEncoder) {
|
||||
let pools_read = self.pools.read();
|
||||
|
||||
let get_dst = |target: &AttrAllocKey| {
|
||||
pools_read
|
||||
.get(&target.attr)
|
||||
.unwrap()
|
||||
.get(target.pool)
|
||||
.unwrap()
|
||||
.get_copy_dest(target.alloc)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// TODO: keep track of loaded/unloaded meshes
|
||||
let on_complete = |_target| {};
|
||||
|
||||
self.staging.flush(commands, get_dst, on_complete);
|
||||
}
|
||||
|
||||
pub fn iter_meshes<T, I, F>(
|
||||
&self,
|
||||
layout: MeshLayoutId,
|
||||
meshes: I,
|
||||
get_handle: F,
|
||||
) -> Result<Vec<MeshLayoutInstances<T>>, PoolError>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
F: Fn(&T) -> &MeshHandle,
|
||||
{
|
||||
let layout = self
|
||||
.mesh_layouts
|
||||
.read()
|
||||
.get(layout.0)
|
||||
.ok_or(PoolError::LayoutUnregistered)?
|
||||
.clone();
|
||||
|
||||
let layout_attrs: Vec<AttrId> = layout.iter().map(|(id, _)| *id).collect();
|
||||
let mut layouts = Vec::<MeshLayoutInstances<T>>::new();
|
||||
|
||||
let allocs_read = self.allocs.read();
|
||||
let mut attr_allocs: Vec<AttrAllocKey> = Vec::with_capacity(layout_attrs.len());
|
||||
for mesh in meshes {
|
||||
let handle = get_handle(&mesh);
|
||||
let alloc = allocs_read.get(handle.0).ok_or(PoolError::InvalidIndex)?;
|
||||
|
||||
attr_allocs.clear();
|
||||
for layout_attr in layout_attrs.iter() {
|
||||
match alloc
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr_alloc| attr_alloc.attr == *layout_attr)
|
||||
{
|
||||
Some(alloc) => attr_allocs.push(alloc.clone()),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
if attr_allocs.len() != layout_attrs.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut layout_bindings = MeshLayoutBindingIndices::default();
|
||||
let mut alloc_infos = MeshAllocInfos::default();
|
||||
for alloc in attr_allocs.iter() {
|
||||
let pools_read = self.pools.read();
|
||||
let pools = pools_read
|
||||
.get(&alloc.attr)
|
||||
.ok_or(PoolError::AttrUnregistered)?;
|
||||
|
||||
let pool = pools.get(alloc.pool).ok_or(PoolError::InvalidIndex)?;
|
||||
let alloc_info = pool.get(alloc.alloc).ok_or(PoolError::InvalidIndex)?;
|
||||
|
||||
layout_bindings.insert(alloc.attr, alloc.pool);
|
||||
alloc_infos.push((alloc.attr, alloc_info));
|
||||
}
|
||||
|
||||
let instance = (mesh, alloc_infos);
|
||||
|
||||
match layouts
|
||||
.iter_mut()
|
||||
.find(|layout| layout.bindings == layout_bindings)
|
||||
{
|
||||
Some(layout) => layout.instances.push(instance),
|
||||
None => layouts.push(MeshLayoutInstances {
|
||||
bindings: layout_bindings,
|
||||
instances: vec![instance],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(layouts)
|
||||
}
|
||||
|
||||
pub fn get_bindings<'a>(&'a self, indices: MeshLayoutBindingIndices) -> MeshLayoutBindings<'a> {
|
||||
let lock = self.pools.read();
|
||||
MeshLayoutBindings { lock, indices }
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
pub mod mesh;
|
||||
|
||||
use bytemuck::Pod;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct GpuVec<T> {
|
||||
device: Arc<wgpu::Device>,
|
||||
data: Vec<T>,
|
||||
buffer: wgpu::Buffer,
|
||||
capacity: usize,
|
||||
usage: wgpu::BufferUsages,
|
||||
label: Option<String>,
|
||||
realign: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Pod> GpuVec<T> {
|
||||
pub fn new(
|
||||
device: Arc<wgpu::Device>,
|
||||
usage: wgpu::BufferUsages,
|
||||
initial_capacity: usize,
|
||||
label: Option<String>,
|
||||
uses_dynamic_offsets: bool,
|
||||
) -> Self {
|
||||
let capacity_bytes = initial_capacity * std::mem::size_of::<T>();
|
||||
let usage = usage | wgpu::BufferUsages::COPY_DST;
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: label.as_ref().map(|s| s.as_str()),
|
||||
size: capacity_bytes as wgpu::BufferAddress,
|
||||
usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let data = Vec::with_capacity(initial_capacity);
|
||||
|
||||
let realign = if uses_dynamic_offsets {
|
||||
if usage.contains(wgpu::BufferUsages::STORAGE) {
|
||||
Some(device.limits().min_storage_buffer_offset_alignment as usize)
|
||||
} else if usage.contains(wgpu::BufferUsages::UNIFORM) {
|
||||
Some(device.limits().min_uniform_buffer_offset_alignment as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
device,
|
||||
data,
|
||||
buffer,
|
||||
capacity: capacity_bytes,
|
||||
usage,
|
||||
label,
|
||||
realign,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, queue: &wgpu::Queue) -> &wgpu::Buffer {
|
||||
let capacity_bytes = self.buf_offset(self.data.capacity());
|
||||
if capacity_bytes != self.capacity && capacity_bytes != 0 {
|
||||
self.capacity = capacity_bytes;
|
||||
self.buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: self.label.as_ref().map(|s| s.as_str()),
|
||||
size: capacity_bytes as wgpu::BufferAddress,
|
||||
usage: self.usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
}
|
||||
|
||||
let mut realigned = Vec::new();
|
||||
let src_buf = if let Some(realign) = self.realign {
|
||||
realigned.resize(self.data.len() * realign, 0);
|
||||
|
||||
for (index, elem) in self.data.iter().enumerate() {
|
||||
let elem = [elem.clone()];
|
||||
let casted: &[u8] = bytemuck::cast_slice(&elem);
|
||||
let dst_offset = index * realign;
|
||||
realigned[dst_offset..(dst_offset + casted.len())].copy_from_slice(casted);
|
||||
}
|
||||
|
||||
realigned.as_slice()
|
||||
} else {
|
||||
bytemuck::cast_slice(self.data.as_slice())
|
||||
};
|
||||
|
||||
queue.write_buffer(&self.buffer, 0, src_buf);
|
||||
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub fn buf_offset(&self, index: usize) -> usize {
|
||||
if let Some(realign) = self.realign {
|
||||
index * realign
|
||||
} else {
|
||||
index * std::mem::size_of::<T>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Pod> AsRef<wgpu::Buffer> for GpuVec<T> {
|
||||
fn as_ref(&self) -> &wgpu::Buffer {
|
||||
&self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Pod> Deref for GpuVec<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Pod> DerefMut for GpuVec<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use crate::pool::TextureData;
|
||||
|
||||
pub fn load_texture_data(image_raw: &[u8]) -> TextureData {
|
||||
let image_data = image::load_from_memory(image_raw).unwrap();
|
||||
|
||||
use image::GenericImageView;
|
||||
let dimensions = image_data.dimensions();
|
||||
|
||||
let image_rgb = image_data.as_rgb8().unwrap().to_vec();
|
||||
let mut image_rgba = Vec::<u8>::new();
|
||||
for rgb in image_rgb.chunks(3) {
|
||||
image_rgba.extend_from_slice(rgb);
|
||||
image_rgba.push(0xff);
|
||||
}
|
||||
|
||||
let texture_data = TextureData {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
data: image_rgba,
|
||||
};
|
||||
|
||||
texture_data
|
||||
}
|
166
src/viewport.rs
166
src/viewport.rs
|
@ -1,166 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ViewportInfo {
|
||||
pub output_format: wgpu::TextureFormat,
|
||||
pub depth_format: wgpu::TextureFormat,
|
||||
}
|
||||
|
||||
pub struct ViewportViews<'a> {
|
||||
pub output: &'a wgpu::TextureView,
|
||||
pub depth: &'a wgpu::TextureView,
|
||||
}
|
||||
|
||||
pub trait Viewport {
|
||||
fn get_info(&self) -> ViewportInfo;
|
||||
fn get_queue(&self) -> &wgpu::Queue;
|
||||
fn get_views(&self) -> ViewportViews;
|
||||
}
|
||||
|
||||
pub struct WinitViewport {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub size: winit::dpi::PhysicalSize<u32>,
|
||||
surface: wgpu::Surface,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
info: ViewportInfo,
|
||||
depth_texture: wgpu::Texture,
|
||||
depth_texture_view: wgpu::TextureView,
|
||||
surface_texture: Option<wgpu::SurfaceTexture>,
|
||||
output_view: Option<wgpu::TextureView>,
|
||||
}
|
||||
|
||||
impl WinitViewport {
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
pub async fn from_window(window: &winit::window::Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
|
||||
let surface = unsafe { instance.create_surface(window) };
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::LowPower,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
label: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: *surface.get_supported_formats(&adapter).first().unwrap(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let device = Arc::new(device);
|
||||
let queue = Arc::new(queue);
|
||||
|
||||
let info = ViewportInfo {
|
||||
output_format: config.format,
|
||||
depth_format: Self::DEPTH_FORMAT,
|
||||
};
|
||||
|
||||
let (depth_texture, depth_texture_view) = Self::make_depth_texture(&device, &config);
|
||||
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
size,
|
||||
surface,
|
||||
config,
|
||||
info,
|
||||
depth_texture,
|
||||
depth_texture_view,
|
||||
surface_texture: None,
|
||||
output_view: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acquire(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
if self.output_view.is_none() {
|
||||
let surface_texture = self.surface.get_current_texture()?;
|
||||
self.output_view = Some(
|
||||
surface_texture
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||
);
|
||||
self.surface_texture = Some(surface_texture);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
let (depth_texture, depth_texture_view) =
|
||||
Self::make_depth_texture(&self.device, &self.config);
|
||||
self.depth_texture = depth_texture;
|
||||
self.depth_texture_view = depth_texture_view;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn present(&mut self) {
|
||||
if let Some(surface_texture) = self.surface_texture.take() {
|
||||
surface_texture.present();
|
||||
self.output_view.take();
|
||||
}
|
||||
}
|
||||
|
||||
fn make_depth_texture(
|
||||
device: &wgpu::Device,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
) -> (wgpu::Texture, wgpu::TextureView) {
|
||||
let size = wgpu::Extent3d {
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some("Depth Texture"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
(texture, view)
|
||||
}
|
||||
}
|
||||
|
||||
impl Viewport for WinitViewport {
|
||||
fn get_info(&self) -> ViewportInfo {
|
||||
self.info.clone()
|
||||
}
|
||||
|
||||
fn get_queue(&self) -> &wgpu::Queue {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
fn get_views(&self) -> ViewportViews {
|
||||
let output = self.output_view.as_ref().unwrap();
|
||||
ViewportViews {
|
||||
output,
|
||||
depth: &self.depth_texture_view,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue