Compare commits
No commits in common. 'main' and 'testing' have entirely different histories.
64 changed files with 2332 additions and 4220 deletions
@ -1,674 +0,0 @@ |
|||||||
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. |
|
||||||
|
|
||||||
Tangerine |
|
||||||
Copyright (C) 2021 cinnaboot |
|
||||||
|
|
||||||
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) 2021 cinnaboot |
|
||||||
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>. |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
|
|
||||||
Tangerine |
|
||||||
|
|
||||||
A small OpenGL 3+ renderer and game engine |
|
||||||
NOTE: This is still a work in progress |
|
||||||
|
|
||||||
Features: |
|
||||||
vertices |
|
||||||
pixels |
|
||||||
|
|
||||||
TODO: |
|
||||||
store meshes separately from entities for reuse |
|
||||||
linear memory allocator for assets (branch "render_group_fix") |
|
||||||
maybe update UTIL_ALLOC macro to use arena allocator? |
|
||||||
fix lighting normal bugs (branch "normal_bug") |
|
||||||
finish node animations from assimp |
|
||||||
add input abstraction for SDL |
|
||||||
add a function to update camera transforms only once per frame, per shader |
|
||||||
update camera to use quaternions for rotation |
|
||||||
re-combine simple/default meshes/render_objects |
|
||||||
|
|
||||||
Dependencies: |
|
||||||
glew |
|
||||||
glm |
|
||||||
SDL2 |
|
||||||
stb_image |
|
||||||
TinyGLTF |
|
||||||
|
|
||||||
Build Instructions: |
|
||||||
install dependencies: |
|
||||||
Arch) |
|
||||||
$ sudo pacman -S glew glm sdl2 gcc make |
|
||||||
Fedora) |
|
||||||
$ sudo dnf install glew-devel glm-devel SDL2-devel gcc-c++ make |
|
||||||
checkout, and initialize submodules: (stb_image, tinygltf) |
|
||||||
$ git submodule init |
|
||||||
$ git submodule update |
|
||||||
(optional) checkout large files for running examples: |
|
||||||
(NOTE: https://git-lfs.github.com/) |
|
||||||
$ git lfs pull |
|
||||||
build: |
|
||||||
$ make |
|
||||||
|
|
||||||
examples will be in examples/bin |
|
||||||
|
|
||||||
@ -0,0 +1,22 @@ |
|||||||
|
|
||||||
|
Tangerine |
||||||
|
|
||||||
|
A small OpenGL 3+ renderer and game engine |
||||||
|
NOTE: This is still a work in progress |
||||||
|
|
||||||
|
Features: |
||||||
|
vertices |
||||||
|
pixels |
||||||
|
|
||||||
|
TODO: |
||||||
|
finish node animations from assimp |
||||||
|
add input abstraction for SDL |
||||||
|
store meshes separately from entities for reuse |
||||||
|
add a function to update camera transforms only once per frame, per shader |
||||||
|
|
||||||
|
Dependencies: |
||||||
|
assimp |
||||||
|
glew |
||||||
|
glm |
||||||
|
SDL2 |
||||||
|
stb_image |
||||||
@ -1,45 +1,36 @@ |
|||||||
|
|
||||||
SHELL = /bin/sh
|
|
||||||
CXX = g++
|
CXX = g++
|
||||||
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall \
|
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -I../include -I/usr/include/SDL2
|
||||||
-I../include \
|
LDFLAGS = -lSDL2 -lGLEW -lGL -lassimp
|
||||||
-I/usr/include/SDL2 \
|
|
||||||
-I../ext/tinygltf
|
|
||||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
|
||||||
OBJDIR = build
|
OBJDIR = build
|
||||||
LIB = ../build/libTangerine.a
|
LIB = ../build/libTangerine.a
|
||||||
BINDIR = bin
|
BINDIR = bin
|
||||||
|
|
||||||
#EXAMPLE_SOURCES = \
|
|
||||||
# render_groups/main.cpp \
|
all: mkdirs \
|
||||||
# #assimp_loading/main.cpp
|
$(OBJDIR)/hello_world.o \
|
||||||
# #hello_world/main.cpp \
|
$(OBJDIR)/assimp_loading.o \
|
||||||
# #simple_mesh/main.cpp
|
$(OBJDIR)/simple_mesh.o
|
||||||
#EXAMPLE_OBJECTS = $(patsubst %/, $(OBJDIR)/%.o, $(dir $(EXAMPLE_SOURCES)))
|
.PHONY: all |
||||||
#
|
|
||||||
#all: mkdirs $(EXAMPLE_OBJECTS)
|
-include $(OBJDIR)/*.d |
||||||
#
|
|
||||||
#-include $(OBJDIR)/*.d
|
|
||||||
#
|
|
||||||
#$(EXAMPLE_OBJECTS): $(LIB)
|
|
||||||
# $(CXX) $(CXXFLAGS) -c -MMD $(basename $(notdir $@))/main.cpp -o $@
|
|
||||||
# $(CXX) -o $(BINDIR)/$(notdir $(basename $@)) $(LDFLAGS) $@ $(LIB)
|
|
||||||
#
|
|
||||||
#mkdirs:
|
|
||||||
# @mkdir -p $(BINDIR) $(OBJDIR)
|
|
||||||
#.PHONY: mkdirs
|
|
||||||
#
|
|
||||||
#clean:
|
|
||||||
# rm -rf $(OBJDIR)/* bin/*
|
|
||||||
#.PHONY: clean
|
|
||||||
|
|
||||||
all: mkdirs |
|
||||||
$(CXX) $(CXXFLAGS) main.cpp -o $(BINDIR)/testing $(LIB) $(LDFLAGS)
|
|
||||||
|
|
||||||
mkdirs: |
mkdirs: |
||||||
@mkdir -p $(BINDIR) $(OBJDIR)
|
mkdir -p $(BINDIR) $(OBJDIR)
|
||||||
.PHONY: mkdirs |
.PHONY: mkdirs |
||||||
|
|
||||||
|
$(OBJDIR)/hello_world.o: $(LIB) |
||||||
|
$(CXX) $(CXXFLAGS) -c -MMD hello_world/main.cpp -o $@
|
||||||
|
$(CXX) -o $(BINDIR)/hello_world $(LDFLAGS) $@ $(LIB)
|
||||||
|
|
||||||
|
$(OBJDIR)/assimp_loading.o: $(LIB) |
||||||
|
$(CXX) $(CXXFLAGS) -c -MMD assimp_loading/main.cpp -o $@
|
||||||
|
$(CXX) -o $(BINDIR)/assimp_loading $(LDFLAGS) $@ $(LIB)
|
||||||
|
|
||||||
|
$(OBJDIR)/simple_mesh.o: $(LIB) |
||||||
|
$(CXX) $(CXXFLAGS) -c -MMD simple_mesh/main.cpp -o $@
|
||||||
|
$(CXX) -o $(BINDIR)/simple_mesh $(LDFLAGS) $@ $(LIB)
|
||||||
|
|
||||||
clean: |
clean: |
||||||
rm -rf $(OBJDIR)/* bin/*
|
rm -rf $(OBJDIR)/* bin/*
|
||||||
.PHONY: clean |
.PHONY: clean |
||||||
|
|||||||
@ -0,0 +1,72 @@ |
|||||||
|
|
||||||
|
#include <cstdio> // snprintf |
||||||
|
#include <iostream> // std::cout |
||||||
|
|
||||||
|
#include <assimp/cimport.h> |
||||||
|
#include <assimp/scene.h> |
||||||
|
#include <assimp/postprocess.h> |
||||||
|
|
||||||
|
#include "util.h" |
||||||
|
#include "dumbLog.h" |
||||||
|
#include "mesh.h" |
||||||
|
|
||||||
|
|
||||||
|
void |
||||||
|
debugParseNode(aiNode* node, aiMatrix4x4 xform, uint depth=0) |
||||||
|
{ |
||||||
|
depth++; |
||||||
|
char tabs[256]; |
||||||
|
snprintf(tabs, 256, "%*s", depth * 4, " "); |
||||||
|
std::cout << tabs << node->mName.C_Str() << ", has meshes: " << (node->mNumMeshes > 0) << "\n"; |
||||||
|
|
||||||
|
if (node->mNumMeshes > 0) { |
||||||
|
for (uint i = 0; i < node->mNumMeshes; i++) |
||||||
|
std::cout << tabs << " mesh index: " << node->mMeshes[i] << "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
for (uint i = 0; i < node->mNumChildren; i++) |
||||||
|
debugParseNode(node->mChildren[i], xform, depth); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
debugParseAnimation(aiAnimation* anim) |
||||||
|
{ |
||||||
|
std::cout << "Animation, ticks/s: " << anim->mTicksPerSecond |
||||||
|
<< ", duration: " << anim->mDuration |
||||||
|
<< ", channels: " << anim->mNumChannels |
||||||
|
<< "\n"; |
||||||
|
|
||||||
|
for (uint i = 0; i < anim->mNumChannels; i++) { |
||||||
|
aiNodeAnim* chan = anim->mChannels[i]; |
||||||
|
std::cout << " channel " << i |
||||||
|
<< ", node name: " << chan->mNodeName.C_Str() |
||||||
|
<< ", mNumPositionKeys: " << chan->mNumPositionKeys |
||||||
|
<< ", mNumRotationKeys: " << chan->mNumRotationKeys |
||||||
|
<< ", mNumScalingKeys: " << chan->mNumScalingKeys |
||||||
|
<< "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
logDebugAnimationInfo(const char* model_name) |
||||||
|
{ |
||||||
|
const aiScene* scene = aiImportFile(model_name, aiProcessPreset_TargetRealtime_MaxQuality); |
||||||
|
|
||||||
|
if (!scene) { |
||||||
|
LOG(Error) << "Error loading file: " << model_name << "\n"; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
std::cout << "\n\n--------------------------------------\n"; |
||||||
|
std::cout << "Nodes:\n"; |
||||||
|
aiMatrix4x4 identity_mat; |
||||||
|
debugParseNode(scene->mRootNode, identity_mat); |
||||||
|
|
||||||
|
std::cout << "--------------------------------------\n"; |
||||||
|
std::cout << "Animations:\n"; |
||||||
|
|
||||||
|
if (scene->HasAnimations()) |
||||||
|
debugParseAnimation(scene->mAnimations[0]); |
||||||
|
|
||||||
|
std::cout << "--------------------------------------\n\n\n"; |
||||||
|
} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
|
||||||
|
#include <SDL2/SDL.h> |
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "camera.h" |
||||||
|
#include "dumbLog.h" |
||||||
|
#include "entity.h" |
||||||
|
#include "renderer.h" |
||||||
|
#include "shader_program.h" |
||||||
|
|
||||||
|
|
||||||
|
void |
||||||
|
doFrameCallback(render_state* rs) |
||||||
|
{ |
||||||
|
// NOTE: rotate mesh on z-axis every frame
|
||||||
|
entity& e = rs->render_groups[0].entities[0]; |
||||||
|
static float angle = (float) M_PI_2 / 33; |
||||||
|
static glm::vec3 axis(0, 0, 1); |
||||||
|
entRotate(e, angle, axis); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: remove/refactor this when we get animation working
|
||||||
|
#include "animation_testing.cpp" |
||||||
|
|
||||||
|
int |
||||||
|
main() |
||||||
|
{ |
||||||
|
render_state* rs = renInit("assimp loading"); |
||||||
|
|
||||||
|
if (rs == nullptr) { |
||||||
|
LOG(Error) << "Error Initialzing renderer\n"; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: this needs to be more convenient
|
||||||
|
shader_wrapper sw = { DEFAULT_SHADER, rs->default_shader, nullptr }; |
||||||
|
rs->render_groups = renAllocateGroup(1, sw); |
||||||
|
rs->render_group_count = 1; |
||||||
|
entity& spaceship = rs->render_groups->entities[0]; |
||||||
|
|
||||||
|
cameraInitPerspective( |
||||||
|
rs->cam, |
||||||
|
glm::vec3(200, -150, 150), |
||||||
|
glm::vec3(0, 0, 0), |
||||||
|
glm::vec3(0,0,1) |
||||||
|
); |
||||||
|
|
||||||
|
renAddLight(rs, glm::vec3(200, -150, 150)); |
||||||
|
|
||||||
|
// TODO: look into setting up git-annex for large files. git-lfs works fine
|
||||||
|
// for gitlab, but has no real implementation for self-hosting:
|
||||||
|
// https://github.com/git-lfs/git-lfs/issues/1044
|
||||||
|
// https://git-annex.branchable.com/
|
||||||
|
//
|
||||||
|
// NOTE: testing assimp animation info
|
||||||
|
logDebugAnimationInfo("../data/spaceship.glb"); |
||||||
|
|
||||||
|
if (entInitModel(spaceship, "../data/spaceship.glb")) { |
||||||
|
entScale(spaceship, glm::vec3(20, 20, 20)); |
||||||
|
renDoRenderLoop(rs, 60, doFrameCallback); |
||||||
|
} else { |
||||||
|
LOG(Error) << "Error initializing entity, exiting\n"; |
||||||
|
} |
||||||
|
|
||||||
|
renShutdown(rs); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
Binary file not shown.
@ -1,11 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
in vec3 frag_color; |
|
||||||
|
|
||||||
out vec4 color; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
color = vec4(frag_color, 1); |
|
||||||
} |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
layout (location = 0) in vec3 position; |
|
||||||
layout (location = 1) in vec3 color; |
|
||||||
|
|
||||||
out vec3 frag_color; |
|
||||||
|
|
||||||
layout (std140) uniform matrices |
|
||||||
{ |
|
||||||
mat4 view_xform; |
|
||||||
mat4 proj_xform; |
|
||||||
mat4 normal_xform; |
|
||||||
}; |
|
||||||
|
|
||||||
uniform mat4 node_xform; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
frag_color = color; |
|
||||||
gl_Position = proj_xform * view_xform * node_xform * vec4(position, 1); |
|
||||||
} |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
in vec3 frag_normal; |
|
||||||
|
|
||||||
out vec4 color; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
color = vec4(frag_normal, 1); |
|
||||||
} |
|
||||||
@ -1,26 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
layout (location = 0) in vec3 position; |
|
||||||
layout (location = 1) in vec3 normal; |
|
||||||
|
|
||||||
out vec3 frag_normal; |
|
||||||
|
|
||||||
layout (std140) uniform matrices |
|
||||||
{ |
|
||||||
mat4 view_xform; |
|
||||||
mat4 proj_xform; |
|
||||||
mat4 normal_xform; |
|
||||||
}; |
|
||||||
|
|
||||||
uniform mat4 node_xform; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
// TODO: probably better to do this once in a separate uniform than once |
|
||||||
// for every vertex |
|
||||||
mat4 xform = node_xform; |
|
||||||
xform[3] = vec4(0, 0, 0, 1); // NOTE: undo translation |
|
||||||
frag_normal = vec4(xform * vec4(normal, 1)).xyz; |
|
||||||
gl_Position = proj_xform * view_xform * node_xform * vec4(position, 1); |
|
||||||
} |
|
||||||
@ -1,67 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
in vec3 frag_pos; |
|
||||||
in vec3 frag_normal; |
|
||||||
in vec2 frag_uv; |
|
||||||
|
|
||||||
out vec4 color; |
|
||||||
|
|
||||||
uniform sampler2D sampler; |
|
||||||
|
|
||||||
const uint NUM_LIGHTS = 32u; |
|
||||||
|
|
||||||
layout (std140) uniform lights |
|
||||||
{ |
|
||||||
uint max_p_lights; |
|
||||||
uint active_p_lights; |
|
||||||
uint max_d_lights; |
|
||||||
uint active_d_lights; |
|
||||||
uint padding; |
|
||||||
|
|
||||||
vec4 ambient_color; |
|
||||||
|
|
||||||
vec4 pl_positions[NUM_LIGHTS]; |
|
||||||
vec4 pl_colors[NUM_LIGHTS]; |
|
||||||
uint pl_intensities[NUM_LIGHTS]; // NOTE: 16 bytes * NUM_LIGHTS |
|
||||||
|
|
||||||
vec4 dl_directions[NUM_LIGHTS]; |
|
||||||
vec4 dl_colors[NUM_LIGHTS]; |
|
||||||
uint dl_intensities[NUM_LIGHTS]; // NOTE: 16 bytes * NUM_LIGHTS |
|
||||||
}; |
|
||||||
|
|
||||||
const float CONSTANT_ATTENUATION = 0.1; |
|
||||||
const float LINEAR_ATTENUATION = 0.2; |
|
||||||
const float QUADRATIC_ATTENUATION = 0.02; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
vec4 diffuse_color = vec4(0); |
|
||||||
|
|
||||||
// NOTE: directional lights |
|
||||||
for (uint i = 0u; i < active_d_lights; i++) { |
|
||||||
vec4 light_direction = normalize(dl_directions[i]); |
|
||||||
float diffuse_factor = |
|
||||||
clamp(dot(vec4(frag_normal, 1), light_direction), 0, 1); |
|
||||||
diffuse_color = diffuse_color + |
|
||||||
dl_intensities[i] * diffuse_factor * dl_colors[i]; |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE: point lights |
|
||||||
for (uint i = 0u; i < active_p_lights; i ++) { |
|
||||||
vec3 direction = vec3(pl_positions[i]).xyz - frag_pos; |
|
||||||
float distance = length(direction); |
|
||||||
direction = direction / distance; |
|
||||||
|
|
||||||
float attenuation = CONSTANT_ATTENUATION + |
|
||||||
LINEAR_ATTENUATION * distance + |
|
||||||
QUADRATIC_ATTENUATION * pow(distance, 2); |
|
||||||
float diffuse_factor = clamp(dot(frag_normal, direction), 0, 1); |
|
||||||
vec4 added_color = pl_colors[i] * pl_intensities[i] |
|
||||||
* diffuse_factor / attenuation; |
|
||||||
|
|
||||||
diffuse_color = diffuse_color + added_color; |
|
||||||
} |
|
||||||
|
|
||||||
color = (ambient_color + diffuse_color) * texture(sampler, frag_uv.st); |
|
||||||
} |
|
||||||
@ -1,27 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
layout (location = 0) in vec3 position; |
|
||||||
layout (location = 1) in vec3 normal; |
|
||||||
layout (location = 2) in vec2 uv; |
|
||||||
|
|
||||||
out vec3 frag_pos; |
|
||||||
out vec3 frag_normal; |
|
||||||
out vec2 frag_uv; |
|
||||||
|
|
||||||
layout (std140) uniform matrices |
|
||||||
{ |
|
||||||
mat4 view_xform; |
|
||||||
mat4 proj_xform; |
|
||||||
mat4 normal_xform; |
|
||||||
}; |
|
||||||
|
|
||||||
uniform mat4 node_xform; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
frag_pos = (node_xform * vec4(position, 1)).xyz; |
|
||||||
frag_uv = uv; |
|
||||||
frag_normal = normalize(mat3(node_xform) * normal); |
|
||||||
gl_Position = proj_xform * view_xform * node_xform * vec4(position, 1); |
|
||||||
} |
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,21 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
in vec2 frag_uv; |
|
||||||
|
|
||||||
out vec4 color; |
|
||||||
|
|
||||||
layout (std140) uniform matrices |
|
||||||
{ |
|
||||||
mat4 view_xform; |
|
||||||
mat4 proj_xform; |
|
||||||
mat4 normal_xform; |
|
||||||
}; |
|
||||||
|
|
||||||
uniform mat4 node_xform; |
|
||||||
uniform sampler2D sampler; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
color = texture(sampler, frag_uv.st); |
|
||||||
} |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
#version 330 core |
|
||||||
|
|
||||||
layout (location = 0) in vec3 position; |
|
||||||
layout (location = 1) in vec2 uv; |
|
||||||
|
|
||||||
out vec2 frag_uv; |
|
||||||
|
|
||||||
layout (std140) uniform matrices |
|
||||||
{ |
|
||||||
mat4 view_xform; |
|
||||||
mat4 proj_xform; |
|
||||||
mat4 normal_xform; |
|
||||||
}; |
|
||||||
|
|
||||||
uniform mat4 node_xform; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
frag_uv = uv; |
|
||||||
gl_Position = proj_xform * view_xform * node_xform * vec4(position, 1); |
|
||||||
} |
|
||||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,16 @@ |
|||||||
|
|
||||||
|
#include "renderer.h" |
||||||
|
|
||||||
|
|
||||||
|
int |
||||||
|
main() |
||||||
|
{ |
||||||
|
render_state* rs = renInit("Hello World"); |
||||||
|
|
||||||
|
if (rs == nullptr) |
||||||
|
return 1; |
||||||
|
|
||||||
|
renDoRenderLoop(rs); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
@ -1,344 +0,0 @@ |
|||||||
|
|
||||||
#include <cassert> |
|
||||||
#include <cmath> |
|
||||||
|
|
||||||
#include <glm/gtc/matrix_transform.hpp> |
|
||||||
|
|
||||||
#include "tangerine.h" |
|
||||||
|
|
||||||
|
|
||||||
bool |
|
||||||
loadLights(RenderState* rs) |
|
||||||
{ |
|
||||||
LightsBuffer* lb = rs->lights_buf; |
|
||||||
u32 idx = 0; |
|
||||||
|
|
||||||
#if 0 |
|
||||||
// NOTE: add a directional light
|
|
||||||
idx = (*lb->active_d_lights)++; |
|
||||||
lb->dl_directions[idx] = vec4(-2, 1, 3, 1); |
|
||||||
lb->dl_colors[idx] = vec4(0.5, 0.5, 0.3, 1); |
|
||||||
lb->dl_intensities[idx] = uvec4(1, 0, 0, 0); |
|
||||||
#endif |
|
||||||
// NOTE: add a point light
|
|
||||||
idx = (*lb->active_p_lights)++; |
|
||||||
lb->pl_positions[idx] = vec4(-10, 0, -10, 1); |
|
||||||
lb->pl_colors[idx] = vec4(1, 1, 0.8, 1); |
|
||||||
lb->pl_intensities[idx] = vec4(3, 0, 0, 0); |
|
||||||
|
|
||||||
GLBuffer* lights_ubo = getUBOByName(rs->gl_ctx, "lights"); |
|
||||||
assert(lights_ubo != nullptr); |
|
||||||
updateGLBuffer(lights_ubo, lb->buffer); |
|
||||||
|
|
||||||
// NOTE: add a debug mesh to view the point light source
|
|
||||||
RenderGroup* debug_group = getFreeRenderGroup(rs); |
|
||||||
assert(debug_group); |
|
||||||
ShaderProgram* s_debug = getShaderByName("debug", rs->gl_ctx); |
|
||||||
assert(s_debug); |
|
||||||
initRenderGroup(debug_group, rs->rg_arena, s_debug, 256, "debug_lights"); |
|
||||||
Entity* e = getFreeEntity(debug_group); |
|
||||||
assert(e); |
|
||||||
if (!initEntity(e, |
|
||||||
rs->gl_ctx, |
|
||||||
rs->rg_arena, |
|
||||||
&rs->assets.models[0], // tex_cube from loadScene()
|
|
||||||
s_debug->num_vertex_attribs, |
|
||||||
s_debug->attrib_mappings, |
|
||||||
"debug_light")) |
|
||||||
{ |
|
||||||
LOGF(Error, "Error initializing debug entity for light\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
setEntityPosition(e, vec3(lb->pl_positions[idx])); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
loadCubes(RenderState* rs) |
|
||||||
{ |
|
||||||
// NOTE: load model
|
|
||||||
Model* tex_cube = getModelByPath(&rs->assets, "../data/textured_cube.gltf"); |
|
||||||
if (!tex_cube) return false; |
|
||||||
|
|
||||||
// NOTE: load new shader, or get one of the defaults
|
|
||||||
// NOTE: the default shaders already have their attribute mappings created
|
|
||||||
// in initRenderState, but if you use a custom shader, you will need to
|
|
||||||
// create a GLBufferToAttribMapping manually
|
|
||||||
ShaderProgram* shader_lit = getShaderByName("full_lighting", rs->gl_ctx); |
|
||||||
if (!shader_lit) return false; |
|
||||||
|
|
||||||
// NOTE: init new render group
|
|
||||||
RenderGroup* textured_cubes = getFreeRenderGroup(rs); |
|
||||||
assert(textured_cubes); |
|
||||||
initRenderGroup(textured_cubes, rs->rg_arena, shader_lit, 256, |
|
||||||
"textured_cubes"); |
|
||||||
|
|
||||||
// NOTE: init entities
|
|
||||||
const u32 NUM_CUBES = 5; |
|
||||||
vec3 cube_locs[NUM_CUBES] = { |
|
||||||
vec3( 0, 0, 0), |
|
||||||
vec3(-10, 10, 0), |
|
||||||
vec3(-10, -10, 0), |
|
||||||
vec3( 10, 10, 0), |
|
||||||
vec3( 10, -10, 0), |
|
||||||
}; |
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_CUBES; i++) { |
|
||||||
Entity* e = getFreeEntity(textured_cubes); |
|
||||||
assert(e); |
|
||||||
char cube_name[256] = {0}; |
|
||||||
snprintf(cube_name, 256, "textured_cube%d", i); |
|
||||||
|
|
||||||
if (!initEntity(e, |
|
||||||
rs->gl_ctx, |
|
||||||
rs->rg_arena, |
|
||||||
tex_cube, |
|
||||||
shader_lit->num_vertex_attribs, |
|
||||||
shader_lit->attrib_mappings, |
|
||||||
cube_name)) |
|
||||||
{ |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
setEntityPosition(e, cube_locs[i]); |
|
||||||
scaleEntity(e, 3); |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE: test an entity with multiple meshes
|
|
||||||
bool |
|
||||||
loadSpaceShip(RenderState* rs) |
|
||||||
{ |
|
||||||
Model* ship = getModelByPath(&rs->assets, "../data/spaceship.gltf"); |
|
||||||
if (!ship) |
|
||||||
return false; |
|
||||||
|
|
||||||
ShaderProgram* shader_lit = getShaderByName("full_lighting", rs->gl_ctx); |
|
||||||
if (!shader_lit) |
|
||||||
return false; |
|
||||||
|
|
||||||
// load model into gl
|
|
||||||
RenderGroup* rg = getFreeRenderGroup(rs); |
|
||||||
assert(rg); |
|
||||||
initRenderGroup(rg, rs->rg_arena, shader_lit, 256, "ships"); |
|
||||||
Entity* e = getFreeEntity(rg); |
|
||||||
assert(e); |
|
||||||
|
|
||||||
if (initEntity(e, rs->gl_ctx, |
|
||||||
rs->rg_arena, |
|
||||||
ship, |
|
||||||
shader_lit->num_vertex_attribs, |
|
||||||
shader_lit->attrib_mappings, |
|
||||||
"ship 01")) |
|
||||||
{ |
|
||||||
setEntityPosition(e, vec3(0, -10, -15)); |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
testColoredVertices(RenderState* rs) |
|
||||||
{ |
|
||||||
Mesh m = {0}; |
|
||||||
m.num_vertices = 5; |
|
||||||
m.num_indices = 12; |
|
||||||
|
|
||||||
vec3 vertices[m.num_vertices] = { |
|
||||||
{ 0, 1, 0 }, |
|
||||||
{ -1, -1, -1 }, |
|
||||||
{ -1, -1, 1 }, |
|
||||||
{ 1, -1, 0.5 }, |
|
||||||
}; |
|
||||||
|
|
||||||
u16 indices[m.num_indices] = { |
|
||||||
0, 1, 2, |
|
||||||
0, 2, 3, |
|
||||||
0, 3, 1, |
|
||||||
1, 2, 3 |
|
||||||
}; |
|
||||||
|
|
||||||
vec3 colors[m.num_vertices] = { |
|
||||||
{ 1, 0, 0 }, |
|
||||||
{ 0, 1, 0 }, |
|
||||||
{ 0, 0, 1 }, |
|
||||||
{ 1, 1, 0 } |
|
||||||
}; |
|
||||||
|
|
||||||
m.vertices = vertices; |
|
||||||
m.colors = colors; |
|
||||||
m.indices = indices; |
|
||||||
mat4 xform = mat4(1); |
|
||||||
m.xform = &xform; |
|
||||||
Model mdl = {0}; |
|
||||||
mdl.num_meshes = 1; |
|
||||||
mdl.meshes = &m; |
|
||||||
|
|
||||||
ShaderProgram* shader = getShaderByName("colored_vertices", rs->gl_ctx); |
|
||||||
RenderGroup* rg = getFreeRenderGroup(rs); |
|
||||||
assert(shader && rg); |
|
||||||
initRenderGroup(rg, rs->rg_arena, shader, 256, "colored_pyramids"); |
|
||||||
Entity* e = getFreeEntity(rg); |
|
||||||
assert(e); |
|
||||||
|
|
||||||
if (initEntity(e, rs->gl_ctx, |
|
||||||
rs->rg_arena, |
|
||||||
&mdl, |
|
||||||
shader->num_vertex_attribs, |
|
||||||
shader->attrib_mappings, |
|
||||||
"colored pyramid 01")) |
|
||||||
{ |
|
||||||
setEntityPosition(e, vec3(0, -10, 15)); |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
loadCamera(RenderState* rs) |
|
||||||
{ |
|
||||||
Camera* cam = rs->camera; |
|
||||||
vec3 cam_pos = { 0, 15, 40 }; |
|
||||||
vec3 look_pos = { 0, 0, 0 }; |
|
||||||
vec3 up = { 0, 1, 0 }; |
|
||||||
cameraInitPerspective(cam, cam_pos, look_pos, up); |
|
||||||
GLBuffer* xforms_ubo = getUBOByName(rs->gl_ctx, "matrices"); |
|
||||||
|
|
||||||
if (!xforms_ubo) |
|
||||||
return false; |
|
||||||
|
|
||||||
updateGLBuffer(xforms_ubo, &cam->xforms); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
loadScene(RenderState* rs) |
|
||||||
{ |
|
||||||
if (!loadCubes(rs)) { |
|
||||||
LOGF(Error, "Error loading cubes\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (!testColoredVertices(rs)) { |
|
||||||
LOGF(Error, "Error loading colored vertices\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (!loadSpaceShip(rs)) { |
|
||||||
LOGF(Error, "Error loading ship modeln"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (!loadCamera(rs)) { |
|
||||||
LOGF(Error, "Error loading camera\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return loadLights(rs); |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
orbitPositionZ0(vec4* pos, float angle) |
|
||||||
{ |
|
||||||
// get radius length
|
|
||||||
float r = sqrt(pow(abs(pos->x), 2) + pow(abs(pos->z), 2)); |
|
||||||
// get current angle about z axis
|
|
||||||
float a = atan2f(pos->z, pos->x); |
|
||||||
// apply increment
|
|
||||||
a += angle; |
|
||||||
// get new x/z components
|
|
||||||
pos->x = r * cos(a); |
|
||||||
pos->z = r * sin(a); |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
render_cb_pre(RenderState* rs, void* user_data = nullptr) |
|
||||||
{ |
|
||||||
SDL_Event e; |
|
||||||
|
|
||||||
while (SDL_PollEvent(&e)) { |
|
||||||
if (e.type == SDL_QUIT || |
|
||||||
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) |
|
||||||
{ |
|
||||||
rs->running = false; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE: orbit point light
|
|
||||||
LightsBuffer* lb = rs->lights_buf; |
|
||||||
orbitPositionZ0(&lb->pl_positions[0], 2 * M_PI / 180); |
|
||||||
RenderGroup* rg = getRenderGroupByName(rs, "debug_lights"); |
|
||||||
Entity* ent = &rg->entities[0]; |
|
||||||
setEntityPosition(ent, vec3(lb->pl_positions[0])); |
|
||||||
|
|
||||||
GLBuffer* lights_ubo = getUBOByName(rs->gl_ctx, "lights"); |
|
||||||
assert(lights_ubo); |
|
||||||
updateGLBuffer(lights_ubo, lb->buffer); |
|
||||||
|
|
||||||
// NOTE: orbit camera
|
|
||||||
static vec4 cam_pos; |
|
||||||
static vec3 look_pos; |
|
||||||
static vec3 up; |
|
||||||
static bool initialized = false; |
|
||||||
|
|
||||||
if (!initialized) { |
|
||||||
cam_pos = { 0, 15, 40, 1 }; |
|
||||||
look_pos = { 0, 0, 0 }; |
|
||||||
up = { 0, 1, 0 }; |
|
||||||
initialized = true; |
|
||||||
} |
|
||||||
|
|
||||||
orbitPositionZ0(&cam_pos, 0.5 * M_PI / 180); |
|
||||||
rs->camera->xforms.view = |
|
||||||
glm::lookAt(vec3(cam_pos), vec3(0, 0, 0), vec3(0, 1, 0)); |
|
||||||
|
|
||||||
static GLBuffer* xform_ubo = nullptr; |
|
||||||
|
|
||||||
if (!xform_ubo) { |
|
||||||
xform_ubo = getUBOByName(rs->gl_ctx, "matrices"); |
|
||||||
assert(xform_ubo != nullptr); |
|
||||||
} |
|
||||||
|
|
||||||
updateGLBuffer(xform_ubo, &rs->camera->xforms); |
|
||||||
|
|
||||||
// NOTE: rotate cubes
|
|
||||||
RenderGroup* rg2 = getRenderGroupByName(rs, "textured_cubes"); |
|
||||||
|
|
||||||
for (u32 j = 0; j < rg2->num_entities; j++) { |
|
||||||
Entity& e = rg2->entities[j]; |
|
||||||
float direction = (j % 2 == 0) ? 1 : -1; |
|
||||||
rotateEntity(&e, vec3(0, 1, 0), direction * (float) M_PI / (3 * 60)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
int |
|
||||||
main() |
|
||||||
{ |
|
||||||
RenderState* rs = initRenderState("tangerine example", uvec2(1600, 900)); |
|
||||||
|
|
||||||
if (rs) { |
|
||||||
if (!loadScene(rs)) { |
|
||||||
LOGF(Error, "error loading scene\n"); |
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
doRenderLoop(rs, 60, render_cb_pre, nullptr, nullptr); |
|
||||||
|
|
||||||
freeRenderState(rs); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "error loading scene\n"); |
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
@ -0,0 +1,72 @@ |
|||||||
|
|
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
#include "mesh.h" |
||||||
|
#include "renderer.h" |
||||||
|
|
||||||
|
|
||||||
|
void |
||||||
|
doFrameCallback(render_state* rs) |
||||||
|
{ |
||||||
|
// NOTE: rotate mesh on z-axis every frame
|
||||||
|
entity& e = rs->render_groups[0].entities[0]; |
||||||
|
static float angle = (float) M_PI_2 / 33; |
||||||
|
static glm::vec3 axis(0, 0, 1); |
||||||
|
entRotate(e, angle, axis); |
||||||
|
} |
||||||
|
|
||||||
|
simple_mesh* |
||||||
|
makeSquareMesh() |
||||||
|
{ |
||||||
|
uint num_vertices = 4; |
||||||
|
simple_mesh* sm = meInitMesh(num_vertices); |
||||||
|
sm->num_vertices = num_vertices; |
||||||
|
|
||||||
|
sm->vertices[0] = glm::vec3(-200, 0, 200); |
||||||
|
sm->vertices[1] = glm::vec3(-200, 0, -200); |
||||||
|
sm->vertices[2] = glm::vec3(200, 0, -200); |
||||||
|
sm->vertices[3] = glm::vec3(200, 0, 200); |
||||||
|
sm->vert_colors[0] = glm::vec3(255, 0, 0); |
||||||
|
sm->vert_colors[1] = glm::vec3(255, 0, 0); |
||||||
|
sm->vert_colors[2] = glm::vec3(255, 0, 0); |
||||||
|
sm->vert_colors[3] = glm::vec3(255, 0, 0); |
||||||
|
|
||||||
|
return sm; |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
main() |
||||||
|
{ |
||||||
|
render_state* rs = renInit("simple mesh"); |
||||||
|
|
||||||
|
if (rs == nullptr) { |
||||||
|
LOG(Error) << "Error Initialzing renderer\n"; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: this needs to be more convenient
|
||||||
|
shader_wrapper sw = { SIMPLE_SHADER, nullptr, rs->simple_shader }; |
||||||
|
rs->render_groups = renAllocateGroup(1, sw); |
||||||
|
rs->render_group_count = 1; |
||||||
|
|
||||||
|
entity& e = rs->render_groups[0].entities[0]; |
||||||
|
simple_mesh* sm = makeSquareMesh(); |
||||||
|
|
||||||
|
// TODO: better usage would be: renPushEntity(rs->render_groups[0], e);
|
||||||
|
// would need to allocate a reasonable block size by default (~64), and
|
||||||
|
// double it if pushing to render_group would overflow
|
||||||
|
entInitMesh(e, sm, GL_LINE_LOOP); |
||||||
|
|
||||||
|
cameraInitPerspective( |
||||||
|
rs->cam, |
||||||
|
glm::vec3(0, -500, 0), |
||||||
|
glm::vec3(0, 0, 0), |
||||||
|
glm::vec3(0,0,1) |
||||||
|
); |
||||||
|
|
||||||
|
renDoRenderLoop(rs, 60, doFrameCallback); |
||||||
|
|
||||||
|
renShutdown(rs); |
||||||
|
return 0; |
||||||
|
} |
||||||
@ -1,152 +0,0 @@ |
|||||||
|
|
||||||
// NOTE: get useful debug messages from opengl
|
|
||||||
// https://www.khronos.org/opengl/wiki/Debug_Output
|
|
||||||
|
|
||||||
#ifndef GL_DEBUG_H |
|
||||||
#define GL_DEBUG_H |
|
||||||
|
|
||||||
#include <GL/glew.h> |
|
||||||
|
|
||||||
#include "shader.h" |
|
||||||
|
|
||||||
|
|
||||||
void dumpShader(GLuint prog_id); |
|
||||||
|
|
||||||
const char* glEnumToString(GLenum e); |
|
||||||
|
|
||||||
void openglDebugCallback(GLenum source, |
|
||||||
GLenum type, |
|
||||||
GLuint id, |
|
||||||
GLenum severity, |
|
||||||
GLsizei length, |
|
||||||
const GLchar* message, |
|
||||||
const void* userParam); |
|
||||||
|
|
||||||
#endif // GL_DEBUG_H
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef GL_DEBUG_IMPLEMENTATION |
|
||||||
|
|
||||||
const char* |
|
||||||
glEnumToString(GLenum e) |
|
||||||
{ |
|
||||||
switch (e) { |
|
||||||
case GL_FLOAT_MAT4: return "GL_FLOAT_MAT4"; |
|
||||||
case GL_FLOAT_VEC3: return "GL_FLOAT_VEC3"; |
|
||||||
case GL_FLOAT_VEC4: return "GL_FLOAT_VEC4"; |
|
||||||
|
|
||||||
case GL_BYTE: return "GL_BYTE"; |
|
||||||
case GL_UNSIGNED_BYTE: return "GL_UNSIGNED_BYTE"; |
|
||||||
case GL_SHORT: return "GL_SHORT"; |
|
||||||
case GL_UNSIGNED_SHORT: return "GL_UNSIGNED_SHORT"; |
|
||||||
case GL_INT: return "GL_INT"; |
|
||||||
case GL_UNSIGNED_INT: return "GL_UNSIGNED_INT"; |
|
||||||
case GL_FLOAT: return "GL_FLOAT"; |
|
||||||
case GL_DOUBLE: return "GL_DOUBLE"; |
|
||||||
|
|
||||||
case GL_ARRAY_BUFFER: return "GL_ARRAY_BUFFER"; |
|
||||||
case GL_ELEMENT_ARRAY_BUFFER: return "GL_ELEMENT_ARRAY_BUFFER"; |
|
||||||
case GL_UNIFORM_BUFFER: return "GL_UNIFORM_BUFFER"; |
|
||||||
case GL_TEXTURE_BUFFER: return "GL_TEXTURE_BUFFER"; |
|
||||||
|
|
||||||
case GL_DEBUG_TYPE_ERROR: return "GL_DEBUG_TYPE_ERROR"; |
|
||||||
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: |
|
||||||
return "GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR"; |
|
||||||
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: |
|
||||||
return "GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR"; |
|
||||||
case GL_DEBUG_TYPE_PORTABILITY: return "GL_DEBUG_TYPE_PORTABILITY"; |
|
||||||
case GL_DEBUG_TYPE_PERFORMANCE: return "GL_DEBUG_TYPE_PERFORMANCE"; |
|
||||||
case GL_DEBUG_TYPE_MARKER: return "GL_DEBUG_TYPE_MARKER"; |
|
||||||
case GL_DEBUG_TYPE_PUSH_GROUP: return "GL_DEBUG_TYPE_PUSH_GROUP"; |
|
||||||
case GL_DEBUG_TYPE_POP_GROUP: return "GL_DEBUG_TYPE_POP_GROUP"; |
|
||||||
case GL_DEBUG_TYPE_OTHER: return "GL_DEBUG_TYPE_OTHER"; |
|
||||||
|
|
||||||
case GL_DEBUG_SEVERITY_HIGH: return "GL_DEBUG_SEVERITY_HIGH"; |
|
||||||
case GL_DEBUG_SEVERITY_MEDIUM: return "GL_DEBUG_SEVERITY_MEDIUM"; |
|
||||||
case GL_DEBUG_SEVERITY_LOW: return "GL_DEBUG_SEVERITY_LOW"; |
|
||||||
case GL_DEBUG_SEVERITY_NOTIFICATION: |
|
||||||
return "GL_DEBUG_SEVERITY_NOTIFICATION"; |
|
||||||
|
|
||||||
case GL_NO_ERROR: return "GL_NO_ERROR"; |
|
||||||
case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; |
|
||||||
case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; |
|
||||||
case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; |
|
||||||
case GL_INVALID_FRAMEBUFFER_OPERATION: |
|
||||||
return "GL_INVALID_FRAMEBUFFER_OPERATION"; |
|
||||||
case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; |
|
||||||
|
|
||||||
default: return "???"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
openglDebugCallback(GLenum source, |
|
||||||
GLenum type, |
|
||||||
GLuint id, |
|
||||||
GLenum severity, |
|
||||||
GLsizei length, |
|
||||||
const GLchar* message, |
|
||||||
const void* userParam) |
|
||||||
{ |
|
||||||
// NOTE: filter out notification about using video memory for buffer object
|
|
||||||
if (id == 131185) |
|
||||||
return; |
|
||||||
|
|
||||||
#ifdef TANGERINE_GL_DEBUG_QUIET |
|
||||||
if (type != GL_DEBUG_TYPE_ERROR) |
|
||||||
return; |
|
||||||
#endif |
|
||||||
|
|
||||||
std::cout << "message id: " << id << ", " |
|
||||||
<< ((type == GL_DEBUG_TYPE_ERROR) ? "Error" : "Debug") |
|
||||||
<< (type == GL_DEBUG_TYPE_ERROR ? "** GL Error **" : "") |
|
||||||
<< ", type: " << glEnumToString(type) |
|
||||||
<< ", severity: " << glEnumToString(severity) |
|
||||||
<< ", message: " << message << "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
dumpShader(GLuint prog_id) |
|
||||||
{ |
|
||||||
LOGF(Debug, "------------------------\n"); |
|
||||||
LOGF(Debug, "%s(), dumping shader info, program id: %d\n", |
|
||||||
__FUNCTION__, prog_id); |
|
||||||
|
|
||||||
GLint active_uniforms; |
|
||||||
GLint active_uniform_blocks; |
|
||||||
GLint active_attribs; |
|
||||||
|
|
||||||
// NOTE: unused uniforms/attributes get optimized away
|
|
||||||
// https://www.khronos.org/opengl/wiki/Program_Introspection#Attributes
|
|
||||||
glGetProgramiv(prog_id, GL_ACTIVE_UNIFORMS, &active_uniforms); |
|
||||||
glGetProgramiv(prog_id, GL_ACTIVE_UNIFORM_BLOCKS, &active_uniform_blocks); |
|
||||||
glGetProgramiv(prog_id, GL_ACTIVE_ATTRIBUTES, &active_attribs); |
|
||||||
|
|
||||||
LOGF(Debug, "active uniforms: %d\n", active_uniforms); |
|
||||||
LOGF(Debug, "active uniform blocks: %d\n", active_uniform_blocks); |
|
||||||
LOGF(Debug, "active attributes: %d\n", active_attribs); |
|
||||||
|
|
||||||
GLchar uni_name[256]; |
|
||||||
GLsizei length; |
|
||||||
GLint size; |
|
||||||
GLenum type; |
|
||||||
|
|
||||||
for (int i = 0; i < active_uniforms; i++) { |
|
||||||
glGetActiveUniform(prog_id, i, sizeof(uni_name), |
|
||||||
&length, &size, &type, uni_name); |
|
||||||
LOGF(Debug, "uniform idx: %d, type: %s, name: %s \n", |
|
||||||
i, glEnumToString(type), uni_name); |
|
||||||
} |
|
||||||
|
|
||||||
for (int i = 0; i < active_attribs; i++) { |
|
||||||
glGetActiveAttrib(prog_id, i, sizeof(uni_name), |
|
||||||
&length, &size, &type, uni_name); |
|
||||||
LOGF(Debug, "attribute idx: %d, type: %s, name: %s \n", |
|
||||||
i, glEnumToString(type), uni_name); |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Debug, "------------------------\n"); |
|
||||||
} |
|
||||||
|
|
||||||
#endif // ifdef GL_DEBUG_IMPLEMENTATION
|
|
||||||
|
|
||||||
@ -0,0 +1,18 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
|
||||||
|
struct render_object; |
||||||
|
|
||||||
|
struct node_animation |
||||||
|
{ |
||||||
|
render_object* ro; |
||||||
|
glm::mat4* xform_array; |
||||||
|
uint key_count; |
||||||
|
uint cur_frame; |
||||||
|
}; |
||||||
|
|
||||||
@ -1,76 +0,0 @@ |
|||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <GL/glew.h> |
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/glm.hpp> |
|
||||||
|
|
||||||
#include "types.h" |
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
|
|
||||||
// NOTE: wrapper for stb_image
|
|
||||||
struct Texture |
|
||||||
{ |
|
||||||
i32 w; |
|
||||||
i32 h; |
|
||||||
i32 bits_per_channel; |
|
||||||
i32 num_channels; |
|
||||||
uint data_len; |
|
||||||
u8* pixels; |
|
||||||
u64 filepath_hash; |
|
||||||
char file_path[256]; |
|
||||||
}; |
|
||||||
|
|
||||||
// NOTE: wrapper for tinygltf https://github.com/syoyo/tinygltf
|
|
||||||
// https://github.com/KhronosGroup/glTF
|
|
||||||
struct Mesh |
|
||||||
{ |
|
||||||
u32 num_vertices; |
|
||||||
u32 num_indices; |
|
||||||
vec3* vertices; |
|
||||||
vec3* normals; |
|
||||||
vec2* uvs; |
|
||||||
vec3* colors; |
|
||||||
u16* indices; // NOTE: u16 to match tinygltf library output
|
|
||||||
mat4* xform; |
|
||||||
}; |
|
||||||
|
|
||||||
#define MAX_PATH_SIZE 256 |
|
||||||
struct Model |
|
||||||
{ |
|
||||||
char* filepath; |
|
||||||
u64 filepath_hash; |
|
||||||
uint num_meshes; |
|
||||||
Mesh* meshes; |
|
||||||
Texture* diffuse_texture; |
|
||||||
}; |
|
||||||
|
|
||||||
struct Assets |
|
||||||
{ |
|
||||||
MemoryArena* arena; |
|
||||||
u32 num_models; |
|
||||||
u32 max_models; |
|
||||||
Model* models; |
|
||||||
|
|
||||||
u32 num_textures; |
|
||||||
u32 max_textures; |
|
||||||
Texture* textures; |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
Mesh* meshInit(MemoryArena* arena, |
|
||||||
u32 num_vertices, |
|
||||||
u32 num_indices, |
|
||||||
bool use_normals = false, |
|
||||||
bool use_colors = false, |
|
||||||
bool use_uvs = false); |
|
||||||
|
|
||||||
Model* modelInitManual(MemoryArena* arena, |
|
||||||
u32 num_meshes, |
|
||||||
Mesh* meshes = nullptr); |
|
||||||
|
|
||||||
Model* getModelByPath(Assets* assets, const char* filepath); |
|
||||||
|
|
||||||
Texture* getTextureByPath(Assets* assets, const char* filepath); |
|
||||||
|
|
||||||
@ -1,70 +1,45 @@ |
|||||||
|
|
||||||
#pragma once |
#pragma once |
||||||
|
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/glm.hpp> |
#include <glm/glm.hpp> |
||||||
|
#include <glm/gtc/matrix_transform.hpp> |
||||||
|
#include "util.h" |
||||||
|
|
||||||
#include "types.h" |
|
||||||
#include "shader.h" |
|
||||||
|
|
||||||
|
|
||||||
struct Transforms |
|
||||||
{ |
|
||||||
mat4 view; |
|
||||||
mat4 projection; |
|
||||||
mat4 normal; |
|
||||||
}; |
|
||||||
|
|
||||||
struct Camera |
struct camera |
||||||
{ |
{ |
||||||
float hAngle; |
float hAngle; |
||||||
float vAngle; |
float vAngle; |
||||||
|
|
||||||
vec3 position; |
glm::vec3 position; |
||||||
vec3 forward; |
glm::vec3 forward; |
||||||
vec3 up; |
glm::vec3 up; |
||||||
vec3 left; |
glm::vec3 left; |
||||||
vec3 target; |
glm::vec3 target; |
||||||
vec3 world_up; |
glm::vec3 world_up; |
||||||
|
|
||||||
Transforms xforms; |
glm::mat4 model; |
||||||
|
glm::mat4 view; |
||||||
|
glm::mat4 projection; |
||||||
|
glm::mat4 MVP; |
||||||
}; |
}; |
||||||
|
|
||||||
|
enum projection_type |
||||||
|
{ |
||||||
|
PERSPECTIVE, |
||||||
|
ORTHOGRAPHIC, |
||||||
|
}; |
||||||
|
|
||||||
// FIXME: we should keep to our convention of passing pointers to structs in
|
|
||||||
// these interface functions
|
|
||||||
vec2 |
|
||||||
cameraUnproject(Camera& cam, int x, int y, int vp_width, int vp_height); |
|
||||||
|
|
||||||
vec3 |
v2f cameraUnproject(camera& cam, int x, int y, int vp_width, int vp_height); |
||||||
cameraCreateRay(Camera& cam, ivec2 vp_coords, ivec2 vp_dims); |
|
||||||
|
|
||||||
bool |
v3f cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims); |
||||||
cameraIntersectPlane(Camera& cam, |
|
||||||
vec3 ray, |
|
||||||
vec3 plane_origin, |
|
||||||
vec3 plane_normal, |
|
||||||
vec3& intersection); |
|
||||||
|
|
||||||
void |
bool cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& intersection); |
||||||
cameraInitPerspective(Camera* cam, |
|
||||||
vec3 position, |
|
||||||
vec3 target, |
|
||||||
vec3 world_up, |
|
||||||
float aspect_ratio = 16.f / 9.f); |
|
||||||
|
|
||||||
void |
void cameraInitPerspective(camera& cam, glm::vec3 position, glm::vec3 target, glm::vec3 world_up); |
||||||
cameraMove(Camera& cam, |
|
||||||
bool up, |
|
||||||
bool left, |
|
||||||
bool down, |
|
||||||
bool right, |
|
||||||
bool forward, |
|
||||||
bool backward); |
|
||||||
|
|
||||||
void |
void cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward, bool backward); |
||||||
cameraRotate(Camera& cam, i32 xrel, i32 yrel); |
|
||||||
|
|
||||||
void |
void cameraRotate(camera& cam, int32 xrel, int32 yrel); |
||||||
cameraRoll(Camera& cam, bool CW, bool CCW); |
|
||||||
|
|
||||||
|
void cameraRoll(camera& cam, bool CW, bool CCW); |
||||||
|
|||||||
@ -1,30 +0,0 @@ |
|||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
|
|
||||||
const char* DUMMY_VERTEX_SHADER = R"VS( |
|
||||||
#version 330 core |
|
||||||
layout (location = 0) in vec3 position; |
|
||||||
|
|
||||||
uniform mat4 world_transform; |
|
||||||
uniform mat4 MVP; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
gl_Position = MVP * world_transform * vec4(position, 1); |
|
||||||
} |
|
||||||
)VS"; |
|
||||||
|
|
||||||
const char* DUMMY_FRAGMENT_SHADER = R"FS( |
|
||||||
#version 330 core |
|
||||||
|
|
||||||
out vec4 color; |
|
||||||
|
|
||||||
|
|
||||||
void main() |
|
||||||
{ |
|
||||||
color = vec4(1, 0, 0, 1); |
|
||||||
} |
|
||||||
)FS"; |
|
||||||
|
|
||||||
@ -1,23 +0,0 @@ |
|||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <SDL2/SDL.h> |
|
||||||
|
|
||||||
|
|
||||||
struct InputState |
|
||||||
{ |
|
||||||
bool window_closed; |
|
||||||
bool escape; |
|
||||||
bool left; |
|
||||||
bool right; |
|
||||||
bool up; |
|
||||||
bool down; |
|
||||||
}; |
|
||||||
|
|
||||||
void |
|
||||||
inputProcessEvent(InputState* is, SDL_Event& e); |
|
||||||
|
|
||||||
// NOTE: convenience function that provides a while(SDL_PollEvents()) loop
|
|
||||||
void |
|
||||||
inputProcessEvents(InputState* is); |
|
||||||
|
|
||||||
@ -0,0 +1,34 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "shader_program.h" |
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
struct point_light |
||||||
|
{ |
||||||
|
uint light_ID; |
||||||
|
glm::vec3 position; |
||||||
|
glm::vec3 color; |
||||||
|
float intensity; |
||||||
|
}; |
||||||
|
|
||||||
|
struct light_group |
||||||
|
{ |
||||||
|
point_light* lights; |
||||||
|
uint num_lights; |
||||||
|
uint max_lights; |
||||||
|
bool needs_update; |
||||||
|
}; |
||||||
|
|
||||||
|
// NOTE: max_lights should match the max_lights variable in the fragment shader
|
||||||
|
light_group* lightsInit(uint max_lights = 1000); |
||||||
|
|
||||||
|
void lightsOut(light_group* lights); |
||||||
|
|
||||||
|
bool |
||||||
|
lightsAdd(light_group* lights, glm::vec3 pos, glm::vec3 color, float intensity); |
||||||
|
|
||||||
|
void lightsUpdate(light_group* lights, default_shader_program* shader); |
||||||
|
|
||||||
@ -0,0 +1,65 @@ |
|||||||
|
/*
|
||||||
|
* mesh.h |
||||||
|
* - wrapper for assimp http://www.assimp.org
|
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "animation.h" |
||||||
|
#include "util.h" |
||||||
|
#include "util_image.h" |
||||||
|
|
||||||
|
|
||||||
|
struct mesh_info |
||||||
|
{ |
||||||
|
glm::mat4 model_transform; |
||||||
|
// NOTE: vertices, normals, and tex_coords have the same count
|
||||||
|
uint num_vertices; |
||||||
|
glm::vec3* vertices; |
||||||
|
glm::vec3* normals; |
||||||
|
glm::vec3* texture_coords; // NOTE: stay aligned with other buffers
|
||||||
|
uint num_indices; |
||||||
|
uint* indices; |
||||||
|
util_image diffuse_texture; |
||||||
|
node_animation* node_anim; |
||||||
|
}; |
||||||
|
|
||||||
|
struct mesh_group |
||||||
|
{ |
||||||
|
// TODO: this should be one big allocation since the mesh doesn't change
|
||||||
|
// while running
|
||||||
|
mesh_info** meshes; |
||||||
|
uint num_meshes; |
||||||
|
}; |
||||||
|
|
||||||
|
struct simple_mesh |
||||||
|
{ |
||||||
|
glm::mat4 model_transform; |
||||||
|
uint num_vertices; |
||||||
|
glm::vec3* vertices; |
||||||
|
glm::vec3* vert_colors; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
bool |
||||||
|
meInitAssimp(); |
||||||
|
|
||||||
|
// NOTE: meshes loaded from assimp require texture coordinates, and a diffuse
|
||||||
|
// texture, which can be a seperate file, or embedded in the input file
|
||||||
|
bool |
||||||
|
meLoadFromFile(mesh_group& mesh_group, const char* filepath); |
||||||
|
|
||||||
|
simple_mesh* |
||||||
|
meInitMesh(uint num_vertices); |
||||||
|
|
||||||
|
void |
||||||
|
meFreeMeshGroup(mesh_group& mesh_group); |
||||||
|
|
||||||
|
void |
||||||
|
meFreeSimpleMesh(simple_mesh* mesh); |
||||||
|
|
||||||
|
void |
||||||
|
meShutdownAssimp(); |
||||||
|
|
||||||
@ -0,0 +1,102 @@ |
|||||||
|
|
||||||
|
// attempt to fix vsync with opengl on windows
|
||||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=467617
|
||||||
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmthk/nf-d3dkmthk-d3dkmtwaitforverticalblankevent
|
||||||
|
// NOTE: requires installing windows driver kit addon for msvc
|
||||||
|
// TODO: not working with hdc provided by SDL, try enumerating display devices as described here:
|
||||||
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmthk/nf-d3dkmthk-d3dkmtopenadapterfromhdc
|
||||||
|
// NOTE: the above seems to make some difference in lowering cpu usage, but cpu stays at max frequency even when relatively idle.
|
||||||
|
|
||||||
|
// TODO: if d3dkmt... doensn't work, try DWMFlush() https://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmflush
|
||||||
|
// glfw uses this https://github.com/glfw/glfw/blob/master/src/wgl_context.c
|
||||||
|
//
|
||||||
|
// if that doesn't work... https://docs.microsoft.com/en-us/windows/desktop/api/timeapi/nf-timeapi-timebeginperiod
|
||||||
|
// to increase schedular granularity. can then use sleep(1) for 1ms resolution
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
#include "SDL_syswm.h" |
||||||
|
|
||||||
|
#if defined(_WIN32) |
||||||
|
#include "windows.h" |
||||||
|
#include "d3dkmthk.h" |
||||||
|
|
||||||
|
typedef uint32_t D3DKMT_HANDLE; |
||||||
|
typedef struct { |
||||||
|
D3DKMT_HANDLE hAdapter = NULL; |
||||||
|
UINT vidID = 0; |
||||||
|
} platform_win_device_handles; |
||||||
|
platform_win_device_handles g_platform_win_device_handles; |
||||||
|
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
|
||||||
|
inline bool |
||||||
|
platform_init(SDL_Window* window) |
||||||
|
{ |
||||||
|
#if defined(_WIN32) |
||||||
|
D3DKMT_HANDLE hAdapter = NULL; |
||||||
|
UINT vidID = 0; |
||||||
|
D3DKMT_OPENADAPTERFROMHDC OpenAdapterData; |
||||||
|
DISPLAY_DEVICE dd; |
||||||
|
HDC hdc; |
||||||
|
|
||||||
|
memset(&dd, 0, sizeof(dd)); |
||||||
|
dd.cb = sizeof dd; |
||||||
|
|
||||||
|
for (int i = 0; EnumDisplayDevicesA(NULL, i, &dd, 0); ++i) { |
||||||
|
if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
hdc = CreateDC(NULL, dd.DeviceName, NULL, NULL); |
||||||
|
if (hdc == NULL) |
||||||
|
return false; |
||||||
|
|
||||||
|
OpenAdapterData.hDc = hdc; |
||||||
|
if ((D3DKMTOpenAdapterFromHdc(&OpenAdapterData)) >= 0) { |
||||||
|
DeleteDC(hdc); |
||||||
|
g_platform_win_device_handles.hAdapter = OpenAdapterData.hAdapter; |
||||||
|
g_platform_win_device_handles.vidID = OpenAdapterData.VidPnSourceId; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
DeleteDC(hdc); |
||||||
|
return false; |
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
inline void |
||||||
|
platform_wait_for_vblank(bool wait=true) |
||||||
|
{ |
||||||
|
if (!wait) return; |
||||||
|
|
||||||
|
#if defined(_WIN32) |
||||||
|
_D3DKMT_WAITFORVERTICALBLANKEVENT waitForVBlankData; |
||||||
|
memset(&waitForVBlankData, 0, sizeof(waitForVBlankData)); |
||||||
|
waitForVBlankData.hAdapter = g_platform_win_device_handles.hAdapter; |
||||||
|
waitForVBlankData.VidPnSourceId = g_platform_win_device_handles.vidID; |
||||||
|
NTSTATUS ret = D3DKMTWaitForVerticalBlankEvent(&waitForVBlankData); |
||||||
|
|
||||||
|
switch (ret) { |
||||||
|
case 0x00000000: // STATUS_SUCCESS
|
||||||
|
//OutputDebugString("Success\r\n");
|
||||||
|
break; |
||||||
|
//case STATUS_DEVICE_REMOVED:
|
||||||
|
// OutputDebugString("STATUS_DEVICE_REMOVED\r\n");
|
||||||
|
// break;
|
||||||
|
case STATUS_INVALID_PARAMETER: |
||||||
|
OutputDebugString("STATUS_INVALID_PARAMETER\r\n"); |
||||||
|
break; |
||||||
|
default: |
||||||
|
OutputDebugString("????"); |
||||||
|
} |
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
// TODO: implement/test for linux
|
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,33 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <GL/glew.h> |
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "camera.h" |
||||||
|
#include "lights.h" |
||||||
|
#include "mesh.h" |
||||||
|
#include "shader_program.h" |
||||||
|
|
||||||
|
|
||||||
|
struct render_objects; |
||||||
|
|
||||||
|
render_objects* |
||||||
|
roInitModel(mesh_group meshes); |
||||||
|
|
||||||
|
render_objects* |
||||||
|
roInitSimpleMesh(simple_mesh& mesh_in, GLenum draw_mode); |
||||||
|
|
||||||
|
void |
||||||
|
roFree(render_objects* r_objs); |
||||||
|
|
||||||
|
// TODO: we probably want to re-introduce a world_transform matrix here again
|
||||||
|
// so we can do: projection * view * world_transform * model_transform
|
||||||
|
// that way model animations and world orientation/position can be separated
|
||||||
|
void |
||||||
|
roDraw(render_objects* r_ojbs, |
||||||
|
glm::mat4 world_transform, |
||||||
|
camera& cam, |
||||||
|
shader_wrapper sw, |
||||||
|
light_group* lights); |
||||||
|
|
||||||
@ -0,0 +1,80 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#if defined (_WIN32) |
||||||
|
#include <SDL.h> |
||||||
|
#else |
||||||
|
#include <SDL2/SDL.h> |
||||||
|
#endif |
||||||
|
#include <GL/glew.h> |
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#include "camera.h" |
||||||
|
#include "entity.h" |
||||||
|
#include "lights.h" |
||||||
|
#include "util.h" |
||||||
|
#include "shader_program.h" |
||||||
|
|
||||||
|
|
||||||
|
// NOTE: array of entities rendered with the same shader program
|
||||||
|
struct render_group |
||||||
|
{ |
||||||
|
// TODO: also needs to be resizable
|
||||||
|
entity* entities; |
||||||
|
uint entity_count; |
||||||
|
|
||||||
|
shader_wrapper shader; |
||||||
|
|
||||||
|
// TODO: does it makes sense to have the mesh_type stored on the
|
||||||
|
// render_group? can then easily pass to roDraw() along with the entity
|
||||||
|
// render_objects...
|
||||||
|
// only the mesh object and render object really need to know about the
|
||||||
|
// mesh_type...
|
||||||
|
//
|
||||||
|
//mesh_t mesh_type;
|
||||||
|
}; |
||||||
|
|
||||||
|
struct SDL_Handles; |
||||||
|
|
||||||
|
struct render_state |
||||||
|
{ |
||||||
|
glm::vec2 viewport_dims; |
||||||
|
camera cam; |
||||||
|
util_RGBA clear_col; |
||||||
|
SDL_Handles* handles; |
||||||
|
|
||||||
|
// TODO: this really needs to be a resizable array, or linked list, or
|
||||||
|
// ...gasp, std::vector
|
||||||
|
render_group* render_groups; |
||||||
|
uint render_group_count; |
||||||
|
|
||||||
|
// TODO: hide shaders behind a better abstraction than 'shader_wrapper'
|
||||||
|
default_shader_program* default_shader; |
||||||
|
simple_shader_program* simple_shader; |
||||||
|
|
||||||
|
light_group* lights; |
||||||
|
}; |
||||||
|
|
||||||
|
render_state* |
||||||
|
renInit(const char* title = "Tangerine", |
||||||
|
glm::vec2 viewport_dims = glm::vec2(1280, 720)); |
||||||
|
|
||||||
|
void renShutdown(render_state* rs); |
||||||
|
|
||||||
|
render_group* |
||||||
|
renAllocateGroup(uint entity_count, shader_wrapper shader); |
||||||
|
|
||||||
|
typedef void (*frame_callback_fn) (render_state*); |
||||||
|
|
||||||
|
void renDoRenderLoop(render_state* rs, |
||||||
|
uint framerate = 60, |
||||||
|
frame_callback_fn cb_func = nullptr); |
||||||
|
|
||||||
|
void renRenderFrame(render_state* rs); |
||||||
|
|
||||||
|
bool |
||||||
|
renAddLight(render_state* rs, |
||||||
|
glm::vec3 pos = glm::vec3(0, 0, 0), |
||||||
|
glm::vec3 color = glm::vec3(1, 1, 1), |
||||||
|
float intensity = 1.0); |
||||||
|
|
||||||
@ -1,214 +0,0 @@ |
|||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <SDL2/SDL.h> |
|
||||||
#include <GL/glew.h> |
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/glm.hpp> |
|
||||||
|
|
||||||
#include "asset.h" |
|
||||||
#include "types.h" |
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
|
|
||||||
enum UniformType |
|
||||||
{ |
|
||||||
UNIFORM_SAMPLER, |
|
||||||
UNIFORM_NODE_XFORM, |
|
||||||
UNIFORM_VIEW_XFORM, |
|
||||||
UNIFORM_PROJECTION_XFORM, |
|
||||||
UNIFORM_NORMAL_XFORM, |
|
||||||
UNIFORM_BLOCK_XFORMS, |
|
||||||
UNIFORM_BLOCK_LIGHTS, |
|
||||||
UNIFORM_UNKNOWN, |
|
||||||
UNIFORM_TYPE_COUNT |
|
||||||
}; |
|
||||||
|
|
||||||
struct GLUniform |
|
||||||
{ |
|
||||||
// NOTE: would be nice to use idx as the location parameter because it seems
|
|
||||||
// to match when the uniform isn't part of a uniform block, at least with
|
|
||||||
// intel mesa driver, but apparently that's not guarantied
|
|
||||||
GLuint idx; |
|
||||||
GLint location; |
|
||||||
GLint block_idx; |
|
||||||
UniformType uniform_type; |
|
||||||
GLenum gl_type; // NOTE: GL_UNSIGNED_INT, GL_FLOAT_VEC4
|
|
||||||
GLint num_elements; // NOTE: 1 unless uniform is an array of base types
|
|
||||||
GLint array_stride; // NOTE: bytes between array elements
|
|
||||||
GLint uniform_offset; // NOTE: byte offset from beginning of uniform
|
|
||||||
char* name; |
|
||||||
}; |
|
||||||
|
|
||||||
struct GLUniformBlock |
|
||||||
{ |
|
||||||
GLuint block_id; |
|
||||||
GLint binding_idx; |
|
||||||
UniformType uniform_type; |
|
||||||
GLuint num_uniforms; |
|
||||||
GLUniform* uniforms; |
|
||||||
char* name; |
|
||||||
}; |
|
||||||
|
|
||||||
enum MeshBufferType |
|
||||||
{ |
|
||||||
VERTEX, |
|
||||||
NORMAL, |
|
||||||
UV, |
|
||||||
COLOR, |
|
||||||
MESH_BUFFER_TYPE_COUNT |
|
||||||
}; |
|
||||||
|
|
||||||
// NOTE: we need another struct for vertex attributes that mirrors GLBuffer
|
|
||||||
// because an attribute is associated with a ShaderProgram while a buffer is
|
|
||||||
// associated with the vertex data passed to glBufferData()
|
|
||||||
struct GLVertexAttrib |
|
||||||
{ |
|
||||||
MeshBufferType buf_type; |
|
||||||
GLenum data_type; |
|
||||||
GLenum component_type; |
|
||||||
u32 num_components; |
|
||||||
GLuint location; |
|
||||||
char* name; |
|
||||||
}; |
|
||||||
|
|
||||||
// TODO: add a note explaining usage when we implement complex entities
|
|
||||||
struct GLBufferToAttribMapping |
|
||||||
{ |
|
||||||
GLVertexAttrib* attrib; |
|
||||||
MeshBufferType buf_type; |
|
||||||
}; |
|
||||||
|
|
||||||
struct ShaderProgram |
|
||||||
{ |
|
||||||
GLuint prog_id; |
|
||||||
|
|
||||||
u32 num_blocks; |
|
||||||
GLUniformBlock* uniform_blocks; |
|
||||||
|
|
||||||
u32 num_uniforms; |
|
||||||
GLUniform* uniforms; |
|
||||||
|
|
||||||
u32 num_vertex_attribs; |
|
||||||
GLVertexAttrib* vertex_attribs; |
|
||||||
GLBufferToAttribMapping* attrib_mappings; |
|
||||||
|
|
||||||
char* name; |
|
||||||
u64 hash; // NOTE: hash of vs filpath + fs filepath concat
|
|
||||||
}; |
|
||||||
|
|
||||||
struct GLBuffer |
|
||||||
{ |
|
||||||
GLuint id; |
|
||||||
GLenum target; |
|
||||||
GLenum data_type; |
|
||||||
GLuint data_size; // NOTE: size of buffer in bytes
|
|
||||||
GLint location; // NOTE: if used as backing for vertex attribute
|
|
||||||
GLint binding_idx; // NOTE: if used as backing from uniform buffer object
|
|
||||||
char* name; |
|
||||||
}; |
|
||||||
|
|
||||||
struct GLTexture |
|
||||||
{ |
|
||||||
GLuint id; |
|
||||||
GLenum pixel_format; // NOTE: GL_RGB or GL_RGBA
|
|
||||||
u32 width; |
|
||||||
u32 height; |
|
||||||
u64 filepath_hash; |
|
||||||
}; |
|
||||||
|
|
||||||
struct GLContext |
|
||||||
{ |
|
||||||
GLuint binding_count; |
|
||||||
GLint max_binding_points; |
|
||||||
GLint max_vertex_blocks; |
|
||||||
GLint max_fragment_blocks; |
|
||||||
GLint max_ublock_size; |
|
||||||
GLint max_vertex_attribs; |
|
||||||
|
|
||||||
u32 max_ubos; |
|
||||||
u32 num_ubos; |
|
||||||
GLBuffer* uniform_buffers; |
|
||||||
|
|
||||||
u32 max_shaders; |
|
||||||
u32 num_shaders; |
|
||||||
ShaderProgram* shaders; |
|
||||||
|
|
||||||
u32 max_textures; |
|
||||||
u32 num_textures; |
|
||||||
GLTexture* textures; |
|
||||||
}; |
|
||||||
|
|
||||||
struct GLMesh |
|
||||||
{ |
|
||||||
u32 num_indices; |
|
||||||
GLuint vao_id; |
|
||||||
bool has_texture; |
|
||||||
GLuint tex_id; |
|
||||||
|
|
||||||
mat4* node_xform; |
|
||||||
|
|
||||||
u32 num_vertex_attrib_buffers; |
|
||||||
GLBuffer* vertex_attrib_buffers; |
|
||||||
GLBuffer* element_buf; |
|
||||||
}; |
|
||||||
|
|
||||||
struct Animation; |
|
||||||
|
|
||||||
|
|
||||||
GLContext* initGLContext(MemoryArena* arena, |
|
||||||
u32 max_shaders, |
|
||||||
u32 max_textures, |
|
||||||
u32 max_ubos); |
|
||||||
|
|
||||||
// TODO: add more notes here describing the internals? This is the entry point
|
|
||||||
// to a complex process of programatically assigning properties to our notion
|
|
||||||
// of what a shader needs ie) uniforms, buffer block bindings, etc
|
|
||||||
// NOTE: every shader program is assumed to have one uniform block named
|
|
||||||
// "matrices" that contains the projection and view matrices, and one uniform
|
|
||||||
// named "node_xform" for the node matrix
|
|
||||||
bool addShaderProgram(MemoryArena* arena, |
|
||||||
GLContext* gl_ctx, |
|
||||||
const char* vs, |
|
||||||
const char* fs, |
|
||||||
const char* name); |
|
||||||
|
|
||||||
ShaderProgram* getShaderByName(const char* name, GLContext* gl_ctx); |
|
||||||
|
|
||||||
ShaderProgram* getShaderByID(GLContext* gl_ctx, GLuint prog_id); |
|
||||||
|
|
||||||
ShaderProgram* getShaderByHash(GLContext* gl_ctx, u64 hash); |
|
||||||
|
|
||||||
ShaderProgram* getFreeShader(GLContext* gl_ctx); |
|
||||||
|
|
||||||
GLBuffer* getFreeUBO(GLContext* gl_ctx); |
|
||||||
|
|
||||||
GLBuffer* getUBOByName(GLContext* gl_ctx, const char* name); |
|
||||||
|
|
||||||
GLTexture* getGLTexture(GLContext* gl_ctx, Texture* diffuse_img); |
|
||||||
|
|
||||||
GLBuffer* initGLBackingBuffer(GLContext* gl_ctx, |
|
||||||
MemoryArena* arena, |
|
||||||
const char* name, |
|
||||||
GLenum data_type, |
|
||||||
u32 buf_size, |
|
||||||
void* data); |
|
||||||
|
|
||||||
void updateGLBuffer(GLBuffer* gl_buf, void* data); |
|
||||||
|
|
||||||
void renderVAO(GLMesh* glmesh, |
|
||||||
mat4* node_xform, |
|
||||||
ShaderProgram* shader, |
|
||||||
GLTexture* gl_tex, |
|
||||||
GLenum draw_mode); |
|
||||||
|
|
||||||
GLVertexAttrib* getVertexAttribByName(ShaderProgram* shader, const char* name); |
|
||||||
|
|
||||||
GLMesh loadGLMesh(MemoryArena* arena, |
|
||||||
const Mesh& m, |
|
||||||
GLenum draw_mode, |
|
||||||
GLenum usage, |
|
||||||
GLTexture* diffuse_texture, |
|
||||||
u32 num_mappings, |
|
||||||
GLBufferToAttribMapping mappings[]); |
|
||||||
|
|
||||||
@ -0,0 +1,57 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <GL/glew.h> |
||||||
|
|
||||||
|
#include "types.h" |
||||||
|
|
||||||
|
|
||||||
|
// TODO: implement a 'cavity' effect for the default shader
|
||||||
|
// https://blender.community/c/rightclickselect/J9bbbc/
|
||||||
|
// https://developer.blender.org/rBf1fd5ed74fb0afd602f53860d0b2db46189c218a
|
||||||
|
// https://developer.blender.org/diffusion/B/browse/master/source/blender/draw/engines/workbench/shaders/workbench_cavity_lib.glsl
|
||||||
|
// https://www.casual-effects.com/research/McGuire2011AlchemyAO/VV11AlchemyAO.pdf
|
||||||
|
// https://github.com/evanw/madebyevan.com/blob/master/src/templates/shaders-curvature.jade
|
||||||
|
struct default_shader_program |
||||||
|
{ |
||||||
|
GLuint program_id; |
||||||
|
|
||||||
|
GLuint model_matrix_id; |
||||||
|
GLuint world_transform_id; |
||||||
|
GLuint view_matrix_id; |
||||||
|
GLuint projection_matrix_id; |
||||||
|
GLuint normal_matrix_id; |
||||||
|
|
||||||
|
GLuint vertex_array_id; |
||||||
|
GLuint sampler_id; |
||||||
|
GLuint num_lights_id; |
||||||
|
}; |
||||||
|
|
||||||
|
struct simple_shader_program |
||||||
|
{ |
||||||
|
GLuint program_id; |
||||||
|
GLuint world_transform_id; |
||||||
|
GLuint MVP_id; |
||||||
|
GLuint vertex_array_id; |
||||||
|
}; |
||||||
|
|
||||||
|
struct shader_wrapper |
||||||
|
{ |
||||||
|
shader_t shader_type; |
||||||
|
default_shader_program* default_shader; |
||||||
|
simple_shader_program* simple_shader; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
// TODO: find a way to initialize different shaders with different
|
||||||
|
// uniform layouts with a single function
|
||||||
|
// look at using uniform blocks and retrieving locations with
|
||||||
|
// glGetUniformBlockIndex() and their size with glGetActiveUniformBlockiv()
|
||||||
|
// see chapter 2 in the red book
|
||||||
|
simple_shader_program* |
||||||
|
shaderInitSimple(const char* vertex_code, const char* frag_code); |
||||||
|
|
||||||
|
default_shader_program* |
||||||
|
shaderInitDefault(const char* vertex_code, const char* frag_code); |
||||||
|
|
||||||
|
void shaderFree(uint program_id); |
||||||
@ -1,296 +0,0 @@ |
|||||||
/*
|
|
||||||
* libTangerine, a small, modern openGL renderer specializing in flat shaded, |
|
||||||
* solid color models using pallete textures. See README for build instructions |
|
||||||
* and look in the examples folder for... examples |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* === TODO: === |
|
||||||
* - try to improve the setup interface for examples |
|
||||||
* see if we can remove some boilerplate with more helpers, or |
|
||||||
* re-organizing |
|
||||||
* - add orbit function to camera, see orbitPositionZ0 in example |
|
||||||
* maybe implement with quaternion, so we can set arbitrary orbit axis |
|
||||||
* - update camera interal state and rotations to use quaternions |
|
||||||
* - add scene abstrastion for RenderState |
|
||||||
* - add an example of dynamically switching shaders for an entity |
|
||||||
* - fix debug load times (either by using cgltf, or hiding tinygltf.h) |
|
||||||
* - RenderGroups and Entities need to come into and out of existence during |
|
||||||
* gameplay. So, we need to extend MemoryArena to be a pool allocator |
|
||||||
* instead of an allocate only linear allocator |
|
||||||
* - resizable arrays for entities and asset system (^ see above) |
|
||||||
* - add libTangerine namespace? |
|
||||||
* - make a test case for overflowing default array sizes, rg->assets |
|
||||||
* rg_info->groups, render_group->enitities, rg->textures |
|
||||||
* - test for video memory leaking since we're not actually deleting GLBuffers |
|
||||||
* anywhere |
|
||||||
* - look for glm::mat4 in structs, and replace with pointers (easier debugging) |
|
||||||
* - defaults include for various #defines |
|
||||||
* - make separate shaders for per vertex, and per pixel/fragment lighting |
|
||||||
* see red book chapter 7 |
|
||||||
* - add a bloom/blur render to texture shader for light sources? |
|
||||||
* https://learnopengl.com/Advanced-Lighting/Bloom
|
|
||||||
* |
|
||||||
* === TODONE: === |
|
||||||
* - don't allocate Asset structure on asset arena |
|
||||||
* - store on RenderState as reference |
|
||||||
* - store asset arena to asset structure |
|
||||||
* - condense get_X_ByPath functions with assetLoad functions |
|
||||||
* - move to asset interface |
|
||||||
* - load diffuse texture automatically when loading a model |
|
||||||
* - move entity structure and functions to new file? |
|
||||||
* - load diffuse texture into OpenGL, and store GLTexture structure onto |
|
||||||
* GLContext |
|
||||||
* - need a cache algorithm starting in initEntity() |
|
||||||
* - check for GLTexture by path hash |
|
||||||
* - load texture into gl if not cached |
|
||||||
* - pass result GLTexture to loadGLMesh() |
|
||||||
* - update loadScene function with textured models, and test texture loading |
|
||||||
* - work on cleaner interface for initEntity and loadScene... |
|
||||||
* - add a LOGF macro for printf style logging |
|
||||||
* - replace instances of printf |
|
||||||
* - rename data structures to be in the new format, eg) UpperCaseStyleNames |
|
||||||
* - rename instances of 'model_xform' that refer to a mesh node to something |
|
||||||
* like node_xform. model_xform should be reserved for the entity node |
|
||||||
* - move default shaders out of git-lfs |
|
||||||
* - full lighting model |
|
||||||
* - add buffer backed storage for light arrays in GLSL |
|
||||||
* - remove hard-coded values in full_lighting.frag |
|
||||||
* - test buffer backed lights, probably need to pad any scalars/vec3s |
|
||||||
* - add point light type with tweakable attenuation parameters |
|
||||||
* - test complex entities |
|
||||||
* - need a separate GLBufferToAttribMapping for each mesh on an entity |
|
||||||
* - maybe fixed now with MeshBufferType enum and getMeshData() |
|
||||||
* - allow entities to have an empty diffuse texture (see initEntity()) |
|
||||||
* - use dynamic shader parsing to set 'sampler_id' for shaders with textures |
|
||||||
* - add ambient light to LightsBuffer structure |
|
||||||
* - remember to update offsets in initLights() |
|
||||||
* - use rg_arena for store GLMeshes, see initGLMesh() |
|
||||||
* - merge back into libTangerine |
|
||||||
* - merge render_group_fix branch back into master |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <SDL2/SDL.h> |
|
||||||
|
|
||||||
|
|
||||||
// NOTE: optional compile-time defines:
|
|
||||||
// TANGERINE_GL_DEBUG_QUIET disable extra debug messages from openGL
|
|
||||||
|
|
||||||
|
|
||||||
#include "asset.h" |
|
||||||
#include "camera.h" |
|
||||||
#include "entity.h" |
|
||||||
#include "dumbLog.h" |
|
||||||
#include "GLDebug.h" |
|
||||||
#include "input.h" |
|
||||||
#include "shader.h" |
|
||||||
#include "types.h" |
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
|
|
||||||
struct SDLHandles |
|
||||||
{ |
|
||||||
SDL_Window* window; |
|
||||||
SDL_GLContext sdl_gl_ctx; |
|
||||||
SDL_DisplayMode display_mode; |
|
||||||
u32 SDL_flags; |
|
||||||
}; |
|
||||||
|
|
||||||
struct RenderGroup |
|
||||||
{ |
|
||||||
ShaderProgram* shader; |
|
||||||
u32 num_entities; |
|
||||||
u32 max_entities; |
|
||||||
Entity* entities; |
|
||||||
char* name; |
|
||||||
}; |
|
||||||
|
|
||||||
// TODO: node/tree structure for entities
|
|
||||||
#if 0 |
|
||||||
struct Node; |
|
||||||
|
|
||||||
struct Scene |
|
||||||
{ |
|
||||||
MemoryArena* rg_arena; |
|
||||||
u32 num_render_groups; |
|
||||||
u32 max_render_groups; |
|
||||||
RenderGroup* render_groups; |
|
||||||
|
|
||||||
u32 num_point_lights; |
|
||||||
u32 max_point_lights; |
|
||||||
PointLight* p_lights; |
|
||||||
|
|
||||||
u32 num_d_lights; |
|
||||||
u32 max_d_lights; |
|
||||||
DirectionalLight* d_lights; |
|
||||||
|
|
||||||
u32 num_spot_lights; |
|
||||||
u32 max_spot_lights; |
|
||||||
SpotLight* s_lights; |
|
||||||
|
|
||||||
Node root_node; |
|
||||||
|
|
||||||
Camera cam; |
|
||||||
}; |
|
||||||
#endif |
|
||||||
|
|
||||||
struct GLClearColor |
|
||||||
{ |
|
||||||
GLfloat R; |
|
||||||
GLfloat G; |
|
||||||
GLfloat B; |
|
||||||
GLfloat A; |
|
||||||
}; |
|
||||||
|
|
||||||
// NOTE: structure to match the layout of the 'lights' uniform in a shader
|
|
||||||
// all the pointers are pointers into the address space of 'buffer'. the vec4
|
|
||||||
// pointers are required to start on 16 byte boundaries as per layout std140,
|
|
||||||
// so there may be some padding added between the 'header', and the start of
|
|
||||||
// the arrays
|
|
||||||
// NOTE: this is also very fragile. Since c/c++ doesn't support reflection, we
|
|
||||||
// need to manually update 'initLights()' if we ever update this structure.
|
|
||||||
// And remember to update the 'padding' if the number of 'header' attributes
|
|
||||||
// change
|
|
||||||
struct LightsBuffer |
|
||||||
{ |
|
||||||
u32 buf_size; |
|
||||||
u32* max_p_lights; |
|
||||||
u32* active_p_lights; |
|
||||||
u32* max_d_lights; |
|
||||||
u32* active_d_lights; |
|
||||||
|
|
||||||
vec4* ambient_color; |
|
||||||
|
|
||||||
vec4* pl_positions; |
|
||||||
vec4* pl_colors; |
|
||||||
uvec4* pl_intensities; |
|
||||||
|
|
||||||
vec4* dl_directions; |
|
||||||
vec4* dl_colors; |
|
||||||
uvec4* dl_intensities; |
|
||||||
|
|
||||||
void* buffer; |
|
||||||
}; |
|
||||||
|
|
||||||
struct RenderState |
|
||||||
{ |
|
||||||
bool running; |
|
||||||
GLClearColor clear_col; |
|
||||||
SDLHandles handles; |
|
||||||
GLContext* gl_ctx; |
|
||||||
Assets assets; |
|
||||||
uvec2 window_dims; |
|
||||||
|
|
||||||
MemoryArena* rg_arena; |
|
||||||
u32 num_render_groups; |
|
||||||
u32 max_render_groups; |
|
||||||
RenderGroup* render_groups; |
|
||||||
|
|
||||||
// TODO: should we have a 'scene' abstraction here?
|
|
||||||
// could have render groups, lights, camera
|
|
||||||
// could match up with gltf scene graph where everyting is part of a node
|
|
||||||
LightsBuffer* lights_buf; |
|
||||||
Camera* camera; |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
#define DEFAULT_WIDTH 1280 |
|
||||||
#define DEFAULT_HEIGHT 720 |
|
||||||
#define DEFAULT_SDL_FLAGS SDL_INIT_VIDEO |
|
||||||
#define DEFAULT_MODEL_COUNT 256 |
|
||||||
#define DEFAULT_TEXTURE_COUNT 64 |
|
||||||
#define DEFAULT_SHADER_COUNT 64 |
|
||||||
#define DEFAULT_UBO_COUNT 32 |
|
||||||
#define DEFAULT_RENDER_GROUP_COUNT 256 |
|
||||||
#define DEFAULT_CLEAR_COLOR { 0.2, 0.2, 0.2, 1 } |
|
||||||
#define DEFAULT_AMBIENT_COLOR { 0.1, 0.1, 0.1, 1 } |
|
||||||
#define DEFAULT_MAX_LIGHTS 32 // NOTE: needs to match NUM_LIGHTS in shaders
|
|
||||||
RenderState* initRenderState(const char* window_title = "tangerine", |
|
||||||
uvec2 window_dims = uvec2(DEFAULT_WIDTH, DEFAULT_HEIGHT), |
|
||||||
u32 SDL_flags = DEFAULT_SDL_FLAGS, |
|
||||||
GLClearColor clear_col = DEFAULT_CLEAR_COLOR, |
|
||||||
vec4 ambient_color = DEFAULT_AMBIENT_COLOR, |
|
||||||
u32 max_models = DEFAULT_MODEL_COUNT, |
|
||||||
u32 max_textures = DEFAULT_TEXTURE_COUNT, |
|
||||||
u32 max_shaders = DEFAULT_SHADER_COUNT, |
|
||||||
u32 max_render_groups = DEFAULT_RENDER_GROUP_COUNT, |
|
||||||
u32 max_ubos = DEFAULT_UBO_COUNT, |
|
||||||
u32 max_lights = DEFAULT_MAX_LIGHTS); |
|
||||||
|
|
||||||
void freeRenderState(RenderState*& rs); |
|
||||||
|
|
||||||
#define DEFAULT_ENTITY_COUNT 256 |
|
||||||
void initRenderGroup(RenderGroup* rg, |
|
||||||
MemoryArena* arena, |
|
||||||
ShaderProgram* shader, |
|
||||||
u32 num_entities = DEFAULT_ENTITY_COUNT, |
|
||||||
const char* name = ""); |
|
||||||
|
|
||||||
void freeRenderGroup(RenderGroup* rg, MemoryArena* arena); |
|
||||||
|
|
||||||
RenderGroup* getFreeRenderGroup(RenderState* rs); |
|
||||||
|
|
||||||
RenderGroup* getRenderGroupByName(RenderState* rs, const char* name); |
|
||||||
|
|
||||||
Entity* getFreeEntity(RenderGroup* rg); |
|
||||||
|
|
||||||
Entity* getEntityByName(RenderGroup* rg, const char* name); |
|
||||||
|
|
||||||
// NOTE: callback function signature to use with renDoRenderLoop()
|
|
||||||
typedef void (*frame_callback_fn) (RenderState*, void*); |
|
||||||
|
|
||||||
// NOTE: There are 2 callbacks to use here, cb_func_pre is called before
|
|
||||||
// the call to renRenderFrame(), cb_fun_post is called after
|
|
||||||
// NOTE: if you use cb_func_pre, you will have to use SDL_PollEvent() manually
|
|
||||||
// and at minimum set rs->running = false on SDL_QUIT event
|
|
||||||
void doRenderLoop(RenderState* rs, |
|
||||||
uint framerate = 60, |
|
||||||
frame_callback_fn cb_func_pre = nullptr, |
|
||||||
frame_callback_fn cb_func_post = nullptr, |
|
||||||
void* user_data = nullptr); |
|
||||||
|
|
||||||
void renderFrame(RenderState* rs, const GLClearColor& clear_col); |
|
||||||
|
|
||||||
|
|
||||||
// NOTE: automatic loading of default shaders
|
|
||||||
|
|
||||||
struct ShaderInit |
|
||||||
{ |
|
||||||
const char* name; |
|
||||||
const char* vert_path; |
|
||||||
const char* frag_path; |
|
||||||
}; |
|
||||||
|
|
||||||
#define SHADER_INIT_COUNT 4 |
|
||||||
#define TEXTURE_ONLY_SHADER_INIT { "texture_only", \ |
|
||||||
"../data/texture_only.vert", \
|
|
||||||
"../data/texture_only.frag" } |
|
||||||
#define FULL_LIGHTING_SHADER_INIT { "full_lighting", \ |
|
||||||
"../data/full_lighting.vert", \
|
|
||||||
"../data/full_lighting.frag" } |
|
||||||
#define DEBUG_SHADER_INIT { "debug", \ |
|
||||||
"../data/debug.vert", \
|
|
||||||
"../data/debug.frag", } |
|
||||||
#define COLORED_VERT_SHADER_INIT { "colored_vertices", \ |
|
||||||
"../data/colored_vertices.vert", \
|
|
||||||
"../data/colored_vertices.frag", } |
|
||||||
const ShaderInit SHADER_INIT_LIST[SHADER_INIT_COUNT] |
|
||||||
{ |
|
||||||
TEXTURE_ONLY_SHADER_INIT, |
|
||||||
FULL_LIGHTING_SHADER_INIT, |
|
||||||
DEBUG_SHADER_INIT, |
|
||||||
COLORED_VERT_SHADER_INIT |
|
||||||
}; |
|
||||||
|
|
||||||
bool loadDefaultShaders(RenderState* rs, |
|
||||||
u32 num_shaders = SHADER_INIT_COUNT, |
|
||||||
const ShaderInit shaders[] = SHADER_INIT_LIST); |
|
||||||
|
|
||||||
// NOTE: only useful if the shader attribute names match the names in the
|
|
||||||
// default shaders. If loading a custom shader, this may not work as expected,
|
|
||||||
// and you should use getVertexAttribByName instead
|
|
||||||
GLVertexAttrib* getVertexAttribByType(ShaderProgram* shader, |
|
||||||
MeshBufferType buf_type); |
|
||||||
@ -1,24 +1,16 @@ |
|||||||
|
|
||||||
#pragma once |
#pragma once |
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/glm.hpp> |
|
||||||
|
|
||||||
|
enum shader_t |
||||||
|
{ |
||||||
|
SIMPLE_SHADER, |
||||||
|
DEFAULT_SHADER |
||||||
|
}; |
||||||
|
|
||||||
typedef uint8_t u8; |
enum mesh_t |
||||||
typedef uint16_t u16; |
{ |
||||||
typedef uint32_t u32; |
SIMPLE_MESH, |
||||||
typedef uint64_t u64; |
DEFAULT_MESHES |
||||||
|
}; |
||||||
typedef int32_t i32; |
|
||||||
typedef int64_t i64; |
|
||||||
|
|
||||||
using glm::ivec2; |
|
||||||
using glm::uvec2; |
|
||||||
using glm::uvec4; |
|
||||||
using glm::vec2; |
|
||||||
using glm::vec3; |
|
||||||
using glm::vec4; |
|
||||||
using glm::mat4; |
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,32 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
|
||||||
|
// NOTE: wrapper for stb_image
|
||||||
|
struct util_image |
||||||
|
{ |
||||||
|
int32 w; |
||||||
|
int32 h; |
||||||
|
int32 bits_per_channel; |
||||||
|
int32 num_channels; |
||||||
|
uint data_len; |
||||||
|
uint8* pixels; |
||||||
|
char file_path[256]; |
||||||
|
}; |
||||||
|
|
||||||
|
struct util_RGBA |
||||||
|
{ |
||||||
|
real32 R; |
||||||
|
real32 G; |
||||||
|
real32 B; |
||||||
|
real32 A; |
||||||
|
}; |
||||||
|
|
||||||
|
util_image utilLoadImagePath(const char* full_path); |
||||||
|
|
||||||
|
util_image utilLoadImageBytes(const unsigned char* bytes, uint length); |
||||||
|
|
||||||
|
void utilFreeImage(util_image image); |
||||||
|
|
||||||
@ -1,430 +0,0 @@ |
|||||||
|
|
||||||
#include <cassert> |
|
||||||
#include <cstring> |
|
||||||
|
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/ext/matrix_transform.hpp> |
|
||||||
#include "tiny_gltf.h" |
|
||||||
|
|
||||||
#include "asset.h" |
|
||||||
#include "dumbLog.h" |
|
||||||
|
|
||||||
|
|
||||||
// forward declarations
|
|
||||||
|
|
||||||
// TODO: move to GLDebug.h?
|
|
||||||
void dumpNodes(tinygltf::Model t_mdl); |
|
||||||
bool parseMeshNode(Mesh* m, |
|
||||||
MemoryArena* arena, |
|
||||||
const tinygltf::Node& node, |
|
||||||
const tinygltf::Model& t_mdl); |
|
||||||
|
|
||||||
Model* getCachedModel(Assets* assets, u64 path_hash); |
|
||||||
Model* loadModelFile(Assets* assets, const char* filename); |
|
||||||
Texture* getCachedTexture(Assets* assets, u64 path_hash); |
|
||||||
|
|
||||||
|
|
||||||
// interface
|
|
||||||
|
|
||||||
Mesh* |
|
||||||
meshInit(MemoryArena* arena, |
|
||||||
u32 num_vertices, |
|
||||||
u32 num_indices, |
|
||||||
bool use_normals, |
|
||||||
bool use_colors, |
|
||||||
bool use_uvs) |
|
||||||
{ |
|
||||||
Mesh* m = ARENA_ALLOC(arena, Mesh, 1); |
|
||||||
m->num_vertices = num_vertices; |
|
||||||
m->num_indices = num_indices; |
|
||||||
|
|
||||||
m->vertices = ARENA_ALLOC(arena, vec3, num_vertices); |
|
||||||
m->indices = ARENA_ALLOC(arena, u16, num_indices); |
|
||||||
|
|
||||||
if (use_normals) |
|
||||||
m->normals = ARENA_ALLOC(arena, vec3, num_vertices); |
|
||||||
if (use_colors) |
|
||||||
m->colors = ARENA_ALLOC(arena, vec3, num_vertices); |
|
||||||
if (use_uvs) |
|
||||||
m->uvs = ARENA_ALLOC(arena, vec2, num_vertices); |
|
||||||
|
|
||||||
m->xform = ARENA_ALLOC(arena, mat4, 1); |
|
||||||
*m->xform = mat4(1); |
|
||||||
|
|
||||||
return m; |
|
||||||
} |
|
||||||
|
|
||||||
Model* |
|
||||||
modelInitManual(MemoryArena* arena, u32 num_meshes, Mesh* meshes) |
|
||||||
{ |
|
||||||
Model* mdl = ARENA_ALLOC(arena, Model, 1); |
|
||||||
mdl->meshes = meshes; // NOTE: defaults to nullptr
|
|
||||||
mdl->num_meshes = num_meshes; |
|
||||||
|
|
||||||
return mdl; |
|
||||||
} |
|
||||||
|
|
||||||
Model* |
|
||||||
getModelByPath(Assets* assets, const char* filepath) |
|
||||||
{ |
|
||||||
Model* mdl = getCachedModel(assets, utilFNV64a_str(filepath)); |
|
||||||
|
|
||||||
if (!mdl) |
|
||||||
mdl = loadModelFile(assets, filepath); |
|
||||||
|
|
||||||
return mdl; |
|
||||||
} |
|
||||||
|
|
||||||
Texture* |
|
||||||
getTextureByPath(Assets* assets, const char* filepath) |
|
||||||
{ |
|
||||||
Texture* texture = |
|
||||||
getCachedTexture(assets, utilFNV64a_str(filepath)); |
|
||||||
|
|
||||||
// NOTE: the texture should be loaded when the model is loaded, so it's an
|
|
||||||
// error if we don't find it in cache
|
|
||||||
if (!texture) { |
|
||||||
LOG(Error) << "texture file, " << filepath << " not loaded\n"; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
return texture; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// internal
|
|
||||||
|
|
||||||
Model* |
|
||||||
initModel(Assets* assets, |
|
||||||
tinygltf::Model t_mdl, |
|
||||||
const char* filename) |
|
||||||
{ |
|
||||||
// TODO: re-alloc array when out of space
|
|
||||||
assert(assets->num_models < assets->max_models && assets->arena != nullptr); |
|
||||||
Model* mdl = &assets->models[assets->num_models]; |
|
||||||
assets->num_models++; |
|
||||||
|
|
||||||
uint buf_count = t_mdl.bufferViews.size(); |
|
||||||
mdl->meshes = ARENA_ALLOC(assets->arena, Mesh, buf_count); |
|
||||||
mdl->num_meshes = t_mdl.meshes.size(); |
|
||||||
mdl->filepath = arenaCopyCStr(assets->arena, filename, MAX_PATH_SIZE); |
|
||||||
mdl->filepath_hash = utilFNV64a_str(mdl->filepath); |
|
||||||
|
|
||||||
return mdl; |
|
||||||
} |
|
||||||
|
|
||||||
Texture* |
|
||||||
getFreeTexture(Assets* assets) |
|
||||||
{ |
|
||||||
if (assets->num_textures < assets->max_textures) |
|
||||||
return &assets->textures[assets->num_textures++]; |
|
||||||
|
|
||||||
LOGF(Error, "no free textures\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
Texture* |
|
||||||
copyDiffuseTexture(Assets* assets, const tinygltf::Model& t_mdl) |
|
||||||
{ |
|
||||||
// NOTE: assuming material[0] since we're using pallete texture
|
|
||||||
assert(t_mdl.materials.size() == 1 |
|
||||||
&& t_mdl.textures.size() == 1 |
|
||||||
&& t_mdl.images.size() == 1 |
|
||||||
&& t_mdl.images[0].image.size() > 0); |
|
||||||
tinygltf::Image t_img = t_mdl.images[0]; |
|
||||||
Texture* dtex = getFreeTexture(assets); |
|
||||||
|
|
||||||
if (!dtex) { |
|
||||||
LOG(Error) << "Error Loading diffuse texture\n"; |
|
||||||
// TODO: reclaim arena memory
|
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
dtex->w = t_img.width; |
|
||||||
dtex->h = t_img.height; |
|
||||||
dtex->bits_per_channel = t_img.bits; |
|
||||||
dtex->num_channels = t_img.component; |
|
||||||
dtex->data_len = t_img.image.size(); |
|
||||||
dtex->pixels = ARENA_ALLOC(assets->arena, u8, dtex->data_len); |
|
||||||
std::strncpy(dtex->file_path, t_img.uri.c_str(), sizeof(dtex->file_path)); |
|
||||||
dtex->filepath_hash = utilFNV64a_str(t_img.uri.c_str()); |
|
||||||
std::memcpy(dtex->pixels, t_img.image.data(), dtex->data_len); |
|
||||||
|
|
||||||
return dtex; |
|
||||||
} |
|
||||||
|
|
||||||
Model* |
|
||||||
loadModelFile(Assets* assets, const char* filename) |
|
||||||
{ |
|
||||||
tinygltf::Model t_mdl; |
|
||||||
tinygltf::TinyGLTF gltf_ctx; |
|
||||||
std::string err; |
|
||||||
std::string warn; |
|
||||||
|
|
||||||
LOG(Info) << "Loading model: " << filename << "\n"; |
|
||||||
|
|
||||||
if (!gltf_ctx.LoadASCIIFromFile(&t_mdl, &err, &warn, filename)) { |
|
||||||
LOG(Error) << "Error loading file: " << filename |
|
||||||
<< " , msg: " << err << "\n"; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
#if 0 |
|
||||||
dumpNodes(t_mdl); |
|
||||||
#endif |
|
||||||
|
|
||||||
// NOTE: assume we're working with a single buffer
|
|
||||||
assert(t_mdl.buffers.size() == 1); |
|
||||||
Model* mdl = initModel(assets, t_mdl, filename); |
|
||||||
mdl->diffuse_texture = copyDiffuseTexture(assets, t_mdl); |
|
||||||
if (mdl->diffuse_texture == nullptr) return nullptr; |
|
||||||
|
|
||||||
uint mesh_idx = 0; |
|
||||||
|
|
||||||
for (tinygltf::Node node : t_mdl.nodes) { |
|
||||||
if (node.mesh >= 0) { |
|
||||||
if (!parseMeshNode(&mdl->meshes[mesh_idx++], |
|
||||||
assets->arena, |
|
||||||
node, |
|
||||||
t_mdl)) |
|
||||||
{ |
|
||||||
LOG(Error) << "Error parsing node\n"; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return mdl; |
|
||||||
} |
|
||||||
|
|
||||||
Model* |
|
||||||
getCachedModel(Assets* assets, u64 path_hash) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < assets->num_models; i++) { |
|
||||||
if (assets->models[i].filepath_hash == path_hash) |
|
||||||
return &assets->models[i]; |
|
||||||
} |
|
||||||
|
|
||||||
LOG(Debug) << "asset not cached, hash: " << path_hash << "\n"; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
Texture* |
|
||||||
getCachedTexture(Assets* assets, u64 path_hash) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < assets->num_textures; i++) { |
|
||||||
if (assets->textures[i].filepath_hash == path_hash) |
|
||||||
return &assets->textures[i]; |
|
||||||
} |
|
||||||
|
|
||||||
LOG(Debug) << "asset not cached, hash: " << path_hash << "\n"; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
copyBuffer(uint8_t*& buffer_ref, |
|
||||||
MemoryArena* arena, |
|
||||||
int acc_idx, |
|
||||||
const tinygltf::Model& t_mdl) |
|
||||||
{ |
|
||||||
const tinygltf::Accessor& acc = t_mdl.accessors[acc_idx]; |
|
||||||
const tinygltf::BufferView& bv = t_mdl.bufferViews[acc.bufferView]; |
|
||||||
const tinygltf::Buffer& t_buf = t_mdl.buffers[bv.buffer]; |
|
||||||
|
|
||||||
// TODO: clean up validation
|
|
||||||
if (bv.target == TINYGLTF_TARGET_ARRAY_BUFFER) { |
|
||||||
assert(acc.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT |
|
||||||
&& (acc.type == TINYGLTF_TYPE_VEC3 |
|
||||||
|| acc.type == TINYGLTF_TYPE_VEC2)); |
|
||||||
} else if (bv.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { |
|
||||||
assert(acc.type == TINYGLTF_TYPE_SCALAR |
|
||||||
&& acc.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT); |
|
||||||
} else { |
|
||||||
LOG(Error) << "unknown target\n"; |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
assert(bv.byteStride == 0); |
|
||||||
buffer_ref = ARENA_ALLOC(arena, u8, bv.byteLength); |
|
||||||
std::memcpy(buffer_ref, &t_buf.data[bv.byteOffset], bv.byteLength); |
|
||||||
|
|
||||||
return buffer_ref != nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
// FIXME: need to implement tree structure for blender models to work properly
|
|
||||||
mat4* |
|
||||||
parseNodeTransform(MemoryArena* arena, const tinygltf::Node* node) |
|
||||||
{ |
|
||||||
if (node->rotation.size() == 4 |
|
||||||
&& node->scale.size() == 3 |
|
||||||
&& node->translation.size() == 3) |
|
||||||
{ |
|
||||||
mat4* xform = ARENA_ALLOC(arena, mat4, 1); |
|
||||||
*xform = mat4(1.0f); |
|
||||||
*xform = glm::rotate(*xform, (float) node->rotation[3], |
|
||||||
vec3((float) node->rotation[0], |
|
||||||
(float) node->rotation[1], |
|
||||||
(float) node->rotation[2]) |
|
||||||
); |
|
||||||
*xform = glm::scale(*xform, |
|
||||||
vec3((float) node->scale[0], |
|
||||||
(float) node->scale[0], |
|
||||||
(float) node->scale[0]) |
|
||||||
); |
|
||||||
*xform = glm::translate(*xform, |
|
||||||
vec3((float) node->translation[0], |
|
||||||
(float) node->translation[0], |
|
||||||
(float) node->translation[0]) |
|
||||||
); |
|
||||||
|
|
||||||
return xform; |
|
||||||
} |
|
||||||
|
|
||||||
LOG(Error) << "Unknown transform\n"; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
parseMeshNode(Mesh* m, |
|
||||||
MemoryArena* arena, |
|
||||||
const tinygltf::Node& node, |
|
||||||
const tinygltf::Model& t_mdl) |
|
||||||
{ |
|
||||||
tinygltf::Mesh t_mesh = t_mdl.meshes[node.mesh]; |
|
||||||
// NOTE: assume only 1 primitive object per mesh
|
|
||||||
assert(t_mdl.meshes[node.mesh].primitives.size() == 1); |
|
||||||
tinygltf::Primitive prim = t_mesh.primitives[0]; |
|
||||||
|
|
||||||
// NOTE: verify assumptions about input
|
|
||||||
assert(prim.attributes.find("POSITION") != prim.attributes.end() |
|
||||||
&& prim.attributes.find("NORMAL") != prim.attributes.end() |
|
||||||
&& prim.attributes.find("TEXCOORD_0") != prim.attributes.end() |
|
||||||
&& prim.indices >= 0); |
|
||||||
|
|
||||||
const tinygltf::Accessor& vert_acc = |
|
||||||
t_mdl.accessors[prim.attributes["POSITION"]]; |
|
||||||
const tinygltf::Accessor& index_acc = t_mdl.accessors[prim.indices]; |
|
||||||
m->num_vertices = vert_acc.count; |
|
||||||
m->num_indices = index_acc.count; |
|
||||||
// FIXME: the node transforms from blender only work as part of a node tree
|
|
||||||
#if 1 |
|
||||||
m->xform = ARENA_ALLOC(arena, mat4, 1); |
|
||||||
*m->xform = mat4(1.0f); |
|
||||||
#else |
|
||||||
m->xform = parseNodeTransform(arena, &node); |
|
||||||
#endif |
|
||||||
|
|
||||||
if (m->xform != nullptr |
|
||||||
&& copyBuffer((uint8_t*&) m->vertices, arena, |
|
||||||
prim.attributes["POSITION"], t_mdl) |
|
||||||
&& copyBuffer((uint8_t*&) m->normals, arena, |
|
||||||
prim.attributes["NORMAL"], t_mdl) |
|
||||||
&& copyBuffer((uint8_t*&) m->uvs, arena, |
|
||||||
prim.attributes["TEXCOORD_0"], t_mdl) |
|
||||||
&& copyBuffer((uint8_t*&) m->indices, arena, |
|
||||||
prim.indices, t_mdl)) |
|
||||||
{ |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
const char* |
|
||||||
getTargetStr(int target) |
|
||||||
{ |
|
||||||
switch (target) { |
|
||||||
case 34962: return "GL_ARRAY_BUFFER"; |
|
||||||
case 34963: return "GL_ELEMENT_ARRAY_BUFFER"; |
|
||||||
default: return "UNKNOWN"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const char* |
|
||||||
getElementType(int type) |
|
||||||
{ |
|
||||||
switch (type) { |
|
||||||
case 2: return "TINYGLTF_TYPE_VEC2"; |
|
||||||
case 3: return "TINYGLTF_TYPE_VEC3"; |
|
||||||
case 4: return "TINYGLTF_TYPE_VEC4"; |
|
||||||
case 65: return "TINYGLTF_TYPE_SCALAR"; |
|
||||||
default: return "UNKNOWN"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const char* |
|
||||||
getComponentType(int componentType) |
|
||||||
{ |
|
||||||
switch (componentType) { |
|
||||||
case 5123: return "TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT"; |
|
||||||
case 5124: return "TINYGLTF_COMPONENT_TYPE_INT"; |
|
||||||
case 5125: return "TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT"; |
|
||||||
case 5126: return "TINYGLTF_COMPONENT_TYPE_FLOAT"; |
|
||||||
case 5130: return "TINYGLTF_COMPONENT_TYPE_DOUBLE"; |
|
||||||
default: return "UNKOWN"; } |
|
||||||
} |
|
||||||
|
|
||||||
const char* |
|
||||||
getDrawMode(int drawMode) |
|
||||||
{ |
|
||||||
switch (drawMode) { |
|
||||||
case 0: return "TINYGLTF_MODE_POINTS"; |
|
||||||
case 1: return "TINYGLTF_MODE_LINE"; |
|
||||||
case 2: return "TINYGLTF_MODE_LINE_LOOP"; |
|
||||||
case 3: return "TINYGLTF_MODE_LINE_STRIP"; |
|
||||||
case 4: return "TINYGLTF_MODE_TRIANGLES"; |
|
||||||
case 5: return "TINYGLTF_MODE_TRIANGLE_STRIP"; |
|
||||||
case 6: return "TINYGLTF_MODE_TRIANGLE_FAN"; |
|
||||||
default: return "UNKOWN MODE"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
dumpBuffer(tinygltf::Model mdl, tinygltf::Accessor acc) |
|
||||||
{ |
|
||||||
size_t bv_idx = acc.bufferView; |
|
||||||
assert(bv_idx >= 0 && bv_idx < mdl.bufferViews.size()); |
|
||||||
tinygltf::BufferView bv = mdl.bufferViews[bv_idx]; |
|
||||||
|
|
||||||
LOG(Debug) << "-----------------------\n"; |
|
||||||
LOG(Debug) << "buf idx: " << bv_idx << "\n"; |
|
||||||
LOG(Debug) << "buf target: " << getTargetStr(bv.target) << "\n"; |
|
||||||
LOG(Debug) << "buf len: " << bv.byteLength << "\n"; |
|
||||||
LOG(Debug) << "buf offset: " << bv.byteOffset << "\n"; |
|
||||||
LOG(Debug) << "buf stride: " << bv.byteStride << "\n"; |
|
||||||
LOG(Debug) << "acc component type: " |
|
||||||
<< getComponentType(acc.componentType) << "\n"; |
|
||||||
LOG(Debug) << "acc element type: " |
|
||||||
<< getElementType(acc.type) << "\n"; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
dumpNodes(tinygltf::Model t_mdl) |
|
||||||
{ |
|
||||||
for (tinygltf::Node node : t_mdl.nodes) { |
|
||||||
LOG(Debug) << "##################\n"; |
|
||||||
LOG(Debug) << "node name: " << node.name << "\n"; |
|
||||||
LOG(Debug) << "node mesh idx: " << node.mesh << "\n"; |
|
||||||
|
|
||||||
if (node.mesh >= 0) { |
|
||||||
tinygltf::Mesh t_mesh = t_mdl.meshes[node.mesh]; |
|
||||||
LOG(Debug) << "node mesh name: " << t_mesh.name << "\n"; |
|
||||||
// NOTE: assume only 1 primitive object per mesh
|
|
||||||
assert(t_mdl.meshes[node.mesh].primitives.size() == 1); |
|
||||||
tinygltf::Primitive prim = t_mesh.primitives[0]; |
|
||||||
LOG(Debug) << "node draw mode: " << getDrawMode(prim.mode) << "\n"; |
|
||||||
|
|
||||||
for (auto& att : prim.attributes) { |
|
||||||
LOG(Debug) << "dumping buffer: " << att.first << "\n"; |
|
||||||
dumpBuffer(t_mdl, t_mdl.accessors[att.second]); |
|
||||||
} |
|
||||||
|
|
||||||
LOG(Debug) << "dumping index buffer\n"; |
|
||||||
dumpBuffer(t_mdl, t_mdl.accessors[prim.indices]); |
|
||||||
} else { |
|
||||||
LOG(Debug) << "Not a mesh node\n"; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@ -0,0 +1,104 @@ |
|||||||
|
const char* DEFAULT_VERTEX_SHADER = R"VS( |
||||||
|
#version 330 core |
||||||
|
|
||||||
|
layout (location = 0) in vec3 vertexPosition_modelspace; |
||||||
|
layout (location = 1) in vec3 normal; |
||||||
|
layout (location = 2) in vec3 texCoord; |
||||||
|
|
||||||
|
out vec3 fragVertex; |
||||||
|
out vec3 fragNormal; |
||||||
|
out vec2 fragUV; |
||||||
|
|
||||||
|
uniform mat4 world_transform; |
||||||
|
uniform mat4 model; |
||||||
|
uniform mat4 view; |
||||||
|
uniform mat4 projection; |
||||||
|
|
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
fragNormal = normal; |
||||||
|
fragVertex = vertexPosition_modelspace; |
||||||
|
fragUV = texCoord.st; |
||||||
|
gl_Position = projection * view * |
||||||
|
world_transform * model * vec4(vertexPosition_modelspace, 1); |
||||||
|
} |
||||||
|
)VS"; |
||||||
|
|
||||||
|
// TODO: there's a bug here with the array size of 'lights'
|
||||||
|
// with intel opengl, size can be >1000
|
||||||
|
// but with nvidea opengl size of >200 gives the following error:
|
||||||
|
// error C6020: Constant register limit exceeded at sampler;
|
||||||
|
// more than 1024 registers needed to compile program
|
||||||
|
const char* DEFAULT_FRAGMENT_SHADER = R"FS( |
||||||
|
#version 330 core |
||||||
|
|
||||||
|
in vec3 fragVertex; |
||||||
|
in vec3 fragNormal; |
||||||
|
in vec2 fragUV; |
||||||
|
|
||||||
|
out vec4 color; |
||||||
|
|
||||||
|
uniform mat4 model; |
||||||
|
uniform mat3 normal_matrix; |
||||||
|
uniform sampler2D sampler; |
||||||
|
uniform uint num_lights = 0u; |
||||||
|
|
||||||
|
struct point_light { |
||||||
|
uint light_ID; |
||||||
|
vec3 position; |
||||||
|
vec3 color; |
||||||
|
float intensity; |
||||||
|
}; |
||||||
|
|
||||||
|
uniform point_light lights[200]; |
||||||
|
|
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
vec3 normal = normalize(normal_matrix * fragNormal); |
||||||
|
vec3 fragPosition = vec3(model * vec4(fragVertex, 1)); |
||||||
|
float totalBrightness = 0; |
||||||
|
|
||||||
|
for (uint i = 0u; i < num_lights; i++) { |
||||||
|
vec3 surfaceToLight = lights[i].position - fragPosition; |
||||||
|
float brightness = dot(normal, surfaceToLight) / (length(surfaceToLight) * length(normal)); |
||||||
|
totalBrightness += brightness; |
||||||
|
} |
||||||
|
|
||||||
|
color = clamp(totalBrightness, 0, 1) * texture(sampler, fragUV.st); |
||||||
|
} |
||||||
|
)FS"; |
||||||
|
|
||||||
|
const char* SIMPLE_VERTEX_SHADER = R"SVS( |
||||||
|
#version 330 core |
||||||
|
|
||||||
|
layout (location = 0) in vec3 position; |
||||||
|
layout (location = 1) in vec3 color; |
||||||
|
|
||||||
|
out vec3 frag_color; |
||||||
|
|
||||||
|
uniform mat4 world_transform; |
||||||
|
uniform mat4 MVP; |
||||||
|
|
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
frag_color = color; |
||||||
|
gl_Position = MVP * world_transform * vec4(position, 1); |
||||||
|
} |
||||||
|
)SVS"; |
||||||
|
|
||||||
|
const char* SIMPLE_FRAGMENT_SHADER = R"SFS( |
||||||
|
#version 330 core |
||||||
|
|
||||||
|
in vec3 frag_color; |
||||||
|
|
||||||
|
out vec4 color; |
||||||
|
|
||||||
|
|
||||||
|
void main() |
||||||
|
{ |
||||||
|
color = vec4(frag_color.rgb, 1); |
||||||
|
} |
||||||
|
)SFS"; |
||||||
@ -1,74 +1,95 @@ |
|||||||
|
|
||||||
#define GLM_FORCE_XYZW_ONLY |
#include <cassert> |
||||||
|
|
||||||
#include <glm/gtc/matrix_transform.hpp> |
#include <glm/gtc/matrix_transform.hpp> |
||||||
|
|
||||||
#include "dumbLog.h" |
#include "dumbLog.h" |
||||||
#include "entity.h" |
#include "entity.h" |
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
|
||||||
|
void initDefaults(entity& e); |
||||||
|
|
||||||
|
|
||||||
|
// interface
|
||||||
|
|
||||||
bool |
bool |
||||||
initEntity(Entity* e, |
entInitModel(entity& e, const char* model_path) |
||||||
GLContext* gl_ctx, |
|
||||||
MemoryArena* arena, |
|
||||||
Model* mdl, |
|
||||||
u32 num_attrib_mappings, |
|
||||||
GLBufferToAttribMapping* attrib_mappings, |
|
||||||
const char* name, |
|
||||||
GLenum draw_mode, |
|
||||||
GLenum usage) |
|
||||||
{ |
{ |
||||||
e->num_meshes = mdl->num_meshes; |
initDefaults(e); |
||||||
e->meshes = ARENA_ALLOC(arena, GLMesh, e->num_meshes); |
|
||||||
e->model_xform = ARENA_ALLOC(arena, mat4, 1); |
if (meLoadFromFile(e.meshes, model_path)) |
||||||
*e->model_xform = mat4(1.f); |
{ |
||||||
e->name = arenaCopyCStr(arena, name); |
e.render_objs = roInitModel(e.meshes); |
||||||
e->draw_mode = draw_mode; |
|
||||||
e->usage = usage; |
|
||||||
|
|
||||||
if (mdl->diffuse_texture) { |
|
||||||
e->diffuse_texture = getGLTexture(gl_ctx, mdl->diffuse_texture); |
|
||||||
|
|
||||||
if (!e->diffuse_texture) |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
for (u32 i = 0; i< e->num_meshes; i++) { |
if (e.render_objs != nullptr) |
||||||
GLMesh* glm = &e->meshes[i]; |
return true; |
||||||
*glm = loadGLMesh(arena, |
|
||||||
mdl->meshes[i], |
|
||||||
draw_mode, |
|
||||||
usage, |
|
||||||
e->diffuse_texture, |
|
||||||
num_attrib_mappings, |
|
||||||
attrib_mappings); |
|
||||||
|
|
||||||
if (glm->vao_id == 0) { |
|
||||||
LOGF(Error, "error initializing entity\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
|
entFree(e); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
entInitMesh(entity& e, simple_mesh* mesh, GLenum draw_mode) |
||||||
|
{ |
||||||
|
initDefaults(e); |
||||||
|
e.mesh = mesh; |
||||||
|
e.render_objs = roInitSimpleMesh(*e.mesh, draw_mode); |
||||||
|
|
||||||
|
if (e.render_objs == nullptr) |
||||||
|
return false; |
||||||
|
|
||||||
return true; |
return true; |
||||||
} |
} |
||||||
|
|
||||||
void |
void |
||||||
setEntityPosition(Entity* e, vec3 pos) |
entFree(entity& e) |
||||||
|
{ |
||||||
|
roFree(e.render_objs); |
||||||
|
e.render_objs = nullptr; |
||||||
|
meFreeMeshGroup(e.meshes); |
||||||
|
|
||||||
|
if (e.mesh != nullptr) meFreeSimpleMesh(e.mesh); |
||||||
|
e.mesh = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
entSetWorldPosition(entity& e, glm::vec3 v) |
||||||
|
{ |
||||||
|
e.world_transform[3][0] = v.x; |
||||||
|
e.world_transform[3][1] = v.y; |
||||||
|
e.world_transform[3][2] = v.z; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
entTranslate(entity& e, glm::vec3 v) |
||||||
{ |
{ |
||||||
(*e->model_xform)[3][0] = pos.x; |
e.world_transform = glm::translate(e.world_transform, v); |
||||||
(*e->model_xform)[3][1] = pos.y; |
|
||||||
(*e->model_xform)[3][2] = pos.z; |
|
||||||
} |
} |
||||||
|
|
||||||
void |
void |
||||||
rotateEntity(Entity* e, vec3 axis, float radians) |
entScale(entity& e, glm::vec3 v) |
||||||
{ |
{ |
||||||
*e->model_xform = glm::rotate(*e->model_xform, radians, axis); |
e.world_transform = glm::scale(e.world_transform, v); |
||||||
} |
} |
||||||
|
|
||||||
void |
void |
||||||
scaleEntity(Entity* e, float scale) |
entRotate(entity& e, float angle, glm::vec3 axis) |
||||||
|
{ |
||||||
|
e.world_transform = glm::rotate(e.world_transform, angle, axis); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
void |
||||||
|
initDefaults(entity& e) |
||||||
{ |
{ |
||||||
*e->model_xform = |
e.world_transform = glm::mat4(1.0); |
||||||
glm::scale(*e->model_xform, vec3(scale, scale, scale)); |
entScale(e, glm::vec3(1.0)); |
||||||
|
entSetWorldPosition(e, glm::vec3(0, 0, 0)); |
||||||
} |
} |
||||||
|
|
||||||
|
|||||||
@ -1,42 +0,0 @@ |
|||||||
|
|
||||||
#include "input.h" |
|
||||||
|
|
||||||
|
|
||||||
void |
|
||||||
inputProcessEvent(InputState* is, SDL_Event& e) |
|
||||||
{ |
|
||||||
switch (e.type) { |
|
||||||
case SDL_QUIT: |
|
||||||
is->window_closed = true; |
|
||||||
break; |
|
||||||
case SDL_KEYDOWN: |
|
||||||
switch (e.key.keysym.sym) { |
|
||||||
case SDLK_ESCAPE: is->escape = true; break; |
|
||||||
case SDLK_LEFT: is->left = true; break; |
|
||||||
case SDLK_RIGHT: is->right = true; break; |
|
||||||
case SDLK_UP: is->up = true; break; |
|
||||||
case SDLK_DOWN: is->down = true; break; |
|
||||||
} |
|
||||||
break; |
|
||||||
case SDL_KEYUP: |
|
||||||
switch (e.key.keysym.sym) { |
|
||||||
case SDLK_ESCAPE: is->escape = false; break; |
|
||||||
case SDLK_LEFT: is->left = false; break; |
|
||||||
case SDLK_RIGHT: is->right = false; break; |
|
||||||
case SDLK_UP: is->up = false; break; |
|
||||||
case SDLK_DOWN: is->down = false; break; |
|
||||||
} |
|
||||||
break; |
|
||||||
default: break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
inputProcessEvents(InputState* is) |
|
||||||
{ |
|
||||||
SDL_Event e; |
|
||||||
|
|
||||||
while (SDL_PollEvent(&e)) |
|
||||||
inputProcessEvent(is, e); |
|
||||||
} |
|
||||||
|
|
||||||
@ -0,0 +1,55 @@ |
|||||||
|
|
||||||
|
#include <sstream> // stringstream |
||||||
|
|
||||||
|
#include "lights.h" |
||||||
|
|
||||||
|
|
||||||
|
light_group* |
||||||
|
lightsInit(uint max_lights) |
||||||
|
{ |
||||||
|
light_group* lg = UTIL_ALLOC(1, light_group); |
||||||
|
lg->lights = UTIL_ALLOC(max_lights, point_light); |
||||||
|
lg->max_lights = max_lights; |
||||||
|
return lg; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
lightsOut(light_group* lights) |
||||||
|
{ |
||||||
|
utilSafeFree(lights->lights); |
||||||
|
utilSafeFree(lights); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
lightsAdd(light_group* lights, glm::vec3 pos, glm::vec3 color, float intensity) |
||||||
|
{ |
||||||
|
if (lights->num_lights == lights->max_lights) |
||||||
|
return false; |
||||||
|
|
||||||
|
point_light& pl = lights->lights[lights->num_lights]; |
||||||
|
pl.position = pos; |
||||||
|
pl.color = color; |
||||||
|
pl.intensity = intensity; |
||||||
|
lights->needs_update = true; |
||||||
|
lights->num_lights++; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
lightsUpdate(light_group* lights, default_shader_program* shader) |
||||||
|
{ |
||||||
|
glUniform1ui(shader->num_lights_id, lights->num_lights); |
||||||
|
|
||||||
|
for (uint i = 0; i < lights->num_lights; i++) { |
||||||
|
std::stringstream ss; |
||||||
|
ss << "lights[" << i << "].position"; |
||||||
|
int light_pos_loc = |
||||||
|
glGetUniformLocation(shader->program_id, ss.str().c_str()); |
||||||
|
|
||||||
|
glUniform3fv(light_pos_loc, 1, &lights->lights[i].position[0]); |
||||||
|
} |
||||||
|
|
||||||
|
lights->needs_update = false; |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,234 @@ |
|||||||
|
#include <cassert> |
||||||
|
|
||||||
|
#include <assimp/cimport.h> |
||||||
|
#include <assimp/scene.h> |
||||||
|
#include <assimp/postprocess.h> |
||||||
|
|
||||||
|
#include <glm/glm.hpp> |
||||||
|
#include <glm/geometric.hpp> |
||||||
|
#include <glm/gtc/matrix_transform.hpp> |
||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
|
||||||
|
#include "mesh.h" |
||||||
|
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
|
||||||
|
mesh_info* allocateMeshInfo(uint num_vertices, uint num_indices); |
||||||
|
void assimpLogCB(const char* message, char* user); |
||||||
|
mesh_info* copyMeshInfo(const aiScene* scene, aiMesh* mesh); |
||||||
|
inline glm::vec3 copyVector(aiVector3D v_in, glm::vec3& v_out); |
||||||
|
void freeMesh(mesh_info* mesh); |
||||||
|
bool loadDiffuseTexture(const aiScene* scene, aiMesh* mesh, mesh_info* mi); |
||||||
|
bool validateScene(const aiScene* scene, const char* filepath); |
||||||
|
|
||||||
|
// interface
|
||||||
|
|
||||||
|
bool |
||||||
|
meInitAssimp() |
||||||
|
{ |
||||||
|
LOG(Info) << "Initializing Assimp\n"; |
||||||
|
aiLogStream ls; |
||||||
|
ls.callback = assimpLogCB; |
||||||
|
aiAttachLogStream(&ls); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
meLoadFromFile(mesh_group& mesh_group, const char* filepath) |
||||||
|
{ |
||||||
|
LOG(Info) << "Loading file: " << filepath << "\n"; |
||||||
|
const aiScene* scene = aiImportFile(filepath, aiProcessPreset_TargetRealtime_MaxQuality); |
||||||
|
|
||||||
|
if (!validateScene(scene, filepath)) |
||||||
|
return false; |
||||||
|
|
||||||
|
mesh_group.num_meshes = scene->mNumMeshes; |
||||||
|
mesh_group.meshes = UTIL_ALLOC(mesh_group.num_meshes, mesh_info*); |
||||||
|
|
||||||
|
for (uint i = 0; i < scene->mNumMeshes; i++) { |
||||||
|
aiMesh* mesh = scene->mMeshes[i]; |
||||||
|
mesh_info* mi = copyMeshInfo(scene, mesh); |
||||||
|
|
||||||
|
if (!mesh->HasTextureCoords(0) || |
||||||
|
!loadDiffuseTexture(scene, mesh, mi)) |
||||||
|
{ |
||||||
|
LOG(Error) << "Error loading texture, cleaning up import\n"; |
||||||
|
freeMesh(mi); |
||||||
|
aiReleaseImport(scene); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
mesh_group.meshes[i] = mi; |
||||||
|
} |
||||||
|
|
||||||
|
aiReleaseImport(scene); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
simple_mesh* |
||||||
|
meInitMesh(uint num_vertices) |
||||||
|
{ |
||||||
|
assert(num_vertices > 0); |
||||||
|
simple_mesh* sm = UTIL_ALLOC(1, simple_mesh); |
||||||
|
sm->model_transform = glm::mat4(1.0); |
||||||
|
sm->num_vertices = num_vertices; |
||||||
|
sm->vertices = UTIL_ALLOC(num_vertices, glm::vec3); |
||||||
|
sm->vert_colors = UTIL_ALLOC(num_vertices, glm::vec3); |
||||||
|
return sm; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
meFreeMeshGroup(mesh_group& mesh_group) |
||||||
|
{ |
||||||
|
for (uint i = 0; i < mesh_group.num_meshes; i++) |
||||||
|
freeMesh(mesh_group.meshes[i]); |
||||||
|
|
||||||
|
utilSafeFree(mesh_group.meshes); |
||||||
|
mesh_group.num_meshes = 0; |
||||||
|
mesh_group.meshes = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
meFreeSimpleMesh(simple_mesh* mesh) |
||||||
|
{ |
||||||
|
assert(mesh != nullptr); |
||||||
|
utilSafeFree(mesh->vertices); |
||||||
|
mesh->vertices = nullptr; |
||||||
|
utilSafeFree(mesh->vert_colors); |
||||||
|
mesh->vert_colors = nullptr; |
||||||
|
mesh->num_vertices = 0; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
meShutdownAssimp() |
||||||
|
{ |
||||||
|
aiDetachAllLogStreams(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
mesh_info* |
||||||
|
allocateMeshInfo(uint num_vertices, uint num_indices) |
||||||
|
{ |
||||||
|
mesh_info* mi = UTIL_ALLOC(1, mesh_info); |
||||||
|
mi->model_transform = glm::mat4(1); |
||||||
|
|
||||||
|
// allocate buffers for vertex and index data from mesh
|
||||||
|
mi->num_vertices = num_vertices; |
||||||
|
mi->vertices = UTIL_ALLOC(mi->num_vertices, glm::vec3); |
||||||
|
mi->num_indices = num_indices; |
||||||
|
mi->indices = UTIL_ALLOC(num_indices, uint); |
||||||
|
mi->normals = UTIL_ALLOC(mi->num_vertices, glm::vec3); |
||||||
|
mi->texture_coords = UTIL_ALLOC(mi->num_vertices, glm::vec3); |
||||||
|
|
||||||
|
return mi; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
assimpLogCB(const char* message, char* user) |
||||||
|
{ |
||||||
|
// NOTE: filter 'info' messages from assimp
|
||||||
|
if (!utilMatchPrefix(message, "Info,", 5)) |
||||||
|
LOG(Info) << message << "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
mesh_info* |
||||||
|
copyMeshInfo(const aiScene* scene, aiMesh* mesh) |
||||||
|
{ |
||||||
|
mesh_info* mi = allocateMeshInfo(mesh->mNumVertices, mesh->mNumFaces * 3); |
||||||
|
|
||||||
|
// copy vertices, normals, and texture coords
|
||||||
|
for (uint i = 0; i < mi->num_vertices; i++) { |
||||||
|
copyVector(mesh->mVertices[i], mi->vertices[i]); |
||||||
|
copyVector(mesh->mNormals[i], mi->normals[i]); |
||||||
|
mi->texture_coords[i].x = mesh->mTextureCoords[0][i].x; |
||||||
|
mi->texture_coords[i].y = mesh->mTextureCoords[0][i].y; |
||||||
|
mi->texture_coords[i].z = 0; |
||||||
|
} |
||||||
|
|
||||||
|
// copy indices
|
||||||
|
for (uint i = 0; i < mesh->mNumFaces; i++) |
||||||
|
for (uint j = 0; j < 3; j++) |
||||||
|
mi->indices[i * 3 + j] = mesh->mFaces[i].mIndices[j]; |
||||||
|
|
||||||
|
return mi; |
||||||
|
} |
||||||
|
|
||||||
|
inline glm::vec3 |
||||||
|
copyVector(aiVector3D v_in, glm::vec3& v_out) |
||||||
|
{ |
||||||
|
v_out.x = v_in.x; |
||||||
|
v_out.y = v_in.y; |
||||||
|
v_out.z = v_in.z; |
||||||
|
return v_out; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
freeMesh(mesh_info* mesh) |
||||||
|
{ |
||||||
|
utilFreeImage(mesh->diffuse_texture); |
||||||
|
utilSafeFree(mesh->vertices); |
||||||
|
utilSafeFree(mesh->normals); |
||||||
|
utilSafeFree(mesh->texture_coords); |
||||||
|
utilSafeFree(mesh->indices); |
||||||
|
utilSafeFree(mesh); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
loadDiffuseTexture(const aiScene* scene, aiMesh* mesh, mesh_info* mi) |
||||||
|
{ |
||||||
|
aiMaterial* mat = scene->mMaterials[mesh->mMaterialIndex]; |
||||||
|
aiString file_name; |
||||||
|
|
||||||
|
if (mat->GetTextureCount(aiTextureType_DIFFUSE) < 1) |
||||||
|
return false; |
||||||
|
|
||||||
|
if (AI_SUCCESS != mat->GetTexture( |
||||||
|
aiTextureType_DIFFUSE, 0, &file_name, NULL, NULL, NULL, NULL, NULL)) |
||||||
|
{ |
||||||
|
LOG(Error) << "No diffuse texture from assimp\n"; |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
const aiTexture* tex = scene->GetEmbeddedTexture(file_name.C_Str()); |
||||||
|
|
||||||
|
if (tex != nullptr) { |
||||||
|
LOG(Info) << "has embedded texture\n"; |
||||||
|
mi->diffuse_texture = utilLoadImageBytes((const uint8*) tex->pcData, tex->mWidth); |
||||||
|
} else { |
||||||
|
LOG(Info) << "Loading texture file: " << file_name.C_Str() << "\n"; |
||||||
|
mi->diffuse_texture = utilLoadImagePath(file_name.C_Str()); |
||||||
|
} |
||||||
|
|
||||||
|
if (mi->diffuse_texture.pixels == nullptr) { |
||||||
|
LOG(Error) << "Error loading texture\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
validateScene(const aiScene* scene, const char* filepath) |
||||||
|
{ |
||||||
|
if (!scene) { |
||||||
|
LOG(Error) << "Error loading file: " << filepath << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (scene->mNumMeshes < 1) { |
||||||
|
LOG(Error) << "Scene contains no meshes\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (!scene->mMeshes[0]->HasNormals()) { |
||||||
|
LOG(Error) << "Mesh doesn't have normals\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
@ -0,0 +1,307 @@ |
|||||||
|
|
||||||
|
#include <glm/gtc/matrix_transform.hpp> |
||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
#include "render_object.h" |
||||||
|
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
|
||||||
|
struct default_render_object |
||||||
|
{ |
||||||
|
glm::mat4 node_xform; |
||||||
|
|
||||||
|
GLuint tex_id; |
||||||
|
GLuint vertex_buffer_id; |
||||||
|
GLuint normal_buffer_id; |
||||||
|
GLuint uv_buffer_id; |
||||||
|
GLuint index_buffer_id; |
||||||
|
uint index_buffer_count; |
||||||
|
}; |
||||||
|
|
||||||
|
struct simple_render_object |
||||||
|
{ |
||||||
|
glm::mat4 model_transform; |
||||||
|
GLuint vertex_buffer_id; |
||||||
|
GLuint vertex_color_buffer_id; |
||||||
|
uint vertex_count; |
||||||
|
GLenum draw_mode; |
||||||
|
}; |
||||||
|
|
||||||
|
struct render_objects |
||||||
|
{ |
||||||
|
void* objects; |
||||||
|
uint count; |
||||||
|
mesh_t mesh_type; |
||||||
|
}; |
||||||
|
|
||||||
|
void drawDefault(render_objects* r_objs, |
||||||
|
glm::mat4 world_transform, |
||||||
|
camera& cam, |
||||||
|
shader_wrapper sw, |
||||||
|
light_group* lights); |
||||||
|
void drawSimple(render_objects* r_objs, |
||||||
|
glm::mat4 world_transform, |
||||||
|
camera& cam, |
||||||
|
shader_wrapper sw, |
||||||
|
light_group* lights); |
||||||
|
inline void enableGLFloatBuffer(uint buffer_id, uint location); |
||||||
|
bool initGLFloatBuffer(glm::vec3* buffer, uint count, GLuint& buffer_id); |
||||||
|
bool initGLIndexBuffer(uint* buffer, uint num_indices, GLuint& buffer_id); |
||||||
|
bool initGLTexture(const util_image image, GLuint& tex_id); |
||||||
|
bool loadMeshIntoGL(default_render_object* ro_out, mesh_info* mi_in); |
||||||
|
inline void updateMatrices(default_shader_program* shader, |
||||||
|
camera& cam, |
||||||
|
glm::mat4 world_xform, |
||||||
|
glm::mat4 node_xform); |
||||||
|
|
||||||
|
|
||||||
|
// interface
|
||||||
|
|
||||||
|
render_objects* |
||||||
|
roInitModel(mesh_group mg) |
||||||
|
{ |
||||||
|
uint count = mg.num_meshes; |
||||||
|
assert(count > 0); |
||||||
|
|
||||||
|
render_objects* r_objs = UTIL_ALLOC(1, render_objects); |
||||||
|
r_objs->objects = UTIL_ALLOC(count, default_render_object); |
||||||
|
r_objs->count = count; |
||||||
|
r_objs->mesh_type = DEFAULT_MESHES; |
||||||
|
|
||||||
|
default_render_object* objects = (default_render_object*) r_objs->objects; |
||||||
|
|
||||||
|
for (uint i = 0; i < count; i++) { |
||||||
|
if (!loadMeshIntoGL(&objects[i], mg.meshes[i])) { |
||||||
|
roFree(r_objs); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r_objs; |
||||||
|
} |
||||||
|
|
||||||
|
render_objects* |
||||||
|
roInitSimpleMesh(simple_mesh& mesh_in, GLenum draw_mode) |
||||||
|
{ |
||||||
|
render_objects* r_objs = UTIL_ALLOC(1, render_objects); |
||||||
|
r_objs->count = 1; |
||||||
|
r_objs->mesh_type = SIMPLE_MESH; |
||||||
|
r_objs->objects = UTIL_ALLOC(1, simple_render_object); |
||||||
|
|
||||||
|
simple_render_object* objects = (simple_render_object*) r_objs->objects; |
||||||
|
|
||||||
|
if (initGLFloatBuffer(mesh_in.vertices, |
||||||
|
mesh_in.num_vertices, |
||||||
|
objects->vertex_buffer_id) && |
||||||
|
initGLFloatBuffer(mesh_in.vert_colors, |
||||||
|
mesh_in.num_vertices, |
||||||
|
objects->vertex_color_buffer_id)) |
||||||
|
{ |
||||||
|
objects->model_transform = mesh_in.model_transform; |
||||||
|
objects->vertex_count = mesh_in.num_vertices; |
||||||
|
objects->draw_mode = draw_mode; |
||||||
|
return r_objs; |
||||||
|
} |
||||||
|
|
||||||
|
LOG(Error) << "Failed to initialize render_object\n"; |
||||||
|
roFree(r_objs); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
roFree(render_objects* r_objs) |
||||||
|
{ |
||||||
|
if (r_objs->mesh_type == SIMPLE_MESH) { |
||||||
|
//
|
||||||
|
} else if (r_objs->mesh_type == DEFAULT_MESHES) { |
||||||
|
default_render_object* objects = |
||||||
|
(default_render_object*) r_objs->objects; |
||||||
|
|
||||||
|
for (uint i = 0; i < r_objs->count; i++) { |
||||||
|
glDeleteBuffers(1, &objects[i].vertex_buffer_id); |
||||||
|
glDeleteBuffers(1, &objects[i].normal_buffer_id); |
||||||
|
glDeleteBuffers(1, &objects[i].uv_buffer_id); |
||||||
|
glDeleteBuffers(1, &objects[i].index_buffer_id); |
||||||
|
glDeleteTextures(1, &objects[i].tex_id); |
||||||
|
} |
||||||
|
|
||||||
|
utilSafeFree(r_objs->objects); |
||||||
|
utilSafeFree(r_objs); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: update projection * view matrices once per frame here
|
||||||
|
void |
||||||
|
roDraw(render_objects* r_objs, |
||||||
|
glm::mat4 world_transform, |
||||||
|
camera& cam, |
||||||
|
shader_wrapper sw, |
||||||
|
light_group* lights) |
||||||
|
{ |
||||||
|
if (r_objs->mesh_type == SIMPLE_MESH) |
||||||
|
drawSimple(r_objs, world_transform, cam, sw, lights); |
||||||
|
else if (r_objs->mesh_type == DEFAULT_MESHES) |
||||||
|
drawDefault(r_objs, world_transform, cam, sw, lights); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
void |
||||||
|
drawDefault(render_objects* r_objs, |
||||||
|
glm::mat4 world_transform, |
||||||
|
camera& cam, |
||||||
|
shader_wrapper sw, |
||||||
|
light_group* lights) |
||||||
|
{ |
||||||
|
default_shader_program* shader = sw.default_shader; |
||||||
|
default_render_object* objects = (default_render_object*) r_objs->objects; |
||||||
|
glUseProgram(shader->program_id); |
||||||
|
updateMatrices(shader, cam, world_transform, objects->node_xform); |
||||||
|
if (lights->needs_update) lightsUpdate(lights, shader); |
||||||
|
|
||||||
|
for (uint i = 0; i < r_objs->count; i++) { |
||||||
|
enableGLFloatBuffer(objects[i].vertex_buffer_id, 0); |
||||||
|
enableGLFloatBuffer(objects[i].normal_buffer_id, 1); |
||||||
|
// TODO: could pass in a stride parameter here to enableGLFloatBuffer()
|
||||||
|
// could then use a 2d buffer for uv coords
|
||||||
|
enableGLFloatBuffer(objects[i].uv_buffer_id, 2); |
||||||
|
glBindTexture(GL_TEXTURE_2D, objects[i].tex_id); |
||||||
|
glUniform1i(shader->sampler_id, 0); |
||||||
|
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, objects[i].index_buffer_id); |
||||||
|
glDrawElements(GL_TRIANGLES, |
||||||
|
objects[i].index_buffer_count, |
||||||
|
GL_UNSIGNED_INT, |
||||||
|
0); |
||||||
|
|
||||||
|
glDisableVertexAttribArray(0); |
||||||
|
glDisableVertexAttribArray(1); |
||||||
|
glDisableVertexAttribArray(2); |
||||||
|
} |
||||||
|
|
||||||
|
glUseProgram(0); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
drawSimple(render_objects* r_objs, |
||||||
|
glm::mat4 world_transform, |
||||||
|
camera& cam, |
||||||
|
shader_wrapper sw, |
||||||
|
light_group* lights) |
||||||
|
{ |
||||||
|
simple_render_object* ro = (simple_render_object*) r_objs->objects; |
||||||
|
simple_shader_program* shader = |
||||||
|
(simple_shader_program*) sw.simple_shader; |
||||||
|
glUseProgram(shader->program_id); |
||||||
|
cam.MVP = cam.projection * cam.view * ro->model_transform; |
||||||
|
glUniformMatrix4fv( |
||||||
|
shader->world_transform_id, 1, GL_FALSE, &world_transform[0][0]); |
||||||
|
glUniformMatrix4fv(shader->MVP_id, 1, GL_FALSE, &cam.MVP[0][0]); |
||||||
|
enableGLFloatBuffer(ro->vertex_buffer_id, 0); |
||||||
|
enableGLFloatBuffer(ro->vertex_color_buffer_id, 1); |
||||||
|
glDrawArrays(ro->draw_mode, 0, ro->vertex_count); |
||||||
|
glDisableVertexAttribArray(0); |
||||||
|
glDisableVertexAttribArray(1); |
||||||
|
glUseProgram(0); |
||||||
|
} |
||||||
|
|
||||||
|
inline void |
||||||
|
enableGLFloatBuffer(uint buffer_id, uint location) |
||||||
|
{ |
||||||
|
glEnableVertexAttribArray(location); |
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, buffer_id); |
||||||
|
glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initGLFloatBuffer(glm::vec3* buffer, uint count, GLuint& buffer_id) |
||||||
|
{ |
||||||
|
glGenBuffers(1, &buffer_id); |
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, buffer_id); |
||||||
|
glBufferData(GL_ARRAY_BUFFER, |
||||||
|
count * 3 * sizeof(GLfloat), // NOTE: 3 floats per vertex prop
|
||||||
|
buffer, |
||||||
|
GL_STATIC_DRAW); |
||||||
|
|
||||||
|
return (glGetError() == GL_NO_ERROR); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initGLIndexBuffer(uint* buffer, uint num_indices, GLuint& buffer_id) |
||||||
|
{ |
||||||
|
glGenBuffers(1, &buffer_id); |
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_id); |
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, |
||||||
|
num_indices * sizeof(uint), |
||||||
|
buffer, |
||||||
|
GL_STATIC_DRAW); |
||||||
|
|
||||||
|
return (glGetError() == GL_NO_ERROR); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initGLTexture(const util_image image, GLuint& tex_id) |
||||||
|
{ |
||||||
|
glGenTextures(1, &tex_id); |
||||||
|
glBindTexture(GL_TEXTURE_2D, tex_id); |
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
||||||
|
GLenum pixel_format = (image.num_channels == 3) ? GL_RGB : GL_RGBA; |
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, pixel_format, image.w, image.h, 0, |
||||||
|
pixel_format, GL_UNSIGNED_BYTE, image.pixels); |
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
||||||
|
|
||||||
|
return (glGetError() == GL_NO_ERROR); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
loadMeshIntoGL(default_render_object* ro_out, mesh_info* mi_in) |
||||||
|
{ |
||||||
|
assert(mi_in != nullptr && ro_out != nullptr); |
||||||
|
|
||||||
|
if (initGLFloatBuffer(mi_in->vertices, |
||||||
|
mi_in->num_vertices, |
||||||
|
ro_out->vertex_buffer_id) && |
||||||
|
initGLFloatBuffer(mi_in->normals, |
||||||
|
mi_in->num_vertices, |
||||||
|
ro_out->normal_buffer_id) && |
||||||
|
initGLFloatBuffer(mi_in->texture_coords, |
||||||
|
mi_in->num_vertices, |
||||||
|
ro_out->uv_buffer_id) && |
||||||
|
initGLIndexBuffer(mi_in->indices, |
||||||
|
mi_in->num_indices, |
||||||
|
ro_out->index_buffer_id) && |
||||||
|
initGLTexture(mi_in->diffuse_texture, ro_out->tex_id)) |
||||||
|
{ |
||||||
|
ro_out->node_xform = mi_in->model_transform; |
||||||
|
ro_out->index_buffer_count = mi_in->num_indices; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
LOG(Error) << "Failed to initialize render_object\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: really only need to update the view and projection matrices once per
|
||||||
|
// frame, maybe add another interface function in render_object to call from
|
||||||
|
// renRenderFrame
|
||||||
|
inline void |
||||||
|
updateMatrices(default_shader_program* shader, |
||||||
|
camera& cam, |
||||||
|
glm::mat4 world_xform, |
||||||
|
glm::mat4 node_xform) |
||||||
|
{ |
||||||
|
glUniformMatrix4fv( |
||||||
|
shader->world_transform_id, 1, GL_FALSE, &world_xform[0][0]); |
||||||
|
glUniformMatrix4fv(shader->model_matrix_id, 1, GL_FALSE, &node_xform[0][0]); |
||||||
|
glUniformMatrix4fv(shader->view_matrix_id, 1, GL_FALSE, &cam.view[0][0]); |
||||||
|
glUniformMatrix4fv(shader->projection_matrix_id, 1, GL_FALSE, |
||||||
|
&cam.projection[0][0]); |
||||||
|
glm::mat3 normal_matrix = glm::transpose( |
||||||
|
glm::inverse(glm::mat3(cam.model))); |
||||||
|
glUniformMatrix3fv(shader->normal_matrix_id, 1, GL_FALSE, |
||||||
|
&normal_matrix[0][0]); |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,302 @@ |
|||||||
|
|
||||||
|
#if defined (_WIN32) |
||||||
|
#include <SDL.h> |
||||||
|
#else |
||||||
|
#include <SDL2/SDL.h> |
||||||
|
#endif |
||||||
|
#include <glm/glm.hpp> |
||||||
|
#include <glm/geometric.hpp> |
||||||
|
#include <glm/gtc/matrix_transform.hpp> |
||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
#include "render_object.h" |
||||||
|
#include "renderer.h" |
||||||
|
#include "default_shaders.cpp" |
||||||
|
|
||||||
|
#define CLEAR_COL_R 55.f / 255.f |
||||||
|
#define CLEAR_COL_G 55.f / 255.f |
||||||
|
#define CLEAR_COL_B 55.f / 255.f |
||||||
|
#define CLEAR_COL_A 1.f |
||||||
|
#define USE_SECOND_MONITOR 0 |
||||||
|
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
|
||||||
|
struct SDL_Handles |
||||||
|
{ |
||||||
|
SDL_Window *window; |
||||||
|
SDL_GLContext glContext; |
||||||
|
SDL_DisplayMode currentDisplayMode; |
||||||
|
}; |
||||||
|
|
||||||
|
bool createWindow(const char* title, |
||||||
|
SDL_Handles* handles, |
||||||
|
glm::vec2& viewport_dims); |
||||||
|
bool initContext(SDL_Handles* handles); |
||||||
|
bool initGlOptions(); |
||||||
|
bool initSDL(SDL_Handles* handles); |
||||||
|
bool initShaders(render_state* rs); |
||||||
|
void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, |
||||||
|
GLsizei length, const GLchar* message, const void* userParam); |
||||||
|
void setDefaults(render_state* rs, glm::vec2 viewport_dims); |
||||||
|
|
||||||
|
|
||||||
|
// interface
|
||||||
|
|
||||||
|
render_state* |
||||||
|
renInit(const char* title, glm::vec2 viewport_dims) |
||||||
|
{ |
||||||
|
render_state* rs = UTIL_ALLOC(1, render_state); |
||||||
|
rs->handles = UTIL_ALLOC(1, SDL_Handles); |
||||||
|
setDefaults(rs, viewport_dims); |
||||||
|
|
||||||
|
if (initSDL(rs->handles) && |
||||||
|
createWindow(title, rs->handles, rs->viewport_dims) && |
||||||
|
initContext(rs->handles) && |
||||||
|
initGlOptions() && |
||||||
|
initShaders(rs) && |
||||||
|
meInitAssimp()) |
||||||
|
{ |
||||||
|
return rs; |
||||||
|
} |
||||||
|
|
||||||
|
LOG(Error) << "renderer initialization failed, aborting\n"; |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
renShutdown(render_state* rs) |
||||||
|
{ |
||||||
|
for (uint i = 0; i < rs->render_group_count; i++) { |
||||||
|
for (uint j = 0; j < rs->render_groups[i].entity_count; j++) |
||||||
|
entFree(rs->render_groups[i].entities[j]); |
||||||
|
|
||||||
|
utilSafeFree(rs->render_groups[i].entities); |
||||||
|
} |
||||||
|
|
||||||
|
shaderFree(rs->default_shader->program_id); |
||||||
|
utilSafeFree(rs->default_shader); |
||||||
|
rs->default_shader = nullptr; |
||||||
|
shaderFree(rs->simple_shader->program_id); |
||||||
|
utilSafeFree(rs->simple_shader); |
||||||
|
rs->simple_shader = nullptr; |
||||||
|
|
||||||
|
lightsOut(rs->lights); |
||||||
|
utilSafeFree(rs->render_groups); |
||||||
|
meShutdownAssimp(); |
||||||
|
SDL_GL_DeleteContext(rs->handles->glContext); |
||||||
|
SDL_DestroyWindow(rs->handles->window); |
||||||
|
SDL_Quit(); |
||||||
|
utilSafeFree(rs->handles); |
||||||
|
} |
||||||
|
|
||||||
|
render_group* |
||||||
|
renAllocateGroup(uint entity_count, shader_wrapper shader) |
||||||
|
{ |
||||||
|
assert(entity_count > 0); |
||||||
|
render_group* rg = UTIL_ALLOC(1, render_group); |
||||||
|
rg->entities = UTIL_ALLOC(entity_count, entity); |
||||||
|
rg->entity_count = entity_count; |
||||||
|
rg->shader = shader; |
||||||
|
return rg; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: add a better structure for input
|
||||||
|
void |
||||||
|
renDoRenderLoop(render_state* rs, uint framerate, frame_callback_fn cb_func) |
||||||
|
{ |
||||||
|
uint delay = (framerate > 0) ? 1 / framerate : 0; |
||||||
|
uint frameStart, frameTime; |
||||||
|
bool running = true; |
||||||
|
SDL_Event e; |
||||||
|
|
||||||
|
while (running) { |
||||||
|
frameStart = SDL_GetTicks(); |
||||||
|
|
||||||
|
while (SDL_PollEvent(&e)) { |
||||||
|
if ((e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) |
||||||
|
|| e.type == SDL_QUIT) { |
||||||
|
running = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (cb_func != nullptr) cb_func(rs); |
||||||
|
renRenderFrame(rs); |
||||||
|
SDL_GL_SwapWindow(rs->handles->window); |
||||||
|
frameTime = SDL_GetTicks() - frameStart; |
||||||
|
|
||||||
|
if (delay > frameTime) |
||||||
|
SDL_Delay(delay - frameTime); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
renRenderFrame(render_state* rs) |
||||||
|
{ |
||||||
|
glClearColor(rs->clear_col.R, |
||||||
|
rs->clear_col.G, |
||||||
|
rs->clear_col.B, |
||||||
|
rs->clear_col.A); |
||||||
|
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); |
||||||
|
|
||||||
|
for (uint i = 0; i < rs->render_group_count; i++) { |
||||||
|
render_group& rg = rs->render_groups[i]; |
||||||
|
|
||||||
|
for (uint j = 0; j < rg.entity_count; j++) { |
||||||
|
entity& e = rg.entities[j]; |
||||||
|
roDraw(e.render_objs, |
||||||
|
e.world_transform, |
||||||
|
rs->cam, |
||||||
|
rg.shader, |
||||||
|
rs->lights); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
renAddLight(render_state* rs, glm::vec3 pos, glm::vec3 color, float intensity) |
||||||
|
{ |
||||||
|
if (!lightsAdd(rs->lights, pos, color, intensity)) { |
||||||
|
LOG(Error) << "Error adding light\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
bool |
||||||
|
createWindow(const char* title, SDL_Handles* handles, glm::vec2& viewport_dims) |
||||||
|
{ |
||||||
|
uint display_id = 0; |
||||||
|
if (USE_SECOND_MONITOR && SDL_GetNumVideoDisplays() > 1) |
||||||
|
display_id = 1; |
||||||
|
|
||||||
|
handles->window = SDL_CreateWindow( |
||||||
|
title, |
||||||
|
SDL_WINDOWPOS_CENTERED_DISPLAY(display_id), |
||||||
|
SDL_WINDOWPOS_CENTERED_DISPLAY(display_id), |
||||||
|
viewport_dims.x, |
||||||
|
viewport_dims.y, |
||||||
|
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE |
||||||
|
); |
||||||
|
|
||||||
|
if (!handles->window) { |
||||||
|
LOG(Error) << "Error creating window: " << SDL_GetError() << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initContext(SDL_Handles* handles) |
||||||
|
{ |
||||||
|
handles->glContext = SDL_GL_CreateContext(handles->window); |
||||||
|
|
||||||
|
if (!handles->glContext) { |
||||||
|
LOG(Error) << "Error creating glContext: " << SDL_GetError() << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (SDL_GL_SetSwapInterval(1) != 0) { // vsync
|
||||||
|
LOG(Error) << "SDL Errors: " << SDL_GetError() << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initGlOptions() |
||||||
|
{ |
||||||
|
if (glewInit()) { |
||||||
|
LOG(Error) << "failed to initialize OpenGL\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
LOG(Info) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n"; |
||||||
|
LOG(Info)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n"; |
||||||
|
LOG(Info) << "opengl version: " << glGetString(GL_VERSION) << "\n"; |
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST); |
||||||
|
glEnable(GL_LINE_SMOOTH); |
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); |
||||||
|
#if 0 |
||||||
|
// TODO: blending messes up rendering with mesa on intel graphics 4000
|
||||||
|
glEnable(GL_BLEND); |
||||||
|
glBlendEquation(GL_FUNC_ADD); |
||||||
|
glBlendFunc(GL_ONE, GL_SRC_ALPHA); |
||||||
|
#endif |
||||||
|
|
||||||
|
// TODO: glDebugMessageCallback is only availabe from >v4.3
|
||||||
|
// check and warn if context doesn't support this function here
|
||||||
|
glEnable (GL_DEBUG_OUTPUT); |
||||||
|
glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0); |
||||||
|
// hide VRAM debug messages
|
||||||
|
glDebugMessageControl(GL_DONT_CARE, 33361, GL_DONT_CARE, 0, 0, GL_FALSE); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initSDL(SDL_Handles* handles) |
||||||
|
{ |
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) { |
||||||
|
LOG(Error) << "Error, SDL_Init: " << SDL_GetError() << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); |
||||||
|
SDL_GetCurrentDisplayMode(0, &handles->currentDisplayMode); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
initShaders(render_state* rs) |
||||||
|
{ |
||||||
|
rs->default_shader = |
||||||
|
shaderInitDefault(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER); |
||||||
|
rs->simple_shader = |
||||||
|
shaderInitSimple(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER); |
||||||
|
|
||||||
|
if (rs->default_shader == nullptr || |
||||||
|
rs->simple_shader == nullptr) |
||||||
|
{ |
||||||
|
LOG(Error) << "shader loading failed\n"; |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, |
||||||
|
GLsizei length, const GLchar* message, const void* userParam) |
||||||
|
{ |
||||||
|
LOG((type == GL_DEBUG_TYPE_ERROR) ? Error : Debug) |
||||||
|
<< (type == GL_DEBUG_TYPE_ERROR ? "** GL Error **" : "") |
||||||
|
<< ", type: " << type |
||||||
|
<< ", severity: " << severity |
||||||
|
<< ", message: " << message << "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
setDefaults(render_state* rs, glm::vec2 viewport_dims) |
||||||
|
{ |
||||||
|
rs->viewport_dims = viewport_dims; |
||||||
|
rs->lights = lightsInit(); |
||||||
|
rs->clear_col.R = CLEAR_COL_R; |
||||||
|
rs->clear_col.B = CLEAR_COL_B; |
||||||
|
rs->clear_col.G = CLEAR_COL_G; |
||||||
|
rs->clear_col.A = CLEAR_COL_A; |
||||||
|
} |
||||||
@ -1,748 +0,0 @@ |
|||||||
|
|
||||||
#include <cassert> |
|
||||||
#include <cstddef> |
|
||||||
#include <cstdlib> |
|
||||||
#include <cstdio> |
|
||||||
#include <cstring> |
|
||||||
#include <fstream> |
|
||||||
|
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/gtc/matrix_transform.hpp> |
|
||||||
|
|
||||||
#include "asset.h" |
|
||||||
#include "dumbLog.h" |
|
||||||
#include "dummy_shader.h" |
|
||||||
#include "GLDebug.h" |
|
||||||
#include "shader.h" |
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
|
|
||||||
// NOTE: forward declarations
|
|
||||||
|
|
||||||
bool parseShader(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s); |
|
||||||
|
|
||||||
void loadDummyShader(); |
|
||||||
|
|
||||||
void initCTXSizes(GLContext* gl_ctx); |
|
||||||
|
|
||||||
bool compileAndLinkShader(ShaderProgram* shader, |
|
||||||
const char* vert_src, |
|
||||||
const char* frag_src, |
|
||||||
GLuint& vs_id, |
|
||||||
GLuint& fs_id); |
|
||||||
|
|
||||||
u32 getGLTypeSize(GLenum e); |
|
||||||
|
|
||||||
GLTexture* getFreeGLTexture(GLContext* gl_ctx); |
|
||||||
|
|
||||||
bool loadGLTexture(Texture* image, GLuint& tex_id); |
|
||||||
|
|
||||||
void* getMeshData(const Mesh& m, const GLBufferToAttribMapping& mapping); |
|
||||||
|
|
||||||
|
|
||||||
// NOTE: interface
|
|
||||||
|
|
||||||
GLContext* |
|
||||||
initGLContext(MemoryArena* arena, |
|
||||||
u32 max_shaders, |
|
||||||
u32 max_textures, |
|
||||||
u32 max_ubos) |
|
||||||
{ |
|
||||||
GLContext* gl_ctx = ARENA_ALLOC(arena, GLContext, 1); |
|
||||||
gl_ctx->max_shaders = max_shaders; |
|
||||||
gl_ctx->shaders = ARENA_ALLOC(arena, ShaderProgram, max_shaders); |
|
||||||
gl_ctx->max_ubos = max_ubos; |
|
||||||
gl_ctx->uniform_buffers = ARENA_ALLOC(arena, GLBuffer, max_ubos); |
|
||||||
|
|
||||||
// NOTE: initialize GLBuffer struct members to sane defaults
|
|
||||||
for (u32 i = 0; i < max_ubos; i++) { |
|
||||||
GLBuffer& buf = gl_ctx->uniform_buffers[i]; |
|
||||||
buf.location = -1; |
|
||||||
buf.binding_idx = -1; |
|
||||||
} |
|
||||||
|
|
||||||
gl_ctx->max_textures = max_textures; |
|
||||||
gl_ctx->textures = ARENA_ALLOC(arena, GLTexture, max_textures); |
|
||||||
|
|
||||||
// NOTE: load a dummy shader to avoid chicken and egg problem where we need
|
|
||||||
// GLContext info before we can parse a shader, which needs GLContext info
|
|
||||||
loadDummyShader(); |
|
||||||
initCTXSizes(gl_ctx); |
|
||||||
|
|
||||||
return gl_ctx; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
addShaderProgram(MemoryArena* arena, |
|
||||||
GLContext* gl_ctx, |
|
||||||
const char* vs, |
|
||||||
const char* fs, |
|
||||||
const char* name) |
|
||||||
{ |
|
||||||
LOGF(Info, "loading shader, %s\n", name); |
|
||||||
const u32 max_len = 256; |
|
||||||
char input_str[max_len]; |
|
||||||
snprintf(input_str, max_len, "%s%s", vs, fs); |
|
||||||
u64 hash = utilFNV64a_str(input_str); |
|
||||||
|
|
||||||
// FIXME: replace with utilCStrMatch, and remove getShaderByHash, we don't
|
|
||||||
// need a hashing function if we're not storing in a hash table
|
|
||||||
if (getShaderByHash(gl_ctx, hash)) { |
|
||||||
LOGF(Error, "shader is already loaded\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* s = getFreeShader(gl_ctx); |
|
||||||
|
|
||||||
if (s) { |
|
||||||
s->name = arenaCopyCStr(arena, name); |
|
||||||
s->hash = hash; |
|
||||||
|
|
||||||
size_t vs_size, fs_size; |
|
||||||
const char* v_str = (const char*) SDL_LoadFile(vs, &vs_size); |
|
||||||
const char* f_str = (const char*) SDL_LoadFile(fs, &fs_size); |
|
||||||
GLuint vs_id, fs_id; |
|
||||||
|
|
||||||
if (compileAndLinkShader(s, v_str, f_str, vs_id, fs_id)) { |
|
||||||
glDetachShader(s->prog_id, vs_id); |
|
||||||
glDetachShader(s->prog_id, fs_id); |
|
||||||
glDeleteShader(vs_id); |
|
||||||
glDeleteShader(fs_id); |
|
||||||
|
|
||||||
return parseShader(arena, gl_ctx, s); |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "Error linking shader\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "error loading shader\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* |
|
||||||
getFreeShader(GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
if (gl_ctx->num_shaders >= gl_ctx->max_shaders) { |
|
||||||
LOGF(Error, "GLContext->shaders full\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* s = &gl_ctx->shaders[gl_ctx->num_shaders++]; |
|
||||||
return s; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* |
|
||||||
getShaderByHash(GLContext* gl_ctx, u64 hash) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < gl_ctx->num_shaders; i++) { |
|
||||||
if (gl_ctx->shaders[i].hash == hash) |
|
||||||
return &gl_ctx->shaders[i]; |
|
||||||
} |
|
||||||
|
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* |
|
||||||
getShaderByName(const char* name, GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < gl_ctx->num_shaders; i++) { |
|
||||||
if (utilCStrMatch(name, gl_ctx->shaders[i].name)) |
|
||||||
return &gl_ctx->shaders[i]; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "shader not found, %s\n", name); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* |
|
||||||
getShaderByID(GLContext* gl_ctx, GLuint prog_id) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < gl_ctx->num_shaders; i++) { |
|
||||||
if (gl_ctx->shaders[i].prog_id) |
|
||||||
return &gl_ctx->shaders[i]; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "shader not found, %d\n", prog_id); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
GLBuffer* |
|
||||||
getFreeUBO(GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
if (gl_ctx->num_ubos < gl_ctx->max_ubos) |
|
||||||
return &gl_ctx->uniform_buffers[gl_ctx->num_ubos++]; |
|
||||||
|
|
||||||
LOGF(Error, "no free Uniform Buffer Objects\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
GLBuffer* |
|
||||||
getUBOByName(GLContext* gl_ctx, const char* name) |
|
||||||
{ |
|
||||||
GLBuffer* ubo_out = nullptr; |
|
||||||
|
|
||||||
for (u32 i = 0; i < gl_ctx->num_ubos; i++) { |
|
||||||
GLBuffer* buf = &gl_ctx->uniform_buffers[i]; |
|
||||||
|
|
||||||
if (utilCStrMatch(name, buf->name)) |
|
||||||
ubo_out = buf; |
|
||||||
} |
|
||||||
|
|
||||||
if (ubo_out == nullptr) |
|
||||||
LOGF(Error, "GLBuffer, \"%s\", not found\n", name); |
|
||||||
|
|
||||||
return ubo_out; |
|
||||||
} |
|
||||||
|
|
||||||
GLTexture* |
|
||||||
getGLTexture(GLContext* gl_ctx, Texture* diffuse_img) |
|
||||||
{ |
|
||||||
u64 fp_hash = utilFNV64a_str(diffuse_img->file_path); |
|
||||||
|
|
||||||
for (u32 i = 0; i < gl_ctx->num_textures; i++) { |
|
||||||
GLTexture* glt = &gl_ctx->textures[i]; |
|
||||||
|
|
||||||
if (glt->filepath_hash == fp_hash) |
|
||||||
return glt; |
|
||||||
} |
|
||||||
|
|
||||||
GLTexture* glt = getFreeGLTexture(gl_ctx); |
|
||||||
if (!glt) return nullptr; |
|
||||||
|
|
||||||
glt->pixel_format = (diffuse_img->num_channels == 3) ? GL_RGB : GL_RGBA; |
|
||||||
glt->width = diffuse_img->w; |
|
||||||
glt->height = diffuse_img->h; |
|
||||||
glt->filepath_hash = diffuse_img->filepath_hash; |
|
||||||
|
|
||||||
if (loadGLTexture(diffuse_img, glt->id)) |
|
||||||
return glt; |
|
||||||
|
|
||||||
LOGF(Error, "Error, unable to load texture\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
GLBuffer* initGLBackingBuffer(GLContext* gl_ctx, |
|
||||||
MemoryArena* arena, |
|
||||||
const char* name, |
|
||||||
GLenum data_type, |
|
||||||
u32 buf_size, |
|
||||||
void* data) |
|
||||||
{ |
|
||||||
GLBuffer* buf = getFreeUBO(gl_ctx); |
|
||||||
glGenBuffers(1, &buf->id); |
|
||||||
buf->target = GL_UNIFORM_BUFFER; |
|
||||||
buf->data_type = data_type; |
|
||||||
buf->data_size = buf_size; |
|
||||||
buf->name = arenaCopyCStr(arena, name); |
|
||||||
|
|
||||||
glBindBuffer(buf->target, buf->id); |
|
||||||
glBufferData(buf->target, buf->data_size, data, GL_DYNAMIC_DRAW); |
|
||||||
buf->binding_idx = gl_ctx->binding_count++; |
|
||||||
glBindBufferBase(buf->target, buf->binding_idx, buf->id); |
|
||||||
|
|
||||||
return buf; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
updateGLBuffer(GLBuffer* gl_buf, void* data) |
|
||||||
{ |
|
||||||
assert(gl_buf && data); |
|
||||||
glBindBuffer(gl_buf->target, gl_buf->id); |
|
||||||
glBufferSubData(gl_buf->target, 0, gl_buf->data_size, data); |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
renderVAO(GLMesh* glmesh, |
|
||||||
mat4* node_xform, |
|
||||||
ShaderProgram* shader, |
|
||||||
GLTexture* gl_tex, |
|
||||||
GLenum draw_mode) |
|
||||||
{ |
|
||||||
glUseProgram(shader->prog_id); |
|
||||||
glBindVertexArray(glmesh->vao_id); |
|
||||||
|
|
||||||
for (u32 i = 0; i < shader->num_uniforms; i++) { |
|
||||||
const GLUniform& uniform = shader->uniforms[i]; |
|
||||||
|
|
||||||
if (uniform.uniform_type == UNIFORM_NODE_XFORM) { |
|
||||||
glUniformMatrix4fv(uniform.location, 1, GL_FALSE, |
|
||||||
(float*) node_xform); |
|
||||||
} |
|
||||||
|
|
||||||
else if (glmesh->has_texture && |
|
||||||
uniform.uniform_type == UNIFORM_SAMPLER) |
|
||||||
{ |
|
||||||
glBindTexture(GL_TEXTURE_2D, gl_tex->id); |
|
||||||
glUniform1i(uniform.location, 0); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glmesh->element_buf->id); |
|
||||||
glDrawElements(draw_mode, glmesh->num_indices, GL_UNSIGNED_SHORT, 0); |
|
||||||
glBindVertexArray(0); |
|
||||||
} |
|
||||||
|
|
||||||
GLVertexAttrib* |
|
||||||
getVertexAttribByName(ShaderProgram* shader, const char* name) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < shader->num_vertex_attribs; i++) { |
|
||||||
if (strncmp(shader->vertex_attribs[i].name, name, 256) == 0) |
|
||||||
return &shader->vertex_attribs[i]; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Debug, "attribute: %s, not found on shader: %s\n", name, shader->name); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
GLMesh |
|
||||||
initGLMesh(MemoryArena* arena, |
|
||||||
const Mesh& m, |
|
||||||
u32 num_mappings) |
|
||||||
{ |
|
||||||
GLMesh glm = {0}; |
|
||||||
glm.num_indices = m.num_indices; |
|
||||||
glm.num_vertex_attrib_buffers = num_mappings; |
|
||||||
glm.vertex_attrib_buffers = ARENA_ALLOC(arena, GLBuffer, num_mappings); |
|
||||||
glm.element_buf = ARENA_ALLOC(arena, GLBuffer, 1); |
|
||||||
glm.node_xform = ARENA_ALLOC(arena, mat4, 1); |
|
||||||
*glm.node_xform = mat4(1); |
|
||||||
|
|
||||||
return glm; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
initGLAttribBuffer(GLBuffer* buf, GLenum target, GLVertexAttrib* attrib) |
|
||||||
{ |
|
||||||
glGenBuffers(1, &buf->id); |
|
||||||
buf->target = target; |
|
||||||
buf->data_type = attrib->data_type; |
|
||||||
buf->location = attrib->location; |
|
||||||
// FIXME: this should be allocated on the same arena as the parent GLBuffer
|
|
||||||
// with utilAllocateCStr
|
|
||||||
buf->name = utilAllocateCStr(attrib->name); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO: might as well pass in pointer to GLMesh, since that's how we're
|
|
||||||
// going to use this, can avoid copying GLMesh twice that way
|
|
||||||
GLMesh |
|
||||||
loadGLMesh(MemoryArena* arena, |
|
||||||
const Mesh& m, |
|
||||||
GLenum draw_mode, |
|
||||||
GLenum usage, |
|
||||||
GLTexture* diffuse_texture, |
|
||||||
u32 num_mappings, |
|
||||||
GLBufferToAttribMapping mappings[]) |
|
||||||
{ |
|
||||||
GLMesh glm = initGLMesh(arena, m, num_mappings); |
|
||||||
|
|
||||||
if (diffuse_texture) { |
|
||||||
glm.has_texture = true; |
|
||||||
glm.tex_id = diffuse_texture->id; |
|
||||||
} |
|
||||||
|
|
||||||
glGenVertexArrays(1, &glm.vao_id); |
|
||||||
glBindVertexArray(glm.vao_id); |
|
||||||
|
|
||||||
for (u32 i = 0; i < num_mappings; i++) { |
|
||||||
GLBuffer& buf = glm.vertex_attrib_buffers[i]; |
|
||||||
GLVertexAttrib* attrib = mappings[i].attrib; |
|
||||||
attrib->buf_type = mappings[i].buf_type; |
|
||||||
u32 type_size = getGLTypeSize(attrib->data_type); |
|
||||||
assert(type_size > 0); |
|
||||||
buf.data_size = m.num_vertices * type_size; |
|
||||||
|
|
||||||
void* mesh_buf_data = getMeshData(m, mappings[i]); |
|
||||||
assert(mesh_buf_data); |
|
||||||
initGLAttribBuffer(&buf, GL_ARRAY_BUFFER, attrib); |
|
||||||
glBindBuffer(buf.target, buf.id); |
|
||||||
glBufferData(buf.target, |
|
||||||
buf.data_size, |
|
||||||
mesh_buf_data, |
|
||||||
usage); |
|
||||||
glVertexAttribPointer(attrib->location, attrib->num_components, |
|
||||||
attrib->component_type, GL_FALSE, 0, 0); |
|
||||||
glEnableVertexAttribArray(attrib->location); |
|
||||||
} |
|
||||||
|
|
||||||
// FIXME: should we be re-using initGLAttribBuffer here?
|
|
||||||
glGenBuffers(1, &glm.element_buf->id); |
|
||||||
glm.element_buf->target = GL_ELEMENT_ARRAY_BUFFER; |
|
||||||
glm.element_buf->data_type = GL_UNSIGNED_SHORT; |
|
||||||
glm.element_buf->data_size = m.num_indices * sizeof(u16); |
|
||||||
glBindBuffer(glm.element_buf->target, glm.element_buf->id); |
|
||||||
glBufferData(glm.element_buf->target, |
|
||||||
glm.element_buf->data_size, |
|
||||||
m.indices, |
|
||||||
usage); |
|
||||||
|
|
||||||
// TODO: many of these GL functions can set an error state
|
|
||||||
// TODO: return error status
|
|
||||||
glBindVertexArray(0); |
|
||||||
|
|
||||||
return glm; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// NOTE: internal
|
|
||||||
|
|
||||||
void* |
|
||||||
getMeshData(const Mesh& m, const GLBufferToAttribMapping& mapping) |
|
||||||
{ |
|
||||||
switch (mapping.buf_type) { |
|
||||||
case VERTEX: return m.vertices; |
|
||||||
case NORMAL: return m.normals; |
|
||||||
case UV: return m.uvs; |
|
||||||
case COLOR: return m.colors; |
|
||||||
default: return nullptr; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
GLTexture* |
|
||||||
getFreeGLTexture(GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
if (gl_ctx->num_textures < gl_ctx->max_textures) |
|
||||||
return &gl_ctx->textures[gl_ctx->num_textures++]; |
|
||||||
|
|
||||||
LOGF(Error, "no free textures\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
loadGLTexture(Texture* image, GLuint& tex_id) |
|
||||||
{ |
|
||||||
glGenTextures(1, &tex_id); |
|
||||||
glBindTexture(GL_TEXTURE_2D, tex_id); |
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
|
||||||
GLenum pixel_format = (image->num_channels == 3) ? GL_RGB : GL_RGBA; |
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, pixel_format, image->w, image->h, 0, |
|
||||||
pixel_format, GL_UNSIGNED_BYTE, image->pixels); |
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
|
||||||
|
|
||||||
return (glGetError() == GL_NO_ERROR); |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
compileAndLinkShader(ShaderProgram* shader, |
|
||||||
const char* vert_src, |
|
||||||
const char* frag_src, |
|
||||||
GLuint& vs_id, |
|
||||||
GLuint& fs_id) |
|
||||||
{ |
|
||||||
if (vert_src && frag_src && strlen(vert_src) > 0 && strlen(frag_src) > 0) { |
|
||||||
vs_id = glCreateShader(GL_VERTEX_SHADER); |
|
||||||
fs_id = glCreateShader(GL_FRAGMENT_SHADER); |
|
||||||
glShaderSource(vs_id, 1, &vert_src, NULL); |
|
||||||
glShaderSource(fs_id, 1, &frag_src, NULL); |
|
||||||
|
|
||||||
glCompileShader(vs_id); |
|
||||||
assert(glGetError() == GL_NO_ERROR); |
|
||||||
glCompileShader(fs_id); |
|
||||||
assert(glGetError() == GL_NO_ERROR); |
|
||||||
|
|
||||||
shader->prog_id = glCreateProgram(); |
|
||||||
glAttachShader(shader->prog_id, vs_id); |
|
||||||
glAttachShader(shader->prog_id, fs_id); |
|
||||||
|
|
||||||
glLinkProgram(shader->prog_id); |
|
||||||
GLint is_linked = 0; |
|
||||||
glGetProgramiv(shader->prog_id, GL_LINK_STATUS, &is_linked); |
|
||||||
|
|
||||||
// NOTE: log shader linking errors
|
|
||||||
if (is_linked != GL_TRUE) { |
|
||||||
const u32 max_len = 512; |
|
||||||
char err_str[max_len]; |
|
||||||
i32 write_len; |
|
||||||
glGetProgramInfoLog( |
|
||||||
shader->prog_id, max_len, &write_len, &err_str[0]); |
|
||||||
LOGF(Error, "Info Log: %s\n", err_str); |
|
||||||
glDeleteProgram(shader->prog_id); |
|
||||||
} |
|
||||||
|
|
||||||
return (is_linked == GL_TRUE); |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "empty shader source\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
loadDummyShader() |
|
||||||
{ |
|
||||||
GLuint vs_id = 0, fs_id = 0; |
|
||||||
ShaderProgram temp_shader = {0}; |
|
||||||
bool ret = compileAndLinkShader(&temp_shader, |
|
||||||
DUMMY_VERTEX_SHADER, |
|
||||||
DUMMY_FRAGMENT_SHADER, |
|
||||||
vs_id, |
|
||||||
fs_id); |
|
||||||
assert(ret); |
|
||||||
glDeleteProgram(temp_shader.prog_id); |
|
||||||
} |
|
||||||
|
|
||||||
u32 |
|
||||||
getGLTypeSize(GLenum e) |
|
||||||
{ |
|
||||||
switch (e) { |
|
||||||
case GL_FLOAT_VEC2: return 2 * sizeof(GLfloat); |
|
||||||
case GL_FLOAT_VEC3: return 3 * sizeof(GLfloat); |
|
||||||
case GL_FLOAT_VEC4: return 4 * sizeof(GLfloat); |
|
||||||
case GL_FLOAT_MAT4: return 16 * sizeof(GLfloat); |
|
||||||
default: |
|
||||||
LOGF(Error, "unknown GLenum\n"); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE: returns sizes based on GLSL layout std140
|
|
||||||
// https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout
|
|
||||||
u32 |
|
||||||
getGLTypeSizeStd140(GLenum e) |
|
||||||
{ |
|
||||||
switch (e) { |
|
||||||
case GL_FLOAT_VEC3: return 4 * sizeof(GLfloat); |
|
||||||
case GL_FLOAT_VEC4: return 4 * sizeof(GLfloat); |
|
||||||
case GL_FLOAT_MAT4: return 16 * sizeof(GLfloat); |
|
||||||
default: |
|
||||||
LOGF(Error, "unknown GLenum\n"); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
UniformType |
|
||||||
getUniformType(const char* name) |
|
||||||
{ |
|
||||||
if (utilCStrMatch(name, "sampler")) |
|
||||||
return UNIFORM_SAMPLER; |
|
||||||
else if (utilCStrMatch(name, "node_xform")) |
|
||||||
return UNIFORM_NODE_XFORM; |
|
||||||
else if (utilCStrMatch(name, "normal_xform")) |
|
||||||
return UNIFORM_NORMAL_XFORM; |
|
||||||
else if (utilCStrMatch(name, "view_xform")) |
|
||||||
return UNIFORM_VIEW_XFORM; |
|
||||||
else if (utilCStrMatch(name, "proj_xform")) |
|
||||||
return UNIFORM_PROJECTION_XFORM; |
|
||||||
else if (utilCStrMatch(name, "matrices")) |
|
||||||
return UNIFORM_BLOCK_XFORMS; |
|
||||||
else if (utilCStrMatch(name, "lights")) |
|
||||||
return UNIFORM_BLOCK_LIGHTS; |
|
||||||
else |
|
||||||
return UNIFORM_UNKNOWN; |
|
||||||
} |
|
||||||
|
|
||||||
const GLUniform |
|
||||||
parseUniform(MemoryArena* arena, ShaderProgram* s, u32 uniform_idx) |
|
||||||
{ |
|
||||||
GLUniform unif = {0}; |
|
||||||
GLchar unif_name[256] = {0}; |
|
||||||
GLsizei name_len = 0; |
|
||||||
unif.idx = uniform_idx; |
|
||||||
|
|
||||||
glGetActiveUniform(s->prog_id, |
|
||||||
uniform_idx, |
|
||||||
sizeof(unif_name), |
|
||||||
&name_len, |
|
||||||
&unif.num_elements, |
|
||||||
&unif.gl_type, |
|
||||||
unif_name); |
|
||||||
|
|
||||||
glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_BLOCK_INDEX, |
|
||||||
&unif.block_idx); |
|
||||||
glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_ARRAY_STRIDE, |
|
||||||
&unif.array_stride); |
|
||||||
glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_OFFSET, |
|
||||||
&unif.uniform_offset); |
|
||||||
|
|
||||||
unif.name = arenaCopyCStr(arena, unif_name); |
|
||||||
unif.uniform_type = getUniformType(unif.name); |
|
||||||
unif.location = glGetUniformLocation(s->prog_id, unif.name); |
|
||||||
|
|
||||||
return unif; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
parseShaderUniforms(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
// NOTE: only add uniforms in the default block to the base uniform array
|
|
||||||
GLint num_uniforms_total = 0; |
|
||||||
glGetProgramiv(s->prog_id, GL_ACTIVE_UNIFORMS, &num_uniforms_total); |
|
||||||
GLint indices[num_uniforms_total]; |
|
||||||
|
|
||||||
for (u32 i = 0; i < (u32) num_uniforms_total; i++) { |
|
||||||
GLint block_idx = 0; |
|
||||||
glGetActiveUniformsiv(s->prog_id, 1, &i, |
|
||||||
GL_UNIFORM_BLOCK_INDEX, &block_idx); |
|
||||||
|
|
||||||
if (block_idx == -1) { |
|
||||||
indices[s->num_uniforms] = i; |
|
||||||
s->num_uniforms++; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
s->uniforms = ARENA_ALLOC(arena, GLUniform, s->num_uniforms); |
|
||||||
|
|
||||||
for (u32 i = 0; i < s->num_uniforms; i++) { |
|
||||||
const GLUniform unif = parseUniform(arena, s, indices[i]); |
|
||||||
std::memcpy(&s->uniforms[i], &unif, sizeof(unif)); |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
initCTXSizes(GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
// NOTE: see https://docs.gl/gl3/glGet for other useful context info
|
|
||||||
if (gl_ctx->max_binding_points == 0) { |
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, |
|
||||||
&gl_ctx->max_binding_points); |
|
||||||
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &gl_ctx->max_vertex_blocks); |
|
||||||
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, |
|
||||||
&gl_ctx->max_fragment_blocks); |
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gl_ctx->max_ublock_size); |
|
||||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_ctx->max_vertex_attribs); |
|
||||||
|
|
||||||
#if 1 |
|
||||||
LOGF(Debug, "context size info set\n"); |
|
||||||
LOGF(Debug, "GL_MAX_UNIFORM_BUFFER_BINDINGS: %d\n", |
|
||||||
gl_ctx->max_binding_points); |
|
||||||
LOGF(Debug, "GL_MAX_VERTEX_UNIFORM_BLOCKS: %d\n", |
|
||||||
gl_ctx->max_vertex_blocks); |
|
||||||
LOGF(Debug, "GL_MAX_FRAGMENT_UNIFORM_BLOCKS: %d\n", |
|
||||||
gl_ctx->max_fragment_blocks); |
|
||||||
LOGF(Debug, "GL_MAX_UNIFORM_BLOCK_SIZE: %d\n", |
|
||||||
gl_ctx->max_ublock_size); |
|
||||||
LOGF(Debug, "GL_MAX_VERTEX_ATTRIBS: %d\n", gl_ctx->max_vertex_attribs); |
|
||||||
#endif |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
i32 |
|
||||||
ctxGetUniformBlockBinding(GLContext* gl_ctx, const char* name) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < gl_ctx->num_ubos; i++) { |
|
||||||
GLBuffer& ubo = gl_ctx->uniform_buffers[i]; |
|
||||||
|
|
||||||
if (utilCStrMatch(ubo.name, name)) |
|
||||||
return ubo.binding_idx; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "no buffer found with name: '%s'\n", name); |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
parseUniformBlocks(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
glGetProgramiv(s->prog_id, GL_ACTIVE_UNIFORM_BLOCKS, |
|
||||||
(GLint*) &s->num_blocks); |
|
||||||
s->uniform_blocks = ARENA_ALLOC(arena, GLUniformBlock, s->num_blocks); |
|
||||||
|
|
||||||
for (u32 i = 0; i < s->num_blocks; i++) { |
|
||||||
GLUniformBlock& ub = s->uniform_blocks[i]; |
|
||||||
ub.block_id = i; |
|
||||||
|
|
||||||
GLchar block_name[256] = {0}; |
|
||||||
GLsizei name_len = 0; |
|
||||||
glGetActiveUniformBlockName( |
|
||||||
s->prog_id, i, 256, &name_len, block_name); |
|
||||||
ub.name = arenaCopyCStr(arena, block_name); |
|
||||||
ub.uniform_type = getUniformType(ub.name); |
|
||||||
|
|
||||||
glGetActiveUniformBlockiv(s->prog_id, i, |
|
||||||
GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, (GLint*) &ub.num_uniforms); |
|
||||||
ub.uniforms = ARENA_ALLOC(arena, GLUniform, ub.num_uniforms); |
|
||||||
GLint indices[ub.num_uniforms] = {0}; |
|
||||||
glGetActiveUniformBlockiv(s->prog_id, i, |
|
||||||
GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, (GLint*) &indices); |
|
||||||
|
|
||||||
for (u32 j = 0; j < ub.num_uniforms; j++) { |
|
||||||
const GLUniform unif = parseUniform(arena, s, indices[j]); |
|
||||||
std::memcpy(&ub.uniforms[j], &unif, sizeof(unif)); |
|
||||||
} |
|
||||||
|
|
||||||
ub.binding_idx = ctxGetUniformBlockBinding(gl_ctx, ub.name); |
|
||||||
|
|
||||||
if (ub.binding_idx < 0) |
|
||||||
return false; |
|
||||||
|
|
||||||
glUniformBlockBinding(s->prog_id, i, ub.binding_idx); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO: would be helpful for debugging if we sort the uniforms in a block
|
|
||||||
// by their uniform_offset instead of their idx
|
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
u32 |
|
||||||
getNumAttribComponents(GLenum type) |
|
||||||
{ |
|
||||||
switch (type) { |
|
||||||
case GL_FLOAT_VEC3: return 3; |
|
||||||
case GL_FLOAT_VEC2: return 2; |
|
||||||
default: |
|
||||||
LOGF(Error, "unknown GLenum\n"); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
GLenum |
|
||||||
getAttribComponentType(GLenum type) |
|
||||||
{ |
|
||||||
switch (type) { |
|
||||||
case GL_FLOAT_VEC3: return GL_FLOAT; |
|
||||||
case GL_FLOAT_VEC2: return GL_FLOAT; |
|
||||||
default: |
|
||||||
LOGF(Error, "unknown GLenum\n"); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
parseAttributes(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx) |
|
||||||
{ |
|
||||||
GLint num_attribs; |
|
||||||
glGetProgramiv(s->prog_id, GL_ACTIVE_ATTRIBUTES, &num_attribs); |
|
||||||
s->num_vertex_attribs = num_attribs; |
|
||||||
s->vertex_attribs = ARENA_ALLOC(arena, GLVertexAttrib, num_attribs); |
|
||||||
s->attrib_mappings = |
|
||||||
ARENA_ALLOC(arena, GLBufferToAttribMapping, num_attribs); |
|
||||||
|
|
||||||
GLchar attrib_name[256] = {0}; |
|
||||||
GLsizei length; |
|
||||||
GLint size; |
|
||||||
GLenum type; |
|
||||||
|
|
||||||
for (int i = 0; i < num_attribs; i++) { |
|
||||||
glGetActiveAttrib(s->prog_id, i, sizeof(attrib_name), |
|
||||||
&length, &size, &type, attrib_name); |
|
||||||
GLint location = glGetAttribLocation(s->prog_id, attrib_name); |
|
||||||
GLVertexAttrib* attrib = &s->vertex_attribs[i]; |
|
||||||
attrib->data_type = type; |
|
||||||
attrib->location = location; |
|
||||||
attrib->num_components = getNumAttribComponents(type); |
|
||||||
assert(attrib->num_components > 0); |
|
||||||
attrib->component_type = getAttribComponentType(type); |
|
||||||
assert(attrib->component_type > 0); |
|
||||||
attrib->name = arenaCopyCStr(arena, attrib_name, sizeof(attrib_name)); |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
parseShader(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s) |
|
||||||
{ |
|
||||||
if (parseShaderUniforms(arena, s, gl_ctx) |
|
||||||
&& parseUniformBlocks(arena, s, gl_ctx) |
|
||||||
&& parseAttributes(arena, s, gl_ctx)) |
|
||||||
{ |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
LOGF(Error, "Error parsing shader\n"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
@ -0,0 +1,143 @@ |
|||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
#include "shader_program.h" |
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
bool checkShaderErrors(GLuint program_id); |
||||||
|
void cleanUpShader(GLuint program_id, GLuint vs_id, GLuint fs_id); |
||||||
|
bool compileProgram(GLuint& program_id_out, |
||||||
|
GLuint& vs_id_out, |
||||||
|
GLuint& fs_id_out, |
||||||
|
const char* vertex_code, |
||||||
|
const char* frag_code); |
||||||
|
|
||||||
|
|
||||||
|
//interface
|
||||||
|
|
||||||
|
default_shader_program* |
||||||
|
shaderInitDefault(const char* vertex_code, const char* frag_code) |
||||||
|
{ |
||||||
|
LOG(Info) << "loading default shader\n"; |
||||||
|
default_shader_program* sp = UTIL_ALLOC(1, default_shader_program); |
||||||
|
GLuint vs_id = 0; |
||||||
|
GLuint fs_id = 0; |
||||||
|
|
||||||
|
compileProgram(sp->program_id, vs_id, fs_id, vertex_code, frag_code); |
||||||
|
glGenVertexArrays(1, &sp->vertex_array_id); |
||||||
|
glBindVertexArray(sp->vertex_array_id); |
||||||
|
|
||||||
|
// assign uniforms
|
||||||
|
sp->world_transform_id = |
||||||
|
glGetUniformLocation(sp->program_id, "world_transform"); |
||||||
|
sp->model_matrix_id = glGetUniformLocation(sp->program_id, "model"); |
||||||
|
sp->view_matrix_id = glGetUniformLocation(sp->program_id, "view"); |
||||||
|
sp->projection_matrix_id = |
||||||
|
glGetUniformLocation(sp->program_id, "projection"); |
||||||
|
sp->normal_matrix_id = |
||||||
|
glGetUniformLocation(sp->program_id, "normal_matrix"); |
||||||
|
sp->num_lights_id = glGetUniformLocation(sp->program_id, "num_lights"); |
||||||
|
sp->sampler_id = glGetUniformLocation(sp->program_id, "sampler"); |
||||||
|
|
||||||
|
cleanUpShader(sp->program_id, vs_id, fs_id); |
||||||
|
|
||||||
|
if (!checkShaderErrors(sp->program_id)) { |
||||||
|
glDeleteProgram(sp->program_id); |
||||||
|
utilSafeFree(sp); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
return sp; |
||||||
|
} |
||||||
|
|
||||||
|
simple_shader_program* |
||||||
|
shaderInitSimple(const char* vertex_code, const char* frag_code) |
||||||
|
{ |
||||||
|
LOG(Info) << "loading simple shader\n"; |
||||||
|
simple_shader_program* sp = UTIL_ALLOC(1, simple_shader_program); |
||||||
|
GLuint vs_id = 0; |
||||||
|
GLuint fs_id = 0; |
||||||
|
compileProgram(sp->program_id, vs_id, fs_id, vertex_code, frag_code); |
||||||
|
glGenVertexArrays(1, &sp->vertex_array_id); |
||||||
|
glBindVertexArray(sp->vertex_array_id); |
||||||
|
|
||||||
|
// assign uniforms
|
||||||
|
sp->world_transform_id = |
||||||
|
glGetUniformLocation(sp->program_id, "world_transform"); |
||||||
|
sp->MVP_id = glGetUniformLocation(sp->program_id, "MVP"); |
||||||
|
|
||||||
|
if (!checkShaderErrors(sp->program_id)) { |
||||||
|
glDeleteProgram(sp->program_id); |
||||||
|
utilSafeFree(sp); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
return sp; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
shaderFree(uint program_id) |
||||||
|
{ |
||||||
|
// TODO: can check for valid id here
|
||||||
|
glDeleteProgram(program_id); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
bool |
||||||
|
checkShaderErrors(uint program_id) |
||||||
|
{ |
||||||
|
GLint isLinked = 0; |
||||||
|
GLint info_len = 0; |
||||||
|
glGetProgramiv(program_id, GL_LINK_STATUS, &isLinked); |
||||||
|
|
||||||
|
if (isLinked == GL_FALSE) { |
||||||
|
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_len); |
||||||
|
char* infoLog = UTIL_ALLOC(info_len, char); |
||||||
|
glGetProgramInfoLog(program_id, info_len, &info_len, &infoLog[0]); |
||||||
|
LOG(Error) << infoLog << "\n"; |
||||||
|
utilSafeFree(infoLog); |
||||||
|
glDeleteProgram(program_id); |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
cleanUpShader(GLuint program_id, GLuint vs_id, GLuint fs_id) |
||||||
|
{ |
||||||
|
glDetachShader(program_id, vs_id); |
||||||
|
glDetachShader(program_id, fs_id); |
||||||
|
glDeleteShader(vs_id); |
||||||
|
glDeleteShader(fs_id); |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
compileProgram(GLuint& program_id_out, |
||||||
|
GLuint& vs_id_out, |
||||||
|
GLuint& fs_id_out, |
||||||
|
const char* vertex_code, |
||||||
|
const char* frag_code) |
||||||
|
{ |
||||||
|
vs_id_out = glCreateShader(GL_VERTEX_SHADER); |
||||||
|
fs_id_out = glCreateShader(GL_FRAGMENT_SHADER); |
||||||
|
|
||||||
|
glShaderSource(vs_id_out, 1, &vertex_code, NULL); |
||||||
|
glShaderSource(fs_id_out, 1, &frag_code, NULL); |
||||||
|
glCompileShader(vs_id_out); |
||||||
|
glCompileShader(fs_id_out); |
||||||
|
// TODO: can check for error here
|
||||||
|
|
||||||
|
program_id_out = glCreateProgram(); |
||||||
|
glAttachShader(program_id_out, vs_id_out); |
||||||
|
glAttachShader(program_id_out, fs_id_out); |
||||||
|
glLinkProgram(program_id_out); |
||||||
|
// TODO: can check for error here
|
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
@ -1,408 +0,0 @@ |
|||||||
|
|
||||||
#include <cassert> |
|
||||||
|
|
||||||
#define GLM_FORCE_XYZW_ONLY |
|
||||||
#include <glm/gtc/matrix_transform.hpp> |
|
||||||
|
|
||||||
#include "tangerine.h" |
|
||||||
#define UTIL_IMPLEMENTATION |
|
||||||
#include "util.h" |
|
||||||
#define GL_DEBUG_IMPLEMENTATION |
|
||||||
#include "GLDebug.h" |
|
||||||
|
|
||||||
|
|
||||||
// forward declarations
|
|
||||||
|
|
||||||
bool initGraphics(SDLHandles* handles, const char* title, uvec2 dims); |
|
||||||
|
|
||||||
LightsBuffer* initLights(RenderState* rs, u32 max_lights, vec4 ambient_color); |
|
||||||
|
|
||||||
|
|
||||||
// interface
|
|
||||||
|
|
||||||
RenderState* |
|
||||||
initRenderState(const char* window_title, |
|
||||||
uvec2 window_dims, |
|
||||||
u32 SDL_flags, |
|
||||||
GLClearColor clear_col, |
|
||||||
vec4 ambient_color, |
|
||||||
u32 max_models, |
|
||||||
u32 max_textures, |
|
||||||
u32 max_shaders, |
|
||||||
u32 max_render_groups, |
|
||||||
u32 max_ubos, |
|
||||||
u32 max_lights) |
|
||||||
{ |
|
||||||
LOGF(Info, "Initializing Renderer\n"); |
|
||||||
RenderState* rs = UTIL_ALLOC(1, RenderState); |
|
||||||
|
|
||||||
if (rs) { |
|
||||||
rs->clear_col = clear_col; |
|
||||||
rs->assets.arena = arenaInit(DEFAULT_ARENA_SIZE); |
|
||||||
rs->assets.max_models = max_models; |
|
||||||
rs->assets.models = ARENA_ALLOC(rs->assets.arena, Model, max_models); |
|
||||||
rs->assets.max_textures = max_textures; |
|
||||||
rs->assets.textures = |
|
||||||
ARENA_ALLOC(rs->assets.arena, Texture, max_textures); |
|
||||||
|
|
||||||
rs->rg_arena = arenaInit(DEFAULT_ARENA_SIZE); |
|
||||||
rs->max_render_groups = DEFAULT_RENDER_GROUP_COUNT; |
|
||||||
rs->render_groups = ARENA_ALLOC(rs->rg_arena, RenderGroup, |
|
||||||
DEFAULT_RENDER_GROUP_COUNT); |
|
||||||
rs->window_dims = window_dims; |
|
||||||
rs->handles.SDL_flags = SDL_flags; |
|
||||||
|
|
||||||
if (!initGraphics(&rs->handles, window_title, window_dims)) { |
|
||||||
LOGF(Error, "error initializing renderer\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
rs->gl_ctx = initGLContext(rs->assets.arena, |
|
||||||
max_shaders, |
|
||||||
max_textures, |
|
||||||
max_ubos); |
|
||||||
|
|
||||||
rs->camera = UTIL_ALLOC(1, Camera); |
|
||||||
GLBuffer* xforms_ubo = initGLBackingBuffer(rs->gl_ctx, |
|
||||||
rs->assets.arena, |
|
||||||
"matrices", |
|
||||||
GL_FLOAT, |
|
||||||
sizeof(Transforms), |
|
||||||
&rs->camera->xforms); |
|
||||||
assert(xforms_ubo); |
|
||||||
|
|
||||||
// FIXME: should this be an interface function?
|
|
||||||
rs->lights_buf = initLights(rs, max_lights, ambient_color); |
|
||||||
|
|
||||||
// FIXME: should have an error message instead of assert here, and
|
|
||||||
// clean up arenas/allocations before returning
|
|
||||||
bool ret = loadDefaultShaders(rs); |
|
||||||
assert(ret); |
|
||||||
} |
|
||||||
|
|
||||||
return rs; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
freeRenderState(RenderState*& rs) |
|
||||||
{ |
|
||||||
if (rs) { |
|
||||||
SDL_GL_DeleteContext(rs->handles.sdl_gl_ctx); |
|
||||||
SDL_DestroyWindow(rs->handles.window); |
|
||||||
arenaFree(rs->assets.arena); |
|
||||||
arenaFree(rs->rg_arena); |
|
||||||
utilSafeFree(rs); |
|
||||||
rs = nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
SDL_Quit(); |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
initRenderGroup(RenderGroup* rg, |
|
||||||
MemoryArena* arena, |
|
||||||
ShaderProgram* shader, |
|
||||||
u32 num_entities, |
|
||||||
const char* name) |
|
||||||
{ |
|
||||||
rg->max_entities = num_entities; |
|
||||||
rg->shader = shader; |
|
||||||
rg->name = arenaCopyCStr(arena, name); |
|
||||||
rg->entities = ARENA_ALLOC(arena, Entity, num_entities); |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
freeRenderGroup(RenderGroup* rg, MemoryArena* arena) |
|
||||||
{ |
|
||||||
LOGF(Info, "should probably look into freeing arena memory?\n"); |
|
||||||
assert(0); |
|
||||||
} |
|
||||||
|
|
||||||
RenderGroup* |
|
||||||
getFreeRenderGroup(RenderState* rs) |
|
||||||
{ |
|
||||||
if (rs->num_render_groups < rs->max_render_groups) |
|
||||||
return &rs->render_groups[rs->num_render_groups++]; |
|
||||||
|
|
||||||
LOGF(Error, "no free render group\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
RenderGroup* |
|
||||||
getRenderGroupByName(RenderState* rs, const char* name) |
|
||||||
{ |
|
||||||
RenderGroup* rg_out = nullptr; |
|
||||||
|
|
||||||
for (u32 i = 0; i < rs->num_render_groups; i++) { |
|
||||||
RenderGroup* rg = &rs->render_groups[i]; |
|
||||||
|
|
||||||
if (utilCStrMatch(name, rg->name)) |
|
||||||
rg_out = rg; |
|
||||||
} |
|
||||||
|
|
||||||
if (rg_out == nullptr) |
|
||||||
LOGF(Error, "render group with name, \"%s\", not found\n", name); |
|
||||||
|
|
||||||
return rg_out; |
|
||||||
} |
|
||||||
|
|
||||||
Entity* |
|
||||||
getFreeEntity(RenderGroup* rg) |
|
||||||
{ |
|
||||||
if (rg->num_entities < rg->max_entities) |
|
||||||
return &rg->entities[rg->num_entities++]; |
|
||||||
|
|
||||||
LOGF(Error, "render group full\n"); |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
Entity* |
|
||||||
getEntityByName(RenderGroup* rg, const char* name) |
|
||||||
{ |
|
||||||
Entity* e_out = nullptr; |
|
||||||
|
|
||||||
for (u32 i = 0; i < rg->num_entities; i++) { |
|
||||||
Entity* e = &rg->entities[i]; |
|
||||||
|
|
||||||
if (utilCStrMatch(name, e->name)) |
|
||||||
e_out = e; |
|
||||||
} |
|
||||||
|
|
||||||
if (e_out == nullptr) |
|
||||||
LOGF(Error, "Entity with name, \"%s\", not found\n", name); |
|
||||||
|
|
||||||
return e_out; |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
doRenderLoop(RenderState* rs, |
|
||||||
u32 framerate, |
|
||||||
frame_callback_fn cb_func_pre, |
|
||||||
frame_callback_fn cb_func_post, |
|
||||||
void* user_data) |
|
||||||
{ |
|
||||||
u32 delay = (framerate > 0) ? 1 / framerate : 0; |
|
||||||
u32 frameStart, frameTime; |
|
||||||
rs->running = true; |
|
||||||
SDL_Event e; |
|
||||||
|
|
||||||
while (rs->running) { |
|
||||||
frameStart = SDL_GetTicks(); |
|
||||||
|
|
||||||
if (cb_func_pre != nullptr) { |
|
||||||
cb_func_pre(rs, user_data); |
|
||||||
} else { |
|
||||||
while (SDL_PollEvent(&e)) { |
|
||||||
if (e.type == SDL_QUIT || |
|
||||||
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) |
|
||||||
{ |
|
||||||
rs->running = false; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
renderFrame(rs, rs->clear_col); |
|
||||||
|
|
||||||
if (cb_func_post != nullptr) |
|
||||||
cb_func_post(rs, user_data); |
|
||||||
|
|
||||||
SDL_GL_SwapWindow(rs->handles.window); |
|
||||||
frameTime = SDL_GetTicks() - frameStart; |
|
||||||
|
|
||||||
if (delay > frameTime) |
|
||||||
SDL_Delay(delay - frameTime); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void |
|
||||||
renderFrame(RenderState* rs, const GLClearColor& clear_col) |
|
||||||
{ |
|
||||||
glClearColor(clear_col.R, |
|
||||||
clear_col.G, |
|
||||||
clear_col.B, |
|
||||||
clear_col.A); |
|
||||||
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); |
|
||||||
|
|
||||||
for (u32 i = 0; i < rs->num_render_groups; i++) { |
|
||||||
RenderGroup* rg = &rs->render_groups[i]; |
|
||||||
|
|
||||||
for (u32 j = 0; j < rg->num_entities; j++) { |
|
||||||
Entity* e = &rg->entities[j]; |
|
||||||
|
|
||||||
for (u32 k = 0; k < e->num_meshes; k++) { |
|
||||||
GLMesh& glm = e->meshes[k]; |
|
||||||
renderVAO(&glm, e->model_xform, rg->shader, |
|
||||||
e->diffuse_texture, e->draw_mode); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool |
|
||||||
loadDefaultShaders(RenderState* rs, |
|
||||||
u32 num_shaders, |
|
||||||
const ShaderInit shaders[]) |
|
||||||
{ |
|
||||||
for (u32 i = 0; i < num_shaders; i++) { |
|
||||||
const ShaderInit& si = shaders[i]; |
|
||||||
|
|
||||||
if (!addShaderProgram(rs->assets.arena, |
|
||||||
rs->gl_ctx, |
|
||||||
si.vert_path, |
|
||||||
si.frag_path, |
|
||||||
si.name)) |
|
||||||
{ |
|
||||||
LOG(Error) << "failed to load shader " << si.name << "\n"; |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
ShaderProgram* shader = getShaderByName(si.name, rs->gl_ctx); |
|
||||||
assert(shader); |
|
||||||
|
|
||||||
// NOTE: not every buffer will be available for every shader, so we
|
|
||||||
// enumerate them all, and store the ones that are present
|
|
||||||
u32 attrib_idx = 0; |
|
||||||
|
|
||||||
for (u32 i = 0; i < MESH_BUFFER_TYPE_COUNT; i++) { |
|
||||||
MeshBufferType buf_type = (MeshBufferType) i; |
|
||||||
GLVertexAttrib* attrib = getVertexAttribByType(shader, buf_type); |
|
||||||
|
|
||||||
if (attrib) |
|
||||||
shader->attrib_mappings[attrib_idx++] = { attrib, buf_type }; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
GLVertexAttrib* |
|
||||||
getVertexAttribByType(ShaderProgram* shader, MeshBufferType buf_type) |
|
||||||
{ |
|
||||||
switch (buf_type) { |
|
||||||
case VERTEX: return getVertexAttribByName(shader, "position"); |
|
||||||
case NORMAL: return getVertexAttribByName(shader, "normal"); |
|
||||||
case UV: return getVertexAttribByName(shader, "uv"); |
|
||||||
case COLOR: return getVertexAttribByName(shader, "color"); |
|
||||||
default: return nullptr; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// internal
|
|
||||||
|
|
||||||
bool |
|
||||||
initGraphics(SDLHandles* handles, const char* title, uvec2 dims) |
|
||||||
{ |
|
||||||
handles->window = SDL_CreateWindow( |
|
||||||
title, |
|
||||||
SDL_WINDOWPOS_CENTERED_DISPLAY(0), |
|
||||||
SDL_WINDOWPOS_CENTERED_DISPLAY(0), |
|
||||||
dims.x, |
|
||||||
dims.y, |
|
||||||
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); |
|
||||||
|
|
||||||
if (SDL_Init(handles->SDL_flags) != 0) { |
|
||||||
std::cout << "error, sdl init: " << SDL_GetError() << "\n"; |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
SDL_GL_SetSwapInterval(1); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, |
|
||||||
SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, |
|
||||||
SDL_GL_CONTEXT_PROFILE_CORE); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); |
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); |
|
||||||
SDL_GetCurrentDisplayMode(0, &handles->display_mode); |
|
||||||
|
|
||||||
handles->sdl_gl_ctx = SDL_GL_CreateContext(handles->window); |
|
||||||
|
|
||||||
if (!handles->sdl_gl_ctx) { |
|
||||||
std::cout << "error creating context\n"; |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (glewInit()) { |
|
||||||
std::cout << "error initializing opengl\n"; |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
std::cout << "opengl vendor: " << glGetString(GL_VENDOR) << "\n"; |
|
||||||
std::cout << "opengl renderer: " << glGetString(GL_RENDERER) << "\n"; |
|
||||||
std::cout << "opengl version: " << glGetString(GL_VERSION) << "\n"; |
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST); |
|
||||||
glEnable(GL_LINE_SMOOTH); |
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); |
|
||||||
|
|
||||||
glEnable (GL_DEBUG_OUTPUT); |
|
||||||
glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0); |
|
||||||
|
|
||||||
return handles->window != nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
LightsBuffer* |
|
||||||
initLights(RenderState* rs, |
|
||||||
u32 max_lights, |
|
||||||
vec4 ambient_color) |
|
||||||
{ |
|
||||||
// FIXME: revisit for 'Scene' abstraction
|
|
||||||
// FIXME: see if we can simplify this with the use of offsetof()
|
|
||||||
// https://en.cppreference.com/w/cpp/types/offsetof
|
|
||||||
|
|
||||||
LightsBuffer* lb = ARENA_ALLOC(rs->assets.arena, LightsBuffer, 1); |
|
||||||
lb->buf_size = 8 * sizeof(u32) // NOTE: 'header'
|
|
||||||
+ sizeof(vec4) // NOTE: ambient color
|
|
||||||
+ 6 * max_lights * sizeof(vec4); // NOTE: vector arrays
|
|
||||||
LOGF(Debug, "buf_size: %d\n", lb->buf_size); |
|
||||||
lb->buffer = ARENA_ALLOC(rs->assets.arena, u8, lb->buf_size); |
|
||||||
|
|
||||||
lb->max_p_lights = (u32*) lb->buffer; |
|
||||||
lb->active_p_lights = |
|
||||||
(u32*) arenaGetAddressOffset(lb->max_p_lights, sizeof(u32)); |
|
||||||
lb->max_d_lights = |
|
||||||
(u32*) arenaGetAddressOffset(lb->active_p_lights, sizeof(u32)); |
|
||||||
lb->active_d_lights = |
|
||||||
(u32*) arenaGetAddressOffset(lb->max_d_lights, sizeof(u32)); |
|
||||||
|
|
||||||
*lb->max_p_lights = max_lights; |
|
||||||
*lb->max_d_lights = max_lights; |
|
||||||
|
|
||||||
// NOTE: add padding, we're not actually using this since 4 * u32 is on a
|
|
||||||
// 16 byte boundary, but will be helpful if we need to add more 'headers'
|
|
||||||
// in the future
|
|
||||||
void* arr_start = arenaGetAddressOffset(lb->buffer, 8 * sizeof(u32)); |
|
||||||
|
|
||||||
// NOTE: ambient color
|
|
||||||
lb->ambient_color = (vec4*) arr_start; |
|
||||||
*lb->ambient_color = ambient_color; |
|
||||||
|
|
||||||
// NOTE: set offsets for array pointers
|
|
||||||
u32 arr_size = max_lights * sizeof(vec4); |
|
||||||
lb->pl_positions = //(vec4*) arr_start;
|
|
||||||
(vec4*) arenaGetAddressOffset(arr_start, sizeof(vec4)); |
|
||||||
lb->pl_colors = |
|
||||||
(vec4*) arenaGetAddressOffset(lb->pl_positions, arr_size); |
|
||||||
lb->pl_intensities = |
|
||||||
(uvec4*) arenaGetAddressOffset(lb->pl_colors, arr_size); |
|
||||||
lb->dl_directions = |
|
||||||
(vec4*) arenaGetAddressOffset(lb->pl_intensities, arr_size); |
|
||||||
lb->dl_colors = |
|
||||||
(vec4*) arenaGetAddressOffset(lb->dl_directions, arr_size); |
|
||||||
lb->dl_intensities = |
|
||||||
(uvec4*) arenaGetAddressOffset(lb->dl_colors, arr_size); |
|
||||||
|
|
||||||
initGLBackingBuffer(rs->gl_ctx, |
|
||||||
rs->assets.arena, |
|
||||||
"lights", |
|
||||||
GL_BYTE, |
|
||||||
lb->buf_size, |
|
||||||
lb->buffer); |
|
||||||
|
|
||||||
return lb; |
|
||||||
} |
|
||||||
|
|
||||||
@ -1,5 +0,0 @@ |
|||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION |
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION |
|
||||||
#define TINYGLTF_IMPLEMENTATION |
|
||||||
#include "tiny_gltf.h" |
|
||||||
@ -0,0 +1,141 @@ |
|||||||
|
|
||||||
|
#include <cassert> |
||||||
|
#include <cmath> |
||||||
|
#include <cstdlib> |
||||||
|
#include <cstdio> |
||||||
|
#include <cstring> |
||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
|
||||||
|
const uint MAX_FILESIZE = 2 * 1024 * 1024; // 2MB
|
||||||
|
const uint MAX_STRING_LENGTH = 1024; |
||||||
|
|
||||||
|
|
||||||
|
//-----------------
|
||||||
|
// C Strings
|
||||||
|
|
||||||
|
const char* |
||||||
|
utilBaseName(const char* path_str) |
||||||
|
{ |
||||||
|
assert(std::strlen(path_str) < MAX_STRING_LENGTH); |
||||||
|
const char* output = std::strrchr(path_str, '/'); |
||||||
|
if (output) |
||||||
|
return output; |
||||||
|
else |
||||||
|
return path_str; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
utilCopyCStr(char* dest, const char* src, uint max_len) |
||||||
|
{ |
||||||
|
assert(std::strlen(src) < MAX_STRING_LENGTH && max_len <= MAX_STRING_LENGTH); |
||||||
|
if (std::strlen(src) + 1 > max_len) |
||||||
|
return false; |
||||||
|
|
||||||
|
std::memcpy(dest, src, std::strlen(src) + 1); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
char* |
||||||
|
utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len) |
||||||
|
{ |
||||||
|
size_t l1 = std::strlen(base_dir); |
||||||
|
size_t l2 = std::strlen(file_name); |
||||||
|
size_t padded = l1 + l2 + 2; // NOTE: null term + '/'
|
||||||
|
|
||||||
|
assert(padded <= MAX_STRING_LENGTH && padded <= max_len); |
||||||
|
int c = std::snprintf(out, padded, "%s/%s", base_dir, file_name); |
||||||
|
assert(c > 0); |
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
bool |
||||||
|
utilMatchPrefix(const char* lhs, const char* rhs, int sz) |
||||||
|
{ |
||||||
|
int rc = strncmp(lhs, rhs, sz); |
||||||
|
return (rc >= 0); |
||||||
|
} |
||||||
|
|
||||||
|
//-----------------
|
||||||
|
// Memory allocation
|
||||||
|
|
||||||
|
void * |
||||||
|
utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line) |
||||||
|
{ |
||||||
|
assert(item_count > 0); // that was a fun bug
|
||||||
|
|
||||||
|
void* mem = std::calloc(item_count, type_size); |
||||||
|
|
||||||
|
if (mem == nullptr) { |
||||||
|
LOG(Error) << "Memory allocation failed, called from " |
||||||
|
<< file_name << ":" << line; |
||||||
|
} |
||||||
|
|
||||||
|
assert(mem != nullptr); // might as well stop execution here
|
||||||
|
|
||||||
|
return mem; |
||||||
|
} |
||||||
|
|
||||||
|
void utilSafeFree(const void* mem) |
||||||
|
{ |
||||||
|
if (mem != nullptr) std::free((void *) mem); |
||||||
|
} |
||||||
|
|
||||||
|
//-----------------
|
||||||
|
// File I/O
|
||||||
|
|
||||||
|
// TODO: don't use ftell() to get filesize
|
||||||
|
// https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek%28%29+and+ftell%28%29+to+compute+the+size+of+a+regular+file
|
||||||
|
char * |
||||||
|
utilDumpTextFile(const char* filename) |
||||||
|
{ |
||||||
|
LOG(Info) << "loading filename, " << filename << "\n"; |
||||||
|
std::FILE* fp = std::fopen(filename, "rt"); |
||||||
|
assert(fp); |
||||||
|
|
||||||
|
std::fseek(fp, 0, SEEK_END); |
||||||
|
uint length = std::ftell(fp); |
||||||
|
std::fseek(fp, 0, SEEK_SET); |
||||||
|
assert(length < MAX_FILESIZE); |
||||||
|
// TODO: check error codes for fseek and ftell
|
||||||
|
|
||||||
|
char* buf = UTIL_ALLOC(length, char); |
||||||
|
assert(buf); |
||||||
|
|
||||||
|
std::fread(buf, sizeof(char), length, fp); |
||||||
|
// TODO: check fp w/ ferror() here
|
||||||
|
|
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: might want to do the base_dir concat in this function to prevent clobbering
|
||||||
|
// user files on accident
|
||||||
|
bool |
||||||
|
utilWriteTextFile(const char* filename, const char* text) |
||||||
|
{ |
||||||
|
size_t text_len = std::strlen(text); |
||||||
|
|
||||||
|
if (text_len >= MAX_FILESIZE) { |
||||||
|
LOG(Error) << "that string is too big\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
std::FILE* fp = fopen(filename, "wt"); |
||||||
|
if (fp) { |
||||||
|
size_t written = fwrite(text, sizeof(char), text_len, fp); |
||||||
|
fclose(fp); |
||||||
|
if (written == text_len) { |
||||||
|
LOG(Debug) << "successfuly wrote " << written << " bytes\n"; |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
LOG(Error) << "error writing to file: " << filename << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
LOG(Debug) << text << "\n"; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,60 @@ |
|||||||
|
|
||||||
|
#include <cstring> |
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION |
||||||
|
#include "stb_image.h" |
||||||
|
|
||||||
|
#include "dumbLog.h" |
||||||
|
#include "util_image.h" |
||||||
|
|
||||||
|
|
||||||
|
util_image parseSTBResult(util_image img); |
||||||
|
|
||||||
|
|
||||||
|
util_image |
||||||
|
utilLoadImagePath(const char* full_path) |
||||||
|
{ |
||||||
|
LOG(Info) << "Loading Image: " << full_path << "\n"; |
||||||
|
|
||||||
|
util_image image; |
||||||
|
stbi_set_flip_vertically_on_load(1); |
||||||
|
return parseSTBResult(image); |
||||||
|
} |
||||||
|
|
||||||
|
util_image |
||||||
|
utilLoadImageBytes(const uint8* bytes, uint length) |
||||||
|
{ |
||||||
|
LOG(Info) << "Loading binary image data\n"; |
||||||
|
util_image image; |
||||||
|
stbi_set_flip_vertically_on_load(1); |
||||||
|
image.pixels = stbi_load_from_memory(bytes, length, &image.w, &image.h, &image.num_channels, 0); |
||||||
|
return parseSTBResult(image); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
utilFreeImage(util_image image) |
||||||
|
{ |
||||||
|
image.w = image.h = image.data_len = 0; |
||||||
|
image.bits_per_channel = 8; |
||||||
|
image.num_channels = 4; |
||||||
|
std::memset(image.file_path, 0, std::strlen(image.file_path) + 1); |
||||||
|
utilSafeFree(image.pixels); |
||||||
|
} |
||||||
|
|
||||||
|
util_image |
||||||
|
parseSTBResult(util_image image) |
||||||
|
{ |
||||||
|
image.bits_per_channel = 8; |
||||||
|
image.data_len = image.w * image.h * image.num_channels; // NOTE: assumes 8 bits per channel
|
||||||
|
|
||||||
|
if (image.pixels == 0) { |
||||||
|
LOG(Error) << stbi_failure_reason() << "\n"; |
||||||
|
utilFreeImage(image); |
||||||
|
return image; |
||||||
|
} |
||||||
|
|
||||||
|
LOG(Info) << "Image properties, data_len: " << image.data_len |
||||||
|
<< ", width: " << image.w << ", height: " << image.h << "\n"; |
||||||
|
|
||||||
|
return image; |
||||||
|
} |
||||||
Loading…
Reference in new issue