Compare commits
112 Commits
64 changed files with 4220 additions and 2332 deletions
@ -1,3 +1,8 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text |
||||
*.glb filter=lfs diff=lfs merge=lfs -text |
||||
*.blend filter=lfs diff=lfs merge=lfs -text |
||||
examples/data/spaceship.bin filter=lfs diff=lfs merge=lfs -text |
||||
examples/data/textured_cube.bin filter=lfs diff=lfs merge=lfs -text |
||||
examples/data/spaceship.gltf filter=lfs diff=lfs merge=lfs -text |
||||
examples/data/textured_cube.gltf filter=lfs diff=lfs merge=lfs -text |
||||
examples/data/Color[[:space:]]Palette[[:space:]]140.png filter=lfs diff=lfs merge=lfs -text |
||||
|
||||
@ -1,7 +1,9 @@
|
||||
bin/ |
||||
build/ |
||||
ext/assimp |
||||
doc/ |
||||
msvc/.vs/ |
||||
msvc/x64/ |
||||
tags |
||||
*.swp |
||||
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
[submodule "ext/stb_libs"] |
||||
path = ext/stb_libs |
||||
url = https://github.com/nothings/stb.git |
||||
[submodule "ext/tinygltf"] |
||||
path = ext/tinygltf |
||||
url = https://github.com/syoyo/tinygltf |
||||
|
||||
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE |
||||
Version 3, 29 June 2007 |
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |
||||
Everyone is permitted to copy and distribute verbatim copies |
||||
of this license document, but changing it is not allowed. |
||||
|
||||
Preamble |
||||
|
||||
The GNU General Public License is a free, copyleft license for |
||||
software and other kinds of works. |
||||
|
||||
The licenses for most software and other practical works are designed |
||||
to take away your freedom to share and change the works. By contrast, |
||||
the GNU General Public License is intended to guarantee your freedom to |
||||
share and change all versions of a program--to make sure it remains free |
||||
software for all its users. We, the Free Software Foundation, use the |
||||
GNU General Public License for most of our software; it applies also to |
||||
any other work released this way by its authors. You can apply it to |
||||
your programs, too. |
||||
|
||||
When we speak of free software, we are referring to freedom, not |
||||
price. Our General Public Licenses are designed to make sure that you |
||||
have the freedom to distribute copies of free software (and charge for |
||||
them if you wish), that you receive source code or can get it if you |
||||
want it, that you can change the software or use pieces of it in new |
||||
free programs, and that you know you can do these things. |
||||
|
||||
To protect your rights, we need to prevent others from denying you |
||||
these rights or asking you to surrender the rights. Therefore, you have |
||||
certain responsibilities if you distribute copies of the software, or if |
||||
you modify it: responsibilities to respect the freedom of others. |
||||
|
||||
For example, if you distribute copies of such a program, whether |
||||
gratis or for a fee, you must pass on to the recipients the same |
||||
freedoms that you received. You must make sure that they, too, receive |
||||
or can get the source code. And you must show them these terms so they |
||||
know their rights. |
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps: |
||||
(1) assert copyright on the software, and (2) offer you this License |
||||
giving you legal permission to copy, distribute and/or modify it. |
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains |
||||
that there is no warranty for this free software. For both users' and |
||||
authors' sake, the GPL requires that modified versions be marked as |
||||
changed, so that their problems will not be attributed erroneously to |
||||
authors of previous versions. |
||||
|
||||
Some devices are designed to deny users access to install or run |
||||
modified versions of the software inside them, although the manufacturer |
||||
can do so. This is fundamentally incompatible with the aim of |
||||
protecting users' freedom to change the software. The systematic |
||||
pattern of such abuse occurs in the area of products for individuals to |
||||
use, which is precisely where it is most unacceptable. Therefore, we |
||||
have designed this version of the GPL to prohibit the practice for those |
||||
products. If such problems arise substantially in other domains, we |
||||
stand ready to extend this provision to those domains in future versions |
||||
of the GPL, as needed to protect the freedom of users. |
||||
|
||||
Finally, every program is threatened constantly by software patents. |
||||
States should not allow patents to restrict development and use of |
||||
software on general-purpose computers, but in those that do, we wish to |
||||
avoid the special danger that patents applied to a free program could |
||||
make it effectively proprietary. To prevent this, the GPL assures that |
||||
patents cannot be used to render the program non-free. |
||||
|
||||
The precise terms and conditions for copying, distribution and |
||||
modification follow. |
||||
|
||||
TERMS AND CONDITIONS |
||||
|
||||
0. Definitions. |
||||
|
||||
"This License" refers to version 3 of the GNU General Public License. |
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of |
||||
works, such as semiconductor masks. |
||||
|
||||
"The Program" refers to any copyrightable work licensed under this |
||||
License. Each licensee is addressed as "you". "Licensees" and |
||||
"recipients" may be individuals or organizations. |
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work |
||||
in a fashion requiring copyright permission, other than the making of an |
||||
exact copy. The resulting work is called a "modified version" of the |
||||
earlier work or a work "based on" the earlier work. |
||||
|
||||
A "covered work" means either the unmodified Program or a work based |
||||
on the Program. |
||||
|
||||
To "propagate" a work means to do anything with it that, without |
||||
permission, would make you directly or secondarily liable for |
||||
infringement under applicable copyright law, except executing it on a |
||||
computer or modifying a private copy. Propagation includes copying, |
||||
distribution (with or without modification), making available to the |
||||
public, and in some countries other activities as well. |
||||
|
||||
To "convey" a work means any kind of propagation that enables other |
||||
parties to make or receive copies. Mere interaction with a user through |
||||
a computer network, with no transfer of a copy, is not conveying. |
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" |
||||
to the extent that it includes a convenient and prominently visible |
||||
feature that (1) displays an appropriate copyright notice, and (2) |
||||
tells the user that there is no warranty for the work (except to the |
||||
extent that warranties are provided), that licensees may convey the |
||||
work under this License, and how to view a copy of this License. If |
||||
the interface presents a list of user commands or options, such as a |
||||
menu, a prominent item in the list meets this criterion. |
||||
|
||||
1. Source Code. |
||||
|
||||
The "source code" for a work means the preferred form of the work |
||||
for making modifications to it. "Object code" means any non-source |
||||
form of a work. |
||||
|
||||
A "Standard Interface" means an interface that either is an official |
||||
standard defined by a recognized standards body, or, in the case of |
||||
interfaces specified for a particular programming language, one that |
||||
is widely used among developers working in that language. |
||||
|
||||
The "System Libraries" of an executable work include anything, other |
||||
than the work as a whole, that (a) is included in the normal form of |
||||
packaging a Major Component, but which is not part of that Major |
||||
Component, and (b) serves only to enable use of the work with that |
||||
Major Component, or to implement a Standard Interface for which an |
||||
implementation is available to the public in source code form. A |
||||
"Major Component", in this context, means a major essential component |
||||
(kernel, window system, and so on) of the specific operating system |
||||
(if any) on which the executable work runs, or a compiler used to |
||||
produce the work, or an object code interpreter used to run it. |
||||
|
||||
The "Corresponding Source" for a work in object code form means all |
||||
the source code needed to generate, install, and (for an executable |
||||
work) run the object code and to modify the work, including scripts to |
||||
control those activities. However, it does not include the work's |
||||
System Libraries, or general-purpose tools or generally available free |
||||
programs which are used unmodified in performing those activities but |
||||
which are not part of the work. For example, Corresponding Source |
||||
includes interface definition files associated with source files for |
||||
the work, and the source code for shared libraries and dynamically |
||||
linked subprograms that the work is specifically designed to require, |
||||
such as by intimate data communication or control flow between those |
||||
subprograms and other parts of the work. |
||||
|
||||
The Corresponding Source need not include anything that users |
||||
can regenerate automatically from other parts of the Corresponding |
||||
Source. |
||||
|
||||
The Corresponding Source for a work in source code form is that |
||||
same work. |
||||
|
||||
2. Basic Permissions. |
||||
|
||||
All rights granted under this License are granted for the term of |
||||
copyright on the Program, and are irrevocable provided the stated |
||||
conditions are met. This License explicitly affirms your unlimited |
||||
permission to run the unmodified Program. The output from running a |
||||
covered work is covered by this License only if the output, given its |
||||
content, constitutes a covered work. This License acknowledges your |
||||
rights of fair use or other equivalent, as provided by copyright law. |
||||
|
||||
You may make, run and propagate covered works that you do not |
||||
convey, without conditions so long as your license otherwise remains |
||||
in force. You may convey covered works to others for the sole purpose |
||||
of having them make modifications exclusively for you, or provide you |
||||
with facilities for running those works, provided that you comply with |
||||
the terms of this License in conveying all material for which you do |
||||
not control copyright. Those thus making or running the covered works |
||||
for you must do so exclusively on your behalf, under your direction |
||||
and control, on terms that prohibit them from making any copies of |
||||
your copyrighted material outside their relationship with you. |
||||
|
||||
Conveying under any other circumstances is permitted solely under |
||||
the conditions stated below. Sublicensing is not allowed; section 10 |
||||
makes it unnecessary. |
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
||||
|
||||
No covered work shall be deemed part of an effective technological |
||||
measure under any applicable law fulfilling obligations under article |
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
||||
similar laws prohibiting or restricting circumvention of such |
||||
measures. |
||||
|
||||
When you convey a covered work, you waive any legal power to forbid |
||||
circumvention of technological measures to the extent such circumvention |
||||
is effected by exercising rights under this License with respect to |
||||
the covered work, and you disclaim any intention to limit operation or |
||||
modification of the work as a means of enforcing, against the work's |
||||
users, your or third parties' legal rights to forbid circumvention of |
||||
technological measures. |
||||
|
||||
4. Conveying Verbatim Copies. |
||||
|
||||
You may convey verbatim copies of the Program's source code as you |
||||
receive it, in any medium, provided that you conspicuously and |
||||
appropriately publish on each copy an appropriate copyright notice; |
||||
keep intact all notices stating that this License and any |
||||
non-permissive terms added in accord with section 7 apply to the code; |
||||
keep intact all notices of the absence of any warranty; and give all |
||||
recipients a copy of this License along with the Program. |
||||
|
||||
You may charge any price or no price for each copy that you convey, |
||||
and you may offer support or warranty protection for a fee. |
||||
|
||||
5. Conveying Modified Source Versions. |
||||
|
||||
You may convey a work based on the Program, or the modifications to |
||||
produce it from the Program, in the form of source code under the |
||||
terms of section 4, provided that you also meet all of these conditions: |
||||
|
||||
a) The work must carry prominent notices stating that you modified |
||||
it, and giving a relevant date. |
||||
|
||||
b) The work must carry prominent notices stating that it is |
||||
released under this License and any conditions added under section |
||||
7. This requirement modifies the requirement in section 4 to |
||||
"keep intact all notices". |
||||
|
||||
c) You must license the entire work, as a whole, under this |
||||
License to anyone who comes into possession of a copy. This |
||||
License will therefore apply, along with any applicable section 7 |
||||
additional terms, to the whole of the work, and all its parts, |
||||
regardless of how they are packaged. This License gives no |
||||
permission to license the work in any other way, but it does not |
||||
invalidate such permission if you have separately received it. |
||||
|
||||
d) If the work has interactive user interfaces, each must display |
||||
Appropriate Legal Notices; however, if the Program has interactive |
||||
interfaces that do not display Appropriate Legal Notices, your |
||||
work need not make them do so. |
||||
|
||||
A compilation of a covered work with other separate and independent |
||||
works, which are not by their nature extensions of the covered work, |
||||
and which are not combined with it such as to form a larger program, |
||||
in or on a volume of a storage or distribution medium, is called an |
||||
"aggregate" if the compilation and its resulting copyright are not |
||||
used to limit the access or legal rights of the compilation's users |
||||
beyond what the individual works permit. Inclusion of a covered work |
||||
in an aggregate does not cause this License to apply to the other |
||||
parts of the aggregate. |
||||
|
||||
6. Conveying Non-Source Forms. |
||||
|
||||
You may convey a covered work in object code form under the terms |
||||
of sections 4 and 5, provided that you also convey the |
||||
machine-readable Corresponding Source under the terms of this License, |
||||
in one of these ways: |
||||
|
||||
a) Convey the object code in, or embodied in, a physical product |
||||
(including a physical distribution medium), accompanied by the |
||||
Corresponding Source fixed on a durable physical medium |
||||
customarily used for software interchange. |
||||
|
||||
b) Convey the object code in, or embodied in, a physical product |
||||
(including a physical distribution medium), accompanied by a |
||||
written offer, valid for at least three years and valid for as |
||||
long as you offer spare parts or customer support for that product |
||||
model, to give anyone who possesses the object code either (1) a |
||||
copy of the Corresponding Source for all the software in the |
||||
product that is covered by this License, on a durable physical |
||||
medium customarily used for software interchange, for a price no |
||||
more than your reasonable cost of physically performing this |
||||
conveying of source, or (2) access to copy the |
||||
Corresponding Source from a network server at no charge. |
||||
|
||||
c) Convey individual copies of the object code with a copy of the |
||||
written offer to provide the Corresponding Source. This |
||||
alternative is allowed only occasionally and noncommercially, and |
||||
only if you received the object code with such an offer, in accord |
||||
with subsection 6b. |
||||
|
||||
d) Convey the object code by offering access from a designated |
||||
place (gratis or for a charge), and offer equivalent access to the |
||||
Corresponding Source in the same way through the same place at no |
||||
further charge. You need not require recipients to copy the |
||||
Corresponding Source along with the object code. If the place to |
||||
copy the object code is a network server, the Corresponding Source |
||||
may be on a different server (operated by you or a third party) |
||||
that supports equivalent copying facilities, provided you maintain |
||||
clear directions next to the object code saying where to find the |
||||
Corresponding Source. Regardless of what server hosts the |
||||
Corresponding Source, you remain obligated to ensure that it is |
||||
available for as long as needed to satisfy these requirements. |
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided |
||||
you inform other peers where the object code and Corresponding |
||||
Source of the work are being offered to the general public at no |
||||
charge under subsection 6d. |
||||
|
||||
A separable portion of the object code, whose source code is excluded |
||||
from the Corresponding Source as a System Library, need not be |
||||
included in conveying the object code work. |
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any |
||||
tangible personal property which is normally used for personal, family, |
||||
or household purposes, or (2) anything designed or sold for incorporation |
||||
into a dwelling. In determining whether a product is a consumer product, |
||||
doubtful cases shall be resolved in favor of coverage. For a particular |
||||
product received by a particular user, "normally used" refers to a |
||||
typical or common use of that class of product, regardless of the status |
||||
of the particular user or of the way in which the particular user |
||||
actually uses, or expects or is expected to use, the product. A product |
||||
is a consumer product regardless of whether the product has substantial |
||||
commercial, industrial or non-consumer uses, unless such uses represent |
||||
the only significant mode of use of the product. |
||||
|
||||
"Installation Information" for a User Product means any methods, |
||||
procedures, authorization keys, or other information required to install |
||||
and execute modified versions of a covered work in that User Product from |
||||
a modified version of its Corresponding Source. The information must |
||||
suffice to ensure that the continued functioning of the modified object |
||||
code is in no case prevented or interfered with solely because |
||||
modification has been made. |
||||
|
||||
If you convey an object code work under this section in, or with, or |
||||
specifically for use in, a User Product, and the conveying occurs as |
||||
part of a transaction in which the right of possession and use of the |
||||
User Product is transferred to the recipient in perpetuity or for a |
||||
fixed term (regardless of how the transaction is characterized), the |
||||
Corresponding Source conveyed under this section must be accompanied |
||||
by the Installation Information. But this requirement does not apply |
||||
if neither you nor any third party retains the ability to install |
||||
modified object code on the User Product (for example, the work has |
||||
been installed in ROM). |
||||
|
||||
The requirement to provide Installation Information does not include a |
||||
requirement to continue to provide support service, warranty, or updates |
||||
for a work that has been modified or installed by the recipient, or for |
||||
the User Product in which it has been modified or installed. Access to a |
||||
network may be denied when the modification itself materially and |
||||
adversely affects the operation of the network or violates the rules and |
||||
protocols for communication across the network. |
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, |
||||
in accord with this section must be in a format that is publicly |
||||
documented (and with an implementation available to the public in |
||||
source code form), and must require no special password or key for |
||||
unpacking, reading or copying. |
||||
|
||||
7. Additional Terms. |
||||
|
||||
"Additional permissions" are terms that supplement the terms of this |
||||
License by making exceptions from one or more of its conditions. |
||||
Additional permissions that are applicable to the entire Program shall |
||||
be treated as though they were included in this License, to the extent |
||||
that they are valid under applicable law. If additional permissions |
||||
apply only to part of the Program, that part may be used separately |
||||
under those permissions, but the entire Program remains governed by |
||||
this License without regard to the additional permissions. |
||||
|
||||
When you convey a copy of a covered work, you may at your option |
||||
remove any additional permissions from that copy, or from any part of |
||||
it. (Additional permissions may be written to require their own |
||||
removal in certain cases when you modify the work.) You may place |
||||
additional permissions on material, added by you to a covered work, |
||||
for which you have or can give appropriate copyright permission. |
||||
|
||||
Notwithstanding any other provision of this License, for material you |
||||
add to a covered work, you may (if authorized by the copyright holders of |
||||
that material) supplement the terms of this License with terms: |
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the |
||||
terms of sections 15 and 16 of this License; or |
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or |
||||
author attributions in that material or in the Appropriate Legal |
||||
Notices displayed by works containing it; or |
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or |
||||
requiring that modified versions of such material be marked in |
||||
reasonable ways as different from the original version; or |
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or |
||||
authors of the material; or |
||||
|
||||
e) Declining to grant rights under trademark law for use of some |
||||
trade names, trademarks, or service marks; or |
||||
|
||||
f) Requiring indemnification of licensors and authors of that |
||||
material by anyone who conveys the material (or modified versions of |
||||
it) with contractual assumptions of liability to the recipient, for |
||||
any liability that these contractual assumptions directly impose on |
||||
those licensors and authors. |
||||
|
||||
All other non-permissive additional terms are considered "further |
||||
restrictions" within the meaning of section 10. If the Program as you |
||||
received it, or any part of it, contains a notice stating that it is |
||||
governed by this License along with a term that is a further |
||||
restriction, you may remove that term. If a license document contains |
||||
a further restriction but permits relicensing or conveying under this |
||||
License, you may add to a covered work material governed by the terms |
||||
of that license document, provided that the further restriction does |
||||
not survive such relicensing or conveying. |
||||
|
||||
If you add terms to a covered work in accord with this section, you |
||||
must place, in the relevant source files, a statement of the |
||||
additional terms that apply to those files, or a notice indicating |
||||
where to find the applicable terms. |
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the |
||||
form of a separately written license, or stated as exceptions; |
||||
the above requirements apply either way. |
||||
|
||||
8. Termination. |
||||
|
||||
You may not propagate or modify a covered work except as expressly |
||||
provided under this License. Any attempt otherwise to propagate or |
||||
modify it is void, and will automatically terminate your rights under |
||||
this License (including any patent licenses granted under the third |
||||
paragraph of section 11). |
||||
|
||||
However, if you cease all violation of this License, then your |
||||
license from a particular copyright holder is reinstated (a) |
||||
provisionally, unless and until the copyright holder explicitly and |
||||
finally terminates your license, and (b) permanently, if the copyright |
||||
holder fails to notify you of the violation by some reasonable means |
||||
prior to 60 days after the cessation. |
||||
|
||||
Moreover, your license from a particular copyright holder is |
||||
reinstated permanently if the copyright holder notifies you of the |
||||
violation by some reasonable means, this is the first time you have |
||||
received notice of violation of this License (for any work) from that |
||||
copyright holder, and you cure the violation prior to 30 days after |
||||
your receipt of the notice. |
||||
|
||||
Termination of your rights under this section does not terminate the |
||||
licenses of parties who have received copies or rights from you under |
||||
this License. If your rights have been terminated and not permanently |
||||
reinstated, you do not qualify to receive new licenses for the same |
||||
material under section 10. |
||||
|
||||
9. Acceptance Not Required for Having Copies. |
||||
|
||||
You are not required to accept this License in order to receive or |
||||
run a copy of the Program. Ancillary propagation of a covered work |
||||
occurring solely as a consequence of using peer-to-peer transmission |
||||
to receive a copy likewise does not require acceptance. However, |
||||
nothing other than this License grants you permission to propagate or |
||||
modify any covered work. These actions infringe copyright if you do |
||||
not accept this License. Therefore, by modifying or propagating a |
||||
covered work, you indicate your acceptance of this License to do so. |
||||
|
||||
10. Automatic Licensing of Downstream Recipients. |
||||
|
||||
Each time you convey a covered work, the recipient automatically |
||||
receives a license from the original licensors, to run, modify and |
||||
propagate that work, subject to this License. You are not responsible |
||||
for enforcing compliance by third parties with this License. |
||||
|
||||
An "entity transaction" is a transaction transferring control of an |
||||
organization, or substantially all assets of one, or subdividing an |
||||
organization, or merging organizations. If propagation of a covered |
||||
work results from an entity transaction, each party to that |
||||
transaction who receives a copy of the work also receives whatever |
||||
licenses to the work the party's predecessor in interest had or could |
||||
give under the previous paragraph, plus a right to possession of the |
||||
Corresponding Source of the work from the predecessor in interest, if |
||||
the predecessor has it or can get it with reasonable efforts. |
||||
|
||||
You may not impose any further restrictions on the exercise of the |
||||
rights granted or affirmed under this License. For example, you may |
||||
not impose a license fee, royalty, or other charge for exercise of |
||||
rights granted under this License, and you may not initiate litigation |
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
||||
any patent claim is infringed by making, using, selling, offering for |
||||
sale, or importing the Program or any portion of it. |
||||
|
||||
11. Patents. |
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this |
||||
License of the Program or a work on which the Program is based. The |
||||
work thus licensed is called the contributor's "contributor version". |
||||
|
||||
A contributor's "essential patent claims" are all patent claims |
||||
owned or controlled by the contributor, whether already acquired or |
||||
hereafter acquired, that would be infringed by some manner, permitted |
||||
by this License, of making, using, or selling its contributor version, |
||||
but do not include claims that would be infringed only as a |
||||
consequence of further modification of the contributor version. For |
||||
purposes of this definition, "control" includes the right to grant |
||||
patent sublicenses in a manner consistent with the requirements of |
||||
this License. |
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
||||
patent license under the contributor's essential patent claims, to |
||||
make, use, sell, offer for sale, import and otherwise run, modify and |
||||
propagate the contents of its contributor version. |
||||
|
||||
In the following three paragraphs, a "patent license" is any express |
||||
agreement or commitment, however denominated, not to enforce a patent |
||||
(such as an express permission to practice a patent or covenant not to |
||||
sue for patent infringement). To "grant" such a patent license to a |
||||
party means to make such an agreement or commitment not to enforce a |
||||
patent against the party. |
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, |
||||
and the Corresponding Source of the work is not available for anyone |
||||
to copy, free of charge and under the terms of this License, through a |
||||
publicly available network server or other readily accessible means, |
||||
then you must either (1) cause the Corresponding Source to be so |
||||
available, or (2) arrange to deprive yourself of the benefit of the |
||||
patent license for this particular work, or (3) arrange, in a manner |
||||
consistent with the requirements of this License, to extend the patent |
||||
license to downstream recipients. "Knowingly relying" means you have |
||||
actual knowledge that, but for the patent license, your conveying the |
||||
covered work in a country, or your recipient's use of the covered work |
||||
in a country, would infringe one or more identifiable patents in that |
||||
country that you have reason to believe are valid. |
||||
|
||||
If, pursuant to or in connection with a single transaction or |
||||
arrangement, you convey, or propagate by procuring conveyance of, a |
||||
covered work, and grant a patent license to some of the parties |
||||
receiving the covered work authorizing them to use, propagate, modify |
||||
or convey a specific copy of the covered work, then the patent license |
||||
you grant is automatically extended to all recipients of the covered |
||||
work and works based on it. |
||||
|
||||
A patent license is "discriminatory" if it does not include within |
||||
the scope of its coverage, prohibits the exercise of, or is |
||||
conditioned on the non-exercise of one or more of the rights that are |
||||
specifically granted under this License. You may not convey a covered |
||||
work if you are a party to an arrangement with a third party that is |
||||
in the business of distributing software, under which you make payment |
||||
to the third party based on the extent of your activity of conveying |
||||
the work, and under which the third party grants, to any of the |
||||
parties who would receive the covered work from you, a discriminatory |
||||
patent license (a) in connection with copies of the covered work |
||||
conveyed by you (or copies made from those copies), or (b) primarily |
||||
for and in connection with specific products or compilations that |
||||
contain the covered work, unless you entered into that arrangement, |
||||
or that patent license was granted, prior to 28 March 2007. |
||||
|
||||
Nothing in this License shall be construed as excluding or limiting |
||||
any implied license or other defenses to infringement that may |
||||
otherwise be available to you under applicable patent law. |
||||
|
||||
12. No Surrender of Others' Freedom. |
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or |
||||
otherwise) that contradict the conditions of this License, they do not |
||||
excuse you from the conditions of this License. If you cannot convey a |
||||
covered work so as to satisfy simultaneously your obligations under this |
||||
License and any other pertinent obligations, then as a consequence you may |
||||
not convey it at all. For example, if you agree to terms that obligate you |
||||
to collect a royalty for further conveying from those to whom you convey |
||||
the Program, the only way you could satisfy both those terms and this |
||||
License would be to refrain entirely from conveying the Program. |
||||
|
||||
13. Use with the GNU Affero General Public License. |
||||
|
||||
Notwithstanding any other provision of this License, you have |
||||
permission to link or combine any covered work with a work licensed |
||||
under version 3 of the GNU Affero General Public License into a single |
||||
combined work, and to convey the resulting work. The terms of this |
||||
License will continue to apply to the part which is the covered work, |
||||
but the special requirements of the GNU Affero General Public License, |
||||
section 13, concerning interaction through a network will apply to the |
||||
combination as such. |
||||
|
||||
14. Revised Versions of this License. |
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of |
||||
the GNU General Public License from time to time. Such new versions will |
||||
be similar in spirit to the present version, but may differ in detail to |
||||
address new problems or concerns. |
||||
|
||||
Each version is given a distinguishing version number. If the |
||||
Program specifies that a certain numbered version of the GNU General |
||||
Public License "or any later version" applies to it, you have the |
||||
option of following the terms and conditions either of that numbered |
||||
version or of any later version published by the Free Software |
||||
Foundation. If the Program does not specify a version number of the |
||||
GNU General Public License, you may choose any version ever published |
||||
by the Free Software Foundation. |
||||
|
||||
If the Program specifies that a proxy can decide which future |
||||
versions of the GNU General Public License can be used, that proxy's |
||||
public statement of acceptance of a version permanently authorizes you |
||||
to choose that version for the Program. |
||||
|
||||
Later license versions may give you additional or different |
||||
permissions. However, no additional obligations are imposed on any |
||||
author or copyright holder as a result of your choosing to follow a |
||||
later version. |
||||
|
||||
15. Disclaimer of Warranty. |
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
||||
|
||||
16. Limitation of Liability. |
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
||||
SUCH DAMAGES. |
||||
|
||||
17. Interpretation of Sections 15 and 16. |
||||
|
||||
If the disclaimer of warranty and limitation of liability provided |
||||
above cannot be given local legal effect according to their terms, |
||||
reviewing courts shall apply local law that most closely approximates |
||||
an absolute waiver of all civil liability in connection with the |
||||
Program, unless a warranty or assumption of liability accompanies a |
||||
copy of the Program in return for a fee. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
How to Apply These Terms to Your New Programs |
||||
|
||||
If you develop a new program, and you want it to be of the greatest |
||||
possible use to the public, the best way to achieve this is to make it |
||||
free software which everyone can redistribute and change under these terms. |
||||
|
||||
To do so, attach the following notices to the program. It is safest |
||||
to attach them to the start of each source file to most effectively |
||||
state the exclusion of warranty; and each file should have at least |
||||
the "copyright" line and a pointer to where the full notice is found. |
||||
|
||||
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>. |
||||
@ -0,0 +1,45 @@
|
||||
|
||||
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 |
||||
|
||||
@ -1,22 +0,0 @@
|
||||
|
||||
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,36 +1,45 @@
|
||||
|
||||
SHELL = /bin/sh
|
||||
CXX = g++
|
||||
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -I../include -I/usr/include/SDL2
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL -lassimp
|
||||
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall \
|
||||
-I../include \
|
||||
-I/usr/include/SDL2 \
|
||||
-I../ext/tinygltf
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
||||
OBJDIR = build
|
||||
LIB = ../build/libTangerine.a
|
||||
BINDIR = bin
|
||||
|
||||
|
||||
all: mkdirs \
|
||||
$(OBJDIR)/hello_world.o \
|
||||
$(OBJDIR)/assimp_loading.o \
|
||||
$(OBJDIR)/simple_mesh.o
|
||||
.PHONY: all |
||||
|
||||
-include $(OBJDIR)/*.d |
||||
#EXAMPLE_SOURCES = \
|
||||
# render_groups/main.cpp \
|
||||
# #assimp_loading/main.cpp
|
||||
# #hello_world/main.cpp \
|
||||
# #simple_mesh/main.cpp
|
||||
#EXAMPLE_OBJECTS = $(patsubst %/, $(OBJDIR)/%.o, $(dir $(EXAMPLE_SOURCES)))
|
||||
#
|
||||
#all: mkdirs $(EXAMPLE_OBJECTS)
|
||||
#
|
||||
#-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: |
||||
mkdir -p $(BINDIR) $(OBJDIR)
|
||||
@mkdir -p $(BINDIR) $(OBJDIR)
|
||||
.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: |
||||
rm -rf $(OBJDIR)/* bin/*
|
||||
.PHONY: clean |
||||
|
||||
@ -1,72 +0,0 @@
|
||||
|
||||
#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"; |
||||
} |
||||
@ -1,68 +0,0 @@
|
||||
|
||||
#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.
@ -0,0 +1,11 @@
|
||||
#version 330 core |
||||
|
||||
in vec3 frag_color; |
||||
|
||||
out vec4 color; |
||||
|
||||
|
||||
void main() |
||||
{ |
||||
color = vec4(frag_color, 1); |
||||
} |
||||
@ -0,0 +1,22 @@
|
||||
#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); |
||||
} |
||||
@ -0,0 +1,11 @@
|
||||
#version 330 core |
||||
|
||||
in vec3 frag_normal; |
||||
|
||||
out vec4 color; |
||||
|
||||
|
||||
void main() |
||||
{ |
||||
color = vec4(frag_normal, 1); |
||||
} |
||||
@ -0,0 +1,26 @@
|
||||
#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); |
||||
} |
||||
@ -0,0 +1,67 @@
|
||||
#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); |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
#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.
@ -0,0 +1,21 @@
|
||||
#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); |
||||
} |
||||
@ -0,0 +1,22 @@
|
||||
#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.
@ -1,16 +0,0 @@
|
||||
|
||||
#include "renderer.h" |
||||
|
||||
|
||||
int |
||||
main() |
||||
{ |
||||
render_state* rs = renInit("Hello World"); |
||||
|
||||
if (rs == nullptr) |
||||
return 1; |
||||
|
||||
renDoRenderLoop(rs); |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,344 @@
|
||||
|
||||
#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; |
||||
} |
||||
|
||||
@ -1,72 +0,0 @@
|
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,152 @@
|
||||
|
||||
// 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
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
|
||||
#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; |
||||
}; |
||||
|
||||
@ -0,0 +1,76 @@
|
||||
|
||||
#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); |
||||
|
||||
@ -0,0 +1,30 @@
|
||||
|
||||
#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,40 +1,37 @@
|
||||
|
||||
#pragma once |
||||
|
||||
#include <glm/glm.hpp> |
||||
|
||||
#include "mesh.h" |
||||
#include "render_object.h" |
||||
#include "asset.h" |
||||
#include "shader.h" |
||||
#include "types.h" |
||||
#include "util.h" |
||||
|
||||
|
||||
// TODO: having a separate code path for each shader is annoying and error
|
||||
// prone. could try using opaque pointers at high level (renderer, entity),
|
||||
// and then cast to the appropriate render_object/mesh/shader type in the lower
|
||||
// level functions based on the mesh_type property
|
||||
struct entity |
||||
struct Entity |
||||
{ |
||||
glm::mat4 world_transform; |
||||
// TODO: hide simple_mesh/mesh_group pointer behind abstraction
|
||||
simple_mesh* mesh; |
||||
|
||||
// TODO: should be a pointer into a global array of mesh_info(s) or
|
||||
// mesh_groups stored on the render_state object
|
||||
mesh_group meshes; |
||||
render_objects* render_objs; |
||||
u32 num_meshes; |
||||
GLMesh* meshes; |
||||
GLTexture* diffuse_texture; // NOTE: pointer into gl_ctx->textures array
|
||||
mat4* model_xform; |
||||
char* name; |
||||
|
||||
GLenum draw_mode; // NOTE: GL_LINES, GL_TRIANGLES (default)
|
||||
GLenum usage; // NOTE: GL_STATIC_DRAW (default), GL_DYNAMIC_DRAW
|
||||
}; |
||||
|
||||
bool entInitModel(entity& e, const char* model_path); |
||||
|
||||
bool entInitMesh(entity& e, simple_mesh* mesh, GLenum draw_mode); |
||||
|
||||
void entFree(entity& e); |
||||
|
||||
void entSetWorldPosition(entity& e, glm::vec3 v); |
||||
|
||||
void entTranslate(entity& e, glm::vec3 v); |
||||
bool initEntity(Entity* e, |
||||
GLContext* gl_ctx, |
||||
MemoryArena* arena, |
||||
Model* mdl, |
||||
u32 num_attrib_mappings, |
||||
GLBufferToAttribMapping* attrib_mappings, |
||||
const char* name = "", |
||||
GLenum draw_mode = GL_TRIANGLES, |
||||
GLenum usage = GL_STATIC_DRAW); |
||||
|
||||
void entScale(entity& e, glm::vec3 v); |
||||
void setEntityPosition(Entity* e, vec3 pos); |
||||
|
||||
void entRotate(entity& e, float angle, glm::vec3 axis); |
||||
void rotateEntity(Entity* e, vec3 axis, float radians); |
||||
|
||||
void scaleEntity(Entity* e, float scale); |
||||
|
||||
@ -0,0 +1,23 @@
|
||||
|
||||
#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); |
||||
|
||||
@ -1,34 +0,0 @@
|
||||
|
||||
#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); |
||||
|
||||
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* 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(); |
||||
|
||||
@ -1,102 +0,0 @@
|
||||
|
||||
// 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
|
||||
} |
||||
|
||||
@ -1,33 +0,0 @@
|
||||
|
||||
#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); |
||||
|
||||
@ -1,80 +0,0 @@
|
||||
|
||||
#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); |
||||
|
||||
@ -0,0 +1,214 @@
|
||||
|
||||
#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[]); |
||||
|
||||
@ -1,57 +0,0 @@
|
||||
|
||||
#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); |
||||
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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,16 +1,24 @@
|
||||
|
||||
#pragma once |
||||
|
||||
#include <cstdint> |
||||
#define GLM_FORCE_XYZW_ONLY |
||||
#include <glm/glm.hpp> |
||||
|
||||
enum shader_t |
||||
{ |
||||
SIMPLE_SHADER, |
||||
DEFAULT_SHADER |
||||
}; |
||||
|
||||
enum mesh_t |
||||
{ |
||||
SIMPLE_MESH, |
||||
DEFAULT_MESHES |
||||
}; |
||||
typedef uint8_t u8; |
||||
typedef uint16_t u16; |
||||
typedef uint32_t u32; |
||||
typedef uint64_t u64; |
||||
|
||||
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; |
||||
|
||||
|
||||
@ -1,100 +1,194 @@
|
||||
|
||||
#pragma once |
||||
#ifndef UTIL_H |
||||
#define UTIL_H |
||||
|
||||
#include <cstdint> |
||||
#include <cassert> |
||||
#include <cstring> |
||||
#include <cstdio> |
||||
#include <cstdlib> |
||||
|
||||
#include "dumbLog.h" |
||||
#include "types.h" |
||||
|
||||
typedef float real32; |
||||
typedef float GLfloat; |
||||
typedef double real64; |
||||
typedef int32_t bool32; |
||||
typedef int32_t int32; |
||||
typedef int64_t int64; |
||||
typedef uint8_t uint8; |
||||
typedef uint32_t uint32; |
||||
typedef uint32_t uint; |
||||
|
||||
//-----------------
|
||||
// Hashing
|
||||
|
||||
// NOTE: FNV1a hashing algorithm http://www.isthe.com/chongo/tech/comp/fnv/
|
||||
#define FNV1_64_INIT ((u64) 0xcbf29ce484222325ULL) |
||||
#define FNV_64_PRIME ((u64) 0x100000001b3ULL) |
||||
u64 utilFNV64a_str(const char* str, u64 hval = FNV1_64_INIT); |
||||
|
||||
struct v2f |
||||
//-----------------
|
||||
// Memory allocation
|
||||
|
||||
struct MemoryArena |
||||
{ |
||||
v2f(): x(0), y(0) {} |
||||
v2f(real64 a, real64 b): x(a), y(b) {} |
||||
real64 x; |
||||
real64 y; |
||||
size_t max_size; |
||||
size_t free_size; |
||||
void* head; |
||||
void* next_free; |
||||
}; |
||||
|
||||
struct v2i |
||||
#define DEFAULT_ARENA_SIZE 10 * 1024 * 1024 // 10MB
|
||||
MemoryArena* arenaInit(size_t initial_size = DEFAULT_ARENA_SIZE); |
||||
|
||||
void arenaFree(MemoryArena*& arena); |
||||
|
||||
u32 arenaGetFreeSize(MemoryArena* arena); |
||||
|
||||
#define ARENA_ALLOC(arena, type, count) \ |
||||
(type*) arenaAllocateBlock(arena, sizeof(type) * count) |
||||
void* arenaAllocateBlock(MemoryArena* arena, size_t block_size); |
||||
|
||||
void* arenaGetAddressOffset(void* address, u32 offset); |
||||
|
||||
#define MAX_NAME_LENGTH 256 |
||||
char* arenaCopyCStr(MemoryArena* arena, |
||||
const char* input, |
||||
u32 max_len = MAX_NAME_LENGTH); |
||||
|
||||
#define UTIL_ALLOC(count, type) (type*) utilAllocate(count, sizeof(type)) |
||||
void* utilAllocate(u32 count, u32 type_size); |
||||
|
||||
char* utilAllocateCStr(const char* str, u32 max_len = 256); |
||||
|
||||
void utilSafeFree(void* p); |
||||
|
||||
//---------------
|
||||
// C string utils
|
||||
|
||||
bool utilCStrMatch(const char* str1, const char* str2); |
||||
|
||||
|
||||
#endif // UTIL_H
|
||||
|
||||
|
||||
#ifdef UTIL_IMPLEMENTATION |
||||
|
||||
//-----------------
|
||||
// Hashing
|
||||
|
||||
u64 |
||||
utilFNV64a_str(const char* str, u64 hval) |
||||
{ |
||||
v2i(int a, int b): x(a), y(b) {} |
||||
v2i() : x(0), y(0) {} |
||||
int32 x; |
||||
int32 y; |
||||
}; |
||||
unsigned char* s = (unsigned char *)str; // unsigned string
|
||||
|
||||
// FNV-1a hash each octet of the string
|
||||
while (*s) { |
||||
// xor the bottom with the current octet
|
||||
hval ^= (uint64_t)*s++; |
||||
// multiply by the 64 bit FNV magic prime mod 2^64
|
||||
hval *= FNV_64_PRIME; |
||||
} |
||||
|
||||
struct v3f |
||||
return hval; |
||||
} |
||||
|
||||
//-----------------
|
||||
// Memory allocation
|
||||
|
||||
MemoryArena* |
||||
arenaInit(size_t initial_size) |
||||
{ |
||||
v3f(): x(0), y(0), z(0) {} |
||||
v3f(real64 a, real64 b, real64 c): x(a), y(b), z(c) {} |
||||
real64 x; |
||||
real64 y; |
||||
real64 z; |
||||
}; |
||||
u32 sz = sizeof(MemoryArena); |
||||
MemoryArena* arena = |
||||
(MemoryArena*) std::calloc(initial_size + sz, sizeof(u8)); |
||||
arena->head = arena->next_free = (uint8_t*) arena + sz; |
||||
arena->max_size = initial_size; |
||||
return arena; |
||||
} |
||||
|
||||
inline real32 |
||||
SafeRatio(real32 dividend, real32 divisor) |
||||
void |
||||
arenaFree(MemoryArena*& arena) |
||||
{ |
||||
if (divisor == 0) |
||||
// TODO: log warning (don't require aixlog)
|
||||
return 0; |
||||
else |
||||
return dividend / divisor; |
||||
if (arena != nullptr) { |
||||
std::free(arena); |
||||
arena = nullptr; |
||||
} |
||||
} |
||||
|
||||
inline int32 |
||||
SafeTruncateToInt32(int64 val) |
||||
uint |
||||
arenaGetFreeSize(MemoryArena* arena) |
||||
{ |
||||
assert(val <= INT32_MAX && val >= INT32_MIN); |
||||
return (int32) val; |
||||
return (uint8_t*) arena->head |
||||
+ arena->max_size |
||||
- (uint8_t*) arena->next_free; |
||||
} |
||||
|
||||
inline void |
||||
utilConvertColor(GLfloat buf[3], uint32 color) |
||||
void* |
||||
arenaAllocateBlock(MemoryArena* arena, size_t block_size) |
||||
{ |
||||
// NOTE: not using the alpha values
|
||||
buf[0] = (GLfloat) ((color >> 24) & 0xFF) / (GLfloat) 255; |
||||
buf[1] = (GLfloat) ((color >> 16) & 0xFF) / (GLfloat) 255; |
||||
buf[2] = (GLfloat) ((color >> 8) & 0xFF) / (GLfloat) 255; |
||||
// TODO: resizable memory arena
|
||||
assert(arenaGetFreeSize(arena) >= block_size); |
||||
assert(block_size > 0); |
||||
void* ret = arena->next_free; |
||||
arena->next_free = (uint8_t*) arena->next_free + block_size; |
||||
arena->free_size = arenaGetFreeSize(arena); |
||||
return ret; |
||||
} |
||||
|
||||
//-----------------
|
||||
// C Strings
|
||||
void* |
||||
arenaGetAddressOffset(void* address, u32 offset) |
||||
{ |
||||
return (void*) ((u8*) address + offset); |
||||
} |
||||
|
||||
// NOTE: max_len should be the allocated size of dest
|
||||
bool utilCopyCStr(char* dest, const char* src, uint max_len); |
||||
char* |
||||
arenaCopyCStr(MemoryArena* arena, const char* input, u32 max_len) |
||||
{ |
||||
u32 name_len = std::strlen(input) + 1; |
||||
assert(name_len > 1 && name_len < max_len); |
||||
char* out = ARENA_ALLOC(arena, char, name_len); |
||||
std::strncpy(out, input, name_len); |
||||
return out; |
||||
} |
||||
|
||||
// NOTE: returns new string with '/' between
|
||||
// NOTE: max_len should be the size of return buffer.
|
||||
char* utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len); |
||||
void* |
||||
utilAllocate(u32 count, u32 type_size) |
||||
{ |
||||
void* out = std::calloc(count, type_size); |
||||
assert(out != nullptr); |
||||
return out; |
||||
} |
||||
|
||||
const char* utilBaseName(const char* path_str); |
||||
char* |
||||
utilAllocateCStr(const char* str, u32 max_len) |
||||
{ |
||||
u32 len = strlen(str) + 1; |
||||
|
||||
// NOTE: returns true if the first 'sz' characters from each string match
|
||||
bool utilMatchPrefix(const char* lhs, const char* rhs, int sz); |
||||
if (len > max_len) { |
||||
LOGF(Error, "%s , longer than %i\n", str, max_len); |
||||
return nullptr; |
||||
} |
||||
|
||||
//-----------------
|
||||
// Memory allocation
|
||||
char* out = (char*) std::calloc(len, sizeof(u8)); |
||||
strncpy(out, str, len - 1); |
||||
return out; |
||||
} |
||||
|
||||
// NOTE: Wrapper for calloc that will send error message on out of memory
|
||||
#define UTIL_ALLOC(len, type) (type *) utilLogAlloc((len), sizeof(type), __FILE__, __LINE__) |
||||
void* utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line); |
||||
void |
||||
utilSafeFree(void* p) |
||||
{ |
||||
if (p) |
||||
free(p); |
||||
else |
||||
LOGF(Error, "free called on nullptr\n"); |
||||
} |
||||
|
||||
void utilSafeFree(const void* mem); |
||||
//---------------
|
||||
// C string utils
|
||||
|
||||
//-----------------
|
||||
// File I/O
|
||||
bool |
||||
utilCStrMatch(const char* str1, const char* str2) |
||||
{ |
||||
if (!str1 || !str2) |
||||
return false; |
||||
|
||||
char* utilDumpTextFile(const char* filename); |
||||
u32 l1 = strlen(str1); |
||||
u32 l2 = strlen(str2); |
||||
|
||||
bool utilWriteTextFile(const char* filename, const char* text); |
||||
return (l1 == l2 && strncmp(str1, str2, l1) == 0); |
||||
} |
||||
|
||||
#endif |
||||
|
||||
@ -1,32 +0,0 @@
|
||||
|
||||
#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); |
||||
|
||||
@ -0,0 +1,430 @@
|
||||
|
||||
#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"; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ -1,104 +0,0 @@
|
||||
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,95 +1,74 @@
|
||||
|
||||
#include <cassert> |
||||
|
||||
#define GLM_FORCE_XYZW_ONLY |
||||
#include <glm/gtc/matrix_transform.hpp> |
||||
|
||||
#include "dumbLog.h" |
||||
#include "entity.h" |
||||
#include "util.h" |
||||
|
||||
|
||||
// forward declarations
|
||||
|
||||
void initDefaults(entity& e); |
||||
|
||||
|
||||
// interface
|
||||
|
||||
bool |
||||
entInitModel(entity& e, const char* model_path) |
||||
initEntity(Entity* e, |
||||
GLContext* gl_ctx, |
||||
MemoryArena* arena, |
||||
Model* mdl, |
||||
u32 num_attrib_mappings, |
||||
GLBufferToAttribMapping* attrib_mappings, |
||||
const char* name, |
||||
GLenum draw_mode, |
||||
GLenum usage) |
||||
{ |
||||
initDefaults(e); |
||||
|
||||
if (meLoadFromFile(e.meshes, model_path)) |
||||
{ |
||||
e.render_objs = roInitModel(e.meshes); |
||||
|
||||
if (e.render_objs != nullptr) |
||||
return true; |
||||
} |
||||
|
||||
entFree(e); |
||||
e->num_meshes = mdl->num_meshes; |
||||
e->meshes = ARENA_ALLOC(arena, GLMesh, e->num_meshes); |
||||
e->model_xform = ARENA_ALLOC(arena, mat4, 1); |
||||
*e->model_xform = mat4(1.f); |
||||
e->name = arenaCopyCStr(arena, name); |
||||
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; |
||||
} |
||||
|
||||
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) |
||||
for (u32 i = 0; i< e->num_meshes; i++) { |
||||
GLMesh* glm = &e->meshes[i]; |
||||
*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; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void |
||||
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.world_transform = glm::translate(e.world_transform, v); |
||||
} |
||||
|
||||
void |
||||
entScale(entity& e, glm::vec3 v) |
||||
setEntityPosition(Entity* e, vec3 pos) |
||||
{ |
||||
e.world_transform = glm::scale(e.world_transform, v); |
||||
(*e->model_xform)[3][0] = pos.x; |
||||
(*e->model_xform)[3][1] = pos.y; |
||||
(*e->model_xform)[3][2] = pos.z; |
||||
} |
||||
|
||||
void |
||||
entRotate(entity& e, float angle, glm::vec3 axis) |
||||
rotateEntity(Entity* e, vec3 axis, float radians) |
||||
{ |
||||
e.world_transform = glm::rotate(e.world_transform, angle, axis); |
||||
*e->model_xform = glm::rotate(*e->model_xform, radians, axis); |
||||
} |
||||
|
||||
|
||||
// internal
|
||||
|
||||
void |
||||
initDefaults(entity& e) |
||||
scaleEntity(Entity* e, float scale) |
||||
{ |
||||
e.world_transform = glm::mat4(1.0); |
||||
entScale(e, glm::vec3(1.0)); |
||||
entSetWorldPosition(e, glm::vec3(0, 0, 0)); |
||||
*e->model_xform = |
||||
glm::scale(*e->model_xform, vec3(scale, scale, scale)); |
||||
} |
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
|
||||
#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); |
||||
} |
||||
|
||||
@ -1,55 +0,0 @@
|
||||
|
||||
#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; |
||||
} |
||||
|
||||
@ -1,234 +0,0 @@
|
||||
#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; |
||||
} |
||||
@ -1,307 +0,0 @@
|
||||
|
||||
#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]); |
||||
} |
||||
|
||||
@ -1,302 +0,0 @@
|
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,748 @@
|
||||
|
||||
#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; |
||||
} |
||||
|
||||
@ -1,143 +0,0 @@
|
||||
|
||||
#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; |
||||
} |
||||
|
||||
@ -0,0 +1,408 @@
|
||||
|
||||
#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; |
||||
} |
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION |
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION |
||||
#define TINYGLTF_IMPLEMENTATION |
||||
#include "tiny_gltf.h" |
||||
@ -1,141 +0,0 @@
|
||||
|
||||
#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; |
||||
} |
||||
|
||||
@ -1,60 +0,0 @@
|
||||
|
||||
#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