Compare commits
No commits in common. "main" and "marching-cubes" have entirely different histories.
main
...
marching-c
|
@ -1,2 +1 @@
|
||||||
/target
|
/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]
|
[package]
|
||||||
name = "cyborg"
|
name = "cyborg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = { version = "^1.0", features = ["derive"] }
|
bytemuck = { version="1.7", features=["derive"] }
|
||||||
glam = "0.20"
|
glam = "0.20"
|
||||||
multimap = "0.8"
|
gltf = { version="1.0", features=["utils"] }
|
||||||
noise = "^0.7"
|
image = "0.24"
|
||||||
notify = "^4"
|
|
||||||
parking_lot = "^0.11"
|
|
||||||
pollster = "0.2"
|
pollster = "0.2"
|
||||||
puffin = "^0.13"
|
slab = "0.4"
|
||||||
rand = "^0.8"
|
tobj = "3.0"
|
||||||
rayon = "1"
|
wgpu = "0.12"
|
||||||
slab = "^0.4"
|
|
||||||
smallmap = "^1.0"
|
|
||||||
smallvec = "^1.0"
|
|
||||||
strum = { version = "0.24", features = ["derive"] }
|
|
||||||
wgpu = "^0.13"
|
|
||||||
winit = "0.26"
|
winit = "0.26"
|
||||||
|
naga = { version = "0.8.5", features = ["wgsl-in", "glsl-in", "wgsl-out", "serialize", "deserialize"] }
|
||||||
[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"]
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
> `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
|
|
|
@ -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 glam::{Mat4, Quat, Vec2, Vec3};
|
||||||
use std::f32::consts::LN_2;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, VirtualKeyCode};
|
use winit::event::{ElementState, VirtualKeyCode};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
pub trait Camera {
|
||||||
pub struct Camera {
|
fn get_eye(&self) -> [f32; 4];
|
||||||
pub eye: [f32; 4],
|
fn get_vp(&self) -> [[f32; 4]; 4];
|
||||||
pub vp: [[f32; 4]; 4],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Flycam {
|
pub struct Flycam {
|
||||||
// input
|
// input
|
||||||
// currently held keys
|
is_up_pressed: bool,
|
||||||
is_world_up_pressed: bool,
|
is_down_pressed: bool,
|
||||||
is_world_down_pressed: bool,
|
|
||||||
is_cam_up_pressed: bool,
|
|
||||||
is_cam_down_pressed: bool,
|
|
||||||
is_forward_pressed: bool,
|
is_forward_pressed: bool,
|
||||||
is_backward_pressed: bool,
|
is_backward_pressed: bool,
|
||||||
is_left_pressed: bool,
|
is_left_pressed: bool,
|
||||||
is_right_pressed: bool,
|
is_right_pressed: bool,
|
||||||
// accumulated mouse movement yet to be processed
|
|
||||||
mouse_dx: f32,
|
mouse_dx: f32,
|
||||||
mouse_dy: f32,
|
mouse_dy: f32,
|
||||||
|
|
||||||
// state
|
// state
|
||||||
// timestamp
|
|
||||||
last_update: Instant,
|
last_update: Instant,
|
||||||
// camera orientation
|
pan: f32,
|
||||||
euler_x: f32,
|
tilt: f32,
|
||||||
euler_y: f32,
|
|
||||||
// camera movement state
|
|
||||||
velocity: Vec3,
|
|
||||||
position: Vec3,
|
position: Vec3,
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
// camera movement
|
speed: f32,
|
||||||
turn_sensitivity: f32, // coefficient for mouse_dx/dy -> euler_x/y
|
turn_speed: f32,
|
||||||
thrust_mag: f32, // coefficient for thrust acceleration vector
|
|
||||||
damping_coeff: f32, // coefficient for damping acceleration vector
|
|
||||||
// camera frustum
|
|
||||||
aspect: f32,
|
aspect: f32,
|
||||||
fovy: f32,
|
fovy: f32,
|
||||||
znear: f32,
|
znear: f32,
|
||||||
|
@ -48,14 +32,10 @@ pub struct Flycam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flycam {
|
impl Flycam {
|
||||||
/// thrust_speed: top speed when using a single thruster, in units/second
|
pub fn new(speed: f32, turn_speed: f32) -> Self {
|
||||||
/// 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 {
|
|
||||||
Self {
|
Self {
|
||||||
is_world_up_pressed: false,
|
is_up_pressed: false,
|
||||||
is_world_down_pressed: false,
|
is_down_pressed: false,
|
||||||
is_cam_up_pressed: false,
|
|
||||||
is_cam_down_pressed: false,
|
|
||||||
is_forward_pressed: false,
|
is_forward_pressed: false,
|
||||||
is_backward_pressed: false,
|
is_backward_pressed: false,
|
||||||
is_left_pressed: false,
|
is_left_pressed: false,
|
||||||
|
@ -63,13 +43,11 @@ impl Flycam {
|
||||||
mouse_dx: 0.0,
|
mouse_dx: 0.0,
|
||||||
mouse_dy: 0.0,
|
mouse_dy: 0.0,
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
euler_x: 0.0,
|
pan: 0.0,
|
||||||
euler_y: 0.0,
|
tilt: 0.0,
|
||||||
velocity: Vec3::new(0.0, 0.0, 0.0),
|
|
||||||
position: Vec3::new(0.0, 0.5, 1.0),
|
position: Vec3::new(0.0, 0.5, 1.0),
|
||||||
turn_sensitivity,
|
speed,
|
||||||
thrust_mag: thrust_speed / damper_half_life * LN_2,
|
turn_speed,
|
||||||
damping_coeff: LN_2 / damper_half_life,
|
|
||||||
aspect: 1.0, // TODO compute from size
|
aspect: 1.0, // TODO compute from size
|
||||||
fovy: std::f32::consts::FRAC_PI_2,
|
fovy: std::f32::consts::FRAC_PI_2,
|
||||||
znear: 0.01,
|
znear: 0.01,
|
||||||
|
@ -79,21 +57,14 @@ impl Flycam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flycam {
|
impl Flycam {
|
||||||
/// update stored keyboard state for use in update()
|
|
||||||
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) {
|
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) {
|
||||||
let is_pressed = state == ElementState::Pressed;
|
let is_pressed = state == ElementState::Pressed;
|
||||||
match key {
|
match key {
|
||||||
VirtualKeyCode::Space => {
|
VirtualKeyCode::Space => {
|
||||||
self.is_world_up_pressed = is_pressed;
|
self.is_up_pressed = is_pressed;
|
||||||
}
|
}
|
||||||
VirtualKeyCode::LShift => {
|
VirtualKeyCode::LShift => {
|
||||||
self.is_world_down_pressed = is_pressed;
|
self.is_down_pressed = is_pressed;
|
||||||
}
|
|
||||||
VirtualKeyCode::Q => {
|
|
||||||
self.is_cam_down_pressed = is_pressed;
|
|
||||||
}
|
|
||||||
VirtualKeyCode::E => {
|
|
||||||
self.is_cam_up_pressed = is_pressed;
|
|
||||||
}
|
}
|
||||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||||
self.is_forward_pressed = is_pressed;
|
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) {
|
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
|
||||||
self.mouse_dx += mouse_dx as f32;
|
self.mouse_dx += mouse_dx as f32;
|
||||||
self.mouse_dy += mouse_dy as f32;
|
self.mouse_dy += mouse_dy as f32;
|
||||||
|
@ -121,128 +91,73 @@ impl Flycam {
|
||||||
self.aspect = (width as f32) / (height as f32);
|
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) {
|
pub fn update(&mut self) {
|
||||||
let dt = self.last_update.elapsed();
|
let dt = self.last_update.elapsed();
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
let dt = dt.as_micros() as f32 / 1_000_000.0;
|
let dt = dt.as_micros() as f32 / 1_000_000.0;
|
||||||
|
|
||||||
self.update_orientation(dt);
|
let t = self.turn_speed;
|
||||||
self.update_kinematic(dt);
|
self.pan += t * self.mouse_dx;
|
||||||
}
|
self.tilt += t * self.mouse_dy;
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
self.mouse_dx = 0.0;
|
self.mouse_dx = 0.0;
|
||||||
self.mouse_dy = 0.0;
|
self.mouse_dy = 0.0;
|
||||||
|
|
||||||
// Clamp euler_x to [-pi/2, pi/2]
|
let tilt_limit = std::f32::consts::FRAC_PI_2;
|
||||||
let euler_x_limit = std::f32::consts::FRAC_PI_2;
|
if self.tilt < -tilt_limit {
|
||||||
if self.euler_x < -euler_x_limit {
|
self.tilt = -tilt_limit;
|
||||||
self.euler_x = -euler_x_limit;
|
} else if self.tilt > tilt_limit {
|
||||||
} else if self.euler_x > euler_x_limit {
|
self.tilt = tilt_limit;
|
||||||
self.euler_x = euler_x_limit;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// update velocity and position from acceleration using forward differences
|
let s = dt * self.speed;
|
||||||
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 axis = Self::key_axis;
|
let axis = Self::key_axis;
|
||||||
|
let truck = s * axis(self.is_backward_pressed, self.is_forward_pressed);
|
||||||
let thruster_cam_x = axis(self.is_left_pressed, self.is_right_pressed);
|
let dolly = s * axis(self.is_right_pressed, self.is_left_pressed);
|
||||||
let thruster_cam_y = axis(self.is_cam_down_pressed, self.is_cam_up_pressed);
|
let boom = s * axis(self.is_down_pressed, self.is_up_pressed);
|
||||||
let thruster_cam_z = -axis(self.is_backward_pressed, self.is_forward_pressed); // forward is -z
|
self.move_position(truck, dolly, boom);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 {
|
fn key_axis(negative: bool, positive: bool) -> f32 {
|
||||||
if negative {
|
if negative {
|
||||||
if positive {
|
if positive {
|
||||||
0.0 // positive + negative cancel out
|
0.0
|
||||||
} else {
|
} else {
|
||||||
-1.0 // negative only
|
-1.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if positive {
|
if positive {
|
||||||
1.0 // positive only
|
1.0
|
||||||
} else {
|
} else {
|
||||||
0.0 // neutral
|
0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the current camera orientation, which can be seen as a rotation (in quaternion form) from
|
fn move_position(&mut self, truck: f32, dolly: f32, boom: f32) {
|
||||||
/// camera axes to world axes
|
// truck direction from straight down
|
||||||
/// glam's YXZ ordering matches the standard roll-pitch-yaw Euler angles
|
let h = Vec2::new(self.pan.sin(), -self.pan.cos());
|
||||||
fn get_orientation(&self) -> glam::Quat {
|
// truck direction from the side
|
||||||
Quat::from_euler(glam::EulerRot::YXZ, self.euler_y, self.euler_x, 0.0)
|
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 {
|
let dolly_to = Vec3::new(-self.pan.cos(), 0.0, -self.pan.sin());
|
||||||
Camera {
|
|
||||||
eye: self.get_eye(),
|
|
||||||
vp: self.get_vp(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
self.position.extend(0.0).to_array()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_vp(&self) -> [[f32; 4]; 4] {
|
fn get_vp(&self) -> [[f32; 4]; 4] {
|
||||||
// view matrix is inverted camera pose (world space to camera space)
|
let orientation = Quat::from_euler(glam::EulerRot::XYZ, self.tilt, self.pan, 0.0);
|
||||||
let rotation = Mat4::from_quat(self.get_orientation().inverse());
|
let rotation = Mat4::from_quat(orientation);
|
||||||
let translation = Mat4::from_translation(-self.position);
|
let view = rotation * Mat4::from_translation(-self.position);
|
||||||
let view = rotation * translation;
|
|
||||||
|
|
||||||
// perspective projection
|
|
||||||
let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar);
|
let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar);
|
||||||
|
|
||||||
let vp = proj * view;
|
let vp = proj * view;
|
||||||
vp.to_cols_array_2d()
|
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
|
pub mod logger;
|
||||||
//! in Rust.
|
pub mod shader;
|
||||||
//!
|
|
||||||
//! # 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()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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::{
|
use winit::{
|
||||||
event::*,
|
event::*,
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DeltaTime {
|
mod camera;
|
||||||
pub dt: f32,
|
mod commands;
|
||||||
last_update: std::time::Instant,
|
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 {
|
struct Grid {
|
||||||
fn default() -> Self {
|
meshes: Vec<MeshInstance>,
|
||||||
DeltaTime {
|
}
|
||||||
dt: 0.0,
|
|
||||||
last_update: std::time::Instant::now(),
|
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 {
|
impl WorldState for Grid {
|
||||||
theta: rand::distributions::Uniform<f32>,
|
fn update(&mut self) {}
|
||||||
radius: rand::distributions::Uniform<f32>,
|
|
||||||
up: rand::distributions::Uniform<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SpawnDistributions {
|
fn render(&self) -> Vec<MeshInstance> {
|
||||||
fn default() -> Self {
|
self.meshes.clone()
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Particle {
|
struct Metaballs {
|
||||||
position: glam::Vec3A,
|
mesh: MeshInstance,
|
||||||
mass: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Velocity {
|
impl Metaballs {
|
||||||
linear: glam::Vec3A,
|
fn new(mut ren: &mut Renderer) -> Self {
|
||||||
}
|
use procgen::marching_cubes::*;
|
||||||
|
|
||||||
#[system]
|
let metaballs = vec![
|
||||||
fn update_dt(#[resource] dt: &mut DeltaTime) {
|
glam::Vec3A::new(-5., -5., 2.),
|
||||||
dt.dt = dt.last_update.elapsed().as_secs_f32();
|
glam::Vec3A::new(8., 0.0, -1.),
|
||||||
dt.last_update = std::time::Instant::now();
|
glam::Vec3A::new(1., 5., -3.),
|
||||||
}
|
];
|
||||||
|
|
||||||
#[system]
|
let field = |x: i32, y: i32, z: i32| {
|
||||||
fn update_app(#[resource] app: &mut Application) {
|
let c = glam::Vec3A::new(x as f32, y as f32, z as f32);
|
||||||
app.update();
|
let mut sum = 0.0;
|
||||||
}
|
for ball in metaballs.iter() {
|
||||||
|
sum += 1.0 / ball.distance(c);
|
||||||
|
}
|
||||||
|
|
||||||
#[system(for_each)]
|
sum - 0.4
|
||||||
fn apply_gravity(#[resource] dt: &DeltaTime, velocity: &mut Velocity) {
|
};
|
||||||
const GRAVITY: f32 = 6.0;
|
|
||||||
velocity.linear.y -= GRAVITY * dt.dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[system(for_each)]
|
let r = 20;
|
||||||
fn move_particles(#[resource] dt: &DeltaTime, particle: &mut Particle, velocity: &Velocity) {
|
let domain = MarchDomain {
|
||||||
particle.position += velocity.linear * (dt.dt / particle.mass);
|
min: Vec3::new(-r, -r, -r),
|
||||||
// TODO angular velocity
|
max: Vec3::new(r, r, r),
|
||||||
}
|
};
|
||||||
|
|
||||||
#[system(for_each)]
|
let vertices = marching_cubes(field, &domain);
|
||||||
fn respawn_particles(
|
let vertices: Vec<mesh::Vertex> = vertices
|
||||||
#[resource] distributions: &SpawnDistributions,
|
.iter()
|
||||||
particle: &mut Particle,
|
.map(|v| mesh::Vertex {
|
||||||
velocity: &mut Velocity,
|
position: v.position.into(),
|
||||||
) {
|
normal: v.normal.into(),
|
||||||
if particle.position.y < -10.0 {
|
tex_coords: [0.0; 2],
|
||||||
particle.position = glam::Vec3A::ZERO;
|
})
|
||||||
let rng = &mut rand::thread_rng();
|
.collect();
|
||||||
velocity.linear.y = distributions.up.sample(rng);
|
|
||||||
|
|
||||||
let theta = distributions.theta.sample(rng);
|
let indices = (0..(vertices.len() as u32)).collect();
|
||||||
let radius = distributions.radius.sample(rng);
|
|
||||||
velocity.linear.x = theta.sin() * radius;
|
let mesh_data = mesh::MeshData {
|
||||||
velocity.linear.z = theta.cos() * radius;
|
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)]
|
impl WorldState for Metaballs {
|
||||||
fn update_transforms(transform: &mut cyborg::scene::Transform, particle: &Particle) {
|
fn update(&mut self) {}
|
||||||
transform.transform = glam::Mat4::from_translation(particle.position.into());
|
|
||||||
|
fn render(&self) -> Vec<MeshInstance> {
|
||||||
|
vec![self.mesh]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[system(for_each)]
|
struct Planet {
|
||||||
fn update_debug(draw_list: &mut cyborg::scene::DebugDrawList, velocity: &Velocity) {
|
speed: f32,
|
||||||
draw_list.clear();
|
offset: f32,
|
||||||
draw_list.indices.extend_from_slice(&[0, 1]);
|
radius: f32,
|
||||||
|
size: f32,
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_update_schedule() -> Schedule {
|
struct Planets {
|
||||||
let mut builder = Schedule::builder();
|
start: std::time::Instant,
|
||||||
builder.add_system(update_dt_system());
|
planets: Vec<Planet>,
|
||||||
builder.add_system(update_app_system());
|
model: GltfModel,
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_terrain(attributes: &mesh::Attributes) -> MeshBuffer {
|
impl Planets {
|
||||||
const MOUNTAINS_RADIUS: f32 = 25.0;
|
fn new(ren: &mut Renderer) -> Self {
|
||||||
const MOUNTAINS_WIDTH: f32 = 10.0;
|
let start = std::time::Instant::now();
|
||||||
const MOUNTAINS_HEIGHT: f32 = 5.0;
|
let model = GltfModel::load(ren);
|
||||||
const MOUNTAINS_FREQUENCY: f32 = 1.0;
|
|
||||||
|
|
||||||
use noise::NoiseFn;
|
let mut planets = Vec::new();
|
||||||
let noise = noise::OpenSimplex::new();
|
for i in 0..10 {
|
||||||
|
let i = i as f32;
|
||||||
let sample = |xy: glam::Vec2| -> f32 {
|
planets.push(Planet {
|
||||||
let mountains_dist = (xy.length() - MOUNTAINS_RADIUS).abs() / MOUNTAINS_WIDTH;
|
speed: 1.618 * 1.5 / i,
|
||||||
let mountains_scale = 1.0 - mountains_dist;
|
offset: 0.0,
|
||||||
if mountains_scale > 0.0 {
|
radius: i * 2.0,
|
||||||
let noise_input = (xy * MOUNTAINS_FREQUENCY).as_dvec2();
|
size: 0.5,
|
||||||
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 cursor: mesh::Index = 0;
|
Self {
|
||||||
for _x in -GRID_RADIUS..GRID_RADIUS {
|
start,
|
||||||
for _z in -GRID_RADIUS..GRID_RADIUS {
|
planets,
|
||||||
let next_line = cursor + GRID_WIDTH as mesh::Index;
|
model,
|
||||||
indices.extend_from_slice(&[
|
|
||||||
cursor,
|
|
||||||
next_line,
|
|
||||||
cursor + 1,
|
|
||||||
next_line,
|
|
||||||
cursor + 1,
|
|
||||||
next_line + 1,
|
|
||||||
]);
|
|
||||||
cursor += 1;
|
|
||||||
}
|
}
|
||||||
cursor += 1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let vertices = AttrBuffer {
|
impl WorldState for Planets {
|
||||||
id: attributes.vertex,
|
fn update(&mut self) {}
|
||||||
count: vertices.len(),
|
|
||||||
data: bytemuck::cast_slice(&vertices).to_vec(),
|
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 {
|
Renderer::new(size, surface, device, queue, config)
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
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 lights = vec![
|
||||||
let mut resources = Resources::default();
|
PointLight {
|
||||||
|
center: glam::Vec3A::new(0.0, 5.0, 0.0),
|
||||||
resources.insert::<DeltaTime>(Default::default());
|
intensity: glam::Vec3A::new(100.0, 100.0, 100.0),
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
mesh::Vertex {
|
PointLight {
|
||||||
position: [0.5, 0.0, 0.5],
|
center: glam::Vec3A::new(-7.0, 5.0, 7.0),
|
||||||
tan_frame: 0,
|
intensity: glam::Vec3A::new(100.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
mesh::Vertex {
|
PointLight {
|
||||||
position: [0.0, -0.5, -0.5],
|
center: glam::Vec3A::new(7.0, 5.0, 7.0),
|
||||||
tan_frame: 0,
|
intensity: glam::Vec3A::new(0.0, 100.0, 0.0),
|
||||||
},
|
},
|
||||||
mesh::Vertex {
|
PointLight {
|
||||||
position: [0.0, 0.5, -0.5],
|
center: glam::Vec3A::new(0.0, 5.0, -7.0),
|
||||||
tan_frame: 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_loop.run(move |event, _, control_flow| match event {
|
||||||
Event::RedrawRequested(_) => {
|
Event::RedrawRequested(_) => {
|
||||||
let mut application = resources.get_mut::<Application>().unwrap();
|
let scene = Scene {
|
||||||
match application.viewport.acquire() {
|
meshes: &state.render(),
|
||||||
Err(wgpu::SurfaceError::Lost) => application.on_surface_lost(),
|
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(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||||
Err(e) => eprintln!("error: {:?}", e),
|
Err(wgpu::SurfaceError::Timeout) => {}
|
||||||
Ok(_) => {
|
Err(e) => println!("error: {:?}", e),
|
||||||
drop(application);
|
};
|
||||||
render_schedule.execute(&mut world, &mut resources);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
update_schedule.execute(&mut world, &mut resources);
|
camera.update();
|
||||||
shader_watcher.watch();
|
state.update();
|
||||||
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
Event::DeviceEvent { ref event, .. } => match event {
|
Event::DeviceEvent { ref event, .. } => match event {
|
||||||
DeviceEvent::MouseMotion { delta } => {
|
DeviceEvent::MouseMotion { delta } => {
|
||||||
let mut application = resources.get_mut::<Application>().unwrap();
|
if is_grabbed {
|
||||||
application.on_mouse_motion(delta.0, delta.1);
|
camera.process_mouse(delta.0, delta.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
ref event,
|
ref event,
|
||||||
window_id,
|
window_id,
|
||||||
} => {
|
} if window_id == window.id() => match event {
|
||||||
let mut application = resources.get_mut::<Application>().unwrap();
|
WindowEvent::KeyboardInput {
|
||||||
if window_id == application.window.id() {
|
input:
|
||||||
match event {
|
KeyboardInput {
|
||||||
WindowEvent::KeyboardInput {
|
virtual_keycode: Some(key),
|
||||||
input:
|
state,
|
||||||
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, .. } => {
|
} else {
|
||||||
application.on_mouse_event(*button, *state);
|
camera.process_keyboard(*key, *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();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
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.
|
use super::handle::*;
|
||||||
//!
|
|
||||||
//! TODO this will all need to be replaced in favor of a way to represent
|
|
||||||
//! querying component and resource data framework-agnostically
|
|
||||||
|
|
||||||
use crate::storage::mesh::MeshHandle;
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub struct MeshInstance {
|
||||||
#[derive(Clone, Debug)]
|
pub mesh: MeshHandle,
|
||||||
pub struct Transform {
|
pub material: MaterialHandle,
|
||||||
pub transform: glam::Mat4,
|
pub transform: glam::Mat4,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub struct Mesh {
|
pub struct PointLight {
|
||||||
pub mesh: MeshHandle,
|
pub center: glam::Vec3A,
|
||||||
|
pub intensity: glam::Vec3A,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
pub struct Scene<'a> {
|
||||||
pub struct DebugVertex {
|
pub meshes: &'a [MeshInstance],
|
||||||
pub position: [f32; 3],
|
pub point_lights: &'a [PointLight],
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
346
src/shader.rs
346
src/shader.rs
|
@ -1,258 +1,88 @@
|
||||||
use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
use std::fs::{File, read_to_string};
|
||||||
use parking_lot::{RwLock, RwLockReadGuard};
|
|
||||||
use slab::Slab;
|
pub fn parse_wgsl(source: &String) -> naga::Module {
|
||||||
use std::collections::{BTreeSet, HashMap};
|
// Create Parser
|
||||||
use std::fs::read_to_string;
|
let mut parser = naga::front::wgsl::Parser::new();
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::mpsc::{channel, Receiver, TryRecvError};
|
// Create empty Module
|
||||||
use std::sync::Arc;
|
let mut module = naga::Module::default();
|
||||||
|
|
||||||
#[repr(transparent)]
|
// Attempt to parse the source code
|
||||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
let module_result = parser.parse(source.as_str());
|
||||||
pub struct ShaderHandle(usize);
|
match module_result {
|
||||||
|
Ok(in_mod) => {
|
||||||
pub struct ShaderStoreReadGuard<'a> {
|
//println!("{:?}", in_mod);
|
||||||
guard: RwLockReadGuard<'a, Slab<wgpu::ShaderModule>>,
|
module = in_mod;
|
||||||
handle: ShaderHandle,
|
},
|
||||||
}
|
Err(error) => { println!("Parsing error:\n{}", error) }
|
||||||
|
}
|
||||||
impl<'a> AsRef<wgpu::ShaderModule> for ShaderStoreReadGuard<'a> {
|
|
||||||
fn as_ref(&self) -> &wgpu::ShaderModule {
|
// Return the Module
|
||||||
self.guard.get(self.handle.0).unwrap()
|
module
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn generate_wgsl(module: &naga::Module) -> String {
|
||||||
#[derive(Debug)]
|
// Create options for the Writer
|
||||||
pub enum ShaderError {
|
let wgsl_flags = naga::back::wgsl::WriterFlags::empty();
|
||||||
InvalidHandle,
|
|
||||||
}
|
// Create a string buffer for the Writer to use
|
||||||
|
let mut wgsl_buffer = String::new();
|
||||||
pub struct ShaderStore {
|
|
||||||
device: Arc<wgpu::Device>,
|
// Create validator (to check if module is OK)
|
||||||
shaders: RwLock<Slab<wgpu::ShaderModule>>,
|
let mut validator = naga::valid::Validator::new(naga::valid::ValidationFlags::empty(), naga::valid::Capabilities::empty());
|
||||||
}
|
|
||||||
|
// Get ModuleInfo (produced by a syntax validator)
|
||||||
impl ShaderStore {
|
let module_info = validator.validate(&module).unwrap();
|
||||||
pub fn new(device: Arc<wgpu::Device>) -> Self {
|
|
||||||
Self {
|
// Create the Writer itself
|
||||||
device,
|
let mut wgsl_writer = naga::back::wgsl::Writer::new(&mut wgsl_buffer, wgsl_flags);
|
||||||
shaders: Default::default(),
|
|
||||||
}
|
// Attempt to write
|
||||||
}
|
wgsl_writer.write(&module, &module_info).expect("wgsl write failed");
|
||||||
|
|
||||||
pub fn load(&self, module: &naga::Module) -> Result<ShaderHandle, ShaderError> {
|
wgsl_buffer
|
||||||
let source = self.generate_wgsl(module)?;
|
}
|
||||||
let shader = self.load_wgsl(source)?;
|
|
||||||
let index = self.shaders.write().insert(shader);
|
pub fn add_includes(source: &String) -> String {
|
||||||
Ok(ShaderHandle(index))
|
let mut combined = String::new();
|
||||||
}
|
|
||||||
|
let source_lines = source.lines();
|
||||||
pub fn reload(&self, handle: &ShaderHandle, module: &naga::Module) -> Result<(), ShaderError> {
|
for line in source_lines {
|
||||||
let mut write = self.shaders.write();
|
// Get a vector of the words in the line
|
||||||
match write.get_mut(handle.0) {
|
let mut words: Vec<&str> = line.split(" ").collect();
|
||||||
Some(stored) => {
|
|
||||||
let source = self.generate_wgsl(module)?;
|
// Check if this is an include statement
|
||||||
let shader = self.load_wgsl(source)?;
|
if words[0] == "#include" {
|
||||||
let _old = std::mem::replace(stored, shader);
|
// Get the rest of the line
|
||||||
Ok(())
|
words.remove(0);
|
||||||
}
|
let file_path: String = words.join(" ");
|
||||||
None => Err(ShaderError::InvalidHandle),
|
|
||||||
}
|
// Get the code from the included file
|
||||||
}
|
let included = read_to_string(file_path).unwrap();
|
||||||
|
|
||||||
fn generate_wgsl(&self, module: &naga::Module) -> Result<String, ShaderError> {
|
combined = format!("{}\n{}", combined, included);
|
||||||
// TODO handle all the errors that can happen here
|
} else {
|
||||||
use naga::back::wgsl::{Writer, WriterFlags};
|
combined = format!("{}\n{}", combined, line);
|
||||||
use naga::valid::{Capabilities, ValidationFlags, Validator};
|
}
|
||||||
|
}
|
||||||
let validation_flags = ValidationFlags::all();
|
|
||||||
let capabilities = Capabilities::empty();
|
combined
|
||||||
let mut validator = Validator::new(validation_flags, capabilities);
|
}
|
||||||
let module_info = validator.validate(&module).unwrap();
|
|
||||||
|
#[cfg(test)]
|
||||||
let wgsl_flags = WriterFlags::empty();
|
mod tests {
|
||||||
let mut wgsl_buffer = String::new();
|
use super::*;
|
||||||
let mut wgsl_writer = Writer::new(&mut wgsl_buffer, wgsl_flags);
|
|
||||||
wgsl_writer
|
#[test]
|
||||||
.write(&module, &module_info)
|
fn preprocess_file() {
|
||||||
.expect("wgsl write failed");
|
// Generate a shader and preprocess it
|
||||||
|
let mut source = read_to_string("src/shader.wgsl").unwrap();
|
||||||
Ok(wgsl_buffer)
|
source = add_includes(&source);
|
||||||
}
|
|
||||||
|
// Parse the WGSL into a usable module
|
||||||
fn load_wgsl(&self, wgsl_source: String) -> Result<wgpu::ShaderModule, ShaderError> {
|
let module = parse_wgsl(&source);
|
||||||
let shader = self
|
|
||||||
.device
|
// Generate a valid WGSL string from the module
|
||||||
.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let gen_wgsl = generate_wgsl(&module);
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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